Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flickering on Live Plotting with Rich #74

Closed
ThSGM opened this issue Feb 8, 2022 · 5 comments
Closed

Flickering on Live Plotting with Rich #74

ThSGM opened this issue Feb 8, 2022 · 5 comments

Comments

@ThSGM
Copy link

ThSGM commented Feb 8, 2022

I tried to modify the example code that was raised in this issue that managed to get plotext working with Rich. This is a fairly minimal script that tries to plot a moving sine curve.

The problem is that it shows a lot of flickering.

I am very new to coding with Python so I'm sure there are things that I'm doing that are not right. Possible issues might be around the def make_plot section, or the section when I initialise the plots, or the final Live section.

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 datetime import datetime
from rich.table import Table

from time import sleep
import plotext as plt

import numpy as np

def make_plot(width, height):
    # plt.clf()
    plt.clear_data()
    plt.scatter(yy, marker="x")
    plt.plotsize(width, height)
    plt.title(str(count))
    plt.colorless()
    return plt.build()

class plotextMixin(JupyterMixin):
    def __init__(self):
        self.decoder = AnsiDecoder()
        self.count = count
        
    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=100),
        Layout(name="main_right", size=30),
    )
    return layout

# Clear the plot
plt.clf()

# Initialise he count
count = 0

# Initialise the plots
xx = np.arange(0, 6*np.pi, 0.1)
yy = np.sin(xx)
uu = np.stack((xx,yy), axis=1)

layout = make_layout()
plotext_layout = layout["plotext"]
mix = plotextMixin()
mix = Panel(mix, style="white on black")
plotext_layout.update(mix)

header_layout = layout["header"]

with Live(layout, refresh_per_second=0.1) as live:
    while True:
        xx = xx + 0.1*count
        yy = np.sin(xx)
        uu = np.stack((xx,yy),axis=1)
        count = count + 1

        plotext_layout.update(mix)

        plt.sleep(0.01)
        plt.show()
@piccolomo
Copy link
Owner

piccolomo commented Feb 14, 2022

Hi @ThSGM,

thanks a lot for dropping your feedback! :-)

I have modified your code and it seem to work fine on my machine. I changed colouring, marker and layout but you could tweek it to suite your preferences. Here it is:

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 rich.text import Text
import plotext as plt

def make_plot(width, height, phase = 0, title = None):
    plt.clf()
    l, frames = 1000, 30
    x = range(1, l + 1)
    y = plt.sin(1, periods = 2, length = l, phase = 2 * phase  / frames)
    plt.scatter(x, y, marker = "fhd")
    plt.plotsize(width, height)
    plt.xaxis(0, "upper")
    plt.yaxis(0, "right")
    plt.title(title)
    #color = 255
    #plt.canvas_color(color)
    #plt.axes_color(color)
    #plt.cls()
    return plt.build()

class plotextMixin(JupyterMixin):
    def __init__(self, phase = 0, title = None):
        self.decoder = AnsiDecoder()
        self.phase = phase
        self.title = title
        
    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.phase, self.title)
        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_column(
        Layout(name = "static", ratio = 1),
        Layout(name = "dynamic"))
    return layout

layout = make_layout()

header = layout['header']
title = plt.colorize("Pl✺ ", "cyan bold") + plt.colorize("text ", "dim bold") + "integration with " + plt.colorize("rich_", "bold dim")
spaces = (plt.terminal_size()[0] - len(plt.uncolorize(title))) // 2
header.update(Text("\n" + " " * spaces + title))

static = layout["static"]
dynamic = layout["dynamic"]

mixin_static = plotextMixin(title = "Static Plot")
mixin_static_panel = Panel(mixin_static)
static.update(mixin_static_panel)

mixin_dynamic = plotextMixin(title = "Dynamic Plot")
mixin_dynamic_panel = Panel(mixin_dynamic)

with Live(layout, refresh_per_second = 0.0001) as live:
    while True:
        mixin_dynamic.phase += 1
        dynamic.update(mixin_dynamic_panel)
        live.refresh()

which outputs a static and dynamic plot:

Peek 2022-02-14 19-26

What do you think? Does it work for you as well? Feel free to drop your version if you find a better one :-)

All the best,
Savino

P.S. Apologies I edited your post by mistake: I have restored later.

@piccolomo piccolomo changed the title Flickering on live plotting with Rich Flickering on Live Plotting with Rich Feb 14, 2022
@ThSGM
Copy link
Author

ThSGM commented Feb 15, 2022

