Change label ordering in bokeh heatmap - python

From the bokeh examples
from bokeh.charts import HeatMap, output_file, show
data = {'fruit': ['apples']*3 + ['bananas']*3 + ['pears']*3,
'fruit_count': [4, 5, 8, 1, 2, 4, 6, 5, 4],
'sample': [1, 2, 3]*3}
hm = HeatMap(data, x='fruit', y='sample', values='fruit_count',
title='Fruits', stat=None)
show(hm)
is there a workaround for changing the order in which the labels are displayed? For example, if I wanted to show pears first?

First, you should not use bokeh.charts. It was deprecated, and has been removed from core Bokeh to a separate bkcharts repo. It is completely unsupported and unmaintained. It will not see any new features, bugfixes, improvements, or documentation. It is a dead end.
There are two good options to create this chart:
1) Use the stable and well-supported bokeh.plotting API. This is slightly more verbose, but gives you explicit control over everything, e.g. the order if the categories. In the code below these are specified as x_range and y_range values to figure:
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource, LinearColorMapper
from bokeh.palettes import Spectral9
from bokeh.plotting import figure
from bokeh.transform import transform
source = ColumnDataSource(data={
'fruit': ['apples']*3 + ['bananas']*3 + ['pears']*3,
'fruit_count': [4, 5, 8, 1, 2, 4, 6, 5, 4],
'sample': ['1', '2', '3']*3,
})
mapper = LinearColorMapper(palette=Spectral9, low=0, high=8)
p = figure(x_range=['apples', 'bananas', 'pears'], y_range=['1', '2', '3'],
title='Fruits')
p.rect(x='fruit', y='sample', width=1, height=1, line_color=None,
fill_color=transform('fruit_count', mapper), source=source)
show(p)
This yields the output below:
You can find much more information (as well as live examples) about categorical data with Bokeh in the Handling Categorical Data sections of the User's Guide.
2) Look into HoloViews, which is a very high level API on top of Bokeh that is actively maintained by a team, and endorsed by the Bokeh team as well. A simple HeatMap in HoloViews is typically a one-liner as with bokeh.charts.

Related

How can I get the coordinates of a click in a plot on python side?

