Bokeh layout changing and plot size incorrect after updating layout

I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.

When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.

I have created a simple example to show what I mean:

from numpy import random

from bokeh.plotting import figure, curdoc

from bokeh.layouts import row, widgetbox, column

from bokeh.models.widgets import Button, Slider

from bokeh.io import push_notebook, show, output_notebook

from bokeh.models.callbacks import CustomJS

output_notebook()

num_samples = 500

def bUpdate(var, val):

global num_samples

if var == “Number of Samples”:

num_samples = int(val)

replotBottom()

def bReCalc():

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p = figure(plot_width=COL1_W, plot_height=250)

p.circle(x, y, color=‘red’)

p.toolbar.logo = None

layout.children[0].children[0].children[0].children[1] = p

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))

p2.circle(x,y)

layout.children[0].children[0].children[1].children[0] = p2

push_notebook(handle=bokeh_handle)

def replotBottom():

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p2 = figure()

p2.circle(x,y)

p2.plot_width=COL1_W

p2.plot_height=COL1_W

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))

p3.circle(x,y,color=‘green’)

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

z = [random.random() for i in range(num_samples)]

p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)

p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)

p4.circle(x,y)

p5.circle(x,z)

layout.children[0].children[0].children[0].children[3]=p2

layout.children[0].children[0].children[1].children[1]=p3

layout.children[0].children[1].children[0] = p4

layout.children[0].children[1].children[1] = p5

push_notebook(handle=bokeh_handle)

cb = CustomJS(code="""

if (IPython.notebook.kernel !== undefined) {

var kernel = IPython.notebook.kernel;

cmd = “bUpdate(’” + cb_obj.title + “’,’” + cb_obj.value + “’)”;

kernel.execute(cmd, {}, {});

}

“”")

cbRC = CustomJS(code="""

if (IPython.notebook.kernel !== undefined) {

var kernel = IPython.notebook.kernel;

cmd = “bReCalc()”;

kernel.execute(cmd, {}, {});

}

“”")

TOTAL_WIDTH = 800

COL1_W = int(0.25*TOTAL_WIDTH)-5

COL2_W = int(0.75*TOTAL_WIDTH)-5

WID_W = int(0.5*COL1_W) - 15

btnCalc = Button(label=“Recalculate”, callback=cbRC, width=WID_W)

sldSamples = Slider(start=20, end=1000, step=10, title=“Number of Samples”, callback_policy=“mouseup”, callback=cb, width=WID_W)

layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \

sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \

column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \

figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode=“scale_height”)) \

), \

row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))

bokeh_handle = show(layout, notebook_handle=True)

When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call “show(layout)” in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?

==EDIT==

The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

output_notebook()

def modify_doc(doc):

num_samples = 500

def sampChange(attr, old, new):

global num_samples

num_samples = int(new)

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p2 = figure()

p2.circle(x,y)

p2.plot_width=COL1_W

p2.plot_height=COL1_W

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))

p3.circle(x,y,color=‘green’)

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

z = [random.random() for i in range(num_samples)]

p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)

p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)

p4.circle(x,y)

p5.circle(x,z)

layout.children[0].children[1] = row(p4,p5)

layout.children[0].children[0].children[0].children[3]=p2

layout.children[0].children[0].children[1].children[1]=p3

#layout.children[0].children[1].children[1] = p5

#doc.add_root(layout)

def bReCalc():

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p = figure(plot_width=COL1_W, plot_height=250)

p.circle(x, y, color=‘red’)

p.toolbar.logo = None

layout.children[0].children[0].children[0].children[1] = p

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))

p2.circle(x,y)

layout.children[0].children[0].children[1].children[0] = p2

#doc.add_root(layout)

#push_notebook(handle=bokeh_handle)

TOTAL_WIDTH = 800

COL1_W = int(0.25*TOTAL_WIDTH)-5

COL2_W = int(0.75*TOTAL_WIDTH)-5

WID_W = int(0.5*COL1_W) - 15

btnCalc = Button(label=“Recalculate”, width=WID_W)

sldSamples = Slider(start=20, end=500, step=10, title=“Number of Samples”, callback_policy=“mouseup”, width=WID_W)

btnCalc.on_click(bReCalc)

sldSamples.on_change(‘value’, sampChange)

layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \

sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \

column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \

figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \

), \

row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))

doc.add_root(layout)

bReCalc()

return doc

from bokeh.application.handlers import FunctionHandler

from bokeh.application import Application

handler = FunctionHandler(modify_doc)

app = Application(handler)

show(app)

Hi,

My first suggestion is to not replace plots, if you can possibly avoid it. Not only is this more expensive, it probably pushes up against boundaries and corner cases of Bokeh's automatic synchronization capability. The most well-used and understood (as well as more efficient) method is to create your plots once, up front, then update only their data when they need to change. You can see an example of this in the sliders demo:

  https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66

It's possible that updating existing plots instead of replacing plots will immediately solve the layout issue as well. Bokeh's layout capability is ambitious and complicated. It works well in a number of situations but I can easily believe there are update bugs that might be exposed when replacing complicated composite models like entire plots deep in a nested layout.

Thanks,

Bryan

···

On Sep 1, 2017, at 10:45, [email protected] wrote:

I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.

When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.

I have created a simple example to show what I mean:

    from numpy import random
    from bokeh.plotting import figure, curdoc
    from bokeh.layouts import row, widgetbox, column
    from bokeh.models.widgets import Button, Slider
    from bokeh.io import push_notebook, show, output_notebook
    from bokeh.models.callbacks import CustomJS
    output_notebook()
    num_samples = 500
    def bUpdate(var, val):
        global num_samples
        if var == "Number of Samples":
            num_samples = int(val)
            replotBottom()
    def bReCalc():
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p = figure(plot_width=COL1_W, plot_height=250)
        p.circle(x, y, color='red')
        p.toolbar.logo = None
        layout.children[0].children[0].children[0].children[1] = p
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
        p2.circle(x,y)
        layout.children[0].children[0].children[1].children[0] = p2
        push_notebook(handle=bokeh_handle)
    def replotBottom():
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure()
        p2.circle(x,y)
        p2.plot_width=COL1_W
        p2.plot_height=COL1_W
    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
        p3.circle(x,y,color='green')
        
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        z = [random.random() for i in range(num_samples)]
        p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p4.circle(x,y)
        p5.circle(x,z)
                
        layout.children[0].children[0].children[0].children[3]=p2
        layout.children[0].children[0].children[1].children[1]=p3
        layout.children[0].children[1].children[0] = p4
        layout.children[0].children[1].children[1] = p5
        push_notebook(handle=bokeh_handle)
    
    cb = CustomJS(code="""
    if (IPython.notebook.kernel !== undefined) {
        var kernel = IPython.notebook.kernel;
        cmd = "bUpdate('" + cb_obj.title + "','" + cb_obj.value + "')";
        kernel.execute(cmd, {}, {});
    }
    """)
    
    cbRC = CustomJS(code="""
    if (IPython.notebook.kernel !== undefined) {
        var kernel = IPython.notebook.kernel;
        cmd = "bReCalc()";
        kernel.execute(cmd, {}, {});
    }
    """)
    
    TOTAL_WIDTH = 800
    COL1_W = int(0.25*TOTAL_WIDTH)-5
    COL2_W = int(0.75*TOTAL_WIDTH)-5
    WID_W = int(0.5*COL1_W) - 15
    btnCalc = Button(label="Recalculate", callback=cbRC, width=WID_W)
    sldSamples = Slider(start=20, end=1000, step=10, title="Number of Samples", callback_policy="mouseup", callback=cb, width=WID_W)
    
    layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                                   sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                            column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                                   figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode="scale_height")) \
                          ), \
                       row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
    bokeh_handle = show(layout, notebook_handle=True)

When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call "show(layout)" in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?