Hi!

I'm running a few tests, but the flickering is different when using different terminals on my macOS (notably Alacritty and iTerm). I'll run it on my Raspberry Pi computer to see if it persists there as well.

In the mean, I'm wondering if you could explain a few elements of your code:

  • As I understand it, the refresh_per_second = 0.0001 is extremely slow. Basically refreshes every 10k seconds. What is the reason why you would set the Rich refresh to be so slow? Is this equivalent to setting auto_refresh=False?
  • So how often would live.refresh() be called? As fast as possible?
  • How often is makeplot() run?
  • So everytime makeplot is run, the plot is cleared via plt.clf() -- is there an advantage to plt_clear_data() instead? Are there more efficient ways of plotting or is it best to do a clf?
  • Is there any need to use plt.show()?

@piccolomo
Copy link
Owner

piccolomo commented Feb 15, 2022

Hi @ThSGM ,

rich is a great package but i am not a rich expert (no pun intended): this is what I understood.

  • The refresh_per_second doesn't seem to make a difference: I tried values between 10^12 and 10^-12 with no difference, and I guess it is because there is a while loop soon after, which is what really matter.
  • I have no idea what auto_refresh is.
  • What really decides the refresh rate it seems to be the plotting time, which you could find with the function plt.time(). In my case, I get tens of milliseconds, so I would suggests adding plt.sleep(t) inside the final while loop and tweek t (around what plt.time() gives you) to remove the flickering. Here is how:
with Live(layout, refresh_per_second = 0.0001) as live:
    while True:
        mixin_dynamic.phase += 1
        dynamic.update(mixin_dynamic_panel)
        plt.sleep(0.01) # tweek this time to remove flickering
        live.refresh()
  • This is only an idea which i haven't tested: to try to solve the flickering on any machine or terminal, instead of changing the tweeking time for all machines, try the code plt.sleep(k * plt.time() if plt.time() is not None else 0) where k is a fixed number you need to find out by playing around: I would guess between 0.1 and 10. But try and see.
  • So ye, I guess you are right, plt.cld() would be sufficient. This is because only the data changes from one plot to the next and not the colouring, title, and any other setting. plt.clf() clears all settings (it is therefore safer), plt.cld() only the data plotted (it is therefore faster).
  • There is no need for plt.show() here because the script uses plt.build() which creates the plot and passes it, as a string, to rich, which does the final printing. plt.show() would add an unnecessary further printing outside the rich environment.
  • Btw if you want to make the plot faster, use plt.clc() or plt.cls() to remove colouring and marker = "x" (or any other character, except hd and fhd).

Did it work?

Also is there any specific reason you would use rich? it is a great environment and i am a fan, it is just that it seems you could obtain a similar result with plotext alone using plt.subplots(), but with shorter code. Here is how:

import plotext as plt

l, frames = 1000, 30
x = range(1, l + 1)
def make_data(phase = 0):
    return plt.sin(1, periods = 2, length = l, phase = 2 * phase  / frames)

plt.clf()
plt.subplots(2, 1)

plt.subplot(1, 1)

y = make_data()
plt.scatter(x, y, marker = "fhd")
plt.title("static plot")

plt.subplot(2, 1)

for f in range(100 * frames):
    plt.cld()
    plt.clt()
    y = make_data(f)
    plt.scatter(x, y, marker = "fhd")
    plt.title("dynamic plot")
    #plt.sleep(0.01) # tweek this time to remove fleekering
    plt.show()

which seems a little more responsive, and gives this similar result:
Peek 2022-02-15 12-35
What do you think?

All the best

@ThSGM
Copy link
Author

ThSGM commented Feb 18, 2022

Hello @ThSGM, any update?

(Re-sending on the appropriate account; this response predates this one)

Hello! Thank you for all your help. I'll do a quick reply for now as I've been busy with work.

Basically, your alternative code did not seem to help. I tested it on both macOS iTerm2 and Alacritty and it showed significant flickering. The delay is due to the fact that I wanted to test on a Raspberry Pi/Debian system to see if it was due to the terminal.

I have uploaded a video to show here: https://streamable.com/tl3ktr

But then, even if it does work on Linux/Debian, I think the flickering that persists on iTerm2/Alacritty is still a problem.

@piccolomo
Copy link
Owner

piccolomo commented Mar 2, 2022

Hi @ThSGM, I close the issue for lack of updates. Feel free to reopen it. My last message could help you btw.
all the best and thanks for your time
Savino

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants