How does this Bokeh Column Data Source Work? - python

I am trying to get a better understanding about the column data source in Bokeh (for Python). I found this code, but I can't seem to find the documentation that explains some things I am looking for, For instance:
Where is the callback from the lasso_select tool? I want to see where the expected functionality is described.
How is the functionality of the lasso_select described in code? (What if I want to change it?)
What is happening to the column data source so that the circles outside the lasso-select region change appearance? (I want to know how I can use the column data source for more complex visualization than is shown by this demo. So I'd like to know what dictionary field is being manipulated, and how is it being manipulated. For example, is there a hidden "color" field or something like that, which isn't explicit in this code?)
What code causes the figure to be redrawn when a lasso_select action is made?
I have many more questions related to this and the CDSView, but I'll stop here for now.
from bokeh.io import output_file, show
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
output_file("brushing.html")
x = list(range(-20, 21))
y0 = [abs(xx) for xx in x]
y1 = [xx**2 for xx in x]
# create a column data source for the plots to share
source = ColumnDataSource(data=dict(x=x, y0=y0, y1=y1))
TOOLS = "box_select,lasso_select,help"
# create a new plot and add a renderer
left = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
left.circle('x', 'y0', source=source)
# create another new plot and add a renderer
right = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
right.circle('x', 'y1', source=source)
p = gridplot([[left, right]])
show(p)
This is related to my previous question, where the only answer was very narrow in explaining for that specific question. However, I am really interested in what's going on under the hood to give the results that are seen. It would help my understanding a lot more if I could know some of those details.

1) There is no callback. The tool is responsible for defining a selection geometry, and and then hit-testing baed on that geometry. The hit test results are store in a selection property of the data source. Glyph renderers draw glyphs based on the selection property of their data source. If two glyph renderers (even on different plots) share the same data source, they will both draw the same set of selected/nonselected as a result.
2) If you mean the appearance of the normal vs selected vs non-selected objects, how to configure that is described in the docs here:
https://docs.bokeh.org/en/latest/docs/user_guide/styling.html#selected-and-unselected-glyphs
There are also a few properties on the LassoTool object itself, that control, e.g. whether a selection should be made on every mousemove, or only on mouseup, and what the selection overlay looks like. All of these are recorded in the ReferenceGuide. If you are asking how to change the implementation, as with msot everything in Bokeh, the real work is not done in Python, it is done in the JavaScript library BokehJS. The implementation of the LassoTool is here:
https://github.com/bokeh/bokeh/blob/master/bokehjs/src/lib/models/tools/gestures/lasso_select_tool.ts
If you want something fundamentally different you would need to implement your own custom model, including its JavaScript component. There is an entire User's Guide section about building custom extensions:
https://docs.bokeh.org/en/latest/docs/user_guide/extensions.html
3) The Plot is configured with various Renderers, one of which can be a GlyphRenderer. The GlyphRenderer itself does not draw anything, but it configures various sub-glyphs that are used to draw in specific situations:
glyph draws "normal" versions of glyphs (i.e. when there is no selection on the data source)
selected_glyph draws "selected" versions of glyphs (i.e. the ones inside a lasso or box tool when a selection is active)
nonselected_glyph draws the "non-selected" versions of glyphs (i.e. the ones outside a lasso or box tool when a selection is active) By default the non selection glyph is just a copy of the "normal" glyph with the alpha value set very low.
hover_glyph draws the "hovered" versions of glyphs (i.e. when a hover tool has inspected them)
You configure the appearance in the different situation by configuring properties on the glyphs that are used in each situation. There are sensible defaults for them, but they can be updated as described in the first link of 2)
4) BokehJS has an internal signal/slots event system that is used (among other things) to request canvas redraws whenever various properties change.

Related

Python interactive plot in browser: drag and drop arbitrary elements

I have my backend in Python and I'm trying to create an interactive plot in the web browser, where the user can drag and drop elements across the plot. The plan is then to hook onto the 'drop' event with some JS that updates values in a form and submits the form, which would then cause the backend to update the plot.
So far I have been using mpld3, but I am not invested in it. I would be open to a solution using some other package like Plotly or Bokeh.
What I am looking for is quite similar to this mpld3 example, except that I want to allow the user to drag objects other than the points. Specifically, I want to create some vertical and horizontal lines and allow the user to drag these horizontally and vertically, as illustrated in this picture:
I have gotten so far as to adapt the mpld3 plugin to actually work with the latest version of d3.js, and I am able to drag the points (see gist). However, I am not able to adapt the plugin to make the vertical lines draggable like the points in the example.
My first idea was something like
lines = ax.vlines(xdata,min(ydata),ydata)
plugins.connect(fig, DragPlugin(lines))
vlines() returns a matplotlib.collections.LineCollection item, and I don't know how to access individual lines inside that. I tried simply modifying the type check inside the __init__ (if isinstance(object, mpl.lines.Line2D) to be more permissive, but that didn't work.
My next idea was to just draw the lines using ax.plot():
lines = []
for i in range(len(xdata)):
from_xy = (xdata[i],min(ydata))
to_xy = (xdata[i],ydata[i])
xs = from_xy[0],to_xy[0]
ys = from_xy[1],to_xy[1]
lines.append(ax.plot(xs,ys, 'k-')[0])
In this case the list lines contains matplotlib.lines.Line2D objects, which I thought would be easier to manipulate individually in mpld3 than a LineCollection. However, the Plugin seems to be written to deal with a the entire set of red points as a Line2D object (like a polygonal chain with invisible edges, if you see what I mean). This is what I conclude from the fact that the string 'pts' is used to find the mpld3 id of the points. Just passing a Line2D object to DragPlugin, however, does not work:
# don't use the suffix:
def __init__(self, object):
self.dict_ = {"type": "drag","id": utils.get_id(object)}
# after defining `lines` as above:
plugins.connect(fig, DragPlugin(lines[0]))
I get the following in the browser console:
Uncaught TypeError: obj.elements(...).data(...).style is not a function
Which gets raised at the following part of the JS:
obj.elements()
.data(obj.offsets)
.style("cursor", "default")
.call(drag);
But I have no idea what that JS is doing and it's not documented in the plugin.
Another thing I will need is to restrict the vertical lines to be only draggable horizontally, and conversely for the horizontal lines. But first I would like to get dragging the lines to work at all.