==EDIT==

The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

    output_notebook()
    def modify_doc(doc):
        num_samples = 500
        def sampChange(attr, old, new):
            global num_samples
            num_samples = int(new)
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            p2 = figure()
            p2.circle(x,y)
            p2.plot_width=COL1_W
            p2.plot_height=COL1_W
    
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
            p3.circle(x,y,color='green')
    
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            z = [random.random() for i in range(num_samples)]
            p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
            p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
            p4.circle(x,y)
            p5.circle(x,z)
    
            layout.children[0].children[1] = row(p4,p5)
            layout.children[0].children[0].children[0].children[3]=p2
            layout.children[0].children[0].children[1].children[1]=p3
            #layout.children[0].children[1].children[1] = p5
            #doc.add_root(layout)
        def bReCalc():
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            p = figure(plot_width=COL1_W, plot_height=250)
            p.circle(x, y, color='red')
            p.toolbar.logo = None
            layout.children[0].children[0].children[0].children[1] = p
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
            p2.circle(x,y)
            layout.children[0].children[0].children[1].children[0] = p2
            #doc.add_root(layout)
            #push_notebook(handle=bokeh_handle)
    
        TOTAL_WIDTH = 800
        COL1_W = int(0.25*TOTAL_WIDTH)-5
        COL2_W = int(0.75*TOTAL_WIDTH)-5
        WID_W = int(0.5*COL1_W) - 15
        btnCalc = Button(label="Recalculate", width=WID_W)
        sldSamples = Slider(start=20, end=500, step=10, title="Number of Samples", callback_policy="mouseup", width=WID_W)
        btnCalc.on_click(bReCalc)
        sldSamples.on_change('value', sampChange)
        layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                                       sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                                column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                                       figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \
                              ), \
                           row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
        doc.add_root(layout)
        bReCalc()
        return doc

    from bokeh.application.handlers import FunctionHandler
    from bokeh.application import Application
    handler = FunctionHandler(modify_doc)
    app = Application(handler)
    show(app)

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/ce8e5e96-564b-472a-92ea-3a999c7acb9b%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Hi Bryan,

I realize that this method of replacing the plots is not ideal. But, for the actual application, I have much more complicated plots that don’t have a single data source, so I’m not sure using a simple dictionary is enough to generate one of the plots, since I manually add several text and plot annotations that depend also on the source data.

Is there a different way to hook into the figure data to clear these annotations and redraw them possibly?

Thanks,

Grant

···

On Friday, September 1, 2017 at 11:54:43 AM UTC-4, Bryan Van de ven wrote:

Hi,

My first suggestion is to not replace plots, if you can possibly avoid it. Not only is this more expensive, it probably pushes up against boundaries and corner cases of Bokeh’s automatic synchronization capability. The most well-used and understood (as well as more efficient) method is to create your plots once, up front, then update only their data when they need to change. You can see an example of this in the sliders demo:

    [https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66](https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66)

It’s possible that updating existing plots instead of replacing plots will immediately solve the layout issue as well. Bokeh’s layout capability is ambitious and complicated. It works well in a number of situations but I can easily believe there are update bugs that might be exposed when replacing complicated composite models like entire plots deep in a nested layout.

Thanks,

Bryan

On Sep 1, 2017, at 10:45, [email protected] wrote:

I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.

When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.

I have created a simple example to show what I mean:

from numpy import random
from bokeh.plotting import figure, curdoc
from bokeh.layouts import row, widgetbox, column
from bokeh.models.widgets import Button, Slider
from [bokeh.io](http://bokeh.io) import push_notebook, show, output_notebook
from bokeh.models.callbacks import CustomJS
output_notebook()
num_samples = 500
def bUpdate(var, val):
    global num_samples
    if var == "Number of Samples":
        num_samples = int(val)
        replotBottom()              
def bReCalc():
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p = figure(plot_width=COL1_W, plot_height=250)
    p.circle(x, y, color='red')
    p.toolbar.logo = None
    layout.children[0].children[0].children[0].children[1] = p
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
    p2.circle(x,y)
    layout.children[0].children[0].children[1].children[0] = p2
    push_notebook(handle=bokeh_handle)
def replotBottom():    
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p2 = figure()
    p2.circle(x,y)
    p2.plot_width=COL1_W
    p2.plot_height=COL1_W    
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
    p3.circle(x,y,color='green')
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    z = [random.random() for i in range(num_samples)]
    p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
    p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
    p4.circle(x,y)
    p5.circle(x,z)
    layout.children[0].children[0].children[0].children[3]=p2
    layout.children[0].children[0].children[1].children[1]=p3
    layout.children[0].children[1].children[0] = p4
    layout.children[0].children[1].children[1] = p5
    push_notebook(handle=bokeh_handle)
cb = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "bUpdate('" + cb_obj.title + "','" + cb_obj.value + "')";
    kernel.execute(cmd, {}, {});
}
""")
cbRC = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "bReCalc()";
    kernel.execute(cmd, {}, {});
}
""")
TOTAL_WIDTH = 800
COL1_W = int(0.25*TOTAL_WIDTH)-5
COL2_W = int(0.75*TOTAL_WIDTH)-5
WID_W = int(0.5*COL1_W) - 15
btnCalc = Button(label="Recalculate", callback=cbRC, width=WID_W)
sldSamples = Slider(start=20, end=1000, step=10, title="Number of Samples", callback_policy="mouseup", callback=cb, width=WID_W)
layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                               sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                        column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                               figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode="scale_height")) \
                      ), \
                   row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
bokeh_handle = show(layout, notebook_handle=True)

When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call “show(layout)” in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?

==EDIT==

The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

output_notebook()
def modify_doc(doc):
    num_samples = 500
    def sampChange(attr, old, new):
        global num_samples
        num_samples = int(new)    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure()
        p2.circle(x,y)
        p2.plot_width=COL1_W
        p2.plot_height=COL1_W    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
        p3.circle(x,y,color='green')
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        z = [random.random() for i in range(num_samples)]
        p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p4.circle(x,y)
        p5.circle(x,z)
        layout.children[0].children[1] = row(p4,p5)
        layout.children[0].children[0].children[0].children[3]=p2
        layout.children[0].children[0].children[1].children[1]=p3
        #layout.children[0].children[1].children[1] = p5
        #doc.add_root(layout)
    def bReCalc():
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p = figure(plot_width=COL1_W, plot_height=250)
        p.circle(x, y, color='red')
        p.toolbar.logo = None
        layout.children[0].children[0].children[0].children[1] = p
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
        p2.circle(x,y)
        layout.children[0].children[0].children[1].children[0] = p2
        #doc.add_root(layout)
        #push_notebook(handle=bokeh_handle)
    TOTAL_WIDTH = 800
    COL1_W = int(0.25*TOTAL_WIDTH)-5
    COL2_W = int(0.75*TOTAL_WIDTH)-5
    WID_W = int(0.5*COL1_W) - 15
    btnCalc = Button(label="Recalculate", width=WID_W)
    sldSamples = Slider(start=20, end=500, step=10, title="Number of Samples", callback_policy="mouseup", width=WID_W)
    btnCalc.on_click(bReCalc)
    sldSamples.on_change('value', sampChange)
    layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                                   sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                            column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                                   figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \
                          ), \
                       row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
    doc.add_root(layout)
    bReCalc()
    return doc
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
handler = FunctionHandler(modify_doc)
app = Application(handler)
show(app)


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/ce8e5e96-564b-472a-92ea-3a999c7acb9b%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Hi,

I didn't mean to imply that it should or needed to be only a single dictionary or data source. You many have many data sources, which is fine. My point is only that you should attempt to update the plot by updating the data sources (however many there are) rather than replacing entire plots.

Here is another example, that replaces several data sources to update multiple plots at once, whenever a selection is made:

  https://github.com/bokeh/bokeh/blob/master/examples/app/selection_histogram.py

