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

Question: Update Bokeh plots from PyComponent #684

Open
juliusbierk opened this issue Jan 11, 2021 · 4 comments
Open

Question: Update Bokeh plots from PyComponent #684

juliusbierk opened this issue Jan 11, 2021 · 4 comments

Comments

@juliusbierk
Copy link

juliusbierk commented Jan 11, 2021

Hi,

I am new to flexx, but really like it. In the bokeh-example, a line plot is updated using JS code.
However, one of the strenghts of bokeh is that we can do numpy calculations and update
the plots dynamically from Python. Is this possible in flexx?

For instance, this does not work:

from flexx import flx
import numpy as np
from bokeh.plotting import figure
from flexx import app, ui
from bokeh.models import ColumnDataSource

x = np.linspace(0, 6, 50)

data={'x': x, 'y': np.sin(x)}

source = ColumnDataSource(data)

p1 = figure(tools="pan,wheel_zoom,box_zoom,reset")
p1.toolbar.logo = None
p1.line(x='x', y='y', source=source)
p1.sizing_mode = 'scale_height'


class Example(app.PyComponent):
    def init(self):
        with ui.VSplit():
            self.plot = ui.BokehWidget.from_plot(p1)
            self.freq = flx.Slider(title='Frequency', max=2.0, value=1.0)

    @flx.reaction
    def update_plot(self):
        new_dict = {'x': x, 'y': np.sin(self.freq.value * x)}
        source.data = new_dict


if __name__ == '__main__':
    app = flx.App(Example)
    m = app.launch('chrome-browser')
    flx.run()

Even though source.data is updated successfully, the plot does not update as it would using just bokeh.
Is there any simple way to get around this?

@juliusbierk
Copy link
Author

Alternatively, I guess I would like to know if there is a way to replace the entire BokehWidget with a new BokehWidget?
I guess this would be quite expensive, but would be an easy way to update the plot.

@juliusbierk
Copy link
Author

juliusbierk commented Jan 11, 2021

Ah!, I found a hackish way to do it

from flexx import flx
import numpy as np
from bokeh.plotting import figure
from flexx import app, ui
from bokeh.models import ColumnDataSource
from bokeh.embed import components

x = np.linspace(0, 6, 50)

data={'x': x, 'y': np.sin(x)}

source = ColumnDataSource(data)

p1 = figure(tools="pan,wheel_zoom,box_zoom,reset")
p1.toolbar.logo = None
p1.line(x='x', y='y', source=source)
p1.sizing_mode = 'scale_height'


class Example(app.PyComponent):
    def init(self):
        with ui.VSplit():
            self.plot = ui.BokehWidget.from_plot(p1)
            self.freq = flx.Slider(title='Frequency', max=2.0, value=1.0)

    @flx.reaction
    def update_plot(self):
        new_dict = {'x': x, 'y': np.sin(self.freq.value * x)}
        source.data = new_dict

        script, div = components(p1)
        script = '\n'.join(script.strip().split('\n')[1:-1])
        self.plot.set_plot_components(
            dict(script=script, div=div, id=self.plot.id))


if __name__ == '__main__':
    app = flx.App(Example)
    m = app.launch('chrome-browser')  # for use during development
    flx.run()

@almarklein
Copy link
Member

Yeah, I'm not sure if there is a way for Flexx to detect changes to source, but it's not done at the moment. You need to update the plot via Flexx' system. It could be worth considering adding an action to the BokehWidget to update the data...

@amitdeliwala
Copy link

You can create a separate JS component that updates the data source of the plot.

class BokehUpdater(flx.JsComponent):
    @flx.action
    def update_plot(self, bw, x_data, y_data):
        print(bw.plot)
        for ren in bw.plot.model.renderers.values():
            if ren.data_source:
                ds = ren.data_source
                break
        if ds:
            ds.data.x= x_data
            ds.dasta.y = y_data

            ds.change.emit()

ds.data is your datasource
Then from the Python side you can call:

self.bokeh_updater.update_plot(self.plot,x_data)

For your example above

class Example(app.PyComponent):
    def init(self):
        with ui.VSplit():
            self.plot = ui.BokehWidget.from_plot(p1)
            self.freq = flx.Slider(title='Frequency', max=2.0, value=1.0)
            self.bokeh_updater = BokehUpdater()

    @flx.reaction
    def update_plot(self):
        new_dict = {'x': x, 'y': np.sin(self.freq.value * x)}
        self.bokeh_updater.update_plot(self.plot,x, list(np.sin(self.freq.value * x)))

Hope this helps

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

3 participants