How does one manage glyphs on a figure?

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD
···

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)

  • Record the event type and start/stop time (on X) of the event in a pandas dataframe

  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

  • File

  • Edit

  • View

  • Insert

  • Cell

  • Kernel

  • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

    // get data source from Callback args

    var data = source.get('data');

    /// get BoxSelectTool dimensions from cb_data parameter of Callback

    var geometry = cb_data['geometry'];

    /// calculate Rect attributes

    var width = geometry['x1'] - geometry['x0'];

    var height = geometry['y1'] - geometry['y0'];

    var x = geometry['x0'] + width/2;

    var y = geometry['y0'] + height/2;

   

    var leftEdge = geometry['x0']

    var rightEdge = geometry['x1']

   

    /// update data source with new Rect attributes

    data['x'].push(x);

    data['y'].push(y);

   

    data['width'].push(width);

    data['height'].push(height);

    data['eventType'] = 'none'

    data['leftEdge'] = leftEdge

    data['rightEdge'] = rightEdge

   

    // trigger update of data source

    source.trigger('change');

   

""")

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

    // get data source from Callback args

    var data = source.get('data');

   

    var width = data['width']

   

    IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

    IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

""")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1**+**np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

        y='y',

        width='width',

        height='height',

        fill_alpha=0.3,

        fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

        y='y',

        width='width',

        height='height',

        fill_alpha=0.3,

        fill_color='gray')

selected = bkM.Rect(x=‘x’,

        y='y',

        width='width',

        height='height',

        fill_alpha=0.3,

        fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

**print** leftEdge

**print** rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

    // get data source from Callback args

    var data = source.get('data');

    /// get BoxSelectTool dimensions from cb_data parameter of Callback

    var geometry = cb_data['geometry'];

    /// calculate Rect attributes

    var width = geometry['x1'] - geometry['x0'];

    var height = geometry['y1'] - geometry['y0'];

    var x = geometry['x0'] + width/2;

    var y = geometry['y0'] + height/2;

   

    var leftEdge = geometry['x0']

    var rightEdge = geometry['x1']

   

    /// update data source with new Rect attributes

    data['x'].push(x);

    data['y'].push(y);

   

    data['width'].push(width);

    data['height'].push(height);

    data['eventType'] = 'none'

    data['leftEdge'] = leftEdge

    data['rightEdge'] = rightEdge

   

    // trigger update of data source

    source.trigger('change');

   

""")

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

    // get data source from Callback args

    var data = source.get('data');

   

    var width = data['width']

   

    IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

    IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

""")

# IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

# IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1**+**np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

        y='y',

        width='width',

        height='height',

        fill_alpha=0.3,

        fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

        y='y',

        width='width',

        height='height',

        fill_alpha=0.3,

        fill_color='gray')

selected = bkM.Rect(x=‘x’,

        y='y',

        width='width',

        height='height',

        fill_alpha=0.3,

        fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

### NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

**print** leftEdge

**print** rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

``

Apologies. An example is included in the attached *.ipynb

  • gD

Example Event Labeller.ipynb (502 KB)

···

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)
  • Record the event type and start/stop time (on X) of the event in a pandas dataframe
  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

  • File

  • Edit

  • View

  • Insert

  • Cell

  • Kernel

  • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');

    /// get BoxSelectTool dimensions from cb_data parameter of Callback
    var geometry = cb_data['geometry'];

    /// calculate Rect attributes
    var width = geometry['x1'] - geometry['x0'];
    var height = geometry['y1'] - geometry['y0'];
    var x = geometry['x0'] + width/2;
    var y = geometry['y0'] + height/2;
    var leftEdge = geometry['x0']
    var rightEdge = geometry['x1']
    /// update data source with new Rect attributes
    data['x'].push(x);
    data['y'].push(y);
    data['width'].push(width);
    data['height'].push(height);
    data['eventType'] = 'none'
    data['leftEdge'] = leftEdge
    data['rightEdge'] = rightEdge
    // trigger update of data source
    source.trigger('change');
""")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');
    var width = data['width']
    IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
    IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

""")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1**+**np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

selected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

**print** leftEdge
**print** rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');

    /// get BoxSelectTool dimensions from cb_data parameter of Callback
    var geometry = cb_data['geometry'];

    /// calculate Rect attributes
    var width = geometry['x1'] - geometry['x0'];
    var height = geometry['y1'] - geometry['y0'];
    var x = geometry['x0'] + width/2;
    var y = geometry['y0'] + height/2;
    var leftEdge = geometry['x0']
    var rightEdge = geometry['x1']
    /// update data source with new Rect attributes
    data['x'].push(x);
    data['y'].push(y);
    data['width'].push(width);
    data['height'].push(height);
    data['eventType'] = 'none'
    data['leftEdge'] = leftEdge
    data['rightEdge'] = rightEdge
    // trigger update of data source
    source.trigger('change');
""")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');
    var width = data['width']
    IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
    IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

""")

# IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

# IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1**+**np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

selected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

### NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

**print** leftEdge
**print** rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

``

I would suggest studying the examples here:

  https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms

Thanks,

Bryan

···

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:

Apologies. An example is included in the attached *.ipynb

- gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!
If you want to know more, read more to find out why...

Thanks!
- gD

-----

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...

- Label the patch as event type A,B, or C (depending upon the button pressed)
- Record the event type and start/stop time (on X) of the event in a pandas dataframe
- Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

  • File

  • Edit

  • View

  • Insert

  • Cell

  • Kernel

  • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

  UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

        // get data source from Callback args

        var data = source.get('data');

        /// get BoxSelectTool dimensions from cb_data parameter of Callback

        var geometry = cb_data['geometry'];

        /// calculate Rect attributes

        var width = geometry['x1'] - geometry['x0'];

        var height = geometry['y1'] - geometry['y0'];

        var x = geometry['x0'] + width/2;

        var y = geometry['y0'] + height/2;

        var leftEdge = geometry['x0']

        var rightEdge = geometry['x1']

        /// update data source with new Rect attributes

        data['x'].push(x);

        data['y'].push(y);

        data['width'].push(width);

        data['height'].push(height);

        data['eventType'] = 'none'

        data['leftEdge'] = leftEdge

        data['rightEdge'] = rightEdge

        // trigger update of data source

        source.trigger('change');

    """)

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

        // get data source from Callback args

        var data = source.get('data');

        var width = data['width']

        IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

        IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );

    """)

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = 'time'

p.yaxis.axis_label = 'position'

p.line(x,y,line_width=4,name='aLine')

initRect = bkM.Rect(x='x',

            y='y',

            width='width',

            height='height',

            fill_alpha=0.3,

            fill_color='gray')

notSelected = bkM.Rect(x='x',

            y='y',

            width='width',

            height='height',

            fill_alpha=0.3,

            fill_color='gray')

selected = bkM.Rect(x='x',

            y='y',

            width='width',

            height='height',

            fill_alpha=0.3,

            fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')

p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

    print leftEdge

    print rightEdge

b2 = Button(description='Fixation',value=False)

b2.on_click(foundSacc)

b2.width = '20%'

b1 = Button(description='Saccade',value=False)

b1.on_click(foundSacc)

b1.width = '20%'

display(b1,b2)

×

Saccade

Fixation

In :

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

        // get data source from Callback args

        var data = source.get('data');

        /// get BoxSelectTool dimensions from cb_data parameter of Callback

        var geometry = cb_data['geometry'];

        /// calculate Rect attributes

        var width = geometry['x1'] - geometry['x0'];

        var height = geometry['y1'] - geometry['y0'];

        var x = geometry['x0'] + width/2;

        var y = geometry['y0'] + height/2;

        var leftEdge = geometry['x0']

        var rightEdge = geometry['x1']

        /// update data source with new Rect attributes

        data['x'].push(x);

        data['y'].push(y);

        data['width'].push(width);

        data['height'].push(height);

        data['eventType'] = 'none'

        data['leftEdge'] = leftEdge

        data['rightEdge'] = rightEdge

        // trigger update of data source

        source.trigger('change');

    """)

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

        // get data source from Callback args

        var data = source.get('data');

        var width = data['width']

        IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

        IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );

    """)

# IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);

# IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = 'time'

p.yaxis.axis_label = 'position'

p.line(x,y,line_width=4,name='aLine')

initRect = bkM.Rect(x='x',

            y='y',

            width='width',

            height='height',

            fill_alpha=0.3,

            fill_color='gray')

notSelected = bkM.Rect(x='x',

            y='y',

            width='width',

            height='height',

            fill_alpha=0.3,

            fill_color='gray')

selected = bkM.Rect(x='x',

            y='y',

            width='width',

            height='height',

            fill_alpha=0.3,

            fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')

p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

### NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

    print leftEdge

    print rightEdge

b2 = Button(description='Fixation',value=False)

b2.on_click(foundSacc)

b2.width = '20%'

b1 = Button(description='Saccade',value=False)

b1.on_click(foundSacc)

b1.width = '20%'

display(b1,b2)

--
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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Example Event Labeller.ipynb>

Thanks Bryan. Unless I missed it, I don’t see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn’t help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple ‘rects’ have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I’ve explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks

  • gD
···

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven [email protected] wrote:

I would suggest studying the examples here:

    [https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms](https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms)

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz [email protected] wrote:

Apologies. An example is included in the attached *.ipynb

  • gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)
  • Record the event type and start/stop time (on X) of the event in a pandas dataframe
  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

  • File
  • Edit
  • View
  • Insert
  • Cell
  • Kernel
  • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');

    /// get BoxSelectTool dimensions from cb_data parameter of Callback
    var geometry = cb_data['geometry'];

    /// calculate Rect attributes
    var width = geometry['x1'] - geometry['x0'];
    var height = geometry['y1'] - geometry['y0'];
    var x = geometry['x0'] + width/2;
    var y = geometry['y0'] + height/2;
    var leftEdge = geometry['x0']
    var rightEdge = geometry['x1']
    /// update data source with new Rect attributes
    data['x'].push(x);
    data['y'].push(y);
    data['width'].push(width);
    data['height'].push(height);
    data['eventType'] = 'none'
    data['leftEdge'] = leftEdge
    data['rightEdge'] = rightEdge
    // trigger update of data source
    source.trigger('change');
""")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');
    var width = data['width']
    IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
    IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

""")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

selected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge
print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');

    /// get BoxSelectTool dimensions from cb_data parameter of Callback
    var geometry = cb_data['geometry'];

    /// calculate Rect attributes
    var width = geometry['x1'] - geometry['x0'];
    var height = geometry['y1'] - geometry['y0'];
    var x = geometry['x0'] + width/2;
    var y = geometry['y0'] + height/2;
    var leftEdge = geometry['x0']
    var rightEdge = geometry['x1']
    /// update data source with new Rect attributes
    data['x'].push(x);
    data['y'].push(y);
    data['width'].push(width);
    data['height'].push(height);
    data['eventType'] = 'none'
    data['leftEdge'] = leftEdge
    data['rightEdge'] = rightEdge
    // trigger update of data source
    source.trigger('change');
""")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');
    var width = data['width']
    IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
    IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

""")

IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

selected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge
print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

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/b0400249-fb53-482c-8768-c2134923b72a%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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.


Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay "in sync" with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the "push_notebook" function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python -> JS, although there are other folks interested in and working on ideas to enable the "reverse" direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(...) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the "Continuous Updating" notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

* recreate a new plot
* set the glyph to be invisble
* update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like "I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen".

Thanks,

Bryan

···

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz <[email protected]> wrote:

Thanks Bryan. Unless I missed it, I don't see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn't help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple 'rects' have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I've explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks
- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven <[email protected]> wrote:
I would suggest studying the examples here:

        https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms

Thanks,

Bryan

> On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:
>
> Apologies. An example is included in the attached *.ipynb
>
> - gD
>
> On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
> Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.
>
>
> Code from my notebook is pasted below!
> If you want to know more, read more to find out why...
>
> Thanks!
> - gD
>
> -----
>
> My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...
>
> - Label the patch as event type A,B, or C (depending upon the button pressed)
> - Record the event type and start/stop time (on X) of the event in a pandas dataframe
> - Update the glyph to color-code the event
>
> I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)
>
>
>
>
>
>
>
>
> Python 2
>
>
>
>
>
>
>
>
>
> • File
>
> • Edit
>
> • View
>
> • Insert
>
> • Cell
>
> • Kernel
>
> • Help
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> CodeMarkdownRaw NBConvertHeading
>
>
>
>
>
> CellToolbar
>
>
>
>
>
> In [1]:
>
>
> from __future__ import division
>
>
>
>
>
> import pandas as pd
>
>
> import numpy as np
>
>
>
>
>
> import bokeh.plotting as bkP
>
>
> import bokeh.models as bkM
>
>
> from bokeh.palettes import Spectral6
>
>
> from bokeh.embed import file_html
>
>
> from bokeh.resources import CDN
>
>
>
>
>
> bkP.output_notebook()
>
>
>
>
>
> x
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> from __future__ import division
>
>
> ​
>
> import pandas as pd
>
>
> import numpy as np
>
>
> ​
>
> import bokeh.plotting as bkP
>
>
> import bokeh.models as bkM
>
>
> from bokeh.palettes import Spectral6
>
>
> from bokeh.embed import file_html
>
>
> from bokeh.resources import CDN
>
>
> ​
>
> bkP.output_notebook()
>
>
> ​
>
> ​
>
>
>
>
>
>
>
>
>
>
>
>
>
> /Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used
>
>
>
>
>
> UserWarning)
>
>
>
>
>
> BokehJS successfully loaded
>
>
>
>
>
>
>
>
> In [41]:
>
>
>
>
>
> x
>
>
>
>
>
>
>
>
>
>
> source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
>
>
> ​
>
>
>
>
> boxCallback = bkM.CustomJS(args=dict(source=source), code="""
>
>
>
>
> // get data source from Callback args
>
>
> var data = source.get('data');
>
>
> ​
>
> /// get BoxSelectTool dimensions from cb_data parameter of Callback
>
>
> var geometry = cb_data['geometry'];
>
>
> ​
>
> /// calculate Rect attributes
>
>
> var width = geometry['x1'] - geometry['x0'];
>
>
> var height = geometry['y1'] - geometry['y0'];
>
>
> var x = geometry['x0'] + width/2;
>
>
> var y = geometry['y0'] + height/2;
>
>
>
>
> var leftEdge = geometry['x0']
>
>
> var rightEdge = geometry['x1']
>
>
>
>
> /// update data source with new Rect attributes
>
>
> data['x'].push(x);
>
>
> data['y'].push(y);
>
>
>
>
> data['width'].push(width);
>
>
> data['height'].push(height);
>
>
> data['eventType'] = 'none'
>
>
> data['leftEdge'] = leftEdge
>
>
> data['rightEdge'] = rightEdge
>
>
>
>
> // trigger update of data source
>
>
> source.trigger('change');
>
>
>
>
> """)
>
>
> ​
>
>
>
>
> tapCallback = bkM.CustomJS(args=dict(source=source), code="""
>
>
>
>
> // get data source from Callback args
>
>
> var data = source.get('data');
>
>
>
>
> var width = data['width']
>
>
>
>
> IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
>
>
> IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
>
>
> ​
>
>
>
> """)
>
>
> ​
>
>
>
>
>
>
>
>
>
>
> Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?
>
>
> In [42]:
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> leftEdge =
>
>
> rightEdge =
>
>
> ​
>
> p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
>
>
> ​
>
> x = np.deg2rad(range(720))
>
>
> y = 1+np.sin(x)
>
>
> ​
>
> p.xaxis.axis_label = 'time'
>
>
> p.yaxis.axis_label = 'position'
>
>
> ​
>
> p.line(x,y,line_width=4,name='aLine')
>
>
> ​
>
>
>
>
> initRect = bkM.Rect(x='x',
>
>
> y='y',
>
>
> width='width',
>
>
> height='height',
>
>
> fill_alpha=0.3,
>
>
> fill_color='gray')
>
>
> ​
>
>
>
>
> notSelected = bkM.Rect(x='x',
>
>
> y='y',
>
>
> width='width',
>
>
> height='height',
>
>
> fill_alpha=0.3,
>
>
> fill_color='gray')
>
>
> ​
>
>
>
>
> selected = bkM.Rect(x='x',
>
>
> y='y',
>
>
> width='width',
>
>
> height='height',
>
>
> fill_alpha=0.3,
>
>
> fill_color='green')
>
>
> ​
>
> p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
>
>
> p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
>
>
> p.add_tools(bkM.TapTool(callback=tapCallback))
>
>
> ​
>
> bkP.show(p)
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> Out[42]:
>
>
> <Bokeh Notebook handle for In[42]>
>
>
> In [44]:
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> from ipywidgets import *
>
>
> from IPython.display import display
>
>
> ​
>
>
>
>
> def foundSacc(sender):
>
>
> print leftEdge
>
>
> print rightEdge
>
>
>
>
> b2 = Button(description='Fixation',value=False)
>
>
> b2.on_click(foundSacc)
>
>
> b2.width = '20%'
>
>
> ​
>
> b1 = Button(description='Saccade',value=False)
>
>
> b1.on_click(foundSacc)
>
>
> b1.width = '20%'
>
>
> ​
>
> display(b1,b2)
>
>
>
>
>
>
>
>
> ×
>
>
> Saccade
>
>
> Fixation
>
>
>
>
>
> In :
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> from __future__ import division
>
>
> ​
>
> import pandas as pd
>
>
> import numpy as np
>
>
> ​
>
> import bokeh.plotting as bkP
>
>
> import bokeh.models as bkM
>
>
> from bokeh.palettes import Spectral6
>
>
> from bokeh.embed import file_html
>
>
> from bokeh.resources import CDN
>
>
> ​
>
> bkP.output_notebook()
>
>
> ​
>
> source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
>
>
> ​
>
>
>
>
> boxCallback = bkM.CustomJS(args=dict(source=source), code="""
>
>
>
>
> // get data source from Callback args
>
>
> var data = source.get('data');
>
>
> ​
>
> /// get BoxSelectTool dimensions from cb_data parameter of Callback
>
>
> var geometry = cb_data['geometry'];
>
>
> ​
>
> /// calculate Rect attributes
>
>
> var width = geometry['x1'] - geometry['x0'];
>
>
> var height = geometry['y1'] - geometry['y0'];
>
>
> var x = geometry['x0'] + width/2;
>
>
> var y = geometry['y0'] + height/2;
>
>
>
>
> var leftEdge = geometry['x0']
>
>
> var rightEdge = geometry['x1']
>
>
>
>
> /// update data source with new Rect attributes
>
>
> data['x'].push(x);
>
>
> data['y'].push(y);
>
>
>
>
> data['width'].push(width);
>
>
> data['height'].push(height);
>
>
> data['eventType'] = 'none'
>
>
> data['leftEdge'] = leftEdge
>
>
> data['rightEdge'] = rightEdge
>
>
>
>
> // trigger update of data source
>
>
> source.trigger('change');
>
>
>
>
> """)
>
>
> ​
>
>
>
>
> tapCallback = bkM.CustomJS(args=dict(source=source), code="""
>
>
>
>
> // get data source from Callback args
>
>
> var data = source.get('data');
>
>
>
>
> var width = data['width']
>
>
>
>
> IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
>
>
> IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
>
>
> ​
>
>
>
> """)
>
>
> # IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);
>
>
> # IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );
>
>
> ​
>
> leftEdge =
>
>
> rightEdge =
>
>
> ​
>
> p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
>
>
> ​
>
> x = np.deg2rad(range(720))
>
>
> y = 1+np.sin(x)
>
>
> ​
>
> p.xaxis.axis_label = 'time'
>
>
> p.yaxis.axis_label = 'position'
>
>
> ​
>
> p.line(x,y,line_width=4,name='aLine')
>
>
> ​
>
>
>
>
> initRect = bkM.Rect(x='x',
>
>
> y='y',
>
>
> width='width',
>
>
> height='height',
>
>
> fill_alpha=0.3,
>
>
> fill_color='gray')
>
>
> ​
>
>
>
>
> notSelected = bkM.Rect(x='x',
>
>
> y='y',
>
>
> width='width',
>
>
> height='height',
>
>
> fill_alpha=0.3,
>
>
> fill_color='gray')
>
>
> ​
>
>
>
>
> selected = bkM.Rect(x='x',
>
>
> y='y',
>
>
> width='width',
>
>
> height='height',
>
>
> fill_alpha=0.3,
>
>
> fill_color='green')
>
>
> ​
>
> p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
>
>
> p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
>
>
> p.add_tools(bkM.TapTool(callback=tapCallback))
>
>
> ​
>
> bkP.show(p)
>
>
> ​
>
> ########################################################################
>
>
> ########################################################################
>
>
> ### NewCell
>
>
> ​
>
> from ipywidgets import *
>
>
> from IPython.display import display
>
>
> ​
>
>
>
>
> def foundSacc(sender):
>
>
> print leftEdge
>
>
> print rightEdge
>
>
>
>
> b2 = Button(description='Fixation',value=False)
>
>
> b2.on_click(foundSacc)
>
>
> b2.width = '20%'
>
>
> ​
>
> b1 = Button(description='Saccade',value=False)
>
>
> b1.on_click(foundSacc)
>
>
> b1.width = '20%'
>
>
> ​
>
> display(b1,b2)
>
>
>
>
>
>
>
>
>
>
>
>
>
> --
> 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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
> <Example Event Labeller.ipynb>

