Comments (27)
Author of Rich here. I think this is a fantastic idea, and I would be happy to help!
from plotext.
And Ta Daaa:
from rich.layout import Layout
from rich.live import Live
from rich.ansi import AnsiDecoder
from rich.console import RenderGroup
from rich.jupyter import JupyterMixin
from rich.panel import Panel
from time import sleep
import plotext as plt
def make_plot(*size):
plt.clf()
plt.scatter(plt.sin(1000, 3))
plt.plotsize(*size)
plt.title("Plotext Integration in Rich - Test")
return plt.build()
class plotextMixin(JupyterMixin):
def __init__(self):
self.decoder = AnsiDecoder()
def __rich_console__(self, console, options):
self.width = options.max_width or console.width
self.height = options.height or console.height
canvas = make_plot(self.width, self.height)
self.rich_canvas = RenderGroup(*self.decoder.decode(canvas))
yield self.rich_canvas
def make_layout():
layout = Layout(name="root")
layout.split(
Layout(name="header", size=3),
Layout(name="main", ratio=1),
)
layout["main"].split_row(
Layout(name="plotext", size=120),
Layout(name="main_right"),
)
return layout
layout = make_layout()
plotext_layout = layout["plotext"]
mix = plotextMixin()
mix = Panel(mix)
plotext_layout.update(mix)
with Live(layout, refresh_per_second=0.1) as live:
while True:
sleep(0.1)
In this example it is the plotext
plot that adapts to the size of the layout (and not vice-versa like in previous code).
If the Panel
around the plot is not desired, just comment the line mix = Panel(mix)
which will remove the rich
frame around the plot and give it few extra space.
Indeed modifying the __rich_console__
method of the class Layout
didn't seem to work but it worked when I modified the same method of the class JupyterMixin
.
Thanks @willmcgugan and @whisller for the kind help, and if you want to add any final magical touch (if not more serious corrections) feel free to update.
from plotext.
Hi @whisller and @willmcgugan ,
I have successfully plotted, in colors and with size adaptation, using layout
as in this example:
from rich.layout import Layout
from rich.jupyter import JupyterMixin
import plotext as plt
from rich.ansi import AnsiDecoder
from rich.console import RenderGroup
from rich.live import Live
from time import sleep
def make_layout():
layout = Layout(name="root")
layout.split(
Layout(name="header", size=3),
Layout(name="main", ratio=1),
)
layout["main"].split_row(
Layout(name="plotext"),
Layout(name="main_right"),
)
return layout
class PlotextIntegration(JupyterMixin):
def __init__(self, canvas):
decoder = AnsiDecoder()
self.rich_canvas = RenderGroup(*decoder.decode(canvas))
def __rich_console__(self, console, options):
yield self.rich_canvas
def make_plot():
plt.clf()
plt.scatter(plt.sin(1000, 3))
plt.plotsize(100, 54)
plt.title("Plotext Integration in Rich - Test")
plt.show(hide = True)
from plotext.plot import _fig
size = _fig.width, _fig.height
return plt.get_canvas(), size
layout = make_layout()
plotext_layout = layout["plotext"]
canvas, size = make_plot()
plti = PlotextIntegration(canvas)
plotext_layout.update(plti)
plotext_layout.size, plotext_layout.height = size
with Live(layout, refresh_per_second=0.1) as live:
while True:
sleep(0.1)
which produces the following output:
The layout on the left adapts to the size of the plot now, but not vice-versa because the size of a layout seems to be calculated only once it is printed.
The only minor issue I may need to resolve in the next version is to get the figure size without the convoluted method of importing _fig
. Other then that it seems to work.
☺
What do you think?
from plotext.
@willmcgugan @piccolomo I have first "working" draft, at least plot is being semi displayed ;)
from time import sleep
from rich.console import Console
from rich.console import ConsoleOptions, RenderResult
from rich.jupyter import JupyterMixin
from rich.layout import Layout
import plotext as plt
from rich.live import Live
from rich.panel import Panel
from rich.segment import Segment
class PlotextIntegration(JupyterMixin):
def __init__(self, figure):
self._figure = figure
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
for row in self._figure.subplot.matrix:
line = ""
for character in row:
line += character[0]
yield Segment(line)
yield Segment.line()
def make_layout():
layout = Layout(name="root")
layout.split(
Layout(name="header", size=3),
Layout(name="main", ratio=1),
)
layout["main"].split_row(
Layout(name="main_left"),
Layout(name="main_right"),
)
return layout
def make_plot():
plt.scatter(plt.sin(100, 3))
plt.plotsize(50, 50)
plt.title("Plot Example")
plt.show(True)
from plotext.plot import _fig
return _fig
layout = make_layout()
layout["main_left"].update(Panel(PlotextIntegration(make_plot())))
with Live(layout, refresh_per_second=0.1) as live:
while True:
sleep(0.1)
Few questions/obstacles that come to my mind:
- Multiple plots within same dashboard. Seems that plotext uses few global variables.
- Accessing width/height of Panel/Window from within rich to set it to plotex before rendering. I assume that might not be possible and we would need some monkey patching here.
- Setting up title/colour/other attributes should be easier. Some monkey patching might be required
from plotext.
With regards to coloring, you could modify your code to generate Segment instances rather than string with ansi codes, but that would be a lot of work and you wouldn't want to break the non-Rich functionality.
There is a non-documented AnsiDecoder class that may help. If you construct an AnsiDecoder you can feed it strings with ansi codes and it will yield Text instances which you can print with Rich.
If you can't calculate the size in advance you could measure the Text objects that come back from the ansi decoder.
Putting that together, something along these lines might just be enough (untested):
from rich.ansi import AnsiDecoder
from rich.console import RenderGroup
from rich.measure import Measurement
class PlotextIntegration(JupyterMixin):
def __init__(self, canvas) -> None:
decoder = AnsiDecoder()
self.rich_canvas = RenderGroup(*decoder.decode(canvas))
def __rich_(self):
return self.rich_canvas
def __rich_measure__(self, console, options) -> Measurement:
return Measurement.get(console, options, self.rich_canvas)
from plotext.
So generally I am super interested in integrating those two libraries. For two reasons, rich
is really good when it comes to prepare dashboards in CLI in python, and plotext
is nicest plotting library I found for python so far, which works natively in CLI rather than generates images.
Problem that I noticed when tried to make those two to work sadly is...it doesn't work ;) I believe there would have to be some sort of wrapper that would handle block sizes, resize, same as format that is accepted by rich
.
In my opinion that integration could boost both libraries, as that's something which is missing in python environment.
from plotext.
That was one of the few excuses I would accept. ;-) Congratulations in advance, @whisller !
from plotext.
let's do it then! [obviously after @whisller fatherhood priorities :-) ]
from plotext.
@piccolomo it seems that design of plotext was not intended to display multiple charts on single window. E.g. shared access to _fig
. Is it something you would have a time to change?
from plotext.
You may be better off with a Table there. Tables will adapt to their contents, so all you would need is that __rich_measure__
method. You can use Table.grid
to create a table with no divider lines...
from plotext.
@piccolomo nice one!
So seems that we're getting there :)
The layout on the left adapts to the size of the plot now, but not vice-versa because the size of a layout seems to be calculated only once it is printed.
Yeah, that was my worry as well. @willmcgugan are there any methods that integration can implement to be called during render/post render that could be used to set width/height of plotext instance? Something that could allow dynamically change the size of it when window is resized.
It seems that if we would tackle things from below list we should be fine:
- Allow to create multiple instances of
plotext
- Dynamically pass width/height to
plotext
when window is being resized inrich
- Pass background colour/plot colour to
plotext
based whatrich
settings are
Thank you guys for the effort!
from plotext.
Would this also integrate then with textual, which would be amazing?
from plotext.
@henryiii It would! Which is why I'm excited about this project.
@piccolomo @whisller You would need to add a __rich_measure__
method. This returns a Measurement
object with two values; the maximum and minimum width of the renderable. In the case of a plot I guess you could pick a minimum width that could render anything useful, I'd guess 20 characters. For the maximum it would be the console width since you can scale up to any size.
So the measure method would be something like this:
def __rich_measure__(self, console, options):
return Measurement(20, console.width)
Now in __rich_console__
the options object has a max_width
attribute which you should use to render the plot. There's also a height
argument. If that's None
then you can render at any height, otherwise it will be an int you must use as the height.
How you want to calculate the dimensions is up to you. You might want to add it to the constructor so the developer can set it explicitly. For instance if you set plot_width in the constructor your measure method could be this:
def __rich_measure__(self, console, options):
return Measurement(self.plot_width, self.plot_height)
There are a lot of examples in the Rich source. panel.py may be a good place to start. Hope that helps!
from plotext.
I think the exception may be swallowed by the stdout / stderr capturing there.
Your renderable is broken. You forgot to pass the console, and options args through, and you need to return the result.
Easy fix:
def __rich_console__(self, console, options):
return super().__rich_console__(console, options)
from plotext.
Hello, I tried to use your example with the latest version of plotext (4.1.2), but the get_canvas function appears to have removed (or renamed?):
>>> import plotext as plt
>>> plt.__version__
'4.1.2'
>>> plt.get_canvas()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'plotext' has no attribute 'get_canvas'
Edit: I saw in the release notes that get_canvas
is now build
.
The example mostly works again, but the hide
argument to plt.show
is gone, so a "dummy" chart gets printed before the Rich layout renders.
from plotext.
Hi @whisller,
I have an easy reply: I have no idea :-)
I was not aware of that package, which seems really cool actually.
I am not even sure if it is a good idea and how to do it.
If there are other request or someone is interesting in integrating it, why not? :-)
Is there any reason in particular you think it would be a good idea?
Thanks for your message,
Savino
from plotext.
I would be interested in helping, if it won't be a massive work. So ye I am available. but keep in mind i know nothing about the internal architecture of rich
.
For now one thing that could help if you haven't seen already is the get_canvas()
function in my library, which outputs the plot in string form and could help inserting it into a rich
wrapper. Use it after show(hide = True)
, with option hide
set to True.
Let me know, please, or write me privately at [email protected]
Thanks, Savino
from plotext.
@piccolomo @willmcgugan awesome! I think this integration would benefit greatly both sides :)
I will try to find some time to sit and investigate, but realistically speaking it will not be earlier than after few weeks from now (most likely I will be with my partner on labour ward in next few hours/days ;)).
from plotext.
Hi @whisller,
thanks a lot for the message. I have been looking at your example and I have these considerations:
- It will be ideal if one could get the exact size of the layout one wants to plot in. In this way one could use the entire selected layout without empty spaces and the plot could dynamically adapt to the size of the layout. I tried to find a way to get the exact size of any given layout without success. Any help? Perhaps @willmcgugan could help.
- I could make
_fig
accessible in next version or create a function then return the plot matrix instead. Could it be a better idea to use the functionget_canvas()
which return the plot string? Here is a modified example:
from time import sleep
from rich.console import Console
from rich.console import ConsoleOptions, RenderResult
from rich.jupyter import JupyterMixin
from rich.layout import Layout
import plotext as plt
from rich.live import Live
from rich.panel import Panel
from rich.segment import Segment
class PlotextIntegration(JupyterMixin):
def __init__(self, canvas):
self.canvas = canvas
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
lines = self.canvas.split('\n')
for row in lines:
yield Segment(row)
yield Segment.line()
def make_layout():
layout = Layout(name="root")
layout.split(
Layout(name="header", size=3),
Layout(name="main", ratio=1),
)
layout["main"].split_row(
Layout(name="main_left"),
Layout(name="main_right"),
)
return layout
def make_plot():
plt.scatter(plt.sin(100, 3))
plt.plotsize(50, 50)
plt.title("Plotext - Rich Integration Test")
plt.cls()
plt.show(hide = True)
return plt.get_canvas()
layout = make_layout()
layout["main_left"].update(Panel(PlotextIntegration(make_plot())))
with Live(layout, refresh_per_second=0.1) as live:
while True:
sleep(0.1)
- The problem of this example is that it doesn't preserve coloring, as a colored character like this one
\x1b[107m\x1b[30mx\x1b[0m\x1b[0m
covers way more then one character and it will be cut out by the layout limited size. So for colored plots one need to use the matrix method suggested by @whisller again and apply the coloring internally with somerich
internal method. Perhaps @willmcgugan could help us with the coloring of each character: the problem there is that we would need to unify the coloring standards I guess (color codes used).
All the best
Savino
from plotext.
Hi @willmcgugan ,
thanks for reply. Any idea on how to get any layout size (width and height)?
from plotext.
@willmcgugan I tried without success to follow your last recommendations.
I actually think it may not be possible to let the plot dimension adapt to the layout (while the opposite is possible as I have shown) because the layout dimension seem to be calculated only when already printed or at least after the layout receives an update with some content (with a given dimension like a plotext
plot).
I also tried to modify the __rich_console__
method inside the Layout
class to obtain the layout dimensions from either console
or options
, but it returned a printed exception with no name specification. Any idea or help?
Could the example I have shown before be enough? Optionally one could add a Panel
around the plot, if one reduces the plot size by a couple of characters in both width
and height
.
from plotext.
I actually think it may not be possible to let the plot dimension adapt to the layout (while the opposite is possible as I have shown) because the layout dimension seem to be calculated only when already printed or at least after the layout receives an update with some content (with a given dimension like a plotext plot).
What I was think was is that you render the plot inside __rich_console__
with a width of options.max_width
and a height of options.height
. i.e. something like this:
def __rich_console__(self, console, options):
...
plt.plotsize(options.max_width, options.height or console.height)
That way it would be the Layout class that specifies the size of the plot, and it would adapt with the size of the terminal.
I also tried to modify the rich_console method inside the Layout class to obtain the layout dimensions from either console or options, but it returned a printed exception with no name specification. Any idea or help?
That shouldn't be required. Do you have an exception or error message ?
from plotext.
Hi @willmcgugan, I will try to implement your suggestion.
In the meantime I am trying to change the Layout
class, but I receive an error. Here is the code:
from rich.layout import Layout
from rich.live import Live
from time import sleep
class myLayout(Layout):
def __init__(self):
super().__init__()
def __rich_console__(self, console, options):
super().__rich_console__()
layout = myLayout()
with Live(layout, refresh_per_second=0.1) as live:
while True:
sleep(0.1)
and the error:
Error in sys.excepthook:
Original exception was:
which seems to be due to the __rich_console__()
method. Any help?
thanks
from plotext.
I close the issue, but feel free to reopen it, in case there were errors in the code or other things to add for integration.
All the best
from plotext.
Hi @whisller, do you think there are other related issues to solve before closing?
from plotext.
Hi @randerzander ,
thanks a lot for the message: , I updated the previous code using the function plt.build()
.
For next version, I was thinking of publishing a guide .md
page with an updated code on how to integrate those two environments.
from plotext.
Hi all,
The new version 4.1.3 is available on github and pypi.
I have created a page guide relative to the integration of plotext
and rich
, which could be reached here.
Thanks anyone for the help.
Savino
from plotext.
Related Issues (20)
- Space in staked bar chart if first value is 0
- OSError with Plotext Event Plot on Windows10 HOT 1
- Out of bound on a simple_stacked_bar with more than 19 stacked dataset HOT 4
- Circular import error on plotext attributes HOT 2
- Time format HOT 2
- wide chars(chinese/japanese,etc) overflowed in legend HOT 2
- AttributeError: module 'plotext' has no attribute 'box' HOT 3
- Is it possible to include asciichartpy style like for the Basic plots?
- BUG: `bar` does not recognize numpy strings as strings
- Feature request: Setting a maximum to bar plots HOT 2
- Terminal Output for Docker-Compose HOT 1
- Tickable bar Plot
- Color per label in bar plot
- Color for the scatter plots
- Time Shift Issue: plot Function Displays Time One Hour Off Compared to Bar Graph
- Line-thickness control? HOT 5
- `plotext.show()` with no data changes subsequent plots HOT 1
- yscale("log") throws math domain error for multiple data sets with multiple show
- _date.py string_to_time can make time zone error
- CandleStick looks wrong on some bars HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from plotext.