Display hovertool for the uppermost glyph in bokeh?

I have a graph with several hundreds of glyphs on it, generated in bokeh. All the glyphs have a hovertool associated with them that shows a popup window with an image in it when moused over.
Some of the glyphs have 4-5 glyphs layered underneath them, and when someone mouses over them, all the hovertools are displayed.
Is it possible to only display the hovertool for the uppermost glyph?
As of Bokeh 1.3.4 there is no way to control the number of tooltips displayed without creating some sort of a custom extension. There is an open issue #9087 Provide max_tooltips property on HoverTool that is targeted for one of the next few releases.
But note: that work will just take a tooltip out of multiple to show. The draw order for a single glyph is not well-defined, it can change due to: spatial indexing, selections, "Level of Detail" mode being active, and other things. I don't know that there will ever be a reliable notion of "total z-order" such that e.g the "top" circle could be the one picked (and always be the same index). A more sophisticated selection manager could sort or condition on other CDS column values, etc. though, so you could potentially explicitly provide the order you intend.

Add Label Annotations on Axes

I currently annotate my charts with the last value of each series by adding a Label and supplying my the name of corresponding range it's plotted on:
Label(
...
x=data.index.max(),
y=data.loc[data.index.max(), 'my_col'],
y_range_name='my_range'
...
)
Which gives me:
How do I move the labels so they are positioned on their respective axis?
Example:
Please note that my labels' y-positioning is off, so I need some help with that aspect too. I've tried tweaking the y_offset but this has not yielded any consistently good results.
My data are always numerical time series.
As of Bokeh 1.2 there is no built-in annotation or glyph that will display outside the central plot area. There is an open issue on GitHub that this is similar to that you can follow or comment on. For the time being, something like this would require making a custom extension

adding tooltips to line plots in bokeh

Is there any way at the moment to add constant tooltips to a bokeh line plot in python? I did not find anything about it in the documention.
I am looking for a way to allow adding the tooltip interactively ala matlab. However, doing so via the code at first is acceptable.
thanks.
Currently (as of 0.8.1) the line glyph does not support hit testing, so it does not support a hover tool, either. However, if it suffices to have a hover tooltip on just the "points" of the line, then several people have uses a second set of transparent markers located at the same points as a workaround. Something like:
line(x, y)
circle(x, y, size=8, alpha=0)
There is an open issue for adding line hit testing, it should hopefully been in one of the next few releases.

Interactive selection of series in a matplotlib plot

I have been looking for a way to be able to select which series are visible on a plot, after a plot is created.
I need this as i often have plots with many series. they are too many to plot at the same time, and i need to quickly and interactively select which series are visible. Ideally there will be a window with a list of series in the plot and checkboxes, where the series with the checked checkbox is visible.
Does anyone know if this has been already implemented somewhere?, if not then can someone guide me of how can i do it myself?
Thanks!
Omar
It all depends on how much effort you are willing to do and what the exact requirements are, but you can bet it has already been implemented somewhere :-)
If the aim is mainly to not clutter the image, it may be sufficient to use the built-in capabilities; you can find relevant code in the matplotlib examples library:
http://matplotlib.org/examples/event_handling/legend_picking.html
http://matplotlib.org/examples/widgets/check_buttons.html
If you really want to have a UI, so you can guard the performance by limiting the amount of plots / data, you would typically use a GUI toolbox such as GTK, QT or WX. Look here for some articles and example code:
http://eli.thegreenplace.net/2009/05/23/more-pyqt-plotting-demos/
A list with checkboxes will be fine if you have a few plots or less, but for more plots a popup menu would probably be better. I am not sure whether either of these is possible with matplotlib though.
The way I implemented this once was to use a slider to select the plot from a list - basically you use the slider to set the index of the series that should be shown. I had a few hundred series per dataset, so it was a good way to quickly glance through them.
My code for setting this up was roughly like this:
fig = pyplot.figure()
slax = self.fig.add_axes((0.1,0.05,0.35,0.05))
sl = matplotlib.widgets.Slider(slax, "Trace #", 0, len(plotlist), valinit=0.0)
def update_trace():
ax.clear()
tracenum = int(np.floor(sl.val))
ax.plot(plotlist[tracenum])
fig.canvas.draw()
sl.on_changed(update_trace)
ax = self.fig.add_axes((0.6, 0.2, 0.35, 0.7))
fig.add_subplot(axes=self.traceax)
update_trace()
Here's an example:
Now that plot.ly has opened sourced their libraries, it is a really good choice for interactive plots in python. See for example: https://plot.ly/python/legend/#legend-names. You can click on the legend traces and select/deselect traces.
If you want to embed in an Ipython/Jupyter Notebook, that is also straightforward: https://plot.ly/ipython-notebooks/gallery/

Categories