--
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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

First, thank you very much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I’ve posted earlier.

Regardless of what I’ve accomplished, heres the functionality that I would like to add:

  • Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).

  • Select the event (e.g. using tap-tool).

  • Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.

  • I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).

  • Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event’s label.

Thanks again.

  • gD
···

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven [email protected] wrote:

Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay “in sync” with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the “push_notebook” function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python → JS, although there are other folks interested in and working on ideas to enable the “reverse” direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(…) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the “Continuous Updating” notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

  • recreate a new plot

  • set the glyph to be invisble

  • update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like “I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen”.

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz [email protected] wrote:

Thanks Bryan. Unless I missed it, I don’t see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn’t help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple ‘rects’ have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I’ve explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven [email protected] wrote:

I would suggest studying the examples here:

    [https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms](https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms)

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz [email protected] wrote:

Apologies. An example is included in the attached *.ipynb

  • gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)
  • Record the event type and start/stop time (on X) of the event in a pandas dataframe
  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

  • File
  • Edit
  • View
  • Insert
  • Cell
  • Kernel
  • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');

    /// get BoxSelectTool dimensions from cb_data parameter of Callback
    var geometry = cb_data['geometry'];

    /// calculate Rect attributes
    var width = geometry['x1'] - geometry['x0'];
    var height = geometry['y1'] - geometry['y0'];
    var x = geometry['x0'] + width/2;
    var y = geometry['y0'] + height/2;
    var leftEdge = geometry['x0']
    var rightEdge = geometry['x1']
    /// update data source with new Rect attributes
    data['x'].push(x);
    data['y'].push(y);
    data['width'].push(width);
    data['height'].push(height);
    data['eventType'] = 'none'
    data['leftEdge'] = leftEdge
    data['rightEdge'] = rightEdge
    // trigger update of data source
    source.trigger('change');
""")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');
    var width = data['width']
    IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
    IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

""")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

selected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge
print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');

    /// get BoxSelectTool dimensions from cb_data parameter of Callback
    var geometry = cb_data['geometry'];

    /// calculate Rect attributes
    var width = geometry['x1'] - geometry['x0'];
    var height = geometry['y1'] - geometry['y0'];
    var x = geometry['x0'] + width/2;
    var y = geometry['y0'] + height/2;
    var leftEdge = geometry['x0']
    var rightEdge = geometry['x1']
    /// update data source with new Rect attributes
    data['x'].push(x);
    data['y'].push(y);
    data['width'].push(width);
    data['height'].push(height);
    data['eventType'] = 'none'
    data['leftEdge'] = leftEdge
    data['rightEdge'] = rightEdge
    // trigger update of data source
    source.trigger('change');
""")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

    // get data source from Callback args
    var data = source.get('data');
    var width = data['width']
    IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
    IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

""")

IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='gray')

selected = bkM.Rect(x=‘x’,

        y='y',
        width='width',
        height='height',
        fill_alpha=0.3,
        fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge
print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

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/b0400249-fb53-482c-8768-c2134923b72a%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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com.

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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

* The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app

* Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

···

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz <[email protected]> wrote:

First, thank you *very* much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I've posted earlier.

Regardless of what I've accomplished, heres the functionality that I would like to add:

- Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
- Select the event (e.g. using tap-tool).
- Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
- I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
- Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event's label.

Thanks again.
- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven <[email protected]> wrote:
Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay "in sync" with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the "push_notebook" function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python -> JS, although there are other folks interested in and working on ideas to enable the "reverse" direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(...) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the "Continuous Updating" notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

* recreate a new plot
* set the glyph to be invisble
* update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like "I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen".

Thanks,

Bryan

> On Jun 9, 2016, at 7:54 AM, Gabriel Diaz <[email protected]> wrote:
>
> Thanks Bryan. Unless I missed it, I don't see an answer to my question in those otherwise very helpful guides.
>
> Let me try and restate my issue, in case it lacked clarity (that large code dump didn't help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.
>
> However, I cannot find where information about individual glpyhs objects stored.
>
> The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple 'rects' have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?
>
> I've explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.
>
> Thanks
> - gD
>
>
> ----------------------
>
> Gabriel J. Diaz, Ph.D.
> Assistant Professor
> Rochester Institute of Technology
> Chester F. Carlson Center for Imaging Science
>
> Founder of PerForM Labs
> Click for demos.
>
> Office 2108, Building #76
> Rochester, NY 14623
> Office: (585) 475-6215
> [email protected]
>
>
>
>
>
> On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven <[email protected]> wrote:
> I would suggest studying the examples here:
>
> https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms
>
> Thanks,
>
> Bryan
>
>
> > On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:
> >
> > Apologies. An example is included in the attached *.ipynb
> >
> > - gD
> >
> > On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
> > Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.
> >
> >
> > Code from my notebook is pasted below!
> > If you want to know more, read more to find out why...
> >
> > Thanks!
> > - gD
> >
> > -----
> >
> > My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...
> >
> > - Label the patch as event type A,B, or C (depending upon the button pressed)
> > - Record the event type and start/stop time (on X) of the event in a pandas dataframe
> > - Update the glyph to color-code the event
> >
> > I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)
> >
> >
> >
> >
> >
> >
> >
> >
> > Python 2
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > • File
> >
> > • Edit
> >
> > • View
> >
> > • Insert
> >
> > • Cell
> >
> > • Kernel
> >
> > • Help
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > CodeMarkdownRaw NBConvertHeading
> >
> >
> >
> >
> >
> > CellToolbar
> >
> >
> >
> >
> >
> > In [1]:
> >
> >
> > from __future__ import division
> >
> >
> >
> >
> >
> > import pandas as pd
> >
> >
> > import numpy as np
> >
> >
> >
> >
> >
> > import bokeh.plotting as bkP
> >
> >
> > import bokeh.models as bkM
> >
> >
> > from bokeh.palettes import Spectral6
> >
> >
> > from bokeh.embed import file_html
> >
> >
> > from bokeh.resources import CDN
> >
> >
> >
> >
> >
> > bkP.output_notebook()
> >
> >
> >
> >
> >
> > x
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > from __future__ import division
> >
> >
> > ​
> >
> > import pandas as pd
> >
> >
> > import numpy as np
> >
> >
> > ​
> >
> > import bokeh.plotting as bkP
> >
> >
> > import bokeh.models as bkM
> >
> >
> > from bokeh.palettes import Spectral6
> >
> >
> > from bokeh.embed import file_html
> >
> >
> > from bokeh.resources import CDN
> >
> >
> > ​
> >
> > bkP.output_notebook()
> >
> >
> > ​
> >
> > ​
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > /Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used
> >
> >
> >
> >
> >
> > UserWarning)
> >
> >
> >
> >
> >
> > BokehJS successfully loaded
> >
> >
> >
> >
> >
> >
> >
> >
> > In [41]:
> >
> >
> >
> >
> >
> > x
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
> >
> >
> > ​
> >
> >
> >
> >
> > boxCallback = bkM.CustomJS(args=dict(source=source), code="""
> >
> >
> >
> >
> > // get data source from Callback args
> >
> >
> > var data = source.get('data');
> >
> >
> > ​
> >
> > /// get BoxSelectTool dimensions from cb_data parameter of Callback
> >
> >
> > var geometry = cb_data['geometry'];
> >
> >
> > ​
> >
> > /// calculate Rect attributes
> >
> >
> > var width = geometry['x1'] - geometry['x0'];
> >
> >
> > var height = geometry['y1'] - geometry['y0'];
> >
> >
> > var x = geometry['x0'] + width/2;
> >
> >
> > var y = geometry['y0'] + height/2;
> >
> >
> >
> >
> > var leftEdge = geometry['x0']
> >
> >
> > var rightEdge = geometry['x1']
> >
> >
> >
> >
> > /// update data source with new Rect attributes
> >
> >
> > data['x'].push(x);
> >
> >
> > data['y'].push(y);
> >
> >
> >
> >
> > data['width'].push(width);
> >
> >
> > data['height'].push(height);
> >
> >
> > data['eventType'] = 'none'
> >
> >
> > data['leftEdge'] = leftEdge
> >
> >
> > data['rightEdge'] = rightEdge
> >
> >
> >
> >
> > // trigger update of data source
> >
> >
> > source.trigger('change');
> >
> >
> >
> >
> > """)
> >
> >
> > ​
> >
> >
> >
> >
> > tapCallback = bkM.CustomJS(args=dict(source=source), code="""
> >
> >
> >
> >
> > // get data source from Callback args
> >
> >
> > var data = source.get('data');
> >
> >
> >
> >
> > var width = data['width']
> >
> >
> >
> >
> > IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
> >
> >
> > IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
> >
> >
> > ​
> >
> >
> >
> > """)
> >
> >
> > ​
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?
> >
> >
> > In [42]:
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > leftEdge =
> >
> >
> > rightEdge =
> >
> >
> > ​
> >
> > p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
> >
> >
> > ​
> >
> > x = np.deg2rad(range(720))
> >
> >
> > y = 1+np.sin(x)
> >
> >
> > ​
> >
> > p.xaxis.axis_label = 'time'
> >
> >
> > p.yaxis.axis_label = 'position'
> >
> >
> > ​
> >
> > p.line(x,y,line_width=4,name='aLine')
> >
> >
> > ​
> >
> >
> >
> >
> > initRect = bkM.Rect(x='x',
> >
> >
> > y='y',
> >
> >
> > width='width',
> >
> >
> > height='height',
> >
> >
> > fill_alpha=0.3,
> >
> >
> > fill_color='gray')
> >
> >
> > ​
> >
> >
> >
> >
> > notSelected = bkM.Rect(x='x',
> >
> >
> > y='y',
> >
> >
> > width='width',
> >
> >
> > height='height',
> >
> >
> > fill_alpha=0.3,
> >
> >
> > fill_color='gray')
> >
> >
> > ​
> >
> >
> >
> >
> > selected = bkM.Rect(x='x',
> >
> >
> > y='y',
> >
> >
> > width='width',
> >
> >
> > height='height',
> >
> >
> > fill_alpha=0.3,
> >
> >
> > fill_color='green')
> >
> >
> > ​
> >
> > p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
> >
> >
> > p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
> >
> >
> > p.add_tools(bkM.TapTool(callback=tapCallback))
> >
> >
> > ​
> >
> > bkP.show(p)
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > Out[42]:
> >
> >
> > <Bokeh Notebook handle for In[42]>
> >
> >
> > In [44]:
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > from ipywidgets import *
> >
> >
> > from IPython.display import display
> >
> >
> > ​
> >
> >
> >
> >
> > def foundSacc(sender):
> >
> >
> > print leftEdge
> >
> >
> > print rightEdge
> >
> >
> >
> >
> > b2 = Button(description='Fixation',value=False)
> >
> >
> > b2.on_click(foundSacc)
> >
> >
> > b2.width = '20%'
> >
> >
> > ​
> >
> > b1 = Button(description='Saccade',value=False)
> >
> >
> > b1.on_click(foundSacc)
> >
> >
> > b1.width = '20%'
> >
> >
> > ​
> >
> > display(b1,b2)
> >
> >
> >
> >
> >
> >
> >
> >
> > ×
> >
> >
> > Saccade
> >
> >
> > Fixation
> >
> >
> >
> >
> >
> > In :
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > from __future__ import division
> >
> >
> > ​
> >
> > import pandas as pd
> >
> >
> > import numpy as np
> >
> >
> > ​
> >
> > import bokeh.plotting as bkP
> >
> >
> > import bokeh.models as bkM
> >
> >
> > from bokeh.palettes import Spectral6
> >
> >
> > from bokeh.embed import file_html
> >
> >
> > from bokeh.resources import CDN
> >
> >
> > ​
> >
> > bkP.output_notebook()
> >
> >
> > ​
> >
> > source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
> >
> >
> > ​
> >
> >
> >
> >
> > boxCallback = bkM.CustomJS(args=dict(source=source), code="""
> >
> >
> >
> >
> > // get data source from Callback args
> >
> >
> > var data = source.get('data');
> >
> >
> > ​
> >
> > /// get BoxSelectTool dimensions from cb_data parameter of Callback
> >
> >
> > var geometry = cb_data['geometry'];
> >
> >
> > ​
> >
> > /// calculate Rect attributes
> >
> >
> > var width = geometry['x1'] - geometry['x0'];
> >
> >
> > var height = geometry['y1'] - geometry['y0'];
> >
> >
> > var x = geometry['x0'] + width/2;
> >
> >
> > var y = geometry['y0'] + height/2;
> >
> >
> >
> >
> > var leftEdge = geometry['x0']
> >
> >
> > var rightEdge = geometry['x1']
> >
> >
> >
> >
> > /// update data source with new Rect attributes
> >
> >
> > data['x'].push(x);
> >
> >
> > data['y'].push(y);
> >
> >
> >
> >
> > data['width'].push(width);
> >
> >
> > data['height'].push(height);
> >
> >
> > data['eventType'] = 'none'
> >
> >
> > data['leftEdge'] = leftEdge
> >
> >
> > data['rightEdge'] = rightEdge
> >
> >
> >
> >
> > // trigger update of data source
> >
> >
> > source.trigger('change');
> >
> >
> >
> >
> > """)
> >
> >
> > ​
> >
> >
> >
> >
> > tapCallback = bkM.CustomJS(args=dict(source=source), code="""
> >
> >
> >
> >
> > // get data source from Callback args
> >
> >
> > var data = source.get('data');
> >
> >
> >
> >
> > var width = data['width']
> >
> >
> >
> >
> > IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
> >
> >
> > IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
> >
> >
> > ​
> >
> >
> >
> > """)
> >
> >
> > # IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);
> >
> >
> > # IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );
> >
> >
> > ​
> >
> > leftEdge =
> >
> >
> > rightEdge =
> >
> >
> > ​
> >
> > p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
> >
> >
> > ​
> >
> > x = np.deg2rad(range(720))
> >
> >
> > y = 1+np.sin(x)
> >
> >
> > ​
> >
> > p.xaxis.axis_label = 'time'
> >
> >
> > p.yaxis.axis_label = 'position'
> >
> >
> > ​
> >
> > p.line(x,y,line_width=4,name='aLine')
> >
> >
> > ​
> >
> >
> >
> >
> > initRect = bkM.Rect(x='x',
> >
> >
> > y='y',
> >
> >
> > width='width',
> >
> >
> > height='height',
> >
> >
> > fill_alpha=0.3,
> >
> >
> > fill_color='gray')
> >
> >
> > ​
> >
> >
> >
> >
> > notSelected = bkM.Rect(x='x',
> >
> >
> > y='y',
> >
> >
> > width='width',
> >
> >
> > height='height',
> >
> >
> > fill_alpha=0.3,
> >
> >
> > fill_color='gray')
> >
> >
> > ​
> >
> >
> >
> >
> > selected = bkM.Rect(x='x',
> >
> >
> > y='y',
> >
> >
> > width='width',
> >
> >
> > height='height',
> >
> >
> > fill_alpha=0.3,
> >
> >
> > fill_color='green')
> >
> >
> > ​
> >
> > p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
> >
> >
> > p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
> >
> >
> > p.add_tools(bkM.TapTool(callback=tapCallback))
> >
> >
> > ​
> >
> > bkP.show(p)
> >
> >
> > ​
> >
> > ########################################################################
> >
> >
> > ########################################################################
> >
> >
> > ### NewCell
> >
> >
> > ​
> >
> > from ipywidgets import *
> >
> >
> > from IPython.display import display
> >
> >
> > ​
> >
> >
> >
> >
> > def foundSacc(sender):
> >
> >
> > print leftEdge
> >
> >
> > print rightEdge
> >
> >
> >
> >
> > b2 = Button(description='Fixation',value=False)
> >
> >
> > b2.on_click(foundSacc)
> >
> >
> > b2.width = '20%'
> >
> >
> > ​
> >
> > b1 = Button(description='Saccade',value=False)
> >
> >
> > b1.on_click(foundSacc)
> >
> >
> > b1.width = '20%'
> >
> >
> > ​
> >
> > display(b1,b2)
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > --
> > 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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
> > For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
> > <Example Event Labeller.ipynb>
>
> --
> 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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com\.
> 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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a "circle glyph" isn't an actual, specific circle(s) that gets drawn somewhere, it's a thing that can draw circles when you pair it with some specific data. That's why most of the use-cases are geared towards creating all the glyphs you *might ever* need up front, and then just changing the data for them as necessary.

Bryan

···

On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven <[email protected]> wrote:

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

* The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app

* Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz <[email protected]> wrote:

First, thank you *very* much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I've posted earlier.

Regardless of what I've accomplished, heres the functionality that I would like to add:

- Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
- Select the event (e.g. using tap-tool).
- Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
- I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
- Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event's label.

Thanks again.
- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven <[email protected]> wrote:
Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay "in sync" with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the "push_notebook" function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python -> JS, although there are other folks interested in and working on ideas to enable the "reverse" direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(...) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the "Continuous Updating" notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

* recreate a new plot
* set the glyph to be invisble
* update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like "I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen".

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz <[email protected]> wrote:

Thanks Bryan. Unless I missed it, I don't see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn't help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple 'rects' have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I've explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks
- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven <[email protected]> wrote:
I would suggest studying the examples here:

       https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:

Apologies. An example is included in the attached *.ipynb

- gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!
If you want to know more, read more to find out why...

Thanks!
- gD

-----

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...

- Label the patch as event type A,B, or C (depending upon the button pressed)
- Record the event type and start/stop time (on X) of the event in a pandas dataframe
- Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

     • File

     • Edit

     • View

     • Insert

     • Cell

     • Kernel

     • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

       // get data source from Callback args

       var data = source.get('data');

       /// get BoxSelectTool dimensions from cb_data parameter of Callback

       var geometry = cb_data['geometry'];

       /// calculate Rect attributes

       var width = geometry['x1'] - geometry['x0'];

       var height = geometry['y1'] - geometry['y0'];

       var x = geometry['x0'] + width/2;

       var y = geometry['y0'] + height/2;

       var leftEdge = geometry['x0']

       var rightEdge = geometry['x1']

       /// update data source with new Rect attributes

       data['x'].push(x);

       data['y'].push(y);

       data['width'].push(width);

       data['height'].push(height);

       data['eventType'] = 'none'

       data['leftEdge'] = leftEdge

       data['rightEdge'] = rightEdge

       // trigger update of data source

       source.trigger('change');

   """)

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

       // get data source from Callback args

       var data = source.get('data');

       var width = data['width']

       IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

       IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );

   """)

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = 'time'

p.yaxis.axis_label = 'position'

p.line(x,y,line_width=4,name='aLine')

initRect = bkM.Rect(x='x',

           y='y',

           width='width',

           height='height',

           fill_alpha=0.3,

           fill_color='gray')

notSelected = bkM.Rect(x='x',

           y='y',

           width='width',

           height='height',

           fill_alpha=0.3,

           fill_color='gray')

selected = bkM.Rect(x='x',

           y='y',

           width='width',

           height='height',

           fill_alpha=0.3,

           fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')

p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

   print leftEdge

   print rightEdge

b2 = Button(description='Fixation',value=False)

b2.on_click(foundSacc)

b2.width = '20%'

b1 = Button(description='Saccade',value=False)

b1.on_click(foundSacc)

b1.width = '20%'

display(b1,b2)

×

Saccade

Fixation

In :

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

       // get data source from Callback args

       var data = source.get('data');

       /// get BoxSelectTool dimensions from cb_data parameter of Callback

       var geometry = cb_data['geometry'];

       /// calculate Rect attributes

       var width = geometry['x1'] - geometry['x0'];

       var height = geometry['y1'] - geometry['y0'];

       var x = geometry['x0'] + width/2;

       var y = geometry['y0'] + height/2;

       var leftEdge = geometry['x0']

       var rightEdge = geometry['x1']

       /// update data source with new Rect attributes

       data['x'].push(x);

       data['y'].push(y);

       data['width'].push(width);

       data['height'].push(height);

       data['eventType'] = 'none'

       data['leftEdge'] = leftEdge

       data['rightEdge'] = rightEdge

       // trigger update of data source

       source.trigger('change');

   """)

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

       // get data source from Callback args

       var data = source.get('data');

       var width = data['width']

       IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

       IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );

   """)

# IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);

# IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = 'time'

p.yaxis.axis_label = 'position'

p.line(x,y,line_width=4,name='aLine')

initRect = bkM.Rect(x='x',

           y='y',

           width='width',

           height='height',

           fill_alpha=0.3,

           fill_color='gray')

notSelected = bkM.Rect(x='x',

           y='y',

           width='width',

           height='height',

           fill_alpha=0.3,

           fill_color='gray')

