Disable scientific notation on axes using Bokeh - python

How can you disable scientific output of numbers on an axis in bokeh?
For example, I want 400000 and not 4.00e+5
In mpl: ax.get_xaxis().get_major_formatter().set_scientific(False)

You can disable scientific notation with this:
fig = plt.figure(title='xxx', x_axis_type='datetime')
fig.left[0].formatter.use_scientific = False

Note that as of Bokeh v0.9.1, Marek's answer will no longer work due to changes in the interface for Charts. The following code (from GitHub) is a fully-functional example of how to turn off scientific notation in a high level chart.
from bokeh.embed import components
from bokeh.models import Axis
from bokeh.charts import Bar
data = {"y": [6, 7, 2, 4, 5], "z": [1, 5, 12, 4, 2]}
bar = Bar(data)
yaxis = bar.select(dict(type=Axis, layout="left"))[0]
yaxis.formatter.use_scientific = False
script, div = components(bar)
print(script)
print(div)
The key lines are:
yaxis = bar.select(dict(type=Axis, layout="left"))[0]
yaxis.formatter.use_scientific = False

I was trying to turn off scientific notation from a logarithmic axis, and the above answers did not work for me.
I found this: python bokeh plot how to format axis display
In that spirit, this worked for me:
from bokeh.models import BasicTickFormatter
fig = plt.figure(title='xxx', x_axis_type='datetime',y_axis_type='log')
fig.yaxis.formatter = BasicTickFormatter(use_scientific=False)

To disable the scientific output in Bokeh, use use_scientific attribute of the formatter you use.
You can find more information regarding use_scientific attribute here:
a description of the attribute in the code of bokeh: BasicTickFormatter
class (line 28)
documentation of use_scientific attr
Example (originaly comes from Bokeh issues discussion):
from bokeh.models import Axis
yaxis = bar.chart.plot.select(dict(type=Axis, layout="left"))[0]
yaxis.formatter.use_scientific = False
bar.chart.show()

Related

How to add language parametr to NumeralTickFormatter in HoverTool in bokeh

Let's assume we have the following constructor for HoverTool in bokeh:
hover = HoverTool(tooltips=[("min_temp", "#{min}{0,0.0}°C")])
By default, this creates a tooltip where numbers for the value "min_temp" use comma as a thousand separator and dot for decimals - for example, 10,000.123. However, I want to use spaces to separate thousands instead of a comma, that is 10 000.123.
From what I understand from the documentation, HoverTool uses the NumeralTickFormatter class in bokeh to format numerals, and this class supports the language parametr, but I have no idea how can I change that from the HoverTool strings (besides rewriting the parametr in source code). I think this could solve my problem.
I don't know how to set the "language" in the NumeralTickFormatter but you can make use of the CustomJSHover and create you own rule with a few lines.
Minimal Example
from bokeh.plotting import figure, output_notebook, show
from bokeh.models import HoverTool, CustomJSHover
output_notebook()
p = figure(plot_width=400, plot_height=400, title=None)
x = [999.5, 1000.7, 1001.0]
y = [2, 5, 3]
p.circle(x, y, size=10)
custom = CustomJSHover(code=
'''
var num = special_vars.x.toFixed(2).toString()
var splitted = num.split(".")
for (var i=splitted[0].length-3; 0<i; i-=3){
num = num.slice(0,i)+" "+num.slice(i)
}
return num
''')
p.add_tools(HoverTool(
tooltips=[('x','$x{custom}' )],
formatters={'$x':custom}
))
show(p)
Output

In Bokeh Python, LabelSet annotation does not work well if the x and y axes contain alphabet letters