Thanks,

Bryan

···

On Sep 1, 2017, at 10:58, [email protected] wrote:

Hi Bryan,

I realize that this method of replacing the plots is not ideal. But, for the actual application, I have much more complicated plots that don't have a single data source, so I'm not sure using a simple dictionary is enough to generate one of the plots, since I manually add several text and plot annotations that depend also on the source data.

Is there a different way to hook into the figure data to clear these annotations and redraw them possibly?

Thanks,
Grant

On Friday, September 1, 2017 at 11:54:43 AM UTC-4, Bryan Van de ven wrote:
Hi,

My first suggestion is to not replace plots, if you can possibly avoid it. Not only is this more expensive, it probably pushes up against boundaries and corner cases of Bokeh's automatic synchronization capability. The most well-used and understood (as well as more efficient) method is to create your plots once, up front, then update only their data when they need to change. You can see an example of this in the sliders demo:

        https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66

It's possible that updating existing plots instead of replacing plots will immediately solve the layout issue as well. Bokeh's layout capability is ambitious and complicated. It works well in a number of situations but I can easily believe there are update bugs that might be exposed when replacing complicated composite models like entire plots deep in a nested layout.

Thanks,

Bryan

> On Sep 1, 2017, at 10:45, [email protected] wrote:
>
> I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.
>
> When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.
>
> I have created a simple example to show what I mean:
>
> from numpy import random
> from bokeh.plotting import figure, curdoc
> from bokeh.layouts import row, widgetbox, column
> from bokeh.models.widgets import Button, Slider
> from bokeh.io import push_notebook, show, output_notebook
> from bokeh.models.callbacks import CustomJS
> output_notebook()
> num_samples = 500
> def bUpdate(var, val):
> global num_samples
> if var == "Number of Samples":
> num_samples = int(val)
> replotBottom()
> def bReCalc():
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> p = figure(plot_width=COL1_W, plot_height=250)
> p.circle(x, y, color='red')
> p.toolbar.logo = None
> layout.children[0].children[0].children[0].children[1] = p
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
> p2.circle(x,y)
> layout.children[0].children[0].children[1].children[0] = p2
> push_notebook(handle=bokeh_handle)
> def replotBottom():
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> p2 = figure()
> p2.circle(x,y)
> p2.plot_width=COL1_W
> p2.plot_height=COL1_W
>
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
> p3.circle(x,y,color='green')
>
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> z = [random.random() for i in range(num_samples)]
> p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
> p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
> p4.circle(x,y)
> p5.circle(x,z)
>
> layout.children[0].children[0].children[0].children[3]=p2
> layout.children[0].children[0].children[1].children[1]=p3
> layout.children[0].children[1].children[0] = p4
> layout.children[0].children[1].children[1] = p5
> push_notebook(handle=bokeh_handle)
>
> cb = CustomJS(code="""
> if (IPython.notebook.kernel !== undefined) {
> var kernel = IPython.notebook.kernel;
> cmd = "bUpdate('" + cb_obj.title + "','" + cb_obj.value + "')";
> kernel.execute(cmd, {}, {});
> }
> """)
>
> cbRC = CustomJS(code="""
> if (IPython.notebook.kernel !== undefined) {
> var kernel = IPython.notebook.kernel;
> cmd = "bReCalc()";
> kernel.execute(cmd, {}, {});
> }
> """)
>
> TOTAL_WIDTH = 800
> COL1_W = int(0.25*TOTAL_WIDTH)-5
> COL2_W = int(0.75*TOTAL_WIDTH)-5
> WID_W = int(0.5*COL1_W) - 15
> btnCalc = Button(label="Recalculate", callback=cbRC, width=WID_W)
> sldSamples = Slider(start=20, end=1000, step=10, title="Number of Samples", callback_policy="mouseup", callback=cb, width=WID_W)
>
> layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
> sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
> column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
> figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode="scale_height")) \
> ), \
> row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
> bokeh_handle = show(layout, notebook_handle=True)
>
> When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call "show(layout)" in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?
>
> ==EDIT==
>
> The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
>
> output_notebook()
> def modify_doc(doc):
> num_samples = 500
> def sampChange(attr, old, new):
> global num_samples
> num_samples = int(new)
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> p2 = figure()
> p2.circle(x,y)
> p2.plot_width=COL1_W
> p2.plot_height=COL1_W
>
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
> p3.circle(x,y,color='green')
>
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> z = [random.random() for i in range(num_samples)]
> p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
> p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
> p4.circle(x,y)
> p5.circle(x,z)
>
> layout.children[0].children[1] = row(p4,p5)
> layout.children[0].children[0].children[0].children[3]=p2
> layout.children[0].children[0].children[1].children[1]=p3
> #layout.children[0].children[1].children[1] = p5
> #doc.add_root(layout)
> def bReCalc():
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> p = figure(plot_width=COL1_W, plot_height=250)
> p.circle(x, y, color='red')
> p.toolbar.logo = None
> layout.children[0].children[0].children[0].children[1] = p
> x = [random.random() for i in range(num_samples)]
> y = [random.random() for i in range(num_samples)]
> p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
> p2.circle(x,y)
> layout.children[0].children[0].children[1].children[0] = p2
> #doc.add_root(layout)
> #push_notebook(handle=bokeh_handle)
>
> TOTAL_WIDTH = 800
> COL1_W = int(0.25*TOTAL_WIDTH)-5
> COL2_W = int(0.75*TOTAL_WIDTH)-5
> WID_W = int(0.5*COL1_W) - 15
> btnCalc = Button(label="Recalculate", width=WID_W)
> sldSamples = Slider(start=20, end=500, step=10, title="Number of Samples", callback_policy="mouseup", width=WID_W)
> btnCalc.on_click(bReCalc)
> sldSamples.on_change('value', sampChange)
> layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
> sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
> column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
> figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \
> ), \
> row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
> doc.add_root(layout)
> bReCalc()
> return doc
>
> from bokeh.application.handlers import FunctionHandler
> from bokeh.application import Application
> handler = FunctionHandler(modify_doc)
> app = Application(handler)
> show(app)
>
> --
> You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
> To post to this group, send email to [email protected].
> To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/ce8e5e96-564b-472a-92ea-3a999c7acb9b%40continuum.io.
> For more options, visit https://groups.google.com/a/continuum.io/d/optout.

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/18cc171f-8dc0-4817-81d7-2acd3bed00cf%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

So I was able to get the toy example to work by using ColumnDataSources, but I’m running into issues with trying to do real data. I have a plot that consists of several smaller plots worth of data that has been concatenated to display as a segment plot. The source data comes from several circular regions that I place side by side, so currently, I add a line plot to the figure in a loop to draw a circle around each region. I can’t think of a way to do that with data sources, since the NUMBER of sources will change depending on the number of plots I am displaying in the same figure. Any idea how to get around that?

···

On Friday, September 1, 2017 at 12:05:46 PM UTC-4, Bryan Van de ven wrote:

Hi,

I didn’t mean to imply that it should or needed to be only a single dictionary or data source. You many have many data sources, which is fine. My point is only that you should attempt to update the plot by updating the data sources (however many there are) rather than replacing entire plots.

Here is another example, that replaces several data sources to update multiple plots at once, whenever a selection is made:

    [https://github.com/bokeh/bokeh/blob/master/examples/app/selection_histogram.py](https://github.com/bokeh/bokeh/blob/master/examples/app/selection_histogram.py)

Thanks,

Bryan

On Sep 1, 2017, at 10:58, [email protected] wrote:

Hi Bryan,

I realize that this method of replacing the plots is not ideal. But, for the actual application, I have much more complicated plots that don’t have a single data source, so I’m not sure using a simple dictionary is enough to generate one of the plots, since I manually add several text and plot annotations that depend also on the source data.

Is there a different way to hook into the figure data to clear these annotations and redraw them possibly?

Thanks,

Grant

On Friday, September 1, 2017 at 11:54:43 AM UTC-4, Bryan Van de ven wrote:

Hi,

My first suggestion is to not replace plots, if you can possibly avoid it. Not only is this more expensive, it probably pushes up against boundaries and corner cases of Bokeh’s automatic synchronization capability. The most well-used and understood (as well as more efficient) method is to create your plots once, up front, then update only their data when they need to change. You can see an example of this in the sliders demo:

    [https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66](https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66)

It’s possible that updating existing plots instead of replacing plots will immediately solve the layout issue as well. Bokeh’s layout capability is ambitious and complicated. It works well in a number of situations but I can easily believe there are update bugs that might be exposed when replacing complicated composite models like entire plots deep in a nested layout.

Thanks,

Bryan

On Sep 1, 2017, at 10:45, [email protected] wrote:

I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.

When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.

I have created a simple example to show what I mean:

from numpy import random
from bokeh.plotting import figure, curdoc
from bokeh.layouts import row, widgetbox, column
from bokeh.models.widgets import Button, Slider
from [bokeh.io](http://bokeh.io) import push_notebook, show, output_notebook
from bokeh.models.callbacks import CustomJS
output_notebook()
num_samples = 500
def bUpdate(var, val):
    global num_samples
    if var == "Number of Samples":
        num_samples = int(val)
        replotBottom()              
def bReCalc():
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p = figure(plot_width=COL1_W, plot_height=250)
    p.circle(x, y, color='red')
    p.toolbar.logo = None
    layout.children[0].children[0].children[0].children[1] = p
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
    p2.circle(x,y)
    layout.children[0].children[0].children[1].children[0] = p2
    push_notebook(handle=bokeh_handle)
def replotBottom():    
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p2 = figure()
    p2.circle(x,y)
    p2.plot_width=COL1_W
    p2.plot_height=COL1_W    

    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
    p3.circle(x,y,color='green')
   
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    z = [random.random() for i in range(num_samples)]
    p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
    p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
    p4.circle(x,y)
    p5.circle(x,z)
           
    layout.children[0].children[0].children[0].children[3]=p2
    layout.children[0].children[0].children[1].children[1]=p3
    layout.children[0].children[1].children[0] = p4
    layout.children[0].children[1].children[1] = p5
    push_notebook(handle=bokeh_handle)

cb = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "bUpdate('" + cb_obj.title + "','" + cb_obj.value + "')";
    kernel.execute(cmd, {}, {});
}
""")

cbRC = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "bReCalc()";
    kernel.execute(cmd, {}, {});
}
""")

TOTAL_WIDTH = 800
COL1_W = int(0.25*TOTAL_WIDTH)-5
COL2_W = int(0.75*TOTAL_WIDTH)-5
WID_W = int(0.5*COL1_W) - 15
btnCalc = Button(label="Recalculate", callback=cbRC, width=WID_W)
sldSamples = Slider(start=20, end=1000, step=10, title="Number of Samples", callback_policy="mouseup", callback=cb, width=WID_W)

layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                               sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                        column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                               figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode="scale_height")) \
                      ), \
                   row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
bokeh_handle = show(layout, notebook_handle=True)

When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call “show(layout)” in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?

==EDIT==

The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

output_notebook()
def modify_doc(doc):
    num_samples = 500
    def sampChange(attr, old, new):
        global num_samples
        num_samples = int(new)    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure()
        p2.circle(x,y)
        p2.plot_width=COL1_W
        p2.plot_height=COL1_W    

        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
        p3.circle(x,y,color='green')

        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        z = [random.random() for i in range(num_samples)]
        p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p4.circle(x,y)
        p5.circle(x,z)

        layout.children[0].children[1] = row(p4,p5)
        layout.children[0].children[0].children[0].children[3]=p2
        layout.children[0].children[0].children[1].children[1]=p3
        #layout.children[0].children[1].children[1] = p5
        #doc.add_root(layout)
    def bReCalc():
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p = figure(plot_width=COL1_W, plot_height=250)
        p.circle(x, y, color='red')
        p.toolbar.logo = None
        layout.children[0].children[0].children[0].children[1] = p
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
        p2.circle(x,y)
        layout.children[0].children[0].children[1].children[0] = p2
        #doc.add_root(layout)
        #push_notebook(handle=bokeh_handle)

    TOTAL_WIDTH = 800
    COL1_W = int(0.25*TOTAL_WIDTH)-5
    COL2_W = int(0.75*TOTAL_WIDTH)-5
    WID_W = int(0.5*COL1_W) - 15
    btnCalc = Button(label="Recalculate", width=WID_W)
    sldSamples = Slider(start=20, end=500, step=10, title="Number of Samples", callback_policy="mouseup", width=WID_W)
    btnCalc.on_click(bReCalc)
    sldSamples.on_change('value', sampChange)
    layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                                   sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                            column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                                   figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \
                          ), \
                       row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
    doc.add_root(layout)
    bReCalc()
    return doc

from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
handler = FunctionHandler(modify_doc)
app = Application(handler)
show(app)


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/ce8e5e96-564b-472a-92ea-3a999c7acb9b%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/18cc171f-8dc0-4817-81d7-2acd3bed00cf%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Hi,

MultiLine (or perhaps Patches, if you want the regions to auto-close) lets you draw multiple lines with a single glyph and data source:

  http://bokeh.pydata.org/en/latest/docs/user_guide/plotting.html#multiple-lines:

So you can change the number of loops drawn by changing the data in one data source.

Thanks,

Bryan

···

On Sep 1, 2017, at 12:25, [email protected] wrote:

So I was able to get the toy example to work by using ColumnDataSources, but I'm running into issues with trying to do real data. I have a plot that consists of several smaller plots worth of data that has been concatenated to display as a segment plot. The source data comes from several circular regions that I place side by side, so currently, I add a line plot to the figure in a loop to draw a circle around each region. I can't think of a way to do that with data sources, since the NUMBER of sources will change depending on the number of plots I am displaying in the same figure. Any idea how to get around that?

On Friday, September 1, 2017 at 12:05:46 PM UTC-4, Bryan Van de ven wrote:
Hi,

I didn't mean to imply that it should or needed to be only a single dictionary or data source. You many have many data sources, which is fine. My point is only that you should attempt to update the plot by updating the data sources (however many there are) rather than replacing entire plots.

Here is another example, that replaces several data sources to update multiple plots at once, whenever a selection is made:

        https://github.com/bokeh/bokeh/blob/master/examples/app/selection_histogram.py

Thanks,

Bryan