selected = bkM.Rect(x='x',

           y='y',

           width='width',

           height='height',

           fill_alpha=0.3,

           fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')

p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

### NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

   print leftEdge

   print rightEdge

b2 = Button(description='Fixation',value=False)

b2.on_click(foundSacc)

b2.width = '20%'

b1 = Button(description='Saccade',value=False)

b1.on_click(foundSacc)

b1.width = '20%'

display(b1,b2)

--
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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Example Event Labeller.ipynb>

--
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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com\.
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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

Ok, thanks for the update in terminology.

I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don’t know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!

The notebook is not a hard requirement, but it would be nice.

So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.

  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

I’m not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.

  • gD
···

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven [email protected] wrote:

As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a “circle glyph” isn’t an actual, specific circle(s) that gets drawn somewhere, it’s a thing that can draw circles when you pair it with some specific data. That’s why most of the use-cases are geared towards creating all the glyphs you might ever need up front, and then just changing the data for them as necessary.

Bryan

On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven [email protected] wrote:

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

  • The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app
  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz [email protected] wrote:

First, thank you very much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I’ve posted earlier.

Regardless of what I’ve accomplished, heres the functionality that I would like to add:

  • Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
  • Select the event (e.g. using tap-tool).
  • Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
  • I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
  • Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event’s label.

Thanks again.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven [email protected] wrote:

Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay “in sync” with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the “push_notebook” function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python → JS, although there are other folks interested in and working on ideas to enable the “reverse” direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(…) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the “Continuous Updating” notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

  • recreate a new plot
  • set the glyph to be invisble
  • update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like “I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen”.

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz [email protected] wrote:

Thanks Bryan. Unless I missed it, I don’t see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn’t help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple ‘rects’ have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I’ve explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven [email protected] wrote:

I would suggest studying the examples here:

   [https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms](https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms)

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz [email protected] wrote:

Apologies. An example is included in the attached *.ipynb

  • gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)
  • Record the event type and start/stop time (on X) of the event in a pandas dataframe
  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

 • File
 • Edit
 • View
 • Insert
 • Cell
 • Kernel
 • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');

   /// get BoxSelectTool dimensions from cb_data parameter of Callback
   var geometry = cb_data['geometry'];

   /// calculate Rect attributes
   var width = geometry['x1'] - geometry['x0'];
   var height = geometry['y1'] - geometry['y0'];
   var x = geometry['x0'] + width/2;
   var y = geometry['y0'] + height/2;
   var leftEdge = geometry['x0']
   var rightEdge = geometry['x1']
   /// update data source with new Rect attributes
   data['x'].push(x);
   data['y'].push(y);
   data['width'].push(width);
   data['height'].push(height);
   data['eventType'] = 'none'
   data['leftEdge'] = leftEdge
   data['rightEdge'] = rightEdge
   // trigger update of data source
   source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');
   var width = data['width']
   IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
   IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

selected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');

   /// get BoxSelectTool dimensions from cb_data parameter of Callback
   var geometry = cb_data['geometry'];

   /// calculate Rect attributes
   var width = geometry['x1'] - geometry['x0'];
   var height = geometry['y1'] - geometry['y0'];
   var x = geometry['x0'] + width/2;
   var y = geometry['y0'] + height/2;
   var leftEdge = geometry['x0']
   var rightEdge = geometry['x1']
   /// update data source with new Rect attributes
   data['x'].push(x);
   data['y'].push(y);
   data['width'].push(width);
   data['height'].push(height);
   data['eventType'] = 'none'
   data['leftEdge'] = leftEdge
   data['rightEdge'] = rightEdge
   // trigger update of data source
   source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');
   var width = data['width']
   IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
   IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

selected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

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/b0400249-fb53-482c-8768-c2134923b72a%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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com.

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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com.

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/9D84794E-71DE-4931-9834-68F107FCD65A%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

http://demo.bokeh.org

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

Weather

And here is an example that updates secondary plots as a result of a selection on a first plot:

Selection Histogram

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can "hide" an existing glyph either by:

  • setting .visible = False on the glyph:

    from bokeh\.plotting import *
    p = figure()
    p.circle([1,2,3], [4,5,6])
    r = p.square([1,2,3], [5,6,7])
    r.glyph.visible = False
    show(p)
    
  • setting the glyphs fill/line colors to None (or alpha to zero):

     from bokeh.plotting import *
     p = figure()
     p.circle([1,2,3], [4,5,6])
     r = p.square([1,2,3], [5,6,7])
     r.glyph.fill_color = None
     r.glyph.line_color = None
     show(p)
    
  • clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph *objects* themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and "remove it".

Bryan

Ok, so we’re on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS → Python communication issues.

Thanks again for your input! I’ll be sure to post a solution if I find one.

  • gD
···

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven [email protected] wrote:

Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

    [http://demo.bokehplots.com/](http://demo.bokehplots.com/)

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

    [http://demo.bokehplots.com/apps/weather](http://demo.bokehplots.com/apps/weather)

And here is an example that updates secondary plots as a result of a selection on a first plot:

    [http://demo.bokehplots.com/apps/selection_histogram](http://demo.bokehplots.com/apps/selection_histogram)

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can “hide” an existing glyph either by:

  • setting .visible = False on the glyph:

     from bokeh.plotting import *
    
     p = figure()
    
     p.circle([1,2,3], [4,5,6])
    
     r = p.square([1,2,3], [5,6,7])
    
     r.glyph.visible = False
    
     output_file("/tmp/foo.html")
    
     show(p)
    
  • setting the glyphs fill/line colors to None (or alpha to zero):

     from bokeh.plotting import *
    
     p = figure()
    
     p.circle([1,2,3], [4,5,6])
    
     r = p.square([1,2,3], [5,6,7])
    
     r.glyph.fill_color = None
    
     r.glyph.line_color = None
    
     output_file("/tmp/foo.html")
    
     show(p)
    
  • clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph objects themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and “remove it”.

Bryan

On Jun 9, 2016, at 11:28 AM, Gabriel Diaz [email protected] wrote:

Ok, thanks for the update in terminology.

I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don’t know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!

The notebook is not a hard requirement, but it would be nice.

So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.

  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

I’m not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven [email protected] wrote:

As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a “circle glyph” isn’t an actual, specific circle(s) that gets drawn somewhere, it’s a thing that can draw circles when you pair it with some specific data. That’s why most of the use-cases are geared towards creating all the glyphs you might ever need up front, and then just changing the data for them as necessary.

Bryan

On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven [email protected] wrote:

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

  • The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app
  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz [email protected] wrote:

First, thank you very much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I’ve posted earlier.

Regardless of what I’ve accomplished, heres the functionality that I would like to add:

  • Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
  • Select the event (e.g. using tap-tool).
  • Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
  • I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
  • Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event’s label.

Thanks again.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven [email protected] wrote:

Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay “in sync” with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the “push_notebook” function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python → JS, although there are other folks interested in and working on ideas to enable the “reverse” direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(…) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the “Continuous Updating” notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

  • recreate a new plot
  • set the glyph to be invisble
  • update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like “I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen”.

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz [email protected] wrote:

Thanks Bryan. Unless I missed it, I don’t see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn’t help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple ‘rects’ have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I’ve explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven [email protected] wrote:

I would suggest studying the examples here:

   [https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms](https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms)

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz [email protected] wrote:

Apologies. An example is included in the attached *.ipynb

  • gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)
  • Record the event type and start/stop time (on X) of the event in a pandas dataframe
  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

 • File
 • Edit
 • View
 • Insert
 • Cell
 • Kernel
 • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');

   /// get BoxSelectTool dimensions from cb_data parameter of Callback
   var geometry = cb_data['geometry'];

   /// calculate Rect attributes
   var width = geometry['x1'] - geometry['x0'];
   var height = geometry['y1'] - geometry['y0'];
   var x = geometry['x0'] + width/2;
   var y = geometry['y0'] + height/2;
   var leftEdge = geometry['x0']
   var rightEdge = geometry['x1']
   /// update data source with new Rect attributes
   data['x'].push(x);
   data['y'].push(y);
   data['width'].push(width);
   data['height'].push(height);
   data['eventType'] = 'none'
   data['leftEdge'] = leftEdge
   data['rightEdge'] = rightEdge
   // trigger update of data source
   source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');
   var width = data['width']
   IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
   IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

selected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');

   /// get BoxSelectTool dimensions from cb_data parameter of Callback
   var geometry = cb_data['geometry'];

   /// calculate Rect attributes
   var width = geometry['x1'] - geometry['x0'];
   var height = geometry['y1'] - geometry['y0'];
   var x = geometry['x0'] + width/2;
   var y = geometry['y0'] + height/2;
   var leftEdge = geometry['x0']
   var rightEdge = geometry['x1']
   /// update data source with new Rect attributes
   data['x'].push(x);
   data['y'].push(y);
   data['width'].push(width);
   data['height'].push(height);
   data['eventType'] = 'none'
   data['leftEdge'] = leftEdge
   data['rightEdge'] = rightEdge
   // trigger update of data source
   source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');
   var width = data['width']
   IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
   IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

selected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

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/b0400249-fb53-482c-8768-c2134923b72a%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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com.

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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com.

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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com.

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/FE7B66D0-8D13-468A-AE23-DF443D441F79%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

To follow up…

Code attached.

I’ve stuck with the method of using the python kernel to execute code in JS that can effect the Python environment. I could not find documentation or a tutorial that demonstrated a way to use server mode to avoid the use of javascript callbacks for the placement of new events (rectangles).

To remind you, my goal is to place events (rectangles) using the box tool, and then use tap tool + ipywidgets to assign each placed event to one of three visually distinct event categories (A, B, and C). To accomplish this, I’ve created four glyphs: one that is a default glyph to be used prior to group assignment, and then one for A, B, and C. Once an event is categorized by the user, its data will be moved from the undefined source to source A, B, or C.

Because I’m much more familiar with Python than JS, I intend to use taptool to select the rectangle, but Python to manage passing its information between two data sources. This requires that Python know which rectangle has been clicked by the tap tool. My approach is to include an “boxID” field in each data source so that each rectangle has a unique identification number. Inside the taptool JS callback, the boxID of the selected event rectangle event is passed back to Python. Subsequently, when an ipywidget button indicating a group assignment (A,B, or C) is pressed, then the Python callback will transfer the source entry of the event from an default source to the appropriate group’s source.

I’m nearly there. As you can see in my other post, the holdup is that I cannot figure out how to get the information about the selected item inside the tap-tool CB, so that I can the identification number I’ve stored in its source.

  • gD

Event Labeller.ipynb (56.7 KB)

···

On Thu, Jun 9, 2016 at 12:54 PM, Gabriel Diaz [email protected] wrote:

Ok, so we’re on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS → Python communication issues.

Thanks again for your input! I’ll be sure to post a solution if I find one.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]


Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven [email protected] wrote:

Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

    [http://demo.bokehplots.com/](http://demo.bokehplots.com/)

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

    [http://demo.bokehplots.com/apps/weather](http://demo.bokehplots.com/apps/weather)

And here is an example that updates secondary plots as a result of a selection on a first plot:

    [http://demo.bokehplots.com/apps/selection_histogram](http://demo.bokehplots.com/apps/selection_histogram)

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can “hide” an existing glyph either by:

  • setting .visible = False on the glyph:

     from bokeh.plotting import *
    
     p = figure()
    
     p.circle([1,2,3], [4,5,6])
    
     r = p.square([1,2,3], [5,6,7])
    
     r.glyph.visible = False
    
     output_file("/tmp/foo.html")
    
     show(p)
    
  • setting the glyphs fill/line colors to None (or alpha to zero):

     from bokeh.plotting import *
    
     p = figure()
    
     p.circle([1,2,3], [4,5,6])
    
     r = p.square([1,2,3], [5,6,7])
    
     r.glyph.fill_color = None
    
     r.glyph.line_color = None
    
     output_file("/tmp/foo.html")
    
     show(p)
    
  • clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph objects themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and “remove it”.

Bryan

On Jun 9, 2016, at 11:28 AM, Gabriel Diaz [email protected] wrote:

Ok, thanks for the update in terminology.

I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don’t know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!

The notebook is not a hard requirement, but it would be nice.

So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.

  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

I’m not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven [email protected] wrote:

As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a “circle glyph” isn’t an actual, specific circle(s) that gets drawn somewhere, it’s a thing that can draw circles when you pair it with some specific data. That’s why most of the use-cases are geared towards creating all the glyphs you might ever need up front, and then just changing the data for them as necessary.

Bryan

On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven [email protected] wrote:

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

  • The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app
  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz [email protected] wrote:

First, thank you very much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I’ve posted earlier.

Regardless of what I’ve accomplished, heres the functionality that I would like to add:

  • Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
  • Select the event (e.g. using tap-tool).
  • Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
  • I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
  • Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event’s label.

Thanks again.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven [email protected] wrote:

Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay “in sync” with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the “push_notebook” function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python → JS, although there are other folks interested in and working on ideas to enable the “reverse” direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(…) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the “Continuous Updating” notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

  • recreate a new plot
  • set the glyph to be invisble
  • update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like “I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen”.

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz [email protected] wrote:

Thanks Bryan. Unless I missed it, I don’t see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn’t help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple ‘rects’ have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I’ve explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven [email protected] wrote:

I would suggest studying the examples here:

   [https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms](https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms)

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz [email protected] wrote:

Apologies. An example is included in the attached *.ipynb

  • gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)
  • Record the event type and start/stop time (on X) of the event in a pandas dataframe
  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

 • File
 • Edit
 • View
 • Insert
 • Cell
 • Kernel
 • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');

   /// get BoxSelectTool dimensions from cb_data parameter of Callback
   var geometry = cb_data['geometry'];

   /// calculate Rect attributes
   var width = geometry['x1'] - geometry['x0'];
   var height = geometry['y1'] - geometry['y0'];
   var x = geometry['x0'] + width/2;
   var y = geometry['y0'] + height/2;
   var leftEdge = geometry['x0']
   var rightEdge = geometry['x1']
   /// update data source with new Rect attributes
   data['x'].push(x);
   data['y'].push(y);
   data['width'].push(width);
   data['height'].push(height);
   data['eventType'] = 'none'
   data['leftEdge'] = leftEdge
   data['rightEdge'] = rightEdge
   // trigger update of data source
   source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');
   var width = data['width']
   IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
   IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

selected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');

   /// get BoxSelectTool dimensions from cb_data parameter of Callback
   var geometry = cb_data['geometry'];

   /// calculate Rect attributes
   var width = geometry['x1'] - geometry['x0'];
   var height = geometry['y1'] - geometry['y0'];
   var x = geometry['x0'] + width/2;
   var y = geometry['y0'] + height/2;
   var leftEdge = geometry['x0']
   var rightEdge = geometry['x1']
   /// update data source with new Rect attributes
   data['x'].push(x);
   data['y'].push(y);
   data['width'].push(width);
   data['height'].push(height);
   data['eventType'] = 'none'
   data['leftEdge'] = leftEdge
   data['rightEdge'] = rightEdge
   // trigger update of data source
   source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

   // get data source from Callback args
   var data = source.get('data');
   var width = data['width']
   IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
   IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='gray')

selected = bkM.Rect(x=‘x’,

       y='y',
       width='width',
       height='height',
       fill_alpha=0.3,
       fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

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/b0400249-fb53-482c-8768-c2134923b72a%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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com.

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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com.

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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com.

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/FE7B66D0-8D13-468A-AE23-DF443D441F79%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Gabriel,

If I understand what you are looking for, this is the JS callback code to accomplish it:

  tapCallback = bkM.CustomJS(args=dict(source = undefinedSource,fS=fixSource, sS=saccSource), code="""
    var idx = cb_obj.get('selected')['1d'].indices[0];
    selectedID = cb_obj.get('data')['boxID'][idx];

    console.log("idx " + idx);
    console.log("boxIDs " + cb_obj.get('data')['boxID']);
    console.log("selectedID " + selectedID);

    var k = IPython.notebook.kernel;
    k.execute("selectedID = " + selectedID );
  """)

I've added some console.log statements so you can see what's going on more closely if you open your brower's JS console. I also had to add the callback to the TapTool below, it was not passed to the initializer.

As a side note, I am not sure that idx and selectedID will ever be different. But I can't prove that so this is definitely the "safest" approach.

Cool notebook BTW! Would love to show off/tweet about it when you are done, if it is something you can publicly share.

Bryan

···

On Jun 12, 2016, at 10:10 AM, Gabriel Diaz <[email protected]> wrote:

To follow up...

Code attached.

I've stuck with the method of using the python kernel to execute code in JS that can effect the Python environment. I could not find documentation or a tutorial that demonstrated a way to use server mode to avoid the use of javascript callbacks for the placement of new events (rectangles).

To remind you, my goal is to place events (rectangles) using the box tool, and then use tap tool + ipywidgets to assign each placed event to one of three visually distinct event categories (A, B, and C). To accomplish this, I've created four glyphs: one that is a default glyph to be used prior to group assignment, and then one for A, B, and C. Once an event is categorized by the user, its data will be moved from the undefined source to source A, B, or C.

Because I'm much more familiar with Python than JS, I intend to use taptool to select the rectangle, but Python to manage passing its information between two data sources. This requires that Python know which rectangle has been clicked by the tap tool. My approach is to include an "boxID" field in each data source so that each rectangle has a unique identification number. Inside the taptool JS callback, the boxID of the selected event rectangle event is passed back to Python. Subsequently, when an ipywidget button indicating a group assignment (A,B, or C) is pressed, then the Python callback will transfer the source entry of the event from an default source to the appropriate group's source.

I'm nearly there. As you can see in my other post, the holdup is that I cannot figure out how to get the information about the selected item inside the tap-tool CB, so that I can the identification number I've stored in its source.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:54 PM, Gabriel Diaz <[email protected]> wrote:
Ok, so we're on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS -> Python communication issues.

Thanks again for your input! I'll be sure to post a solution if I find one.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven <[email protected]> wrote:
Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

        http://demo.bokehplots.com/

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

        http://demo.bokehplots.com/apps/weather

And here is an example that updates secondary plots as a result of a selection on a first plot:

        http://demo.bokehplots.com/apps/selection_histogram

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can "hide" an existing glyph either by:

* setting .visible = False on the glyph:

        from bokeh.plotting import *
        p = figure()
        p.circle([1,2,3], [4,5,6])
        r = p.square([1,2,3], [5,6,7])
        r.glyph.visible = False
        output_file("/tmp/foo.html")
        show(p)

* setting the glyphs fill/line colors to None (or alpha to zero):

        from bokeh.plotting import *
        p = figure()
        p.circle([1,2,3], [4,5,6])
        r = p.square([1,2,3], [5,6,7])
        r.glyph.fill_color = None
        r.glyph.line_color = None
        output_file("/tmp/foo.html")
        show(p)

* clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph *objects* themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and "remove it".

Bryan

> On Jun 9, 2016, at 11:28 AM, Gabriel Diaz <[email protected]> wrote:
>
> Ok, thanks for the update in terminology.
>
> I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don't know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!
>
> The notebook is not a hard requirement, but it would be nice.
>
> So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.
>
> * Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.
>
> I'm not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.
>
>
> - gD
>
>
> ----------------------
>
> Gabriel J. Diaz, Ph.D.
> Assistant Professor
> Rochester Institute of Technology
> Chester F. Carlson Center for Imaging Science
>
> Founder of PerForM Labs
> Click for demos.
>
> Office 2108, Building #76
> Rochester, NY 14623
> Office: (585) 475-6215
> [email protected]
>
>
>
>
>
> On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven <[email protected]> wrote:
> As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a "circle glyph" isn't an actual, specific circle(s) that gets drawn somewhere, it's a thing that can draw circles when you pair it with some specific data. That's why most of the use-cases are geared towards creating all the glyphs you *might ever* need up front, and then just changing the data for them as necessary.
>
> Bryan
>
> > On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven <[email protected]> wrote:
> >
> > OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:
> >
> > * The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app
> >
> > * Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.
> >
> > Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.
> >
> > Bryan
> >
> >> On Jun 9, 2016, at 10:32 AM, Gabriel Diaz <[email protected]> wrote:
> >>
> >> First, thank you *very* much for such a thoughtful and detailed response. I now understand the problem with much more clarity.
> >>
> >> Here is my intention:
> >>
> >> I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I've posted earlier.
> >>
> >> Regardless of what I've accomplished, heres the functionality that I would like to add:
> >>
> >> - Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
> >> - Select the event (e.g. using tap-tool).
> >> - Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
> >> - I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
> >> - Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event's label.
> >>
> >> Thanks again.
> >> - gD
> >>
> >>
> >>
> >>
> >> ----------------------
> >>
> >> Gabriel J. Diaz, Ph.D.
> >> Assistant Professor
> >> Rochester Institute of Technology
> >> Chester F. Carlson Center for Imaging Science
> >>
> >> Founder of PerForM Labs
> >> Click for demos.
> >>
> >> Office 2108, Building #76
> >> Rochester, NY 14623
> >> Office: (585) 475-6215
> >> [email protected]
> >>
> >>
> >>
> >>
> >>
> >> On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven <[email protected]> wrote:
> >> Hi,
> >>
> >> Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay "in sync" with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.
> >>
> >> However, there are other options for keeping the JS and python objects in sync, namely the "push_notebook" function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python -> JS, although there are other folks interested in and working on ideas to enable the "reverse" direction updates as well.
> >>
> >> Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(...) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the "Continuous Updating" notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:
> >>
> >> * recreate a new plot
> >> * set the glyph to be invisble
> >> * update the glyphs data
> >>
> >> Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like "I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen".
> >>
> >> Thanks,
> >>
> >> Bryan
> >>
> >>
> >>> On Jun 9, 2016, at 7:54 AM, Gabriel Diaz <[email protected]> wrote:
> >>>
> >>> Thanks Bryan. Unless I missed it, I don't see an answer to my question in those otherwise very helpful guides.
> >>>
> >>> Let me try and restate my issue, in case it lacked clarity (that large code dump didn't help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.
> >>>
> >>> However, I cannot find where information about individual glpyhs objects stored.
> >>>
> >>> The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple 'rects' have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?
> >>>
> >>> I've explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.
> >>>
> >>> Thanks
> >>> - gD
> >>>
> >>>
> >>> ----------------------
> >>>
> >>> Gabriel J. Diaz, Ph.D.
> >>> Assistant Professor
> >>> Rochester Institute of Technology
> >>> Chester F. Carlson Center for Imaging Science
> >>>
> >>> Founder of PerForM Labs
> >>> Click for demos.
> >>>
> >>> Office 2108, Building #76
> >>> Rochester, NY 14623
> >>> Office: (585) 475-6215
> >>> [email protected]
> >>>
> >>>
> >>>
> >>>
> >>>
> >>> On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven <[email protected]> wrote:
> >>> I would suggest studying the examples here:
> >>>
> >>> https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms
> >>>
> >>> Thanks,
> >>>
> >>> Bryan
> >>>
> >>>
> >>>> On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:
> >>>>
> >>>> Apologies. An example is included in the attached *.ipynb
> >>>>
> >>>> - gD
> >>>>
> >>>> On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
> >>>> Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.
> >>>>
> >>>>
> >>>> Code from my notebook is pasted below!
> >>>> If you want to know more, read more to find out why...
> >>>>
> >>>> Thanks!
> >>>> - gD
> >>>>
> >>>> -----
> >>>>
> >>>> My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...
> >>>>
> >>>> - Label the patch as event type A,B, or C (depending upon the button pressed)
> >>>> - Record the event type and start/stop time (on X) of the event in a pandas dataframe
> >>>> - Update the glyph to color-code the event
> >>>>
> >>>> I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> Python 2
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> • File
> >>>>
> >>>> • Edit
> >>>>
> >>>> • View
> >>>>
> >>>> • Insert
> >>>>
> >>>> • Cell
> >>>>
> >>>> • Kernel
> >>>>
> >>>> • Help
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> CodeMarkdownRaw NBConvertHeading
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> CellToolbar
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> In [1]:
> >>>>
> >>>>
> >>>> from __future__ import division
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> import pandas as pd
> >>>>
> >>>>
> >>>> import numpy as np
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> import bokeh.plotting as bkP
> >>>>
> >>>>
> >>>> import bokeh.models as bkM
> >>>>
> >>>>
> >>>> from bokeh.palettes import Spectral6
> >>>>
> >>>>
> >>>> from bokeh.embed import file_html
> >>>>
> >>>>
> >>>> from bokeh.resources import CDN
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> bkP.output_notebook()
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> x
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> from __future__ import division
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> import pandas as pd
> >>>>
> >>>>
> >>>> import numpy as np
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> import bokeh.plotting as bkP
> >>>>
> >>>>
> >>>> import bokeh.models as bkM
> >>>>
> >>>>
> >>>> from bokeh.palettes import Spectral6
> >>>>
> >>>>
> >>>> from bokeh.embed import file_html
> >>>>
> >>>>
> >>>> from bokeh.resources import CDN
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> bkP.output_notebook()
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> /Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> UserWarning)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> BokehJS successfully loaded
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> In [41]:
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> x
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> boxCallback = bkM.CustomJS(args=dict(source=source), code="""
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // get data source from Callback args
> >>>>
> >>>>
> >>>> var data = source.get('data');
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> /// get BoxSelectTool dimensions from cb_data parameter of Callback
> >>>>
> >>>>
> >>>> var geometry = cb_data['geometry'];
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> /// calculate Rect attributes
> >>>>
> >>>>
> >>>> var width = geometry['x1'] - geometry['x0'];
> >>>>
> >>>>
> >>>> var height = geometry['y1'] - geometry['y0'];
> >>>>
> >>>>
> >>>> var x = geometry['x0'] + width/2;
> >>>>
> >>>>
> >>>> var y = geometry['y0'] + height/2;
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> var leftEdge = geometry['x0']
> >>>>
> >>>>
> >>>> var rightEdge = geometry['x1']
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> /// update data source with new Rect attributes
> >>>>
> >>>>
> >>>> data['x'].push(x);
> >>>>
> >>>>
> >>>> data['y'].push(y);
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> data['width'].push(width);
> >>>>
> >>>>
> >>>> data['height'].push(height);
> >>>>
> >>>>
> >>>> data['eventType'] = 'none'
> >>>>
> >>>>
> >>>> data['leftEdge'] = leftEdge
> >>>>
> >>>>
> >>>> data['rightEdge'] = rightEdge
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // trigger update of data source
> >>>>
> >>>>
> >>>> source.trigger('change');
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> """)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> tapCallback = bkM.CustomJS(args=dict(source=source), code="""
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // get data source from Callback args
> >>>>
> >>>>
> >>>> var data = source.get('data');
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> var width = data['width']
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
> >>>>
> >>>>
> >>>> IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>> """)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?
> >>>>
> >>>>
> >>>> In [42]:
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> leftEdge =
> >>>>
> >>>>
> >>>> rightEdge =
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> x = np.deg2rad(range(720))
> >>>>
> >>>>
> >>>> y = 1+np.sin(x)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.xaxis.axis_label = 'time'
> >>>>
> >>>>
> >>>> p.yaxis.axis_label = 'position'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.line(x,y,line_width=4,name='aLine')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> initRect = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='gray')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> notSelected = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='gray')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> selected = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='green')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
> >>>>
> >>>>
> >>>> p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
> >>>>
> >>>>
> >>>> p.add_tools(bkM.TapTool(callback=tapCallback))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> bkP.show(p)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> Out[42]:
> >>>>
> >>>>
> >>>> <Bokeh Notebook handle for In[42]>
> >>>>
> >>>>
> >>>> In [44]:
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> from ipywidgets import *
> >>>>
> >>>>
> >>>> from IPython.display import display
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> def foundSacc(sender):
> >>>>
> >>>>
> >>>> print leftEdge
> >>>>
> >>>>
> >>>> print rightEdge
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> b2 = Button(description='Fixation',value=False)
> >>>>
> >>>>
> >>>> b2.on_click(foundSacc)
> >>>>
> >>>>
> >>>> b2.width = '20%'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> b1 = Button(description='Saccade',value=False)
> >>>>
> >>>>
> >>>> b1.on_click(foundSacc)
> >>>>
> >>>>
> >>>> b1.width = '20%'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> display(b1,b2)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> ×
> >>>>
> >>>>
> >>>> Saccade
> >>>>
> >>>>
> >>>> Fixation
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> In :
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> from __future__ import division
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> import pandas as pd
> >>>>
> >>>>
> >>>> import numpy as np
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> import bokeh.plotting as bkP
> >>>>
> >>>>
> >>>> import bokeh.models as bkM
> >>>>
> >>>>
> >>>> from bokeh.palettes import Spectral6
> >>>>
> >>>>
> >>>> from bokeh.embed import file_html
> >>>>
> >>>>
> >>>> from bokeh.resources import CDN
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> bkP.output_notebook()
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> boxCallback = bkM.CustomJS(args=dict(source=source), code="""
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // get data source from Callback args
> >>>>
> >>>>
> >>>> var data = source.get('data');
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> /// get BoxSelectTool dimensions from cb_data parameter of Callback
> >>>>
> >>>>
> >>>> var geometry = cb_data['geometry'];
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> /// calculate Rect attributes
> >>>>
> >>>>
> >>>> var width = geometry['x1'] - geometry['x0'];
> >>>>
> >>>>
> >>>> var height = geometry['y1'] - geometry['y0'];
> >>>>
> >>>>
> >>>> var x = geometry['x0'] + width/2;
> >>>>
> >>>>
> >>>> var y = geometry['y0'] + height/2;
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> var leftEdge = geometry['x0']
> >>>>
> >>>>
> >>>> var rightEdge = geometry['x1']
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> /// update data source with new Rect attributes
> >>>>
> >>>>
> >>>> data['x'].push(x);
> >>>>
> >>>>
> >>>> data['y'].push(y);
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> data['width'].push(width);
> >>>>
> >>>>
> >>>> data['height'].push(height);
> >>>>
> >>>>
> >>>> data['eventType'] = 'none'
> >>>>
> >>>>
> >>>> data['leftEdge'] = leftEdge
> >>>>
> >>>>
> >>>> data['rightEdge'] = rightEdge
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // trigger update of data source
> >>>>
> >>>>
> >>>> source.trigger('change');
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> """)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> tapCallback = bkM.CustomJS(args=dict(source=source), code="""
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // get data source from Callback args
> >>>>
> >>>>
> >>>> var data = source.get('data');
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> var width = data['width']
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
> >>>>
> >>>>
> >>>> IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>> """)
> >>>>
> >>>>
> >>>> # IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);
> >>>>
> >>>>
> >>>> # IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> leftEdge =
> >>>>
> >>>>
> >>>> rightEdge =
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> x = np.deg2rad(range(720))
> >>>>
> >>>>
> >>>> y = 1+np.sin(x)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.xaxis.axis_label = 'time'
> >>>>
> >>>>
> >>>> p.yaxis.axis_label = 'position'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.line(x,y,line_width=4,name='aLine')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> initRect = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='gray')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> notSelected = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='gray')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> selected = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='green')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
> >>>>
> >>>>
> >>>> p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
> >>>>
> >>>>
> >>>> p.add_tools(bkM.TapTool(callback=tapCallback))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> bkP.show(p)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> ########################################################################
> >>>>
> >>>>
> >>>> ########################################################################
> >>>>
> >>>>
> >>>> ### NewCell
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> from ipywidgets import *
> >>>>
> >>>>
> >>>> from IPython.display import display
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> def foundSacc(sender):
> >>>>
> >>>>
> >>>> print leftEdge
> >>>>
> >>>>
> >>>> print rightEdge
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> b2 = Button(description='Fixation',value=False)
> >>>>
> >>>>
> >>>> b2.on_click(foundSacc)
> >>>>
> >>>>
> >>>> b2.width = '20%'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> b1 = Button(description='Saccade',value=False)
> >>>>
> >>>>
> >>>> b1.on_click(foundSacc)
> >>>>
> >>>>
> >>>> b1.width = '20%'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> display(b1,b2)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> --
> >>>> 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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
> >>>> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
> >>>> <Example Event Labeller.ipynb>
> >>>
> >>> --
> >>> 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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com\.
> >>> 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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com\.
> >> 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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com\.
> 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/FE7B66D0-8D13-468A-AE23-DF443D441F79%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/CAFvLWO3Aq0aN0VR%3DNoaksxBk3ZHHmZicfBR%3DzEmBJKijSP6eEA%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Event Labeller.ipynb>

One more note, In order to do all the automagic stuff, we have to wrap our dict properties, etc in some fancy classes like PropertyDictContainer. They support some standard dict operations, but it looks like iteritems is not one. I also had to change that to .items()

Thanks,

Bryan

···

On Jun 12, 2016, at 10:10 AM, Gabriel Diaz <[email protected]> wrote:

To follow up...

Code attached.

I've stuck with the method of using the python kernel to execute code in JS that can effect the Python environment. I could not find documentation or a tutorial that demonstrated a way to use server mode to avoid the use of javascript callbacks for the placement of new events (rectangles).

To remind you, my goal is to place events (rectangles) using the box tool, and then use tap tool + ipywidgets to assign each placed event to one of three visually distinct event categories (A, B, and C). To accomplish this, I've created four glyphs: one that is a default glyph to be used prior to group assignment, and then one for A, B, and C. Once an event is categorized by the user, its data will be moved from the undefined source to source A, B, or C.

Because I'm much more familiar with Python than JS, I intend to use taptool to select the rectangle, but Python to manage passing its information between two data sources. This requires that Python know which rectangle has been clicked by the tap tool. My approach is to include an "boxID" field in each data source so that each rectangle has a unique identification number. Inside the taptool JS callback, the boxID of the selected event rectangle event is passed back to Python. Subsequently, when an ipywidget button indicating a group assignment (A,B, or C) is pressed, then the Python callback will transfer the source entry of the event from an default source to the appropriate group's source.

I'm nearly there. As you can see in my other post, the holdup is that I cannot figure out how to get the information about the selected item inside the tap-tool CB, so that I can the identification number I've stored in its source.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:54 PM, Gabriel Diaz <[email protected]> wrote:
Ok, so we're on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS -> Python communication issues.

Thanks again for your input! I'll be sure to post a solution if I find one.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven <[email protected]> wrote:
Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

        http://demo.bokehplots.com/

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

        http://demo.bokehplots.com/apps/weather

And here is an example that updates secondary plots as a result of a selection on a first plot:

        http://demo.bokehplots.com/apps/selection_histogram

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can "hide" an existing glyph either by:

* setting .visible = False on the glyph:

        from bokeh.plotting import *
        p = figure()
        p.circle([1,2,3], [4,5,6])
        r = p.square([1,2,3], [5,6,7])
        r.glyph.visible = False
        output_file("/tmp/foo.html")
        show(p)

* setting the glyphs fill/line colors to None (or alpha to zero):

        from bokeh.plotting import *
        p = figure()
        p.circle([1,2,3], [4,5,6])
        r = p.square([1,2,3], [5,6,7])
        r.glyph.fill_color = None
        r.glyph.line_color = None
        output_file("/tmp/foo.html")
        show(p)

* clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph *objects* themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and "remove it".

Bryan

> On Jun 9, 2016, at 11:28 AM, Gabriel Diaz <[email protected]> wrote:
>
> Ok, thanks for the update in terminology.
>
> I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don't know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!
>
> The notebook is not a hard requirement, but it would be nice.
>
> So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.
>
> * Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.
>
> I'm not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.
>
>
> - gD
>
>
> ----------------------
>
> Gabriel J. Diaz, Ph.D.
> Assistant Professor
> Rochester Institute of Technology
> Chester F. Carlson Center for Imaging Science
>
> Founder of PerForM Labs
> Click for demos.
>
> Office 2108, Building #76
> Rochester, NY 14623
> Office: (585) 475-6215
> [email protected]
>
>
>
>
>
> On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven <[email protected]> wrote:
> As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a "circle glyph" isn't an actual, specific circle(s) that gets drawn somewhere, it's a thing that can draw circles when you pair it with some specific data. That's why most of the use-cases are geared towards creating all the glyphs you *might ever* need up front, and then just changing the data for them as necessary.
>
> Bryan
>
> > On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven <[email protected]> wrote:
> >
> > OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:
> >
> > * The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app
> >
> > * Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.
> >
> > Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.
> >
> > Bryan
> >
> >> On Jun 9, 2016, at 10:32 AM, Gabriel Diaz <[email protected]> wrote:
> >>
> >> First, thank you *very* much for such a thoughtful and detailed response. I now understand the problem with much more clarity.
> >>
> >> Here is my intention:
> >>
> >> I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I've posted earlier.
> >>
> >> Regardless of what I've accomplished, heres the functionality that I would like to add:
> >>
> >> - Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
> >> - Select the event (e.g. using tap-tool).
> >> - Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
> >> - I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
> >> - Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event's label.
> >>
> >> Thanks again.
> >> - gD
> >>
> >>
> >>
> >>
> >> ----------------------
> >>
> >> Gabriel J. Diaz, Ph.D.
> >> Assistant Professor
> >> Rochester Institute of Technology
> >> Chester F. Carlson Center for Imaging Science
> >>
> >> Founder of PerForM Labs
> >> Click for demos.
> >>
> >> Office 2108, Building #76
> >> Rochester, NY 14623
> >> Office: (585) 475-6215
> >> [email protected]
> >>
> >>
> >>
> >>
> >>
> >> On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven <[email protected]> wrote:
> >> Hi,
> >>
> >> Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay "in sync" with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.
> >>
> >> However, there are other options for keeping the JS and python objects in sync, namely the "push_notebook" function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python -> JS, although there are other folks interested in and working on ideas to enable the "reverse" direction updates as well.
> >>
> >> Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(...) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the "Continuous Updating" notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:
> >>
> >> * recreate a new plot
> >> * set the glyph to be invisble
> >> * update the glyphs data
> >>
> >> Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like "I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen".
> >>
> >> Thanks,
> >>
> >> Bryan
> >>
> >>
> >>> On Jun 9, 2016, at 7:54 AM, Gabriel Diaz <[email protected]> wrote:
> >>>
> >>> Thanks Bryan. Unless I missed it, I don't see an answer to my question in those otherwise very helpful guides.
> >>>
> >>> Let me try and restate my issue, in case it lacked clarity (that large code dump didn't help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.
> >>>
> >>> However, I cannot find where information about individual glpyhs objects stored.
> >>>
> >>> The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple 'rects' have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?
> >>>
> >>> I've explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.
> >>>
> >>> Thanks
> >>> - gD
> >>>
> >>>
> >>> ----------------------
> >>>
> >>> Gabriel J. Diaz, Ph.D.
> >>> Assistant Professor
> >>> Rochester Institute of Technology
> >>> Chester F. Carlson Center for Imaging Science
> >>>
> >>> Founder of PerForM Labs
> >>> Click for demos.
> >>>
> >>> Office 2108, Building #76
> >>> Rochester, NY 14623
> >>> Office: (585) 475-6215
> >>> [email protected]
> >>>
> >>>
> >>>
> >>>
> >>>
> >>> On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven <[email protected]> wrote:
> >>> I would suggest studying the examples here:
> >>>
> >>> https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms
> >>>
> >>> Thanks,
> >>>
> >>> Bryan
> >>>
> >>>
> >>>> On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:
> >>>>
> >>>> Apologies. An example is included in the attached *.ipynb
> >>>>
> >>>> - gD
> >>>>
> >>>> On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
> >>>> Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.
> >>>>
> >>>>
> >>>> Code from my notebook is pasted below!
> >>>> If you want to know more, read more to find out why...
> >>>>
> >>>> Thanks!
> >>>> - gD
> >>>>
> >>>> -----
> >>>>
> >>>> My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...
> >>>>
> >>>> - Label the patch as event type A,B, or C (depending upon the button pressed)
> >>>> - Record the event type and start/stop time (on X) of the event in a pandas dataframe
> >>>> - Update the glyph to color-code the event
> >>>>
> >>>> I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> Python 2
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> • File
> >>>>
> >>>> • Edit
> >>>>
> >>>> • View
> >>>>
> >>>> • Insert
> >>>>
> >>>> • Cell
> >>>>
> >>>> • Kernel
> >>>>
> >>>> • Help
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> CodeMarkdownRaw NBConvertHeading
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> CellToolbar
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> In [1]:
> >>>>
> >>>>
> >>>> from __future__ import division
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> import pandas as pd
> >>>>
> >>>>
> >>>> import numpy as np
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> import bokeh.plotting as bkP
> >>>>
> >>>>
> >>>> import bokeh.models as bkM
> >>>>
> >>>>
> >>>> from bokeh.palettes import Spectral6
> >>>>
> >>>>
> >>>> from bokeh.embed import file_html
> >>>>
> >>>>
> >>>> from bokeh.resources import CDN
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> bkP.output_notebook()
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> x
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> from __future__ import division
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> import pandas as pd
> >>>>
> >>>>
> >>>> import numpy as np
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> import bokeh.plotting as bkP
> >>>>
> >>>>
> >>>> import bokeh.models as bkM
> >>>>
> >>>>
> >>>> from bokeh.palettes import Spectral6
> >>>>
> >>>>
> >>>> from bokeh.embed import file_html
> >>>>
> >>>>
> >>>> from bokeh.resources import CDN
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> bkP.output_notebook()
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> /Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> UserWarning)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> BokehJS successfully loaded
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> In [41]:
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> x
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> boxCallback = bkM.CustomJS(args=dict(source=source), code="""
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // get data source from Callback args
> >>>>
> >>>>
> >>>> var data = source.get('data');
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> /// get BoxSelectTool dimensions from cb_data parameter of Callback
> >>>>
> >>>>
> >>>> var geometry = cb_data['geometry'];
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> /// calculate Rect attributes
> >>>>
> >>>>
> >>>> var width = geometry['x1'] - geometry['x0'];
> >>>>
> >>>>
> >>>> var height = geometry['y1'] - geometry['y0'];
> >>>>
> >>>>
> >>>> var x = geometry['x0'] + width/2;
> >>>>
> >>>>
> >>>> var y = geometry['y0'] + height/2;
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> var leftEdge = geometry['x0']
> >>>>
> >>>>
> >>>> var rightEdge = geometry['x1']
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> /// update data source with new Rect attributes
> >>>>
> >>>>
> >>>> data['x'].push(x);
> >>>>
> >>>>
> >>>> data['y'].push(y);
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> data['width'].push(width);
> >>>>
> >>>>
> >>>> data['height'].push(height);
> >>>>
> >>>>
> >>>> data['eventType'] = 'none'
> >>>>
> >>>>
> >>>> data['leftEdge'] = leftEdge
> >>>>
> >>>>
> >>>> data['rightEdge'] = rightEdge
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // trigger update of data source
> >>>>
> >>>>
> >>>> source.trigger('change');
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> """)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> tapCallback = bkM.CustomJS(args=dict(source=source), code="""
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // get data source from Callback args
> >>>>
> >>>>
> >>>> var data = source.get('data');
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> var width = data['width']
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
> >>>>
> >>>>
> >>>> IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>> """)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?
> >>>>
> >>>>
> >>>> In [42]:
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> leftEdge =
> >>>>
> >>>>
> >>>> rightEdge =
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> x = np.deg2rad(range(720))
> >>>>
> >>>>
> >>>> y = 1+np.sin(x)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.xaxis.axis_label = 'time'
> >>>>
> >>>>
> >>>> p.yaxis.axis_label = 'position'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.line(x,y,line_width=4,name='aLine')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> initRect = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='gray')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> notSelected = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='gray')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> selected = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='green')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
> >>>>
> >>>>
> >>>> p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
> >>>>
> >>>>
> >>>> p.add_tools(bkM.TapTool(callback=tapCallback))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> bkP.show(p)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> Out[42]:
> >>>>
> >>>>
> >>>> <Bokeh Notebook handle for In[42]>
> >>>>
> >>>>
> >>>> In [44]:
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> from ipywidgets import *
> >>>>
> >>>>
> >>>> from IPython.display import display
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> def foundSacc(sender):
> >>>>
> >>>>
> >>>> print leftEdge
> >>>>
> >>>>
> >>>> print rightEdge
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> b2 = Button(description='Fixation',value=False)
> >>>>
> >>>>
> >>>> b2.on_click(foundSacc)
> >>>>
> >>>>
> >>>> b2.width = '20%'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> b1 = Button(description='Saccade',value=False)
> >>>>
> >>>>
> >>>> b1.on_click(foundSacc)
> >>>>
> >>>>
> >>>> b1.width = '20%'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> display(b1,b2)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> ×
> >>>>
> >>>>
> >>>> Saccade
> >>>>
> >>>>
> >>>> Fixation
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> In :
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> from __future__ import division
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> import pandas as pd
> >>>>
> >>>>
> >>>> import numpy as np
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> import bokeh.plotting as bkP
> >>>>
> >>>>
> >>>> import bokeh.models as bkM
> >>>>
> >>>>
> >>>> from bokeh.palettes import Spectral6
> >>>>
> >>>>
> >>>> from bokeh.embed import file_html
> >>>>
> >>>>
> >>>> from bokeh.resources import CDN
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> bkP.output_notebook()
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> boxCallback = bkM.CustomJS(args=dict(source=source), code="""
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // get data source from Callback args
> >>>>
> >>>>
> >>>> var data = source.get('data');
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> /// get BoxSelectTool dimensions from cb_data parameter of Callback
> >>>>
> >>>>
> >>>> var geometry = cb_data['geometry'];
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> /// calculate Rect attributes
> >>>>
> >>>>
> >>>> var width = geometry['x1'] - geometry['x0'];
> >>>>
> >>>>
> >>>> var height = geometry['y1'] - geometry['y0'];
> >>>>
> >>>>
> >>>> var x = geometry['x0'] + width/2;
> >>>>
> >>>>
> >>>> var y = geometry['y0'] + height/2;
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> var leftEdge = geometry['x0']
> >>>>
> >>>>
> >>>> var rightEdge = geometry['x1']
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> /// update data source with new Rect attributes
> >>>>
> >>>>
> >>>> data['x'].push(x);
> >>>>
> >>>>
> >>>> data['y'].push(y);
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> data['width'].push(width);
> >>>>
> >>>>
> >>>> data['height'].push(height);
> >>>>
> >>>>
> >>>> data['eventType'] = 'none'
> >>>>
> >>>>
> >>>> data['leftEdge'] = leftEdge
> >>>>
> >>>>
> >>>> data['rightEdge'] = rightEdge
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // trigger update of data source
> >>>>
> >>>>
> >>>> source.trigger('change');
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> """)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> tapCallback = bkM.CustomJS(args=dict(source=source), code="""
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> // get data source from Callback args
> >>>>
> >>>>
> >>>> var data = source.get('data');
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> var width = data['width']
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
> >>>>
> >>>>
> >>>> IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>> """)
> >>>>
> >>>>
> >>>> # IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);
> >>>>
> >>>>
> >>>> # IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> leftEdge =
> >>>>
> >>>>
> >>>> rightEdge =
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> x = np.deg2rad(range(720))
> >>>>
> >>>>
> >>>> y = 1+np.sin(x)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.xaxis.axis_label = 'time'
> >>>>
> >>>>
> >>>> p.yaxis.axis_label = 'position'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.line(x,y,line_width=4,name='aLine')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> initRect = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='gray')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> notSelected = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='gray')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> selected = bkM.Rect(x='x',
> >>>>
> >>>>
> >>>> y='y',
> >>>>
> >>>>
> >>>> width='width',
> >>>>
> >>>>
> >>>> height='height',
> >>>>
> >>>>
> >>>> fill_alpha=0.3,
> >>>>
> >>>>
> >>>> fill_color='green')
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
> >>>>
> >>>>
> >>>> p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
> >>>>
> >>>>
> >>>> p.add_tools(bkM.TapTool(callback=tapCallback))
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> bkP.show(p)
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> ########################################################################
> >>>>
> >>>>
> >>>> ########################################################################
> >>>>
> >>>>
> >>>> ### NewCell
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> from ipywidgets import *
> >>>>
> >>>>
> >>>> from IPython.display import display
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> def foundSacc(sender):
> >>>>
> >>>>
> >>>> print leftEdge
> >>>>
> >>>>
> >>>> print rightEdge
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> b2 = Button(description='Fixation',value=False)
> >>>>
> >>>>
> >>>> b2.on_click(foundSacc)
> >>>>
> >>>>
> >>>> b2.width = '20%'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> b1 = Button(description='Saccade',value=False)
> >>>>
> >>>>
> >>>> b1.on_click(foundSacc)
> >>>>
> >>>>
> >>>> b1.width = '20%'
> >>>>
> >>>>
> >>>> ​
> >>>>
> >>>> display(b1,b2)
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>
> >>>> --
> >>>> 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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
> >>>> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
> >>>> <Example Event Labeller.ipynb>
> >>>
> >>> --
> >>> 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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com\.
> >>> 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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com\.
> >> 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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com\.
> 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/FE7B66D0-8D13-468A-AE23-DF443D441F79%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/CAFvLWO3Aq0aN0VR%3DNoaksxBk3ZHHmZicfBR%3DzEmBJKijSP6eEA%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Event Labeller.ipynb>