Maybe the title is not clear, so let me explain my question. I tried LabelSet with all numbers at the x and y axes, it works perfectly. However, when I changed to use all Alphabet letters such as A, A+, B, etc, the LabelSet does not understand and put all annotations on the top left (I attached the image about the error below.)
I tried to debug and find out the problem is at labels = LabelSet(x = 'average_grades', y = 'exam_grades'). If I change x = integer, and y = integer, it works. Please help me to fix it since I want to apply LabelSet in many cases, not only with numbers.
#importing libraries
from bokeh.plotting import figure
from bokeh.io import curdoc
from bokeh.models.annotations import LabelSet, Label
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Select
from bokeh.layouts import layout
#create columndatasource
source=ColumnDataSource(dict(average_grades=["B+","A","D-"],
exam_grades=["A+","C","D"],
student_names=["Stephan","Helder","Riazudidn"]))
#create the figure
grade1 = ["F","D-","D","D+","C-","C","C+","B-","B","B+","A-","A","A+"]
grade2 = ["F","D-","D","D+","C-","C","C+","B-","B","B+","A-","A","A+"]
f = figure(x_range= grade1,
y_range= grade2)
f.plot_width = 1100
f.plot_height = 650
#add labels for glyphs
labels=LabelSet(x='average_grades',y='exam_grades',
text="student_names",
x_offset=20, y_offset=20,
text_color = 'red', source=source, level = 'glyph',
render_mode = "css", text_font_size = "20pt")
f.add_layout(labels)
description = Label(x = 4, y = 2, text="Hello World", render_mode = "css")
f.add_layout(description)
#create glyphs
f.circle(x="average_grades", y="exam_grades", source=source, size=8)
#create function
def update_labels(attr,old,new):
labels.text=select.value
# #labels.text = getattr(select, attr)
#create select widget
options=[("average_grades","Average Grades"),("exam_grades","Exam Grades"),("student_names","Student Names")]
select=Select(title="Attribute",options=options)
select.on_change("value",update_labels)
# #create layout and add to curdoc
lay_out=layout([[select]])
curdoc().add_root(f)
curdoc().add_root(lay_out)
Please, always provide relevant version information with every question.
This just seems like a bug. I can see the problem on latest Bokeh version 2.2.1 however if I try a 2.3 development version, the problem has gone away. So evidently this was fixed at some point recently, though perhaps inadvertently. Please file a GitHub issue so that we can make sure any fix is intentional and preserved under test. The solution is to wait for the next release 2.3 (or a 2.2.2 if one is made).
In the mean time, the only suggestion I can offer is to use integer coordinates in stead of categorical (string) coordinates, then use fixed ticker values with tick label overrides:
p.xaxis.major_label_overrides = {1: "A", 2, "B", ...}
to make it "look the same".

Jupyter Bokeh: Non-existent column name in glyph renderer

I have a GlyphRenderer whose data_source.data is
{'index': [0, 1, 2, 3, 4, 5, 6, 7],
'color': ['#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#fee08b', '#fdae61', '#f46d43', '#d53e4f']}
The renderer's glyph is
Oval(height=0.1, width=0.2, fill_color="color")
When rendering, I see
E-1001 (BAD_COLUMN_NAME): Glyph refers to nonexistent column name: color [renderer: GlyphRenderer(id='1d1031f5-6ee3-4744-a0f7-22309798e313', ...)]
I'm clearly missing something, but this is pretty much lifted from published examples. I verified in a debugger that data_source.column_names is just ['index']; what I don't understand is why the 'color' column doesn't appear in the data source's column_names, or why Bokeh produces this warning (the graph appears to be correctly rendered).
The complete source is available at https://pastebin.com/HXAEEujP
It's generally better to provide all relevant arguments when constructing an object rather than mutating the object after it's already been created. It's especially true for Bokeh - in many cases it does some additional work based on the arguments passed to __init__.
Take a look at this version of your code:
import math
from bokeh.io import show
from bokeh.models import GraphRenderer, StaticLayoutProvider, Oval, GlyphRenderer, ColumnDataSource, MultiLine
from bokeh.palettes import Spectral8
from bokeh.plotting import figure
N = 8
node_indices = list(range(N))
plot = figure(title="Graph Layout Demonstration", x_range=(-1.1, 1.1), y_range=(-1.1, 1.1),
plot_width=250, plot_height=250,
tools="", toolbar_location=None)
node_ds = ColumnDataSource(data=dict(index=node_indices,
color=Spectral8),
name="Node Renderer")
edge_ds = ColumnDataSource(data=dict(start=[0] * N,
end=node_indices),
name="Edge Renderer")
### start of layout code
circ = [i * 2 * math.pi / 8 for i in node_indices]
x = [math.cos(i) for i in circ]
y = [math.sin(i) for i in circ]
graph_layout = dict(zip(node_indices, zip(x, y)))
graph = GraphRenderer(node_renderer=GlyphRenderer(glyph=Oval(height=0.1, width=0.2, fill_color="color"),
data_source=node_ds),
edge_renderer=GlyphRenderer(glyph=MultiLine(),
data_source=edge_ds),
layout_provider=StaticLayoutProvider(graph_layout=graph_layout))
plot.renderers.append(graph)
show(plot)

Change label ordering in bokeh heatmap

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.

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)

Categories