> On Sep 1, 2017, at 10:58, [email protected] wrote:
>
> Hi Bryan,
>
> I realize that this method of replacing the plots is not ideal. But, for the actual application, I have much more complicated plots that don't have a single data source, so I'm not sure using a simple dictionary is enough to generate one of the plots, since I manually add several text and plot annotations that depend also on the source data.
>
> Is there a different way to hook into the figure data to clear these annotations and redraw them possibly?
>
> Thanks,
> Grant
>
> On Friday, September 1, 2017 at 11:54:43 AM UTC-4, Bryan Van de ven wrote:
> Hi,
>
> My first suggestion is to not replace plots, if you can possibly avoid it. Not only is this more expensive, it probably pushes up against boundaries and corner cases of Bokeh's automatic synchronization capability. The most well-used and understood (as well as more efficient) method is to create your plots once, up front, then update only their data when they need to change. You can see an example of this in the sliders demo:
>
> https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66
>
> It's possible that updating existing plots instead of replacing plots will immediately solve the layout issue as well. Bokeh's layout capability is ambitious and complicated. It works well in a number of situations but I can easily believe there are update bugs that might be exposed when replacing complicated composite models like entire plots deep in a nested layout.
>
> Thanks,
>
> Bryan
>
>
> > On Sep 1, 2017, at 10:45, [email protected] wrote:
> >
> > I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.
> >
> > When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.
> >
> > I have created a simple example to show what I mean:
> >
> > from numpy import random
> > from bokeh.plotting import figure, curdoc
> > from bokeh.layouts import row, widgetbox, column
> > from bokeh.models.widgets import Button, Slider
> > from bokeh.io import push_notebook, show, output_notebook
> > from bokeh.models.callbacks import CustomJS
> > output_notebook()
> > num_samples = 500
> > def bUpdate(var, val):
> > global num_samples
> > if var == "Number of Samples":
> > num_samples = int(val)
> > replotBottom()
> > def bReCalc():
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > p = figure(plot_width=COL1_W, plot_height=250)
> > p.circle(x, y, color='red')
> > p.toolbar.logo = None
> > layout.children[0].children[0].children[0].children[1] = p
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
> > p2.circle(x,y)
> > layout.children[0].children[0].children[1].children[0] = p2
> > push_notebook(handle=bokeh_handle)
> > def replotBottom():
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > p2 = figure()
> > p2.circle(x,y)
> > p2.plot_width=COL1_W
> > p2.plot_height=COL1_W
> >
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
> > p3.circle(x,y,color='green')
> >
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > z = [random.random() for i in range(num_samples)]
> > p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
> > p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
> > p4.circle(x,y)
> > p5.circle(x,z)
> >
> > layout.children[0].children[0].children[0].children[3]=p2
> > layout.children[0].children[0].children[1].children[1]=p3
> > layout.children[0].children[1].children[0] = p4
> > layout.children[0].children[1].children[1] = p5
> > push_notebook(handle=bokeh_handle)
> >
> > cb = CustomJS(code="""
> > if (IPython.notebook.kernel !== undefined) {
> > var kernel = IPython.notebook.kernel;
> > cmd = "bUpdate('" + cb_obj.title + "','" + cb_obj.value + "')";
> > kernel.execute(cmd, {}, {});
> > }
> > """)
> >
> > cbRC = CustomJS(code="""
> > if (IPython.notebook.kernel !== undefined) {
> > var kernel = IPython.notebook.kernel;
> > cmd = "bReCalc()";
> > kernel.execute(cmd, {}, {});
> > }
> > """)
> >
> > TOTAL_WIDTH = 800
> > COL1_W = int(0.25*TOTAL_WIDTH)-5
> > COL2_W = int(0.75*TOTAL_WIDTH)-5
> > WID_W = int(0.5*COL1_W) - 15
> > btnCalc = Button(label="Recalculate", callback=cbRC, width=WID_W)
> > sldSamples = Slider(start=20, end=1000, step=10, title="Number of Samples", callback_policy="mouseup", callback=cb, width=WID_W)
> >
> > layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
> > sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
> > column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
> > figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode="scale_height")) \
> > ), \
> > row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
> > bokeh_handle = show(layout, notebook_handle=True)
> >
> > When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call "show(layout)" in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?
> >
> > ==EDIT==
> >
> > The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
> >
> > output_notebook()
> > def modify_doc(doc):
> > num_samples = 500
> > def sampChange(attr, old, new):
> > global num_samples
> > num_samples = int(new)
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > p2 = figure()
> > p2.circle(x,y)
> > p2.plot_width=COL1_W
> > p2.plot_height=COL1_W
> >
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
> > p3.circle(x,y,color='green')
> >
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > z = [random.random() for i in range(num_samples)]
> > p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
> > p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
> > p4.circle(x,y)
> > p5.circle(x,z)
> >
> > layout.children[0].children[1] = row(p4,p5)
> > layout.children[0].children[0].children[0].children[3]=p2
> > layout.children[0].children[0].children[1].children[1]=p3
> > #layout.children[0].children[1].children[1] = p5
> > #doc.add_root(layout)
> > def bReCalc():
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > p = figure(plot_width=COL1_W, plot_height=250)
> > p.circle(x, y, color='red')
> > p.toolbar.logo = None
> > layout.children[0].children[0].children[0].children[1] = p
> > x = [random.random() for i in range(num_samples)]
> > y = [random.random() for i in range(num_samples)]
> > p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
> > p2.circle(x,y)
> > layout.children[0].children[0].children[1].children[0] = p2
> > #doc.add_root(layout)
> > #push_notebook(handle=bokeh_handle)
> >
> > TOTAL_WIDTH = 800
> > COL1_W = int(0.25*TOTAL_WIDTH)-5
> > COL2_W = int(0.75*TOTAL_WIDTH)-5
> > WID_W = int(0.5*COL1_W) - 15
> > btnCalc = Button(label="Recalculate", width=WID_W)
> > sldSamples = Slider(start=20, end=500, step=10, title="Number of Samples", callback_policy="mouseup", width=WID_W)
> > btnCalc.on_click(bReCalc)
> > sldSamples.on_change('value', sampChange)
> > layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
> > sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
> > column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
> > figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \
> > ), \
> > row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
> > doc.add_root(layout)
> > bReCalc()
> > return doc
> >
> > from bokeh.application.handlers import FunctionHandler
> > from bokeh.application import Application
> > handler = FunctionHandler(modify_doc)
> > app = Application(handler)
> > show(app)
> >
> > --
> > You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
> > To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
> > To post to this group, send email to [email protected].
> > To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/ce8e5e96-564b-472a-92ea-3a999c7acb9b%40continuum.io.
> > For more options, visit https://groups.google.com/a/continuum.io/d/optout.
>
>
> --
> You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
> To post to this group, send email to [email protected].
> To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/18cc171f-8dc0-4817-81d7-2acd3bed00cf%40continuum.io.
> For more options, visit https://groups.google.com/a/continuum.io/d/optout.

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/d0f317d5-b866-4562-9227-a58b8cce7729%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Bryan,

Thanks for the quick reply. I was just about to comment that I was able to get it to work with patches.

Now the final hurdle I have is trying to plot a scatterplot where the color is defined by some sort of group. So I want to have for example 5 different colored circles appearing on the same plot with a legend). Currently, I just plot using p.circle() and pass an array of X,Y, colors, and sizes for each point, then I create the legend by plotting some additional text and labels using p.circle() p.text(). I can’t seem to find a good way to auto generate the legend from my (x,y,category) table.

This seems like it would require several different circle plots in the same figure, each with a legend defined, but the number of plots changes during the program execution, so I can’t create the plots at the beginning. Any ideas?

Thanks so much for all of the help,

Grant

···

On Friday, September 1, 2017 at 11:46:25 AM UTC-4, Grant England wrote:

I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.

When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.

I have created a simple example to show what I mean:

from numpy import random

from bokeh.plotting import figure, curdoc

from bokeh.layouts import row, widgetbox, column

from bokeh.models.widgets import Button, Slider

from bokeh.io import push_notebook, show, output_notebook

from bokeh.models.callbacks import CustomJS

output_notebook()

num_samples = 500

def bUpdate(var, val):

global num_samples

if var == “Number of Samples”:

num_samples = int(val)

replotBottom()

def bReCalc():

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p = figure(plot_width=COL1_W, plot_height=250)

p.circle(x, y, color=‘red’)

p.toolbar.logo = None

layout.children[0].children[0].children[0].children[1] = p

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))

p2.circle(x,y)

layout.children[0].children[0].children[1].children[0] = p2

push_notebook(handle=bokeh_handle)

def replotBottom():

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p2 = figure()

p2.circle(x,y)

p2.plot_width=COL1_W

p2.plot_height=COL1_W

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))

p3.circle(x,y,color=‘green’)

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

z = [random.random() for i in range(num_samples)]

p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)

p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)

p4.circle(x,y)

p5.circle(x,z)

layout.children[0].children[0].children[0].children[3]=p2

layout.children[0].children[0].children[1].children[1]=p3

layout.children[0].children[1].children[0] = p4

layout.children[0].children[1].children[1] = p5

push_notebook(handle=bokeh_handle)

cb = CustomJS(code="""

if (IPython.notebook.kernel !== undefined) {

var kernel = IPython.notebook.kernel;

cmd = “bUpdate(’” + cb_obj.title + “’,’” + cb_obj.value + “’)”;

kernel.execute(cmd, {}, {});

}

“”")

cbRC = CustomJS(code="""

if (IPython.notebook.kernel !== undefined) {

var kernel = IPython.notebook.kernel;

cmd = “bReCalc()”;

kernel.execute(cmd, {}, {});

}

“”")

TOTAL_WIDTH = 800

COL1_W = int(0.25*TOTAL_WIDTH)-5

COL2_W = int(0.75*TOTAL_WIDTH)-5

WID_W = int(0.5*COL1_W) - 15

btnCalc = Button(label=“Recalculate”, callback=cbRC, width=WID_W)

sldSamples = Slider(start=20, end=1000, step=10, title=“Number of Samples”, callback_policy=“mouseup”, callback=cb, width=WID_W)

layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \

sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \

column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \

figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode=“scale_height”)) \

), \

row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))

bokeh_handle = show(layout, notebook_handle=True)

When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call “show(layout)” in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?

==EDIT==

The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

output_notebook()

def modify_doc(doc):

num_samples = 500

def sampChange(attr, old, new):

global num_samples

num_samples = int(new)

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p2 = figure()

p2.circle(x,y)

p2.plot_width=COL1_W

p2.plot_height=COL1_W

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))

p3.circle(x,y,color=‘green’)

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

z = [random.random() for i in range(num_samples)]

p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)

p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)

p4.circle(x,y)

p5.circle(x,z)

layout.children[0].children[1] = row(p4,p5)

layout.children[0].children[0].children[0].children[3]=p2

layout.children[0].children[0].children[1].children[1]=p3

#layout.children[0].children[1].children[1] = p5

#doc.add_root(layout)

def bReCalc():

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p = figure(plot_width=COL1_W, plot_height=250)

p.circle(x, y, color=‘red’)

p.toolbar.logo = None

layout.children[0].children[0].children[0].children[1] = p

x = [random.random() for i in range(num_samples)]

y = [random.random() for i in range(num_samples)]

p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))

p2.circle(x,y)

layout.children[0].children[0].children[1].children[0] = p2

#doc.add_root(layout)

#push_notebook(handle=bokeh_handle)

TOTAL_WIDTH = 800

COL1_W = int(0.25*TOTAL_WIDTH)-5

COL2_W = int(0.75*TOTAL_WIDTH)-5

WID_W = int(0.5*COL1_W) - 15

btnCalc = Button(label=“Recalculate”, width=WID_W)

sldSamples = Slider(start=20, end=500, step=10, title=“Number of Samples”, callback_policy=“mouseup”, width=WID_W)

btnCalc.on_click(bReCalc)

sldSamples.on_change(‘value’, sampChange)

layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \

sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \

column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \

figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \

), \

row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))

doc.add_root(layout)

bReCalc()

return doc

from bokeh.application.handlers import FunctionHandler

from bokeh.application import Application

handler = FunctionHandler(modify_doc)

app = Application(handler)

show(app)

Hi,

The second example here (with the big orange and blue circles) may be useful:

  http://bokeh.pydata.org/en/latest/docs/user_guide/annotations.html#legends

Otherwise, if that does not serve your needs then the previous best approach is to have multiple circle glyphs with a subset of data for each color, as you say. However, as of 0.12.7 a new CDSView feature was added:

  http://bokeh.pydata.org/en/latest/docs/user_guide/data.html#filtering-data-with-cdsview

So I think instead of actually splitting the data up, you could drive different calls to circle (with different colors and legend labels) from s single CDS, but using a different view subset for each.

Bryan

···

On Sep 1, 2017, at 13:11, [email protected] wrote:

Bryan,

Thanks for the quick reply. I was just about to comment that I was able to get it to work with patches.

Now the final hurdle I have is trying to plot a scatterplot where the color is defined by some sort of group. So I want to have for example 5 different colored circles appearing on the same plot with a legend). Currently, I just plot using p.circle() and pass an array of X,Y, colors, and sizes for each point, then I create the legend by plotting some additional text and labels using p.circle() p.text(). I can't seem to find a good way to auto generate the legend from my (x,y,category) table.

This seems like it would require several different circle plots in the same figure, each with a legend defined, but the number of plots changes during the program execution, so I can't create the plots at the beginning. Any ideas?

Thanks so much for all of the help,
Grant

On Friday, September 1, 2017 at 11:46:25 AM UTC-4, Grant England wrote:
I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.

When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.

I have created a simple example to show what I mean:

    from numpy import random
    from bokeh.plotting import figure, curdoc
    from bokeh.layouts import row, widgetbox, column
    from bokeh.models.widgets import Button, Slider
    from bokeh.io import push_notebook, show, output_notebook
    from bokeh.models.callbacks import CustomJS
    output_notebook()
    num_samples = 500
    def bUpdate(var, val):
        global num_samples
        if var == "Number of Samples":
            num_samples = int(val)
            replotBottom()
    def bReCalc():
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p = figure(plot_width=COL1_W, plot_height=250)
        p.circle(x, y, color='red')
        p.toolbar.logo = None
        layout.children[0].children[0].children[0].children[1] = p
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
        p2.circle(x,y)
        layout.children[0].children[0].children[1].children[0] = p2
        push_notebook(handle=bokeh_handle)
    def replotBottom():
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure()
        p2.circle(x,y)
        p2.plot_width=COL1_W
        p2.plot_height=COL1_W
    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
        p3.circle(x,y,color='green')
        
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        z = [random.random() for i in range(num_samples)]
        p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p4.circle(x,y)
        p5.circle(x,z)
                
        layout.children[0].children[0].children[0].children[3]=p2
        layout.children[0].children[0].children[1].children[1]=p3
        layout.children[0].children[1].children[0] = p4
        layout.children[0].children[1].children[1] = p5
        push_notebook(handle=bokeh_handle)
    
    cb = CustomJS(code="""
    if (IPython.notebook.kernel !== undefined) {
        var kernel = IPython.notebook.kernel;
        cmd = "bUpdate('" + cb_obj.title + "','" + cb_obj.value + "')";
        kernel.execute(cmd, {}, {});
    }
    """)
    
    cbRC = CustomJS(code="""
    if (IPython.notebook.kernel !== undefined) {
        var kernel = IPython.notebook.kernel;
        cmd = "bReCalc()";
        kernel.execute(cmd, {}, {});
    }
    """)
    
    TOTAL_WIDTH = 800
    COL1_W = int(0.25*TOTAL_WIDTH)-5
    COL2_W = int(0.75*TOTAL_WIDTH)-5
    WID_W = int(0.5*COL1_W) - 15
    btnCalc = Button(label="Recalculate", callback=cbRC, width=WID_W)
    sldSamples = Slider(start=20, end=1000, step=10, title="Number of Samples", callback_policy="mouseup", callback=cb, width=WID_W)
    
    layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                                   sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                            column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                                   figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode="scale_height")) \
                          ), \
                       row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
    bokeh_handle = show(layout, notebook_handle=True)

When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call "show(layout)" in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?

==EDIT==

The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

    output_notebook()
    def modify_doc(doc):
        num_samples = 500
        def sampChange(attr, old, new):
            global num_samples
            num_samples = int(new)
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            p2 = figure()
            p2.circle(x,y)
            p2.plot_width=COL1_W
            p2.plot_height=COL1_W
    
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
            p3.circle(x,y,color='green')
    
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            z = [random.random() for i in range(num_samples)]
            p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
            p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
            p4.circle(x,y)
            p5.circle(x,z)
    
            layout.children[0].children[1] = row(p4,p5)
            layout.children[0].children[0].children[0].children[3]=p2
            layout.children[0].children[0].children[1].children[1]=p3
            #layout.children[0].children[1].children[1] = p5
            #doc.add_root(layout)
        def bReCalc():
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            p = figure(plot_width=COL1_W, plot_height=250)
            p.circle(x, y, color='red')
            p.toolbar.logo = None
            layout.children[0].children[0].children[0].children[1] = p
            x = [random.random() for i in range(num_samples)]
            y = [random.random() for i in range(num_samples)]
            p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
            p2.circle(x,y)
            layout.children[0].children[0].children[1].children[0] = p2
            #doc.add_root(layout)
            #push_notebook(handle=bokeh_handle)
    
        TOTAL_WIDTH = 800
        COL1_W = int(0.25*TOTAL_WIDTH)-5
        COL2_W = int(0.75*TOTAL_WIDTH)-5
        WID_W = int(0.5*COL1_W) - 15
        btnCalc = Button(label="Recalculate", width=WID_W)
        sldSamples = Slider(start=20, end=500, step=10, title="Number of Samples", callback_policy="mouseup", width=WID_W)
        btnCalc.on_click(bReCalc)
        sldSamples.on_change('value', sampChange)
        layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                                       sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                                column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                                       figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \
                              ), \
                           row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
        doc.add_root(layout)
        bReCalc()
        return doc

    from bokeh.application.handlers import FunctionHandler
    from bokeh.application import Application
    handler = FunctionHandler(modify_doc)
    app = Application(handler)
    show(app)

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/16e99e30-89c9-4b0a-8c89-3f8566707c0e%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Hi,
I have this kind of set-up (annotations that can’t use a data-source, legend update, etc…).
What I do in that case is the function that do the plotting actually returns a container of all elements that needs updates outside of the CDS + the main plot, then in the update function (which has access to this container), I perform the CDS’s dict update and the other elements individually.

I think as long as the structure of your plot is relatively static (always the same type, number of annotation), this works well (there’s one caveat previously mentioned in this forum or bitter in that the plot can flicker as redraw will happen for each CDS update if you have several and use DataRange axis)

Regards,

Alex

···

On Sep 1, 2017, at 23:58, [email protected] wrote:

Hi Bryan,

I realize that this method of replacing the plots is not ideal. But, for the actual application, I have much more complicated plots that don’t have a single data source, so I’m not sure using a simple dictionary is enough to generate one of the plots, since I manually add several text and plot annotations that depend also on the source data.

Is there a different way to hook into the figure data to clear these annotations and redraw them possibly?

Thanks,

Grant

On Friday, September 1, 2017 at 11:54:43 AM UTC-4, Bryan Van de ven wrote:

Hi,

My first suggestion is to not replace plots, if you can possibly avoid it. Not only is this more expensive, it probably pushes up against boundaries and corner cases of Bokeh’s automatic synchronization capability. The most well-used and understood (as well as more efficient) method is to create your plots once, up front, then update only their data when they need to change. You can see an example of this in the sliders demo:

    [https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66](https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66)

It’s possible that updating existing plots instead of replacing plots will immediately solve the layout issue as well. Bokeh’s layout capability is ambitious and complicated. It works well in a number of situations but I can easily believe there are update bugs that might be exposed when replacing complicated composite models like entire plots deep in a nested layout.

Thanks,

Bryan

On Sep 1, 2017, at 10:45, [email protected] wrote:

I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.

When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.

I have created a simple example to show what I mean:

from numpy import random
from bokeh.plotting import figure, curdoc
from bokeh.layouts import row, widgetbox, column
from bokeh.models.widgets import Button, Slider
from [bokeh.io](http://bokeh.io/) import push_notebook, show, output_notebook
from bokeh.models.callbacks import CustomJS
output_notebook()
num_samples = 500
def bUpdate(var, val):
    global num_samples
    if var == "Number of Samples":
        num_samples = int(val)
        replotBottom()              
def bReCalc():
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p = figure(plot_width=COL1_W, plot_height=250)
    p.circle(x, y, color='red')
    p.toolbar.logo = None
    layout.children[0].children[0].children[0].children[1] = p
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
    p2.circle(x,y)
    layout.children[0].children[0].children[1].children[0] = p2
    push_notebook(handle=bokeh_handle)
def replotBottom():    
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p2 = figure()
    p2.circle(x,y)
    p2.plot_width=COL1_W
    p2.plot_height=COL1_W    
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
    p3.circle(x,y,color='green')
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    z = [random.random() for i in range(num_samples)]
    p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
    p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
    p4.circle(x,y)
    p5.circle(x,z)
    layout.children[0].children[0].children[0].children[3]=p2
    layout.children[0].children[0].children[1].children[1]=p3
    layout.children[0].children[1].children[0] = p4
    layout.children[0].children[1].children[1] = p5
    push_notebook(handle=bokeh_handle)
cb = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "bUpdate('" + cb_obj.title + "','" + cb_obj.value + "')";
    kernel.execute(cmd, {}, {});
}
""")
cbRC = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "bReCalc()";
    kernel.execute(cmd, {}, {});
}
""")
TOTAL_WIDTH = 800
COL1_W = int(0.25*TOTAL_WIDTH)-5
COL2_W = int(0.75*TOTAL_WIDTH)-5
WID_W = int(0.5*COL1_W) - 15
btnCalc = Button(label="Recalculate", callback=cbRC, width=WID_W)
sldSamples = Slider(start=20, end=1000, step=10, title="Number of Samples", callback_policy="mouseup", callback=cb, width=WID_W)
layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                               sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                        column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                               figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode="scale_height")) \
                      ), \
                   row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
bokeh_handle = show(layout, notebook_handle=True)

When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call “show(layout)” in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?

==EDIT==

The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

output_notebook()
def modify_doc(doc):
    num_samples = 500
    def sampChange(attr, old, new):
        global num_samples
        num_samples = int(new)    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure()
        p2.circle(x,y)
        p2.plot_width=COL1_W
        p2.plot_height=COL1_W    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
        p3.circle(x,y,color='green')
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        z = [random.random() for i in range(num_samples)]
        p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p4.circle(x,y)
        p5.circle(x,z)
        layout.children[0].children[1] = row(p4,p5)
        layout.children[0].children[0].children[0].children[3]=p2
        layout.children[0].children[0].children[1].children[1]=p3
        #layout.children[0].children[1].children[1] = p5
        #doc.add_root(layout)
    def bReCalc():
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p = figure(plot_width=COL1_W, plot_height=250)
        p.circle(x, y, color='red')
        p.toolbar.logo = None
        layout.children[0].children[0].children[0].children[1] = p
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
        p2.circle(x,y)
        layout.children[0].children[0].children[1].children[0] = p2
        #doc.add_root(layout)
        #push_notebook(handle=bokeh_handle)
    TOTAL_WIDTH = 800
    COL1_W = int(0.25*TOTAL_WIDTH)-5
    COL2_W = int(0.75*TOTAL_WIDTH)-5
    WID_W = int(0.5*COL1_W) - 15
    btnCalc = Button(label="Recalculate", width=WID_W)
    sldSamples = Slider(start=20, end=500, step=10, title="Number of Samples", callback_policy="mouseup", width=WID_W)
    btnCalc.on_click(bReCalc)
    sldSamples.on_change('value', sampChange)
    layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                                   sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                            column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                                   figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \
                          ), \
                       row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
    doc.add_root(layout)
    bReCalc()
    return doc
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
handler = FunctionHandler(modify_doc)
app = Application(handler)
show(app)


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/ce8e5e96-564b-472a-92ea-3a999c7acb9b%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/18cc171f-8dc0-4817-81d7-2acd3bed00cf%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

I was able to get everything to work with this kind of update, except when I update the plot’s range. For some reason, the data will update when I update the data source, but when I specify a new range object (or try to update the current one), the axes range don’t actually change until I evaluate show(layout) in another cell. This seems similar to my initial problem.

···

On Saturday, September 2, 2017 at 1:15:38 AM UTC-4, Alexandre Avanian wrote:

Hi,
I have this kind of set-up (annotations that can’t use a data-source, legend update, etc…).
What I do in that case is the function that do the plotting actually returns a container of all elements that needs updates outside of the CDS + the main plot, then in the update function (which has access to this container), I perform the CDS’s dict update and the other elements individually.

I think as long as the structure of your plot is relatively static (always the same type, number of annotation), this works well (there’s one caveat previously mentioned in this forum or bitter in that the plot can flicker as redraw will happen for each CDS update if you have several and use DataRange axis)

Regards,

Alex

On Sep 1, 2017, at 23:58, [email protected] wrote:

Hi Bryan,

I realize that this method of replacing the plots is not ideal. But, for the actual application, I have much more complicated plots that don’t have a single data source, so I’m not sure using a simple dictionary is enough to generate one of the plots, since I manually add several text and plot annotations that depend also on the source data.

Is there a different way to hook into the figure data to clear these annotations and redraw them possibly?

Thanks,

Grant

On Friday, September 1, 2017 at 11:54:43 AM UTC-4, Bryan Van de ven wrote:

Hi,

My first suggestion is to not replace plots, if you can possibly avoid it. Not only is this more expensive, it probably pushes up against boundaries and corner cases of Bokeh’s automatic synchronization capability. The most well-used and understood (as well as more efficient) method is to create your plots once, up front, then update only their data when they need to change. You can see an example of this in the sliders demo:

    [https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66](https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L66)

It’s possible that updating existing plots instead of replacing plots will immediately solve the layout issue as well. Bokeh’s layout capability is ambitious and complicated. It works well in a number of situations but I can easily believe there are update bugs that might be exposed when replacing complicated composite models like entire plots deep in a nested layout.

Thanks,

Bryan

On Sep 1, 2017, at 10:45, [email protected] wrote:

I am trying to make a somewhat complex dashboard in bokeh with several different figure objects and widgets arranged in rows and columns.

When a user interacts with the widgets, some of the plots are updated, and some remain the same. I am creating new plot objects and replacing them with layout.children[i] = x and then sending a push_notebook to update the dashboard, but this causes some of the plots to resize to the minimum canvas size.

I have created a simple example to show what I mean:

from numpy import random
from bokeh.plotting import figure, curdoc
from bokeh.layouts import row, widgetbox, column
from bokeh.models.widgets import Button, Slider
from [bokeh.io](http://bokeh.io/) import push_notebook, show, output_notebook
from bokeh.models.callbacks import CustomJS
output_notebook()
num_samples = 500
def bUpdate(var, val):
    global num_samples
    if var == "Number of Samples":
        num_samples = int(val)
        replotBottom()              
def bReCalc():
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p = figure(plot_width=COL1_W, plot_height=250)
    p.circle(x, y, color='red')
    p.toolbar.logo = None
    layout.children[0].children[0].children[0].children[1] = p
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
    p2.circle(x,y)
    layout.children[0].children[0].children[1].children[0] = p2
    push_notebook(handle=bokeh_handle)
def replotBottom():    
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p2 = figure()
    p2.circle(x,y)
    p2.plot_width=COL1_W
    p2.plot_height=COL1_W    
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
    p3.circle(x,y,color='green')
    x = [random.random() for i in range(num_samples)]
    y = [random.random() for i in range(num_samples)]
    z = [random.random() for i in range(num_samples)]
    p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
    p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
    p4.circle(x,y)
    p5.circle(x,z)
    layout.children[0].children[0].children[0].children[3]=p2
    layout.children[0].children[0].children[1].children[1]=p3
    layout.children[0].children[1].children[0] = p4
    layout.children[0].children[1].children[1] = p5
    push_notebook(handle=bokeh_handle)
cb = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "bUpdate('" + cb_obj.title + "','" + cb_obj.value + "')";
    kernel.execute(cmd, {}, {});
}
""")
cbRC = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "bReCalc()";
    kernel.execute(cmd, {}, {});
}
""")
TOTAL_WIDTH = 800
COL1_W = int(0.25*TOTAL_WIDTH)-5
COL2_W = int(0.75*TOTAL_WIDTH)-5
WID_W = int(0.5*COL1_W) - 15
btnCalc = Button(label="Recalculate", callback=cbRC, width=WID_W)
sldSamples = Slider(start=20, end=1000, step=10, title="Number of Samples", callback_policy="mouseup", callback=cb, width=WID_W)
layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                               sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                        column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                               figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5), sizing_mode="scale_height")) \
                      ), \
                   row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
bokeh_handle = show(layout, notebook_handle=True)

When a user clicks the button or changes the value of the slider, some of the plots get updated and redrawn, while others get randomly resized. But, if you just call “show(layout)” in another cell, the layout displays correctly. Anyone have any ideas for best practices for this type of thing?

==EDIT==

The same type of error occurs, even when embedding a bokeh server similar to this example: https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

output_notebook()
def modify_doc(doc):
    num_samples = 500
    def sampChange(attr, old, new):
        global num_samples
        num_samples = int(new)    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure()
        p2.circle(x,y)
        p2.plot_width=COL1_W
        p2.plot_height=COL1_W    
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p3 = figure(plot_width = COL2_W, plot_height = int(COL1_W*1.5))
        p3.circle(x,y,color='green')
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        z = [random.random() for i in range(num_samples)]
        p4 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p5 = figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W)
        p4.circle(x,y)
        p5.circle(x,z)
        layout.children[0].children[1] = row(p4,p5)
        layout.children[0].children[0].children[0].children[3]=p2
        layout.children[0].children[0].children[1].children[1]=p3
        #layout.children[0].children[1].children[1] = p5
        #doc.add_root(layout)
    def bReCalc():
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p = figure(plot_width=COL1_W, plot_height=250)
        p.circle(x, y, color='red')
        p.toolbar.logo = None
        layout.children[0].children[0].children[0].children[1] = p
        x = [random.random() for i in range(num_samples)]
        y = [random.random() for i in range(num_samples)]
        p2 = figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8))
        p2.circle(x,y)
        layout.children[0].children[0].children[1].children[0] = p2
        #doc.add_root(layout)
        #push_notebook(handle=bokeh_handle)
    TOTAL_WIDTH = 800
    COL1_W = int(0.25*TOTAL_WIDTH)-5
    COL2_W = int(0.75*TOTAL_WIDTH)-5
    WID_W = int(0.5*COL1_W) - 15
    btnCalc = Button(label="Recalculate", width=WID_W)
    sldSamples = Slider(start=20, end=500, step=10, title="Number of Samples", callback_policy="mouseup", width=WID_W)
    btnCalc.on_click(bReCalc)
    sldSamples.on_change('value', sampChange)
    layout = row(column( row( column( btnCalc, figure(plot_width=COL1_W, plot_height=250), \
                                   sldSamples, figure(plot_width=COL1_W, plot_height=COL1_W)), \
                            column( figure(plot_width=COL2_W, plot_height=int(COL1_W*1.8)), \
                                   figure(plot_width=COL2_W, plot_height = int(COL1_W*1.5))) \
                          ), \
                       row(figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W), figure(plot_width = int(TOTAL_WIDTH/2-5), plot_height=COL1_W))))
    doc.add_root(layout)
    bReCalc()
    return doc
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
handler = FunctionHandler(modify_doc)
app = Application(handler)
show(app)


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/ce8e5e96-564b-472a-92ea-3a999c7acb9b%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/18cc171f-8dc0-4817-81d7-2acd3bed00cf%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.