also one more more not :slight_smile: There is logic error in moveBetweenSources. If it gets to the boxID column in the middle of the loop, then it deletes the value, and then the next time around the .index call fails with an exception. Saving the valueIdx once, before the loops starts, should fix that.

Bryan

···

On Jun 12, 2016, at 6:43 PM, Bryan Van de Ven <[email protected]> wrote:

Gabriel,

If I understand what you are looking for, this is the JS callback code to accomplish it:

  tapCallback = bkM.CustomJS(args=dict(source = undefinedSource,fS=fixSource, sS=saccSource), code="""
    var idx = cb_obj.get('selected')['1d'].indices[0];
    selectedID = cb_obj.get('data')['boxID'][idx];

    console.log("idx " + idx);
    console.log("boxIDs " + cb_obj.get('data')['boxID']);
    console.log("selectedID " + selectedID);

    var k = IPython.notebook.kernel;
    k.execute("selectedID = " + selectedID );
  """)

I've added some console.log statements so you can see what's going on more closely if you open your brower's JS console. I also had to add the callback to the TapTool below, it was not passed to the initializer.

As a side note, I am not sure that idx and selectedID will ever be different. But I can't prove that so this is definitely the "safest" approach.

Cool notebook BTW! Would love to show off/tweet about it when you are done, if it is something you can publicly share.

Bryan

On Jun 12, 2016, at 10:10 AM, Gabriel Diaz <[email protected]> wrote:

To follow up...

Code attached.

I've stuck with the method of using the python kernel to execute code in JS that can effect the Python environment. I could not find documentation or a tutorial that demonstrated a way to use server mode to avoid the use of javascript callbacks for the placement of new events (rectangles).

To remind you, my goal is to place events (rectangles) using the box tool, and then use tap tool + ipywidgets to assign each placed event to one of three visually distinct event categories (A, B, and C). To accomplish this, I've created four glyphs: one that is a default glyph to be used prior to group assignment, and then one for A, B, and C. Once an event is categorized by the user, its data will be moved from the undefined source to source A, B, or C.

Because I'm much more familiar with Python than JS, I intend to use taptool to select the rectangle, but Python to manage passing its information between two data sources. This requires that Python know which rectangle has been clicked by the tap tool. My approach is to include an "boxID" field in each data source so that each rectangle has a unique identification number. Inside the taptool JS callback, the boxID of the selected event rectangle event is passed back to Python. Subsequently, when an ipywidget button indicating a group assignment (A,B, or C) is pressed, then the Python callback will transfer the source entry of the event from an default source to the appropriate group's source.

I'm nearly there. As you can see in my other post, the holdup is that I cannot figure out how to get the information about the selected item inside the tap-tool CB, so that I can the identification number I've stored in its source.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:54 PM, Gabriel Diaz <[email protected]> wrote:
Ok, so we're on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS -> Python communication issues.

Thanks again for your input! I'll be sure to post a solution if I find one.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven <[email protected]> wrote:
Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

       http://demo.bokehplots.com/

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

       http://demo.bokehplots.com/apps/weather

And here is an example that updates secondary plots as a result of a selection on a first plot:

       http://demo.bokehplots.com/apps/selection_histogram

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can "hide" an existing glyph either by:

* setting .visible = False on the glyph:

       from bokeh.plotting import *
       p = figure()
       p.circle([1,2,3], [4,5,6])
       r = p.square([1,2,3], [5,6,7])
       r.glyph.visible = False
       output_file("/tmp/foo.html")
       show(p)

* setting the glyphs fill/line colors to None (or alpha to zero):

       from bokeh.plotting import *
       p = figure()
       p.circle([1,2,3], [4,5,6])
       r = p.square([1,2,3], [5,6,7])
       r.glyph.fill_color = None
       r.glyph.line_color = None
       output_file("/tmp/foo.html")
       show(p)

* clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph *objects* themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and "remove it".

Bryan

On Jun 9, 2016, at 11:28 AM, Gabriel Diaz <[email protected]> wrote:

Ok, thanks for the update in terminology.

I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don't know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!

The notebook is not a hard requirement, but it would be nice.

So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.

* Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.

I'm not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven <[email protected]> wrote:
As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a "circle glyph" isn't an actual, specific circle(s) that gets drawn somewhere, it's a thing that can draw circles when you pair it with some specific data. That's why most of the use-cases are geared towards creating all the glyphs you *might ever* need up front, and then just changing the data for them as necessary.

Bryan

On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven <[email protected]> wrote:

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

* The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app

* Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz <[email protected]> wrote:

First, thank you *very* much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I've posted earlier.

Regardless of what I've accomplished, heres the functionality that I would like to add:

- Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
- Select the event (e.g. using tap-tool).
- Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
- I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
- Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event's label.

Thanks again.
- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven <[email protected]> wrote:
Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay "in sync" with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the "push_notebook" function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python -> JS, although there are other folks interested in and working on ideas to enable the "reverse" direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(...) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the "Continuous Updating" notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

* recreate a new plot
* set the glyph to be invisble
* update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like "I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen".

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz <[email protected]> wrote:

Thanks Bryan. Unless I missed it, I don't see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn't help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple 'rects' have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I've explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks
- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven <[email protected]> wrote:
I would suggest studying the examples here:

      https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:

Apologies. An example is included in the attached *.ipynb

- gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!
If you want to know more, read more to find out why...

Thanks!
- gD

-----

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...

- Label the patch as event type A,B, or C (depending upon the button pressed)
- Record the event type and start/stop time (on X) of the event in a pandas dataframe
- Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

    • File

    • Edit

    • View

    • Insert

    • Cell

    • Kernel

    • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

      // get data source from Callback args

      var data = source.get('data');

      /// get BoxSelectTool dimensions from cb_data parameter of Callback

      var geometry = cb_data['geometry'];

      /// calculate Rect attributes

      var width = geometry['x1'] - geometry['x0'];

      var height = geometry['y1'] - geometry['y0'];

      var x = geometry['x0'] + width/2;

      var y = geometry['y0'] + height/2;

      var leftEdge = geometry['x0']

      var rightEdge = geometry['x1']

      /// update data source with new Rect attributes

      data['x'].push(x);

      data['y'].push(y);

      data['width'].push(width);

      data['height'].push(height);

      data['eventType'] = 'none'

      data['leftEdge'] = leftEdge

      data['rightEdge'] = rightEdge

      // trigger update of data source

      source.trigger('change');

  """)

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

      // get data source from Callback args

      var data = source.get('data');

      var width = data['width']

      IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

      IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );

  """)

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = 'time'

p.yaxis.axis_label = 'position'

p.line(x,y,line_width=4,name='aLine')

initRect = bkM.Rect(x='x',

          y='y',

          width='width',

          height='height',

          fill_alpha=0.3,

          fill_color='gray')

notSelected = bkM.Rect(x='x',

          y='y',

          width='width',

          height='height',

          fill_alpha=0.3,

          fill_color='gray')

selected = bkM.Rect(x='x',

          y='y',

          width='width',

          height='height',

          fill_alpha=0.3,

          fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')

p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

  print leftEdge

  print rightEdge

b2 = Button(description='Fixation',value=False)

b2.on_click(foundSacc)

b2.width = '20%'

b1 = Button(description='Saccade',value=False)

b1.on_click(foundSacc)

b1.width = '20%'

display(b1,b2)

×

Saccade

Fixation

In :

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

      // get data source from Callback args

      var data = source.get('data');

      /// get BoxSelectTool dimensions from cb_data parameter of Callback

      var geometry = cb_data['geometry'];

      /// calculate Rect attributes

      var width = geometry['x1'] - geometry['x0'];

      var height = geometry['y1'] - geometry['y0'];

      var x = geometry['x0'] + width/2;

      var y = geometry['y0'] + height/2;

      var leftEdge = geometry['x0']

      var rightEdge = geometry['x1']

      /// update data source with new Rect attributes

      data['x'].push(x);

      data['y'].push(y);

      data['width'].push(width);

      data['height'].push(height);

      data['eventType'] = 'none'

      data['leftEdge'] = leftEdge

      data['rightEdge'] = rightEdge

      // trigger update of data source

      source.trigger('change');

  """)

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

      // get data source from Callback args

      var data = source.get('data');

      var width = data['width']

      IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

      IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );

  """)

# IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);

# IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = 'time'

p.yaxis.axis_label = 'position'

p.line(x,y,line_width=4,name='aLine')

initRect = bkM.Rect(x='x',

          y='y',

          width='width',

          height='height',

          fill_alpha=0.3,

          fill_color='gray')

notSelected = bkM.Rect(x='x',

          y='y',

          width='width',

          height='height',

          fill_alpha=0.3,

          fill_color='gray')

selected = bkM.Rect(x='x',

          y='y',

          width='width',

          height='height',

          fill_alpha=0.3,

          fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')

p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

### NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

  print leftEdge

  print rightEdge

b2 = Button(description='Fixation',value=False)

b2.on_click(foundSacc)

b2.width = '20%'

b1 = Button(description='Saccade',value=False)

b1.on_click(foundSacc)

b1.width = '20%'

display(b1,b2)

--
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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Example Event Labeller.ipynb>

--
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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com\.
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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com\.
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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com\.
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/FE7B66D0-8D13-468A-AE23-DF443D441F79%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/CAFvLWO3Aq0aN0VR%3DNoaksxBk3ZHHmZicfBR%3DzEmBJKijSP6eEA%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Event Labeller.ipynb>

Sorry, one more (this is fun!) you probably also want to add a push_notebook at the end of foundSacc. This will cause the tapped and button-pushed rect to disappear. Otherwise if it remains and can be tapped again, but the index has been deleted from undefinedSource, so an index exception is raised.

Bryan

···

On Jun 12, 2016, at 6:51 PM, Bryan Van de Ven <[email protected]> wrote:

also one more more not :slight_smile: There is logic error in moveBetweenSources. If it gets to the boxID column in the middle of the loop, then it deletes the value, and then the next time around the .index call fails with an exception. Saving the valueIdx once, before the loops starts, should fix that.

Bryan

On Jun 12, 2016, at 6:43 PM, Bryan Van de Ven <[email protected]> wrote:

Gabriel,

If I understand what you are looking for, this is the JS callback code to accomplish it:

  tapCallback = bkM.CustomJS(args=dict(source = undefinedSource,fS=fixSource, sS=saccSource), code="""
    var idx = cb_obj.get('selected')['1d'].indices[0];
    selectedID = cb_obj.get('data')['boxID'][idx];

    console.log("idx " + idx);
    console.log("boxIDs " + cb_obj.get('data')['boxID']);
    console.log("selectedID " + selectedID);

    var k = IPython.notebook.kernel;
    k.execute("selectedID = " + selectedID );
  """)

I've added some console.log statements so you can see what's going on more closely if you open your brower's JS console. I also had to add the callback to the TapTool below, it was not passed to the initializer.

As a side note, I am not sure that idx and selectedID will ever be different. But I can't prove that so this is definitely the "safest" approach.

Cool notebook BTW! Would love to show off/tweet about it when you are done, if it is something you can publicly share.

Bryan

On Jun 12, 2016, at 10:10 AM, Gabriel Diaz <[email protected]> wrote:

To follow up...

Code attached.

I've stuck with the method of using the python kernel to execute code in JS that can effect the Python environment. I could not find documentation or a tutorial that demonstrated a way to use server mode to avoid the use of javascript callbacks for the placement of new events (rectangles).

To remind you, my goal is to place events (rectangles) using the box tool, and then use tap tool + ipywidgets to assign each placed event to one of three visually distinct event categories (A, B, and C). To accomplish this, I've created four glyphs: one that is a default glyph to be used prior to group assignment, and then one for A, B, and C. Once an event is categorized by the user, its data will be moved from the undefined source to source A, B, or C.

Because I'm much more familiar with Python than JS, I intend to use taptool to select the rectangle, but Python to manage passing its information between two data sources. This requires that Python know which rectangle has been clicked by the tap tool. My approach is to include an "boxID" field in each data source so that each rectangle has a unique identification number. Inside the taptool JS callback, the boxID of the selected event rectangle event is passed back to Python. Subsequently, when an ipywidget button indicating a group assignment (A,B, or C) is pressed, then the Python callback will transfer the source entry of the event from an default source to the appropriate group's source.

I'm nearly there. As you can see in my other post, the holdup is that I cannot figure out how to get the information about the selected item inside the tap-tool CB, so that I can the identification number I've stored in its source.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:54 PM, Gabriel Diaz <[email protected]> wrote:
Ok, so we're on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS -> Python communication issues.

Thanks again for your input! I'll be sure to post a solution if I find one.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven <[email protected]> wrote:
Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

      http://demo.bokehplots.com/

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

      http://demo.bokehplots.com/apps/weather

And here is an example that updates secondary plots as a result of a selection on a first plot:

      http://demo.bokehplots.com/apps/selection_histogram

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can "hide" an existing glyph either by:

* setting .visible = False on the glyph:

      from bokeh.plotting import *
      p = figure()
      p.circle([1,2,3], [4,5,6])
      r = p.square([1,2,3], [5,6,7])
      r.glyph.visible = False
      output_file("/tmp/foo.html")
      show(p)

* setting the glyphs fill/line colors to None (or alpha to zero):

      from bokeh.plotting import *
      p = figure()
      p.circle([1,2,3], [4,5,6])
      r = p.square([1,2,3], [5,6,7])
      r.glyph.fill_color = None
      r.glyph.line_color = None
      output_file("/tmp/foo.html")
      show(p)

* clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph *objects* themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and "remove it".

Bryan

On Jun 9, 2016, at 11:28 AM, Gabriel Diaz <[email protected]> wrote:

Ok, thanks for the update in terminology.

I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don't know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!

The notebook is not a hard requirement, but it would be nice.

So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.

* Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.

I'm not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven <[email protected]> wrote:
As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a "circle glyph" isn't an actual, specific circle(s) that gets drawn somewhere, it's a thing that can draw circles when you pair it with some specific data. That's why most of the use-cases are geared towards creating all the glyphs you *might ever* need up front, and then just changing the data for them as necessary.

Bryan

On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven <[email protected]> wrote:

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

* The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app

* Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz <[email protected]> wrote:

First, thank you *very* much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I've posted earlier.

Regardless of what I've accomplished, heres the functionality that I would like to add:

- Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
- Select the event (e.g. using tap-tool).
- Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
- I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
- Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event's label.

Thanks again.
- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven <[email protected]> wrote:
Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay "in sync" with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the "push_notebook" function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python -> JS, although there are other folks interested in and working on ideas to enable the "reverse" direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(...) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the "Continuous Updating" notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

* recreate a new plot
* set the glyph to be invisble
* update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like "I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen".

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz <[email protected]> wrote:

Thanks Bryan. Unless I missed it, I don't see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn't help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple 'rects' have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I've explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks
- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven <[email protected]> wrote:
I would suggest studying the examples here:

     https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:

Apologies. An example is included in the attached *.ipynb

- gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!
If you want to know more, read more to find out why...

Thanks!
- gD

-----

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...

- Label the patch as event type A,B, or C (depending upon the button pressed)
- Record the event type and start/stop time (on X) of the event in a pandas dataframe
- Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

   • File

   • Edit

   • View

   • Insert

   • Cell

   • Kernel

   • Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

     // get data source from Callback args

     var data = source.get('data');

     /// get BoxSelectTool dimensions from cb_data parameter of Callback

     var geometry = cb_data['geometry'];

     /// calculate Rect attributes

     var width = geometry['x1'] - geometry['x0'];

     var height = geometry['y1'] - geometry['y0'];

     var x = geometry['x0'] + width/2;

     var y = geometry['y0'] + height/2;

     var leftEdge = geometry['x0']

     var rightEdge = geometry['x1']

     /// update data source with new Rect attributes

     data['x'].push(x);

     data['y'].push(y);

     data['width'].push(width);

     data['height'].push(height);

     data['eventType'] = 'none'

     data['leftEdge'] = leftEdge

     data['rightEdge'] = rightEdge

     // trigger update of data source

     source.trigger('change');

""")

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

     // get data source from Callback args

     var data = source.get('data');

     var width = data['width']

     IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

     IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );

""")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = 'time'