I have a plot with circles on it and I want users to be able to select circles and then I want to do some stuff on python side depending on the circle.
I have tried JS callbacks but I get the error:
WARNING:bokeh.embed.util: You are generating standalone HTML/JS
output, but trying to use real Python callbacks (i.e. with on_change
or on_event). This combination cannot work. Only JavaScript callbacks
may be used with standalone output. For more information on JavaScript
callbacks with Bokeh, see:
http://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html
Alternatively, to use real Python callbacks, a Bokeh server
application may be used. For more information on building and running
Bokeh applications, see:
http://docs.bokeh.org/en/latest/docs/user_guide/server.html
Here's some code if you want to try to do it.
from bokeh.plotting import figure, output_file, show
output_file("openurl.html")
p = figure(plot_width=400, plot_height=400, tools="tap", title="Click the Dots")
source = ColumnDataSource(data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
color=["navy", "orange", "olive", "firebrick", "gold"]
))
p.circle('x', 'y', color='color', size=20, source=source)```
I didnt quite understand your question, but you can use hover tool to add interaction, or LabelSet to display your x/y along with the dots.
from bokeh.plotting import figure
from bokeh.io import show, output_notebook
output_notebook()
p = figure(plot_width=400, plot_height=400, tools="tap, reset, hover", title="Click
the Dots", tooltips = [('x', '#x'),('y','#y')])
source = ColumnDataSource(data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
color=["navy", "orange", "olive", "firebrick", "gold"]
))
p.circle('x', 'y', color='color', size=20, source=source)
show(p)

query bokeh multi_line with markers

I would like to know if there is a way to add markers to multiline for bokeh. I can get the multiple lines but then p.circle() doesn't seem to work on list of lists. Here is a sample:
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource
from bokeh.palettes import Spectral6, Spectral11
numlines = 2
mypalette = Spectral6[0:numlines]
data = {'x_values': [[1, 2, 3], [1,2,3]],
'y_values': [[1, 2, 3], [4 ,5, 6]], 'labels': ['a', 'b'], 'line_color': mypalette}
source = ColumnDataSource(data=data)
p = figure()
p.multi_line(xs='x_values', ys='y_values', line_color='line_color', source=source)
show(p)
As of Bokeh 0.13.0 there is not. You would need to call p.circle, p.square, etc. for each "sub" line in the multi-line.

Bar chart showing count of each category month-wise using Bokeh

I have data as shown below:
So, from this, I need to display the count in each category year_month_id wise. Since I have 12 months there will be 12 sub-divisions and under each count of
ID within each class.
Something like the image below is what I am looking for.
Now the examples in Bokeh use ColumnDataSource and dictionary mapping, but how do I do this for my dataset.
Can someone please help me with this?
Below is the expected output in tabular and chart format.
I believe the pandas Python package would come in handy for preparing your data for plotting. It's useful for manipulating table-like data structures.
Here is how I went about your problem:
from pandas import DataFrame
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.palettes import Viridis5
# Your sample data
df = DataFrame({'id': [1, 2, 3, 4, 5, 6, 7, 8, 9, 1],
'year_month_id': [201612, 201612, 201612, 201612, 201612, 201612, 201612, 201612, 201612, 201701],
'class': ['A', 'D', 'B', 'other', 'other', 'other', 'A', 'other', 'A', 'B']
})
# Get counts of groups of 'class' and fill in 'year_month_id' column
df2 = DataFrame({'count': df.groupby(["year_month_id", "class"]).size()}).reset_index()
df2 now looks like this:
# Create new column to make plotting easier
df2['class-date'] = df2['class'] + "-" + df2['year_month_id'].map(str)
# x and y axes
class_date = df2['class-date'].tolist()
count = df2['count'].tolist()
# Bokeh's mapping of column names and data lists
source = ColumnDataSource(data=dict(class_date=class_date, count=count, color=Viridis5))
# Bokeh's convenience function for creating a Figure object
p = figure(x_range=class_date, y_range=(0, 5), plot_height=350, title="Counts",
toolbar_location=None, tools="")
# Render and show the vbar plot
p.vbar(x='class_date', top='count', width=0.9, color='color', source=source)
show(p)
So the Bokeh plot looks like this:
Of course you can alter it to suit your needs. The first thing I thought of was making the top of the y_range variable so it could accommodate data better, though I have not tried it myself.

Bokeh: Interact with legend label text

Is there any way to interactively change legend label text in Bokeh?
I've read https://github.com/bokeh/bokeh/issues/2274 and How to interactively display and hide lines in a Bokeh plot? but neither are applicable.
I don't need to modify the colors or anything of more complexity than changing the label text but I can't find a way to do it.
I hope this answer can help others with similar issues.
There is a workaround to this problem: starting from version 0.12.3 your legends can be dynamically modified through a ColumnDataSource object used to generate the given elements. For example:
source_points = ColumnDataSource(dict(
x=[1, 2, 3, 4, 5, 6],
y=[2, 1, 2, 1, 2, 1],
color=['blue','red','blue','red','blue','red'],
category=['hi', 'lo', 'hi', 'lo', 'hi', 'lo']
))
self._figure.circle('x',
'y',
color='color',
legend='category',
source=source_points)
Then you should be able to update the legend by setting the category values again, like:
# must have the same length
source_points.data['category'] = ['stack', 'flow', 'stack', 'flow', 'stack', 'flow']
Note the relation between category and color. If you had something like this:
source = ColumnDataSource(dict(
x=[1, 2, 3, 4, 5, 6],
y=[2, 1, 2, 1, 2, 1],
color=['blue','red','blue','red','blue','red'],
category=['hi', 'hi', 'hi', 'lo', 'hi', 'lo']
))
Then the second hi would show up blue as well. It only matches the first occurrence.
As of Bokeh 0.12.1 it does not look like this is currently supported. Legend objects have a legends property that maps the text to a list of glyphs:
{
"foo": [circle1],
"bar": [line2, circle2]
}
Ideally, you could update this legends property to cause it to re-render. But looking at the source code it appears the value is used at initialization, but there is no plumbing to force a re-render if the value changes. A possible workaround could be to change the value of legends then also immediately set some other property that does trigger a re-render.
In any case making this work on update should not be much work, and would be a nice PR for a new contributor. I'd encourage you to submit a feature request issue on the GitHub issue tracker and, if you have the ability a Pull Request to implement it (we are always happy to help new contributors get started and answer questions)
In my case, I made it work with the next code:
from bokeh.plotting import figure, show
# Create and show the plot
plt = figure()
handle = show(plt, notebook_handle=True)
# Update the legends without generating the whole plot once shown
for legend in plt.legend:
for legend_item, new_value in zip(legend.items, new_legend_values):
legend_item.label['value'] = new_value
push_notebook(handle=handle)
In my case, I was plotting some distributions, and updating then interactively (like an animation of the changes in the distributions). In the legend, I have the parameters of the distribution over time, which I need to update at each iteration, as they change.
Note that this code only works in a Jupyter notebook.
I ended up just redrawing the entire graph each times since the number of lines also varied in my case.
A small working Jupyter notebook example:
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.palettes import brewer
from math import sin, pi
output_notebook()
def update(Sine):
p = figure()
r = []
for i in range(sines.index(Sine) + 1):
y = [sin(xi/(10*(i+1))) for xi in x]
r.append(p.line(x, y, legend=labels[i], color=colors[i], line_width = 3))
show(p, notebook_handle=True)
push_notebook()
sines = ["one sine", "two sines", "three sines"]
labels = ["First sine", "second sine", "Third sine"]
colors = brewer['BuPu'][3]
x = [i for i in range(100)]
interact(update, Sine=sines)

Bokeh - How to Click and Drag?

I would like to click-and-drag the scatter points the points of a bokeh scatter plot. Any ideas how to do this?
(edit: this is an example of what I'd like to do)
For an example of a scatter, the code below generates the scatter plot chart found half-way through this page.
from bokeh.plotting import figure, output_file, show
# create a Figure object
p = figure(width=300, height=300, tools="pan,reset,save")
# add a Circle renderer to this figure
p.circle([1, 2.5, 3, 2], [2, 3, 1, 1.5], radius=0.3, alpha=0.5)
# specify how to output the plot(s)
output_file("foo.html")
# display the figure
show(p)
Multi-gesture edit tools are only a recent addition, landing in version 0.12.14. You can find much more information in the Edit Tools section of the User's Guide.
Specifically to be able to move points as described in the OP, use the PointDrawTool:
Here is a complete example you can run that also has a data table showing the updated coordinates of the glyphs as they are moved (you will need to activate the tool in the toolbar first, it is off by default):
from bokeh.plotting import figure, output_file, show, Column
from bokeh.models import DataTable, TableColumn, PointDrawTool, ColumnDataSource
output_file("tools_point_draw.html")
p = figure(x_range=(0, 10), y_range=(0, 10), tools=[],
title='Point Draw Tool')
p.background_fill_color = 'lightgrey'
source = ColumnDataSource({
'x': [1, 5, 9], 'y': [1, 5, 9], 'color': ['red', 'green', 'yellow']
})
renderer = p.scatter(x='x', y='y', source=source, color='color', size=10)
columns = [TableColumn(field="x", title="x"),
TableColumn(field="y", title="y"),
TableColumn(field='color', title='color')]
table = DataTable(source=source, columns=columns, editable=True, height=200)
draw_tool = PointDrawTool(renderers=[renderer], empty_value='black')
p.add_tools(draw_tool)
p.toolbar.active_tap = draw_tool
show(Column(p, table))

Categories