Giter Club home page Giter Club logo

Comments (27)

willmcgugan avatar willmcgugan commented on August 22, 2024 11

Author of Rich here. I think this is a fantastic idea, and I would be happy to help!

from plotext.

piccolomo avatar piccolomo commented on August 22, 2024 5

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)

with result:
image

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.

piccolomo avatar piccolomo commented on August 22, 2024 3

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:
image
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.

whisller avatar whisller commented on August 22, 2024 2

@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.

willmcgugan avatar willmcgugan commented on August 22, 2024 2

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.

whisller avatar whisller commented on August 22, 2024 1

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.

willmcgugan avatar willmcgugan commented on August 22, 2024 1

That was one of the few excuses I would accept. ;-) Congratulations in advance, @whisller !

from plotext.

piccolomo avatar piccolomo commented on August 22, 2024 1

@willmcgugan,

let's do it then! [obviously after @whisller fatherhood priorities :-) ]

from plotext.

whisller avatar whisller commented on August 22, 2024 1

@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.

willmcgugan avatar willmcgugan commented on August 22, 2024 1

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.

whisller avatar whisller commented on August 22, 2024 1

@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 in rich
  • Pass background colour/plot colour to plotext based what rich settings are

Thank you guys for the effort!

from plotext.

henryiii avatar henryiii commented on August 22, 2024 1

Would this also integrate then with textual, which would be amazing?

from plotext.

willmcgugan avatar willmcgugan commented on August 22, 2024 1

@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.

willmcgugan avatar willmcgugan commented on August 22, 2024 1

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.

randerzander avatar randerzander commented on August 22, 2024 1

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.

piccolomo avatar piccolomo commented on August 22, 2024

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.

piccolomo avatar piccolomo commented on August 22, 2024

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.

whisller avatar whisller commented on August 22, 2024

@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.

piccolomo avatar piccolomo commented on August 22, 2024

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 function get_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 some rich 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.

piccolomo avatar piccolomo commented on August 22, 2024

Hi @willmcgugan ,

thanks for reply. Any idea on how to get any layout size (width and height)?

from plotext.

piccolomo avatar piccolomo commented on August 22, 2024

@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.

willmcgugan avatar willmcgugan commented on August 22, 2024

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.

piccolomo avatar piccolomo commented on August 22, 2024

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.

piccolomo avatar piccolomo commented on August 22, 2024

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.

piccolomo avatar piccolomo commented on August 22, 2024

Hi @whisller, do you think there are other related issues to solve before closing?

from plotext.

piccolomo avatar piccolomo commented on August 22, 2024

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.

piccolomo avatar piccolomo commented on August 22, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.