p.yaxis.axis_label = 'position'

p.line(x,y,line_width=4,name='aLine')

initRect = bkM.Rect(x='x',

         y='y',

         width='width',

         height='height',

         fill_alpha=0.3,

         fill_color='gray')

notSelected = bkM.Rect(x='x',

         y='y',

         width='width',

         height='height',

         fill_alpha=0.3,

         fill_color='gray')

selected = bkM.Rect(x='x',

         y='y',

         width='width',

         height='height',

         fill_alpha=0.3,

         fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')

p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description='Fixation',value=False)

b2.on_click(foundSacc)

b2.width = '20%'

b1 = Button(description='Saccade',value=False)

b1.on_click(foundSacc)

b1.width = '20%'

display(b1,b2)

×

Saccade

Fixation

In :

from __future__ import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code="""

     // get data source from Callback args

     var data = source.get('data');

     /// get BoxSelectTool dimensions from cb_data parameter of Callback

     var geometry = cb_data['geometry'];

     /// calculate Rect attributes

     var width = geometry['x1'] - geometry['x0'];

     var height = geometry['y1'] - geometry['y0'];

     var x = geometry['x0'] + width/2;

     var y = geometry['y0'] + height/2;

     var leftEdge = geometry['x0']

     var rightEdge = geometry['x1']

     /// update data source with new Rect attributes

     data['x'].push(x);

     data['y'].push(y);

     data['width'].push(width);

     data['height'].push(height);

     data['eventType'] = 'none'

     data['leftEdge'] = leftEdge

     data['rightEdge'] = rightEdge

     // trigger update of data source

     source.trigger('change');

""")

tapCallback = bkM.CustomJS(args=dict(source=source), code="""

     // get data source from Callback args

     var data = source.get('data');

     var width = data['width']

     IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);

     IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );

""")

# IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);

# IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = 'time'

p.yaxis.axis_label = 'position'

p.line(x,y,line_width=4,name='aLine')

initRect = bkM.Rect(x='x',

         y='y',

         width='width',

         height='height',

         fill_alpha=0.3,

         fill_color='gray')

notSelected = bkM.Rect(x='x',

         y='y',

         width='width',

         height='height',

         fill_alpha=0.3,

         fill_color='gray')

selected = bkM.Rect(x='x',

         y='y',

         width='width',

         height='height',

         fill_alpha=0.3,

         fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')

p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

### NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description='Fixation',value=False)

b2.on_click(foundSacc)

b2.width = '20%'

b1 = Button(description='Saccade',value=False)

b1.on_click(foundSacc)

b1.width = '20%'

display(b1,b2)

--
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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Example Event Labeller.ipynb>

--
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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com\.
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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com\.
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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com\.
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/FE7B66D0-8D13-468A-AE23-DF443D441F79%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/CAFvLWO3Aq0aN0VR%3DNoaksxBk3ZHHmZicfBR%3DzEmBJKijSP6eEA%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Event Labeller.ipynb>

Thanks for the input, Bryan! Yes, you can share the notebook. There’s still a bit to do, though. For example, I plan on adding an export button that will merge the datasources into a single dataframe for exporting.

Now, it seems that I’m still having issues with the iteritems deficiency.

This is the error I’m suffering from:

TypeError: undefined is not an object (evaluating ‘idx’)

I’m pretty sure that error is coming from the line:

selectedID = cb_obj.get(‘data’)[‘boxID’][idx];

and that the [idx] is the culprit. Any tips there?

  • gD
···

On Sun, Jun 12, 2016 at 7:56 PM, Bryan Van de Ven [email protected] wrote:

Sorry, one more (this is fun!) you probably also want to add a push_notebook at the end of foundSacc. This will cause the tapped and button-pushed rect to disappear. Otherwise if it remains and can be tapped again, but the index has been deleted from undefinedSource, so an index exception is raised.

Bryan

On Jun 12, 2016, at 6:51 PM, Bryan Van de Ven [email protected] wrote:

also one more more not :slight_smile: There is logic error in moveBetweenSources. If it gets to the boxID column in the middle of the loop, then it deletes the value, and then the next time around the .index call fails with an exception. Saving the valueIdx once, before the loops starts, should fix that.

Bryan

On Jun 12, 2016, at 6:43 PM, Bryan Van de Ven [email protected] wrote:

Gabriel,

If I understand what you are looking for, this is the JS callback code to accomplish it:

 tapCallback = bkM.CustomJS(args=dict(source = undefinedSource,fS=fixSource, sS=saccSource), code="""
         var idx = cb_obj.get('selected')['1d'].indices[0];
         selectedID = cb_obj.get('data')['boxID'][idx];
         console.log("idx " + idx);
         console.log("boxIDs " + cb_obj.get('data')['boxID']);
         console.log("selectedID " + selectedID);
         var k = IPython.notebook.kernel;
         k.execute("selectedID = " + selectedID );
 """)

I’ve added some console.log statements so you can see what’s going on more closely if you open your brower’s JS console. I also had to add the callback to the TapTool below, it was not passed to the initializer.

As a side note, I am not sure that idx and selectedID will ever be different. But I can’t prove that so this is definitely the “safest” approach.

Cool notebook BTW! Would love to show off/tweet about it when you are done, if it is something you can publicly share.

Bryan

On Jun 12, 2016, at 10:10 AM, Gabriel Diaz [email protected] wrote:

To follow up…

Code attached.

I’ve stuck with the method of using the python kernel to execute code in JS that can effect the Python environment. I could not find documentation or a tutorial that demonstrated a way to use server mode to avoid the use of javascript callbacks for the placement of new events (rectangles).

To remind you, my goal is to place events (rectangles) using the box tool, and then use tap tool + ipywidgets to assign each placed event to one of three visually distinct event categories (A, B, and C). To accomplish this, I’ve created four glyphs: one that is a default glyph to be used prior to group assignment, and then one for A, B, and C. Once an event is categorized by the user, its data will be moved from the undefined source to source A, B, or C.

Because I’m much more familiar with Python than JS, I intend to use taptool to select the rectangle, but Python to manage passing its information between two data sources. This requires that Python know which rectangle has been clicked by the tap tool. My approach is to include an “boxID” field in each data source so that each rectangle has a unique identification number. Inside the taptool JS callback, the boxID of the selected event rectangle event is passed back to Python. Subsequently, when an ipywidget button indicating a group assignment (A,B, or C) is pressed, then the Python callback will transfer the source entry of the event from an default source to the appropriate group’s source.

I’m nearly there. As you can see in my other post, the holdup is that I cannot figure out how to get the information about the selected item inside the tap-tool CB, so that I can the identification number I’ve stored in its source.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:54 PM, Gabriel Diaz [email protected] wrote:

Ok, so we’re on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS → Python communication issues.

Thanks again for your input! I’ll be sure to post a solution if I find one.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven [email protected] wrote:

Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

  [http://demo.bokehplots.com/](http://demo.bokehplots.com/)

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

  [http://demo.bokehplots.com/apps/weather](http://demo.bokehplots.com/apps/weather)

And here is an example that updates secondary plots as a result of a selection on a first plot:

  [http://demo.bokehplots.com/apps/selection_histogram](http://demo.bokehplots.com/apps/selection_histogram)

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can “hide” an existing glyph either by:

  • setting .visible = False on the glyph:
  from bokeh.plotting import *
  p = figure()
  p.circle([1,2,3], [4,5,6])
  r = p.square([1,2,3], [5,6,7])
  r.glyph.visible = False
  output_file("/tmp/foo.html")
  show(p)
  • setting the glyphs fill/line colors to None (or alpha to zero):
  from bokeh.plotting import *
  p = figure()
  p.circle([1,2,3], [4,5,6])
  r = p.square([1,2,3], [5,6,7])
  r.glyph.fill_color = None
  r.glyph.line_color = None
  output_file("/tmp/foo.html")
  show(p)
  • clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph objects themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and “remove it”.

Bryan

On Jun 9, 2016, at 11:28 AM, Gabriel Diaz [email protected] wrote:

Ok, thanks for the update in terminology.

I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don’t know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!

The notebook is not a hard requirement, but it would be nice.

So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.

  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

I’m not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven [email protected] wrote:

As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a “circle glyph” isn’t an actual, specific circle(s) that gets drawn somewhere, it’s a thing that can draw circles when you pair it with some specific data. That’s why most of the use-cases are geared towards creating all the glyphs you might ever need up front, and then just changing the data for them as necessary.

Bryan

On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven [email protected] wrote:

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

  • The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app
  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz [email protected] wrote:

First, thank you very much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I’ve posted earlier.

Regardless of what I’ve accomplished, heres the functionality that I would like to add:

  • Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
  • Select the event (e.g. using tap-tool).
  • Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
  • I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
  • Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event’s label.

Thanks again.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven [email protected] wrote:

Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay “in sync” with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the “push_notebook” function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python → JS, although there are other folks interested in and working on ideas to enable the “reverse” direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(…) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the “Continuous Updating” notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

  • recreate a new plot
  • set the glyph to be invisble
  • update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like “I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen”.

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz [email protected] wrote:

Thanks Bryan. Unless I missed it, I don’t see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn’t help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple ‘rects’ have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I’ve explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven [email protected] wrote:

I would suggest studying the examples here:

 [https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms](https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms)

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz [email protected] wrote:

Apologies. An example is included in the attached *.ipynb

  • gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)
  • Record the event type and start/stop time (on X) of the event in a pandas dataframe
  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

• File

• Edit

• View

• Insert

• Cell

• Kernel

• Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

 // get data source from Callback args
 var data = source.get('data');

 /// get BoxSelectTool dimensions from cb_data parameter of Callback
 var geometry = cb_data['geometry'];

 /// calculate Rect attributes
 var width = geometry['x1'] - geometry['x0'];
 var height = geometry['y1'] - geometry['y0'];
 var x = geometry['x0'] + width/2;
 var y = geometry['y0'] + height/2;
 var leftEdge = geometry['x0']
 var rightEdge = geometry['x1']
 /// update data source with new Rect attributes
 data['x'].push(x);
 data['y'].push(y);
 data['width'].push(width);
 data['height'].push(height);
 data['eventType'] = 'none'
 data['leftEdge'] = leftEdge
 data['rightEdge'] = rightEdge
 // trigger update of data source
 source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

 // get data source from Callback args
 var data = source.get('data');
 var width = data['width']
 IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
 IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='gray')

selected = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

 // get data source from Callback args
 var data = source.get('data');

 /// get BoxSelectTool dimensions from cb_data parameter of Callback
 var geometry = cb_data['geometry'];

 /// calculate Rect attributes
 var width = geometry['x1'] - geometry['x0'];
 var height = geometry['y1'] - geometry['y0'];
 var x = geometry['x0'] + width/2;
 var y = geometry['y0'] + height/2;
 var leftEdge = geometry['x0']
 var rightEdge = geometry['x1']
 /// update data source with new Rect attributes
 data['x'].push(x);
 data['y'].push(y);
 data['width'].push(width);
 data['height'].push(height);
 data['eventType'] = 'none'
 data['leftEdge'] = leftEdge
 data['rightEdge'] = rightEdge
 // trigger update of data source
 source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

 // get data source from Callback args
 var data = source.get('data');
 var width = data['width']
 IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
 IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='gray')

selected = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

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/b0400249-fb53-482c-8768-c2134923b72a%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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com.

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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com.

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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com.

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/FE7B66D0-8D13-468A-AE23-DF443D441F79%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/CAFvLWO3Aq0aN0VR%3DNoaksxBk3ZHHmZicfBR%3DzEmBJKijSP6eEA%40mail.gmail.com.

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/EDA8ECB0-75DA-4E41-9C5C-0B0DD78157D1%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.


Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

Hrmm, I'm not sure. The code I pasted earlier was working for me. I could see the updates in the console, and I could also see the correct selectedID in the notebook after the callback ran, and with the other changes I mentioned, clicking on the button also copied th right info from one data source to the other. I can maybe imagine there's corner cases if the selection is empty somehow? In that case, this line:

  var idx = cb_obj.get('selected')['1d'].indices[0]

would result in idx being undefined, because indices is an empty list. So maybe put a check in on the length? That's all I can think of offhand.

Thanks,

Bryan

···

On Jun 12, 2016, at 7:25 PM, Gabriel Diaz <[email protected]> wrote:

Thanks for the input, Bryan! Yes, you can share the notebook. There's still a bit to do, though. For example, I plan on adding an export button that will merge the datasources into a single dataframe for exporting.

Now, it seems that I'm still having issues with the iteritems deficiency.

This is the error I'm suffering from:
TypeError: undefined is not an object (evaluating 'idx')

I'm pretty sure that error is coming from the line:
selectedID = cb_obj.get('data')['boxID'][idx];

and that the [idx] is the culprit. Any tips there?

- gD

----------------------

Gabriel J. Diaz, Ph.D.
Assistant Professor
Rochester Institute of Technology
Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs
Click for demos.

Office 2108, Building #76
Rochester, NY 14623
Office: (585) 475-6215
[email protected]

On Sun, Jun 12, 2016 at 7:56 PM, Bryan Van de Ven <[email protected]> wrote:
Sorry, one more (this is fun!) you probably also want to add a push_notebook at the end of foundSacc. This will cause the tapped and button-pushed rect to disappear. Otherwise if it remains and can be tapped again, but the index has been deleted from undefinedSource, so an index exception is raised.

Bryan

> On Jun 12, 2016, at 6:51 PM, Bryan Van de Ven <[email protected]> wrote:
>
> also one more more not :slight_smile: There is logic error in moveBetweenSources. If it gets to the boxID column in the middle of the loop, then it deletes the value, and then the next time around the .index call fails with an exception. Saving the valueIdx once, before the loops starts, should fix that.
>
> Bryan
>
>
>> On Jun 12, 2016, at 6:43 PM, Bryan Van de Ven <[email protected]> wrote:
>>
>> Gabriel,
>>
>> If I understand what you are looking for, this is the JS callback code to accomplish it:
>>
>> tapCallback = bkM.CustomJS(args=dict(source = undefinedSource,fS=fixSource, sS=saccSource), code="""
>> var idx = cb_obj.get('selected')['1d'].indices[0];
>> selectedID = cb_obj.get('data')['boxID'][idx];
>>
>> console.log("idx " + idx);
>> console.log("boxIDs " + cb_obj.get('data')['boxID']);
>> console.log("selectedID " + selectedID);
>>
>> var k = IPython.notebook.kernel;
>> k.execute("selectedID = " + selectedID );
>> """)
>>
>> I've added some console.log statements so you can see what's going on more closely if you open your brower's JS console. I also had to add the callback to the TapTool below, it was not passed to the initializer.
>>
>> As a side note, I am not sure that idx and selectedID will ever be different. But I can't prove that so this is definitely the "safest" approach.
>>
>> Cool notebook BTW! Would love to show off/tweet about it when you are done, if it is something you can publicly share.
>>
>> Bryan
>>
>>> On Jun 12, 2016, at 10:10 AM, Gabriel Diaz <[email protected]> wrote:
>>>
>>> To follow up...
>>>
>>> Code attached.
>>>
>>> I've stuck with the method of using the python kernel to execute code in JS that can effect the Python environment. I could not find documentation or a tutorial that demonstrated a way to use server mode to avoid the use of javascript callbacks for the placement of new events (rectangles).
>>>
>>> To remind you, my goal is to place events (rectangles) using the box tool, and then use tap tool + ipywidgets to assign each placed event to one of three visually distinct event categories (A, B, and C). To accomplish this, I've created four glyphs: one that is a default glyph to be used prior to group assignment, and then one for A, B, and C. Once an event is categorized by the user, its data will be moved from the undefined source to source A, B, or C.
>>>
>>> Because I'm much more familiar with Python than JS, I intend to use taptool to select the rectangle, but Python to manage passing its information between two data sources. This requires that Python know which rectangle has been clicked by the tap tool. My approach is to include an "boxID" field in each data source so that each rectangle has a unique identification number. Inside the taptool JS callback, the boxID of the selected event rectangle event is passed back to Python. Subsequently, when an ipywidget button indicating a group assignment (A,B, or C) is pressed, then the Python callback will transfer the source entry of the event from an default source to the appropriate group's source.
>>>
>>> I'm nearly there. As you can see in my other post, the holdup is that I cannot figure out how to get the information about the selected item inside the tap-tool CB, so that I can the identification number I've stored in its source.
>>>
>>> - gD
>>>
>>>
>>>
>>>
>>> ----------------------
>>>
>>> Gabriel J. Diaz, Ph.D.
>>> Assistant Professor
>>> Rochester Institute of Technology
>>> Chester F. Carlson Center for Imaging Science
>>>
>>> Founder of PerForM Labs
>>> Click for demos.
>>>
>>> Office 2108, Building #76
>>> Rochester, NY 14623
>>> Office: (585) 475-6215
>>> [email protected]
>>>
>>>
>>>
>>>
>>>
>>> On Thu, Jun 9, 2016 at 12:54 PM, Gabriel Diaz <[email protected]> wrote:
>>> Ok, so we're on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS -> Python communication issues.
>>>
>>> Thanks again for your input! I'll be sure to post a solution if I find one.
>>>
>>> - gD
>>>
>>>
>>> ----------------------
>>>
>>> Gabriel J. Diaz, Ph.D.
>>> Assistant Professor
>>> Rochester Institute of Technology
>>> Chester F. Carlson Center for Imaging Science
>>>
>>> Founder of PerForM Labs
>>> Click for demos.
>>>
>>> Office 2108, Building #76
>>> Rochester, NY 14623
>>> Office: (585) 475-6215
>>> [email protected]
>>>
>>>
>>>
>>>
>>>
>>> On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven <[email protected]> wrote:
>>> Gabriel,
>>>
>>> well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:
>>>
>>> http://demo.bokehplots.com/
>>>
>>> Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:
>>>
>>> http://demo.bokehplots.com/apps/weather
>>>
>>>
>>> And here is an example that updates secondary plots as a result of a selection on a first plot:
>>>
>>> http://demo.bokehplots.com/apps/selection_histogram
>>>
>>> Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can "hide" an existing glyph either by:
>>>
>>> * setting .visible = False on the glyph:
>>>
>>> from bokeh.plotting import *
>>> p = figure()
>>> p.circle([1,2,3], [4,5,6])
>>> r = p.square([1,2,3], [5,6,7])
>>> r.glyph.visible = False
>>> output_file("/tmp/foo.html")
>>> show(p)
>>>
>>>
>>> * setting the glyphs fill/line colors to None (or alpha to zero):
>>>
>>> from bokeh.plotting import *
>>> p = figure()
>>> p.circle([1,2,3], [4,5,6])
>>> r = p.square([1,2,3], [5,6,7])
>>> r.glyph.fill_color = None
>>> r.glyph.line_color = None
>>> output_file("/tmp/foo.html")
>>> show(p)
>>>
>>> * clearing the data source for the glyph (making all the columns in the data source be zero-length)
>>>
>>> My main point is just that changing / removing the actual glyph *objects* themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and "remove it".
>>>
>>> Bryan
>>>
>>>> On Jun 9, 2016, at 11:28 AM, Gabriel Diaz <[email protected]> wrote:
>>>>
>>>> Ok, thanks for the update in terminology.
>>>>
>>>> I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don't know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!
>>>>
>>>> The notebook is not a hard requirement, but it would be nice.
>>>>
>>>> So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.
>>>>
>>>> * Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.
>>>>
>>>> I'm not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.
>>>>
>>>>
>>>> - gD
>>>>
>>>>
>>>> ----------------------
>>>>
>>>> Gabriel J. Diaz, Ph.D.
>>>> Assistant Professor
>>>> Rochester Institute of Technology
>>>> Chester F. Carlson Center for Imaging Science
>>>>
>>>> Founder of PerForM Labs
>>>> Click for demos.
>>>>
>>>> Office 2108, Building #76
>>>> Rochester, NY 14623
>>>> Office: (585) 475-6215
>>>> [email protected]
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven <[email protected]> wrote:
>>>> As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a "circle glyph" isn't an actual, specific circle(s) that gets drawn somewhere, it's a thing that can draw circles when you pair it with some specific data. That's why most of the use-cases are geared towards creating all the glyphs you *might ever* need up front, and then just changing the data for them as necessary.
>>>>
>>>> Bryan
>>>>
>>>>> On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven <[email protected]> wrote:
>>>>>
>>>>> OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:
>>>>>
>>>>> * The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app
>>>>>
>>>>> * Is it possible to cast the displayed things as a *fixed* set of glyphs, but with *varying* (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs "up front" that will also make things much simpler.
>>>>>
>>>>> Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.
>>>>>
>>>>> Bryan
>>>>>
>>>>>> On Jun 9, 2016, at 10:32 AM, Gabriel Diaz <[email protected]> wrote:
>>>>>>
>>>>>> First, thank you *very* much for such a thoughtful and detailed response. I now understand the problem with much more clarity.
>>>>>>
>>>>>> Here is my intention:
>>>>>>
>>>>>> I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I've posted earlier.
>>>>>>
>>>>>> Regardless of what I've accomplished, heres the functionality that I would like to add:
>>>>>>
>>>>>> - Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
>>>>>> - Select the event (e.g. using tap-tool).
>>>>>> - Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
>>>>>> - I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
>>>>>> - Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event's label.
>>>>>>
>>>>>> Thanks again.
>>>>>> - gD
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> ----------------------
>>>>>>
>>>>>> Gabriel J. Diaz, Ph.D.
>>>>>> Assistant Professor
>>>>>> Rochester Institute of Technology
>>>>>> Chester F. Carlson Center for Imaging Science
>>>>>>
>>>>>> Founder of PerForM Labs
>>>>>> Click for demos.
>>>>>>
>>>>>> Office 2108, Building #76
>>>>>> Rochester, NY 14623
>>>>>> Office: (585) 475-6215
>>>>>> [email protected]
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven <[email protected]> wrote:
>>>>>> Hi,
>>>>>>
>>>>>> Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay "in sync" with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.
>>>>>>
>>>>>> However, there are other options for keeping the JS and python objects in sync, namely the "push_notebook" function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python -> JS, although there are other folks interested in and working on ideas to enable the "reverse" direction updates as well.
>>>>>>
>>>>>> Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(...) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the "Continuous Updating" notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:
>>>>>>
>>>>>> * recreate a new plot
>>>>>> * set the glyph to be invisble
>>>>>> * update the glyphs data
>>>>>>
>>>>>> Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like "I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen".
>>>>>>
>>>>>> Thanks,
>>>>>>
>>>>>> Bryan
>>>>>>
>>>>>>
>>>>>>> On Jun 9, 2016, at 7:54 AM, Gabriel Diaz <[email protected]> wrote:
>>>>>>>
>>>>>>> Thanks Bryan. Unless I missed it, I don't see an answer to my question in those otherwise very helpful guides.
>>>>>>>
>>>>>>> Let me try and restate my issue, in case it lacked clarity (that large code dump didn't help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.
>>>>>>>
>>>>>>> However, I cannot find where information about individual glpyhs objects stored.
>>>>>>>
>>>>>>> The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple 'rects' have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?
>>>>>>>
>>>>>>> I've explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.
>>>>>>>
>>>>>>> Thanks
>>>>>>> - gD
>>>>>>>
>>>>>>>
>>>>>>> ----------------------
>>>>>>>
>>>>>>> Gabriel J. Diaz, Ph.D.
>>>>>>> Assistant Professor
>>>>>>> Rochester Institute of Technology
>>>>>>> Chester F. Carlson Center for Imaging Science
>>>>>>>
>>>>>>> Founder of PerForM Labs
>>>>>>> Click for demos.
>>>>>>>
>>>>>>> Office 2108, Building #76
>>>>>>> Rochester, NY 14623
>>>>>>> Office: (585) 475-6215
>>>>>>> [email protected]
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven <[email protected]> wrote:
>>>>>>> I would suggest studying the examples here:
>>>>>>>
>>>>>>> https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms
>>>>>>>
>>>>>>> Thanks,
>>>>>>>
>>>>>>> Bryan
>>>>>>>
>>>>>>>
>>>>>>>> On Jun 8, 2016, at 4:13 PM, Gabriel Diaz <[email protected]> wrote:
>>>>>>>>
>>>>>>>> Apologies. An example is included in the attached *.ipynb
>>>>>>>>
>>>>>>>> - gD
>>>>>>>>
>>>>>>>> On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:
>>>>>>>> Hello! I'm looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.
>>>>>>>>
>>>>>>>>
>>>>>>>> Code from my notebook is pasted below!
>>>>>>>> If you want to know more, read more to find out why...
>>>>>>>>
>>>>>>>> Thanks!
>>>>>>>> - gD
>>>>>>>>
>>>>>>>> -----
>>>>>>>>
>>>>>>>> My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will...
>>>>>>>>
>>>>>>>> - Label the patch as event type A,B, or C (depending upon the button pressed)
>>>>>>>> - Record the event type and start/stop time (on X) of the event in a pandas dataframe
>>>>>>>> - Update the glyph to color-code the event
>>>>>>>>
>>>>>>>> I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> Python 2
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> • File
>>>>>>>>
>>>>>>>> • Edit
>>>>>>>>
>>>>>>>> • View
>>>>>>>>
>>>>>>>> • Insert
>>>>>>>>
>>>>>>>> • Cell
>>>>>>>>
>>>>>>>> • Kernel
>>>>>>>>
>>>>>>>> • Help
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> CodeMarkdownRaw NBConvertHeading
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> CellToolbar
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> In [1]:
>>>>>>>>
>>>>>>>>
>>>>>>>> from __future__ import division
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> import pandas as pd
>>>>>>>>
>>>>>>>>
>>>>>>>> import numpy as np
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> import bokeh.plotting as bkP
>>>>>>>>
>>>>>>>>
>>>>>>>> import bokeh.models as bkM
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.palettes import Spectral6
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.embed import file_html
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.resources import CDN
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> bkP.output_notebook()
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> x
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> from __future__ import division
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> import pandas as pd
>>>>>>>>
>>>>>>>>
>>>>>>>> import numpy as np
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> import bokeh.plotting as bkP
>>>>>>>>
>>>>>>>>
>>>>>>>> import bokeh.models as bkM
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.palettes import Spectral6
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.embed import file_html
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.resources import CDN
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> bkP.output_notebook()
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> /Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/__init__.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> UserWarning)
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> BokehJS successfully loaded
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> In [41]:
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> x
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> boxCallback = bkM.CustomJS(args=dict(source=source), code="""
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> // get data source from Callback args
>>>>>>>>
>>>>>>>>
>>>>>>>> var data = source.get('data');
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> /// get BoxSelectTool dimensions from cb_data parameter of Callback
>>>>>>>>
>>>>>>>>
>>>>>>>> var geometry = cb_data['geometry'];
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> /// calculate Rect attributes
>>>>>>>>
>>>>>>>>
>>>>>>>> var width = geometry['x1'] - geometry['x0'];
>>>>>>>>
>>>>>>>>
>>>>>>>> var height = geometry['y1'] - geometry['y0'];
>>>>>>>>
>>>>>>>>
>>>>>>>> var x = geometry['x0'] + width/2;
>>>>>>>>
>>>>>>>>
>>>>>>>> var y = geometry['y0'] + height/2;
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> var leftEdge = geometry['x0']
>>>>>>>>
>>>>>>>>
>>>>>>>> var rightEdge = geometry['x1']
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> /// update data source with new Rect attributes
>>>>>>>>
>>>>>>>>
>>>>>>>> data['x'].push(x);
>>>>>>>>
>>>>>>>>
>>>>>>>> data['y'].push(y);
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> data['width'].push(width);
>>>>>>>>
>>>>>>>>
>>>>>>>> data['height'].push(height);
>>>>>>>>
>>>>>>>>
>>>>>>>> data['eventType'] = 'none'
>>>>>>>>
>>>>>>>>
>>>>>>>> data['leftEdge'] = leftEdge
>>>>>>>>
>>>>>>>>
>>>>>>>> data['rightEdge'] = rightEdge
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> // trigger update of data source
>>>>>>>>
>>>>>>>>
>>>>>>>> source.trigger('change');
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> """)
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> tapCallback = bkM.CustomJS(args=dict(source=source), code="""
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> // get data source from Callback args
>>>>>>>>
>>>>>>>>
>>>>>>>> var data = source.get('data');
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> var width = data['width']
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
>>>>>>>>
>>>>>>>>
>>>>>>>> IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> """)
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?
>>>>>>>>
>>>>>>>>
>>>>>>>> In [42]:
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> leftEdge =
>>>>>>>>
>>>>>>>>
>>>>>>>> rightEdge =
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> x = np.deg2rad(range(720))
>>>>>>>>
>>>>>>>>
>>>>>>>> y = 1+np.sin(x)
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> p.xaxis.axis_label = 'time'
>>>>>>>>
>>>>>>>>
>>>>>>>> p.yaxis.axis_label = 'position'
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> p.line(x,y,line_width=4,name='aLine')
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> initRect = bkM.Rect(x='x',
>>>>>>>>
>>>>>>>>
>>>>>>>> y='y',
>>>>>>>>
>>>>>>>>
>>>>>>>> width='width',
>>>>>>>>
>>>>>>>>
>>>>>>>> height='height',
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_alpha=0.3,
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_color='gray')
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> notSelected = bkM.Rect(x='x',
>>>>>>>>
>>>>>>>>
>>>>>>>> y='y',
>>>>>>>>
>>>>>>>>
>>>>>>>> width='width',
>>>>>>>>
>>>>>>>>
>>>>>>>> height='height',
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_alpha=0.3,
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_color='gray')
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> selected = bkM.Rect(x='x',
>>>>>>>>
>>>>>>>>
>>>>>>>> y='y',
>>>>>>>>
>>>>>>>>
>>>>>>>> width='width',
>>>>>>>>
>>>>>>>>
>>>>>>>> height='height',
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_alpha=0.3,
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_color='green')
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
>>>>>>>>
>>>>>>>>
>>>>>>>> p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
>>>>>>>>
>>>>>>>>
>>>>>>>> p.add_tools(bkM.TapTool(callback=tapCallback))
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> bkP.show(p)
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> Out[42]:
>>>>>>>>
>>>>>>>>
>>>>>>>> <Bokeh Notebook handle for In[42]>
>>>>>>>>
>>>>>>>>
>>>>>>>> In [44]:
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> from ipywidgets import *
>>>>>>>>
>>>>>>>>
>>>>>>>> from IPython.display import display
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> def foundSacc(sender):
>>>>>>>>
>>>>>>>>
>>>>>>>> print leftEdge
>>>>>>>>
>>>>>>>>
>>>>>>>> print rightEdge
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> b2 = Button(description='Fixation',value=False)
>>>>>>>>
>>>>>>>>
>>>>>>>> b2.on_click(foundSacc)
>>>>>>>>
>>>>>>>>
>>>>>>>> b2.width = '20%'
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> b1 = Button(description='Saccade',value=False)
>>>>>>>>
>>>>>>>>
>>>>>>>> b1.on_click(foundSacc)
>>>>>>>>
>>>>>>>>
>>>>>>>> b1.width = '20%'
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> display(b1,b2)
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> ×
>>>>>>>>
>>>>>>>>
>>>>>>>> Saccade
>>>>>>>>
>>>>>>>>
>>>>>>>> Fixation
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> In :
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> from __future__ import division
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> import pandas as pd
>>>>>>>>
>>>>>>>>
>>>>>>>> import numpy as np
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> import bokeh.plotting as bkP
>>>>>>>>
>>>>>>>>
>>>>>>>> import bokeh.models as bkM
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.palettes import Spectral6
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.embed import file_html
>>>>>>>>
>>>>>>>>
>>>>>>>> from bokeh.resources import CDN
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> bkP.output_notebook()
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> boxCallback = bkM.CustomJS(args=dict(source=source), code="""
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> // get data source from Callback args
>>>>>>>>
>>>>>>>>
>>>>>>>> var data = source.get('data');
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> /// get BoxSelectTool dimensions from cb_data parameter of Callback
>>>>>>>>
>>>>>>>>
>>>>>>>> var geometry = cb_data['geometry'];
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> /// calculate Rect attributes
>>>>>>>>
>>>>>>>>
>>>>>>>> var width = geometry['x1'] - geometry['x0'];
>>>>>>>>
>>>>>>>>
>>>>>>>> var height = geometry['y1'] - geometry['y0'];
>>>>>>>>
>>>>>>>>
>>>>>>>> var x = geometry['x0'] + width/2;
>>>>>>>>
>>>>>>>>
>>>>>>>> var y = geometry['y0'] + height/2;
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> var leftEdge = geometry['x0']
>>>>>>>>
>>>>>>>>
>>>>>>>> var rightEdge = geometry['x1']
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> /// update data source with new Rect attributes
>>>>>>>>
>>>>>>>>
>>>>>>>> data['x'].push(x);
>>>>>>>>
>>>>>>>>
>>>>>>>> data['y'].push(y);
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> data['width'].push(width);
>>>>>>>>
>>>>>>>>
>>>>>>>> data['height'].push(height);
>>>>>>>>
>>>>>>>>
>>>>>>>> data['eventType'] = 'none'
>>>>>>>>
>>>>>>>>
>>>>>>>> data['leftEdge'] = leftEdge
>>>>>>>>
>>>>>>>>
>>>>>>>> data['rightEdge'] = rightEdge
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> // trigger update of data source
>>>>>>>>
>>>>>>>>
>>>>>>>> source.trigger('change');
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> """)
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> tapCallback = bkM.CustomJS(args=dict(source=source), code="""
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> // get data source from Callback args
>>>>>>>>
>>>>>>>>
>>>>>>>> var data = source.get('data');
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> var width = data['width']
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
>>>>>>>>
>>>>>>>>
>>>>>>>> IPython.notebook.kernel.execute("rightEdge = " + data['rightEdge'] );
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> """)
>>>>>>>>
>>>>>>>>
>>>>>>>> # IPython.notebook.kernel.execute("leftEdge = width + " + data['x']);
>>>>>>>>
>>>>>>>>
>>>>>>>> # IPython.notebook.kernel.execute("rightEdge = width " + data['x'] );
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> leftEdge =
>>>>>>>>
>>>>>>>>
>>>>>>>> rightEdge =
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> x = np.deg2rad(range(720))
>>>>>>>>
>>>>>>>>
>>>>>>>> y = 1+np.sin(x)
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> p.xaxis.axis_label = 'time'
>>>>>>>>
>>>>>>>>
>>>>>>>> p.yaxis.axis_label = 'position'
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> p.line(x,y,line_width=4,name='aLine')
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> initRect = bkM.Rect(x='x',
>>>>>>>>
>>>>>>>>
>>>>>>>> y='y',
>>>>>>>>
>>>>>>>>
>>>>>>>> width='width',
>>>>>>>>
>>>>>>>>
>>>>>>>> height='height',
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_alpha=0.3,
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_color='gray')
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> notSelected = bkM.Rect(x='x',
>>>>>>>>
>>>>>>>>
>>>>>>>> y='y',
>>>>>>>>
>>>>>>>>
>>>>>>>> width='width',
>>>>>>>>
>>>>>>>>
>>>>>>>> height='height',
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_alpha=0.3,
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_color='gray')
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> selected = bkM.Rect(x='x',
>>>>>>>>
>>>>>>>>
>>>>>>>> y='y',
>>>>>>>>
>>>>>>>>
>>>>>>>> width='width',
>>>>>>>>
>>>>>>>>
>>>>>>>> height='height',
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_alpha=0.3,
>>>>>>>>
>>>>>>>>
>>>>>>>> fill_color='green')
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name='rect')
>>>>>>>>
>>>>>>>>
>>>>>>>> p.add_tools(bkM.BoxSelectTool(dimensions=["width"],callback=boxCallback))
>>>>>>>>
>>>>>>>>
>>>>>>>> p.add_tools(bkM.TapTool(callback=tapCallback))
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> bkP.show(p)
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> ########################################################################
>>>>>>>>
>>>>>>>>
>>>>>>>> ########################################################################
>>>>>>>>
>>>>>>>>
>>>>>>>> ### NewCell
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> from ipywidgets import *
>>>>>>>>
>>>>>>>>
>>>>>>>> from IPython.display import display
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> def foundSacc(sender):
>>>>>>>>
>>>>>>>>
>>>>>>>> print leftEdge
>>>>>>>>
>>>>>>>>
>>>>>>>> print rightEdge
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> b2 = Button(description='Fixation',value=False)
>>>>>>>>
>>>>>>>>
>>>>>>>> b2.on_click(foundSacc)
>>>>>>>>
>>>>>>>>
>>>>>>>> b2.width = '20%'
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> b1 = Button(description='Saccade',value=False)
>>>>>>>>
>>>>>>>>
>>>>>>>> b1.on_click(foundSacc)
>>>>>>>>
>>>>>>>>
>>>>>>>> b1.width = '20%'
>>>>>>>>
>>>>>>>>
>>>>>>>> ​
>>>>>>>>
>>>>>>>> display(b1,b2)
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> 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/b0400249-fb53-482c-8768-c2134923b72a%40continuum.io\.
>>>>>>>> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
>>>>>>>> <Example Event Labeller.ipynb>
>>>>>>>
>>>>>>> --
>>>>>>> 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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com\.
>>>>>>> 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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com\.
>>>>>> 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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com\.
>>>> 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/FE7B66D0-8D13-468A-AE23-DF443D441F79%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/CAFvLWO3Aq0aN0VR%3DNoaksxBk3ZHHmZicfBR%3DzEmBJKijSP6eEA%40mail.gmail.com\.
>>> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
>>> <Event Labeller.ipynb>
>>
>

--
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/EDA8ECB0-75DA-4E41-9C5C-0B0DD78157D1%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/CAFvLWO2ApGLkcN-xYwkS%3DyWWcpA8XZfnesnXRP4huNfyZPCWoA%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

OK! Almost done…I think.

I was unable to resolve the issue with selectedID = cb_obj.get(‘data’)[‘boxID’][idx]; ,
​and implemented an ugly workaround: I index into boxID inside Python.

An unrelated but major impediment was a misunderstanding of how taptool works. I’m surprised to learn that a single tap will produce multiple taptool callbacks. This was leading to the issue with undefined callback indices. I had assumed that the tap tool CB was only run once per click. Now, I’m questioning this assumption. Is it run N times, where N is the number of plotted features? It seems to be the case that it is once per hit-test, or something along those lines. I’ve added some checks to deal with this issue. I would suggest that the docs are updated to provide some more clarity on the topic.

Another question:

How can I use the taptool callback to reset cb_obj(‘selected’)?

You see, if I move something between a datasource, there is a subsequent selection of another object. I believe this is because I am restructuring the contents of the source, but not resetting contents of its datasource.selected, which will subsequently point to the wrong, or missing datasource entries. I believe that I should be resetting datasource.selected inside the JS callback, because the Python datasource.selected will always be empty (again, related to one-way communication). Unfortunately, I don’t think I have any way to know how an empty datasource.selected should be structured in javascript. Can you provide any insight?

Thanks very much!

  • gD

Event Labeller.ipynb (65.6 KB)

···

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Sun, Jun 12, 2016 at 8:44 PM, Bryan Van de Ven [email protected] wrote:

Hrmm, I’m not sure. The code I pasted earlier was working for me. I could see the updates in the console, and I could also see the correct selectedID in the notebook after the callback ran, and with the other changes I mentioned, clicking on the button also copied th right info from one data source to the other. I can maybe imagine there’s corner cases if the selection is empty somehow? In that case, this line:

    var idx = cb_obj.get('selected')['1d'].indices[0]

would result in idx being undefined, because indices is an empty list. So maybe put a check in on the length? That’s all I can think of offhand.

Thanks,

Bryan

On Jun 12, 2016, at 7:25 PM, Gabriel Diaz [email protected] wrote:

Thanks for the input, Bryan! Yes, you can share the notebook. There’s still a bit to do, though. For example, I plan on adding an export button that will merge the datasources into a single dataframe for exporting.

Now, it seems that I’m still having issues with the iteritems deficiency.

This is the error I’m suffering from:

TypeError: undefined is not an object (evaluating ‘idx’)

I’m pretty sure that error is coming from the line:

selectedID = cb_obj.get(‘data’)[‘boxID’][idx];

and that the [idx] is the culprit. Any tips there?

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Sun, Jun 12, 2016 at 7:56 PM, Bryan Van de Ven [email protected] wrote:

Sorry, one more (this is fun!) you probably also want to add a push_notebook at the end of foundSacc. This will cause the tapped and button-pushed rect to disappear. Otherwise if it remains and can be tapped again, but the index has been deleted from undefinedSource, so an index exception is raised.

Bryan

On Jun 12, 2016, at 6:51 PM, Bryan Van de Ven [email protected] wrote:

also one more more not :slight_smile: There is logic error in moveBetweenSources. If it gets to the boxID column in the middle of the loop, then it deletes the value, and then the next time around the .index call fails with an exception. Saving the valueIdx once, before the loops starts, should fix that.

Bryan

On Jun 12, 2016, at 6:43 PM, Bryan Van de Ven [email protected] wrote:

Gabriel,

If I understand what you are looking for, this is the JS callback code to accomplish it:

 tapCallback = bkM.CustomJS(args=dict(source = undefinedSource,fS=fixSource, sS=saccSource), code="""
         var idx = cb_obj.get('selected')['1d'].indices[0];
         selectedID = cb_obj.get('data')['boxID'][idx];
         console.log("idx " + idx);
         console.log("boxIDs " + cb_obj.get('data')['boxID']);
         console.log("selectedID " + selectedID);
         var k = IPython.notebook.kernel;
         k.execute("selectedID = " + selectedID );
 """)

I’ve added some console.log statements so you can see what’s going on more closely if you open your brower’s JS console. I also had to add the callback to the TapTool below, it was not passed to the initializer.

As a side note, I am not sure that idx and selectedID will ever be different. But I can’t prove that so this is definitely the “safest” approach.

Cool notebook BTW! Would love to show off/tweet about it when you are done, if it is something you can publicly share.

Bryan

On Jun 12, 2016, at 10:10 AM, Gabriel Diaz [email protected] wrote:

To follow up…

Code attached.

I’ve stuck with the method of using the python kernel to execute code in JS that can effect the Python environment. I could not find documentation or a tutorial that demonstrated a way to use server mode to avoid the use of javascript callbacks for the placement of new events (rectangles).

To remind you, my goal is to place events (rectangles) using the box tool, and then use tap tool + ipywidgets to assign each placed event to one of three visually distinct event categories (A, B, and C). To accomplish this, I’ve created four glyphs: one that is a default glyph to be used prior to group assignment, and then one for A, B, and C. Once an event is categorized by the user, its data will be moved from the undefined source to source A, B, or C.

Because I’m much more familiar with Python than JS, I intend to use taptool to select the rectangle, but Python to manage passing its information between two data sources. This requires that Python know which rectangle has been clicked by the tap tool. My approach is to include an “boxID” field in each data source so that each rectangle has a unique identification number. Inside the taptool JS callback, the boxID of the selected event rectangle event is passed back to Python. Subsequently, when an ipywidget button indicating a group assignment (A,B, or C) is pressed, then the Python callback will transfer the source entry of the event from an default source to the appropriate group’s source.

I’m nearly there. As you can see in my other post, the holdup is that I cannot figure out how to get the information about the selected item inside the tap-tool CB, so that I can the identification number I’ve stored in its source.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:54 PM, Gabriel Diaz [email protected] wrote:

Ok, so we’re on the same page. THe major obstacle to implementing this in a notebook remains that the Python datasource connected to my Python glyphs does not reflect the JS datasource which is used to update the figure. So, I will take your advice and play around with a Bokeh server implementation. As I understand it, it is pure Python, and should not suffer from the same JS → Python communication issues.

Thanks again for your input! I’ll be sure to post a solution if I find one.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:44 PM, Bryan Van de Ven [email protected] wrote:

Gabriel,

well the reason I asked about the notebook is because I think with a server app you could make something with no (or very little) JavaScript at all. Have you had a chance to look at the hosted examples at:

  [http://demo.bokehplots.com/](http://demo.bokehplots.com/)

Each example links to the source, they are all generally very short (50-100 lines of pure python). In particular here is an example that updates the data in the plot based on a widget:

  [http://demo.bokehplots.com/apps/weather](http://demo.bokehplots.com/apps/weather)

And here is an example that updates secondary plots as a result of a selection on a first plot:

  [http://demo.bokehplots.com/apps/selection_histogram](http://demo.bokehplots.com/apps/selection_histogram)

Your understanding of glyphs seems to be correct. If you have three categories of things A, B, C that might sometimes be visible or not, my suggestion in any case is just to make three different glyphs for A, B, and C (maybe A is represented by rects and B is circles and C is ovals, or whatever is appropriate. How many of each type of glyph is drawn then depends on the data you associate with the glyph) You can “hide” an existing glyph either by:

  • setting .visible = False on the glyph:
  from bokeh.plotting import *
  p = figure()
  p.circle([1,2,3], [4,5,6])
  r = p.square([1,2,3], [5,6,7])
  r.glyph.visible = False
  output_file("/tmp/foo.html")
  show(p)
  • setting the glyphs fill/line colors to None (or alpha to zero):
  from bokeh.plotting import *
  p = figure()
  p.circle([1,2,3], [4,5,6])
  r = p.square([1,2,3], [5,6,7])
  r.glyph.fill_color = None
  r.glyph.line_color = None
  output_file("/tmp/foo.html")
  show(p)
  • clearing the data source for the glyph (making all the columns in the data source be zero-length)

My main point is just that changing / removing the actual glyph objects themselves from an existing plot is going to be tedious and/or difficult. Better to take an existing glyph, and update its associated data, or update its visual properties, than to try and “remove it”.

Bryan

On Jun 9, 2016, at 11:28 AM, Gabriel Diaz [email protected] wrote:

Ok, thanks for the update in terminology.

I should probably say that I will not be deploying this app on a grand-scale. I will be using it in house, with maybe 3-5 users in the first round. I don’t know if it will be used in perpetuity. So, my goal here is to put minimal effort into producing a useable app. So, if the solution requires that I spend considerable effort learning javasript, then I may have to seek out an alternative solution. But, I realize that open source projects benefit from this kind of experiment, so lets see where this takes us. Again, thanks for your efforts, here!

The notebook is not a hard requirement, but it would be nice.

So, as I understand it now, a glyph is just a predefined set of visual attributes. I also get the impression that a column data source tied to a particular glyph can produce several objects (e.g. rectangles) that conform to the visual attributes specified by the glyph. In truth, this was my initial assumption, but I tossed this assumption aside when I saw additional rectangles appear in my figure, but still that the data source (in Python) remained empty. I now understand that this is because the source is modified inside the JS callback function, and used inside JS to update the figure, but that this updated data source is not passed back into the Python environment. One way traffic.

  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

I’m not sure if I fully understand this question. I think this too is an issue with terminology. Let me provide some information that I think is relevant. I will not know how many events are in the data beforehand. I do know that there will be 3 categories of events. Lets be consistent and call the categories A, B, and C. The user may identify several of event A in the signal, (lets say, A1-A3). I would like the ability to subsequently remove A2 without A1 and A3 also disappearing.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 12:04 PM, Bryan Van de Ven [email protected] wrote:

As an aside I think maybe there is terminology we could clarify better in our docs and example. In particular, in Bokeh a “circle glyph” isn’t an actual, specific circle(s) that gets drawn somewhere, it’s a thing that can draw circles when you pair it with some specific data. That’s why most of the use-cases are geared towards creating all the glyphs you might ever need up front, and then just changing the data for them as necessary.

Bryan

On Jun 9, 2016, at 11:01 AM, Bryan Van de Ven [email protected] wrote:

OK great, so we are getting to a place I can potentially make more specific recommendations (this sounds like a very interesting problem, BTW). A few other questions:

  • The notebook is a hard requirement? I believe it is from your description, but wanted to check since some of these things would be simpler in a pure bokeh server app
  • Is it possible to cast the displayed things as a fixed set of glyphs, but with varying (including potentially empty) data for them? This is the use-case Bokeh excels at: I create some glyphs to represent specific classes of things up front, and then later I change the presentation by updating their data sources. If it is possible to define all the needed glyphs “up front” that will also make things much simpler.

Also BTW, using the Jupyter JS functions to execute python code in the kernel is (IMO) a bit convoluted, but it also has precedent, the technique is used in the DataShader project currently, at least.

Bryan

On Jun 9, 2016, at 10:32 AM, Gabriel Diaz [email protected] wrote:

First, thank you very much for such a thoughtful and detailed response. I now understand the problem with much more clarity.

Here is my intention:

I am working with a velocity signal that varies with time (eye tracking data). I want a simple tool for hand labelling three types of events in the signal: fixations, saccades, and pursuits. The events will each have a duration, and so it would be appropriate to mark them with semi-transparent rectangular glyphs that occupy the full Y range and vary only in width. I will refer to these glyphs as events. The signal is quite long, so I will need to show only a portion at a time. This much has been accomplished, with the exception of labelling the event as fixation, saccade, or pursuit. My progress can be seen in the example I’ve posted earlier.

Regardless of what I’ve accomplished, heres the functionality that I would like to add:

  • Create one or several events (a semi-transparent rectangular glyph that spans some portion of time).
  • Select the event (e.g. using tap-tool).
  • Three buttons should then associate the selected event with a label [A,B, or C], and update the glyph color to visually reflect its label.
  • I would also like a button to remove the selected event (to correct for a mistakenly or poorly placed event).
  • Finally, there should be an export button. This will facilitate merging the range spanned by each event into a dataframe, along with each event’s label.

Thanks again.

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Thu, Jun 9, 2016 at 10:08 AM, Bryan Van de Ven [email protected] wrote:

Hi,

Bokeh is a library that spans (at least) two languages and runtime environments, which can sometimes be confusing. Looking at your code, I see you are updating data sources in a JS callback. This only affects the JavaScript data source object in the JavaScript runtime, and will have no reflection into the python space normally. If you need the python/JS to stay “in sync” with one another, that is the primary purpose of the Bokeh server. However, work to make the Bokeh server integrate with the notebook is still ongoing.

However, there are other options for keeping the JS and python objects in sync, namely the “push_notebook” function that was demonstrated in the notebooks I linked earlier. This function allows you to make changes to the python objects (for instance, the glyphs and data sources that you have created) and then have those changes update an existing plot in a notebook cell. Right now this update only happens in one direction: from Python → JS, although there are other folks interested in and working on ideas to enable the “reverse” direction updates as well.

Regarding where to find the glyphs, do you mean on the python side? Or on the JS side? On the python side you you are creating the glyphs and adding them with .add_glyph, you can just use the reference to the glyph you created? Or when you create with, e.g. fig.circle(…) that fucntion returns the glyph renderer, just store it in a variable if you need to use it. Technically, glyph renderers are stored in the .renderers property of Plots, but I would not recommend rooting around there by hand. Specifically the “Continuous Updating” notebook I linked earlier has an example of updating both the data and appearance of an existing glyph using python and push_notebook. This sounds like what you were asking for? There is not any easy way to remove glyphs at the moment, other options would be:

  • recreate a new plot
  • set the glyph to be invisble
  • update the glyphs data

Perhaps it would help to back away from specific implementation questions, because it may be the case that there is some other better path or approach. Can you describe what you are trying to accomplish, at a higher level, without referring to any potential implementation details? That is, more like “I want to have this sort of plot, that shows this kind of data, and these three widgets. When a button is pushed, I want such and such to happen”.

Thanks,

Bryan

On Jun 9, 2016, at 7:54 AM, Gabriel Diaz [email protected] wrote:

Thanks Bryan. Unless I missed it, I don’t see an answer to my question in those otherwise very helpful guides.

Let me try and restate my issue, in case it lacked clarity (that large code dump didn’t help!). I am adding new glyphs to my figure through modification of a column datasource within a javascript callback function. I would subsequently like to access, manipulate, and perhaps remove these glyphs through ipywidget callbacks, using Python.

However, I cannot find where information about individual glpyhs objects stored.

The callback I use is a modified version of the reference example on callbacks for box select. My intuition was that this callback is manipulating the column datasource, which was accumulating the information of multiple glyphs. However, even when multiple ‘rects’ have been added to my figure, the columnData source, when accessed through Python, remains empty. Perhaps I need to invoke the Python kernel from within the JS callback function to return the modified source to the Python workspace?

I’ve explored all the related Bokeh objects, and have done due diligence looking for this answer elsewhere, without luck.

Thanks

  • gD

Gabriel J. Diaz, Ph.D.

Assistant Professor

Rochester Institute of Technology

Chester F. Carlson Center for Imaging Science

Founder of PerForM Labs

Click for demos.

Office 2108, Building #76

Rochester, NY 14623

Office: (585) 475-6215

[email protected]

On Wed, Jun 8, 2016 at 7:26 PM, Bryan Van de Ven [email protected] wrote:

I would suggest studying the examples here:

 [https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms](https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms)

Thanks,

Bryan

On Jun 8, 2016, at 4:13 PM, Gabriel Diaz [email protected] wrote:

Apologies. An example is included in the attached *.ipynb

  • gD

On Wednesday, June 8, 2016 at 5:12:17 PM UTC-4, Gabriel Diaz wrote:

Hello! I’m looking for a way a way to access the glyphs once they have been added to the figure. Upon access, I will need to change visual attributes, and, potentially, to remove the glyph from the figure. Note that the glyphs will be added interactively, through a callback function using the box select tool.

Code from my notebook is pasted below!

If you want to know more, read more to find out why…

Thanks!

  • gD

My goal is to create a somewhat robust tool for that students can use to label events in a time-series. The eventual goal is that they will use box-selection to mark a region, and use one of several ipywidget buttons that will…

  • Label the patch as event type A,B, or C (depending upon the button pressed)
  • Record the event type and start/stop time (on X) of the event in a pandas dataframe
  • Update the glyph to color-code the event

I would also like to provide the user the ability to remove a selected glyph from the dataframe and figure.

Example Event Labeller Last Checkpoint: 5 minutes ago (autosaved)

Python 2

• File

• Edit

• View

• Insert

• Cell

• Kernel

• Help

CodeMarkdownRaw NBConvertHeading

CellToolbar

In [1]:

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

x

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

/Users/diaz/anaconda/lib/python2.7/site-packages/pandas/computation/init.py:19: UserWarning: The installed version of numexpr 2.4.4 is not supported in pandas and will be not be used

UserWarning)

BokehJS successfully loaded

In [41]:

x

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

 // get data source from Callback args
 var data = source.get('data');

 /// get BoxSelectTool dimensions from cb_data parameter of Callback
 var geometry = cb_data['geometry'];

 /// calculate Rect attributes
 var width = geometry['x1'] - geometry['x0'];
 var height = geometry['y1'] - geometry['y0'];
 var x = geometry['x0'] + width/2;
 var y = geometry['y0'] + height/2;
 var leftEdge = geometry['x0']
 var rightEdge = geometry['x1']
 /// update data source with new Rect attributes
 data['x'].push(x);
 data['y'].push(y);
 data['width'].push(width);
 data['height'].push(height);
 data['eventType'] = 'none'
 data['leftEdge'] = leftEdge
 data['rightEdge'] = rightEdge
 // trigger update of data source
 source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

 // get data source from Callback args
 var data = source.get('data');
 var width = data['width']
 IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
 IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

Need an identifier / pointer to the glyph. Once I have that, how to I remove the glpyh?

In [42]:

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='gray')

selected = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

Out[42]:

<Bokeh Notebook handle for In[42]>

In [44]:

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

×

Saccade

Fixation

In :

from future import division

import pandas as pd

import numpy as np

import bokeh.plotting as bkP

import bokeh.models as bkM

from bokeh.palettes import Spectral6

from bokeh.embed import file_html

from bokeh.resources import CDN

bkP.output_notebook()

source = bkM.ColumnDataSource(data=dict(x=, y=, width=, height=,leftEdge=,rightEdge=))

boxCallback = bkM.CustomJS(args=dict(source=source), code=“”"

 // get data source from Callback args
 var data = source.get('data');

 /// get BoxSelectTool dimensions from cb_data parameter of Callback
 var geometry = cb_data['geometry'];

 /// calculate Rect attributes
 var width = geometry['x1'] - geometry['x0'];
 var height = geometry['y1'] - geometry['y0'];
 var x = geometry['x0'] + width/2;
 var y = geometry['y0'] + height/2;
 var leftEdge = geometry['x0']
 var rightEdge = geometry['x1']
 /// update data source with new Rect attributes
 data['x'].push(x);
 data['y'].push(y);
 data['width'].push(width);
 data['height'].push(height);
 data['eventType'] = 'none'
 data['leftEdge'] = leftEdge
 data['rightEdge'] = rightEdge
 // trigger update of data source
 source.trigger('change');

“”")

tapCallback = bkM.CustomJS(args=dict(source=source), code=“”"

 // get data source from Callback args
 var data = source.get('data');
 var width = data['width']
 IPython.notebook.kernel.execute("leftEdge = " + data['leftEdge']);
 IPython.notebook.kernel.execute("rightEdge = "  + data['rightEdge'] );

“”")

IPython.notebook.kernel.execute("leftEdge = width + " + data[‘x’]);

IPython.notebook.kernel.execute("rightEdge = width " + data[‘x’] );

leftEdge =

rightEdge =

p = bkP.figure(width=800, height=350,x_range=[0,13], y_range=(0,2))

x = np.deg2rad(range(720))

y = 1+np.sin(x)

p.xaxis.axis_label = ‘time’

p.yaxis.axis_label = ‘position’

p.line(x,y,line_width=4,name=‘aLine’)

initRect = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='gray')

notSelected = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='gray')

selected = bkM.Rect(x=‘x’,

     y='y',
     width='width',
     height='height',
     fill_alpha=0.3,
     fill_color='green')

p.add_glyph(source, initRect, selection_glyph=selected, nonselection_glyph=notSelected,name=‘rect’)

p.add_tools(bkM.BoxSelectTool(dimensions=[“width”],callback=boxCallback))

p.add_tools(bkM.TapTool(callback=tapCallback))

bkP.show(p)

########################################################################

########################################################################

NewCell

from ipywidgets import *

from IPython.display import display

def foundSacc(sender):

print leftEdge

print rightEdge

b2 = Button(description=‘Fixation’,value=False)

b2.on_click(foundSacc)

b2.width = ‘20%’

b1 = Button(description=‘Saccade’,value=False)

b1.on_click(foundSacc)

b1.width = ‘20%’

display(b1,b2)

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/b0400249-fb53-482c-8768-c2134923b72a%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/F270D184-4A4D-42BE-BD0C-D1B5D77CF158%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/CAFvLWO2nWKTTZ_Ybjpkb7RcP215hbS1SLzy6Osoyd5j8KCV%2B_w%40mail.gmail.com.

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/84D37F6A-0CF7-468E-B2FF-96D4994FFB80%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/CAFvLWO1oe77E2uBUFPYUTN7friv3FH%2BRi9DH5g%3DQpnaOPsZd8Q%40mail.gmail.com.

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/9D84794E-71DE-4931-9834-68F107FCD65A%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/CAFvLWO2S2emW%3DXGx-HjGmPJpE5h21ieVR_WUA63ENXpB9CbU2Q%40mail.gmail.com.

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/FE7B66D0-8D13-468A-AE23-DF443D441F79%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/CAFvLWO3Aq0aN0VR%3DNoaksxBk3ZHHmZicfBR%3DzEmBJKijSP6eEA%40mail.gmail.com.

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/EDA8ECB0-75DA-4E41-9C5C-0B0DD78157D1%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/CAFvLWO2ApGLkcN-xYwkS%3DyWWcpA8XZfnesnXRP4huNfyZPCWoA%40mail.gmail.com.

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/8C41A820-7486-4C0C-8500-FEFFE7912BFE%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

OK! Almost done...I think.

I was unable to resolve the issue with selectedID = cb_obj.get('data')['boxID'][idx]; , ​and implemented an ugly workaround: I index into boxID inside Python.

An unrelated but major impediment was a misunderstanding of how taptool works. I'm surprised to learn that a single tap will produce multiple taptool callbacks. This was leading to the issue with undefined callback indices. I had assumed that the tap tool CB was only run once per click. Now, I'm questioning this assumption. Is it run N times, where N is the number of plotted features? It seems to be the case that it is once per hit-test, or something along those lines. I've added some checks to deal with this issue. I would suggest that the docs are updated to provide some more clarity on the topic.

Unless the "renderers" property is explicitly configured, the tap tool will try to do selection / hit-testing on all available glyphs. If you want to restrict to just one (or some) renderers, you can do something like:

  p = figure(...)
  p.rect(...)
  r = p.circle(...)

  tool = TapTool(..., renderers=[r]) # only checks the circle

I think this is already in the docs somewhere, but I am not sure offhand where (so maybe needs to be more prominent).

Another question:
How can I use the taptool callback to reset cb_obj('selected')?

You see, if I move something between a datasource, there is a subsequent selection of another object. I believe this is because I am restructuring the contents of the source, but not resetting contents of its datasource.selected, which will subsequently point to the wrong, or missing datasource entries. I believe that I should be resetting datasource.selected inside the JS callback, because the Python datasource.selected will always be empty (again, related to one-way communication). Unfortunately, I don't think I have any way to know how an empty datasource.selected should be structured in javascript. Can you provide any insight?

There is a "clear" method on the data source selection manager that you can call:

  https://github.com/bokeh/bokeh/blob/0.11.1/bokehjs/src/coffee/models/plots/plot.coffee#L296

Thanks,

Bryan

···

On Jun 13, 2016, at 10:09 AM, Gabriel Diaz <[email protected]> wrote: