How to create a multi-line plot title in bokeh? - python

How do you create a multiline plot title in bokeh?... same question as https://github.com/bokeh/bokeh/issues/994
Is this resolved yet?
import bokeh.plotting as plt
plt.output_file("test.html")
plt.text(x=[1,2,3], y = [0,0,0], text=['hello\nworld!', 'hello\nworld!', 'hello\nworld!'], angle = 0)
plt.show()
Additionally, can the title text string accept rich text?

In recent versions of Bokeh, labels and text glyphs can accept newlines in the text, and these will be rendered as expected. For multi-line titles, you will have to add explicit Title annotations for each line you want. Here is a complete example:
from bokeh.io import output_file, show
from bokeh.models import Title
from bokeh.plotting import figure
output_file("test.html")
p = figure(x_range=(0, 5))
p.text(x=[1,2,3], y = [0,0,0], text=['hello\nworld!', 'hello\nworld!', 'hello\nworld!'], angle = 0)
p.add_layout(Title(text="Sub-Title", text_font_style="italic"), 'above')
p.add_layout(Title(text="Title", text_font_size="16pt"), 'above')
show(p)
Which produces:
Note that you are limited to the standard "text properties" that Bokeh exposes, since the underlying HTML Canvas does not accept rich text. If you need something like that it might be possible with a custom extension

You can add a simple title to your plot with this:
from bokeh.plotting import figure, show, output_file
output_file("test.html")
p = figure(title="Your title")
p.text(x=[1,2,3], y = [0,0,0], text=['hello\nworld!', 'hello\nworld!', 'hello\nworld!'], angle = 0)
show(p)
Addendum
Here is a working example for plotting a pandas dataframe for you to copy/paste into a jupyter notebook. It's neither elegant nor pythonic. I got it a long time ago from various SO posts. Sorry, that I don't remember which ones anymore, so I can't cite them.
Code
# coding: utf-8
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
import pandas as pd
import numpy as np
# Create some data
np_arr = np.array([[1,1,1], [2,2,2], [3,3,3], [4,4,4]])
pd_df = pd.DataFrame(data=np_arr)
pd_df
# Convert for multi-line plotting
data = [row[1].as_matrix() for row in pd_df.iterrows()]
num_lines = len(pd_df)
cols = [pd_df.columns.values] * num_lines
data
# Init bokeh output for jupyter notebook - Adjust this to your needs
output_notebook()
# Plot
p = figure(plot_width=600, plot_height=300)
p.multi_line(xs=cols, ys=data)
show(p)
Plot

Related

Bokeh change data_source aren't updated by push_notebook

I'm having trouble continuously update a shown figure. Could someone help me, please?
#!/usr/bin/env python
# coding: utf-8
# In[1]:
import numpy as np
import bokeh
from bokeh.io import push_notebook, show, output_notebook
from bokeh import layouts
from bokeh.plotting import figure
output_notebook()
# In[2]:
def to_data_source(y):
y = np.array(y)
x = np.arange(y.size)
return bokeh.models.ColumnDataSource({
'x': x,
'y': y
})
# In[3]:
# this will plot an empty figure
vis = figure()
handle = show(vis, notebook_handle=True)
# In[4]:
# this will plot on the empty figure
line = vis.line()
line.data_source = to_data_source(np.random.randn(30))
push_notebook(handle=handle)
# In[5]:
# this will not update the figure
line.data_source.data['y'] += np.arange(30)
push_notebook(handle=handle)
# In[6]:
# this will not update the figure
line.update(data_source=to_data_source(line.data_source.data['y'] + np.arange(30)))
push_notebook(handle=handle)
# In[7]:
# this will plot the correct figure that should've been updated to the previous `show`
show(vis)
I tried removing the old glyph and adding a new one every time, and it actually works. However, I don't understand why this simple usage that I see everywhere doesn't work here.
Also gist of the notebook here: https://gist.github.com/uduse/f2b17bc67de8fd0ee32f34a87849c8b6
Try to create the handle of the figure after creating the line from vis.
import numpy as np
import bokeh
from bokeh.io import push_notebook, show, output_notebook
from bokeh import layouts
from bokeh.plotting import figure
output_notebook()
line = vis.line()
line.data_source = to_data_source(np.random.randn(30))
## Handle defined here after adding stuff to the figure
handle = show(vis, notebook_handle=True)
push_notebook(handle=handle)
# this will NOW UPDATE the figure
line.data_source.data['y'] += np.arange(30)
push_notebook(handle=handle)
Actually, I'm not sure why exactly it behaves like this, but It should work that way.
I hope this may help you.
This is actually a known bug. See https://github.com/bokeh/bokeh/issues/8244

How can I achieve a multiline title for a holoviews plot?

I would like more than one line as an option for a title of a holoviews plot object. Only strings are allowed, and it seems what works for a print statement does not work for a title.
import numpy as np
import holoviews as hv
hv.extension('bokeh')
from holoviews import opts
plot_title = 'Line 1 \nLine 2'
plot_title
# printing results in two lines, the \n is recognized
print(plot_title)
# the \n is ignored when the string is used for a title
points = [(0.1*i, np.sin(0.1*i)) for i in range(100)]
hv.Curve(points).opts(title=plot_title)
HoloViews uses bokeh as a plotting backend in your example and this feature, i.e multi-line title, isn't supported yet by bokeh. Note that your question is similar to this one: How to create a multi-line plot title in bokeh? and that there is now (27/04/2020) a related open issue on bokeh https://github.com/bokeh/bokeh/issues/7317.
I see two ways of getting multilines title for your plot.
The first one is adapted from the other SO question and makes use of bokeh directly to add titles as a layout:
import numpy as np
import holoviews as hv
import bokeh.io
from bokeh.models import Title
hv.extension('bokeh')
points = [(0.1*i, np.sin(0.1*i)) for i in range(100)]
hv_curve = hv.Curve(points)
bk_curve = hv.render(hv_curve)
bk_curve.add_layout(Title(text="Sub-Title", text_font_style="italic"), 'above')
bk_curve.add_layout(Title(text="Title", text_font_size="16pt", text_font_style="bold"), 'above')
bokeh.io.show(bk_curve)
The second one makes use of panel (a dependency of HoloViews now) to display the titles as two Markdown panes (it could also be one HTML pane) both centered in a Column layout above the curve:
import numpy as np
import holoviews as hv
hv.extension('bokeh')
import panel as pn
pn.extension()
points = [(0.1*i, np.sin(0.1*i)) for i in range(100)]
hv_curve = hv.Curve(points)
panel_layout pn.Column(
pn.pane.Markdown("**Title**", align="center"), # bold
pn.pane.Markdown("*Sub-title*", align="center"), # italic
hv_curve
)
panel_layout
If you run panel_layout.pprint() you'll be able to check the structure of that layout.
Column
[0] Markdown(str, align='center')
[1] Markdown(str, align='center')
[2] HoloViews(Curve)
Note: Run with Holoviews 1.13.2, bokeh 2.0.1 and panel 0.9.5.

Strange Labels on Bokeh Pie/Donut Chart

I'm following the response in this question: Adding labels in pie chart wedge in bokeh
I'm trying to add labels to my Bokeh chart so users can see data values. For some reason, the chart is rendering like this:
I'm not sure why this is happening. I tried commenting out the z variable to see if this made a difference.. it does not.
Here is my code:
import os
import pandas as pd
import pyodbc
from bokeh.plotting import figure, show
from bokeh.io import export_png
from bokeh.models import LabelSet, ColumnDataSource
from bokeh.palettes import Category20
import matplotlib as plt
from math import pi
from bokeh.transform import cumsum
lst = ['On_Time', 'All']
lst2 = [8, 85]
df = pd.DataFrame(list(zip(lst, lst2)),
columns =['Column', 'Value'])
df
df['angle'] = df['value']/df['value'].sum() * 2*pi
df['angle']
df['color'] = ['#084594', '#2171b5']
#z=110*(df['value']/df['value'].sum())
#df['value']=z
#df
p = figure(plot_height=350, title="", toolbar_location=None,
tools="", x_range=(-.5, .5))
p.annular_wedge(x=0, y=1, inner_radius=0.15, outer_radius=0.25, direction="anticlock",
start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
line_color="white", fill_color='color', legend='column', source=df)
df["value"] = df['value'].astype(str)
df["value"] = df["value"].str.pad(35, side = "left")
source = ColumnDataSource(df)
labels = LabelSet(x=0, y=1, text='value', level='glyph',
angle=cumsum('angle', include_zero=True), source=source, render_mode='canvas')
p.add_layout(labels)
p.axis.axis_label=None
p.axis.visible=False
p.grid.grid_line_color = None
show(p)
The labels are "underneath" because you have set
level='glyph'
in the call to LabelSet. There is generally not very many reasons to disturb the default render levels. If you remove this, the label will show up "on top" the way annotations such as LabelSet are intended to.
Also note the "str padding" that the other answer used to position the label alignment. That's slightly hacky but serviceable. Font differences between viewers' browser might make small differences. The alternative is to compute actual, exact x, y positions around the circle where you want the labels to go.

Bokeh skip tick labels for categorical data

I'm using Bokeh version 0.12.13.
I have a mixed numerical and categorical data. I only have one categorical data on the x-axis and the rest is numerical. I converted everything to categorical data to do the plotting (might not be the easiest way to achieve my goal). Now my x-axis tick labels are way denser than I need. I would like to space them out every 10th value so the labels are 10,20,...,90,rest
This is what I tried so far:
import pandas as pd
from bokeh.io import show
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.models.tickers import FixedTicker
# create mock data
n = [str(i) for i in np.arange(1,100)]
n.append('rest')
t = pd.DataFrame([n,list(np.random.randint(25,size=100))]).T
t.columns = ['Number','Value']
t.loc[t['Number']==100,'Number'] = 'Rest'
source = ColumnDataSource(t)
p = figure(plot_width=800, plot_height=400, title="",
x_range=t['Number'].tolist(),toolbar_location=None, tools="")
p.vbar(x='Number', top='Value', width=1, source=source,
line_color="white")
#p.xaxis.ticker = FixedTicker(ticks=[i for i in range(0,100,10)])
show(p)
Ideally, I would like the grid and the x-axis labels to appear every 10th value. Any help on how to get there would be greatly appreciated.
An easier way to do it is to keep the numerical data and use the xaxis.major_label_overrides. Here is the code:
import pandas as pd
from bokeh.io import show
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.models.tickers import FixedTicker
# create mock data
n = np.arange(1,101)
t = pd.DataFrame([n,list(np.random.randint(25,size=100))]).T
t.columns = ['Number','Value']
source = ColumnDataSource(t)
p = figure(plot_width=800, plot_height=400, title="",
toolbar_location=None, tools="")
p.vbar(x='Number', top='Value', width=1, source=source,
line_color="white")
p.xaxis.major_label_overrides = {100: 'Rest'}
show(p)
You can do this now (in Bokeh 2.2.3) using FuncTickFormatter:
# This prints out only every 10th tick label
p.axis.formatter = FuncTickFormatter(code="""
if (index % 10 == 0)
{
return tick;
}
else
{
return "";
}
""")
Sometimes you might want to do this instead of using numerical axis and major_label_overrides e.g. in a heatmap to get positioning of the content rects in the right place, or if you don't have numerical data at all but still want gaps in the axis labels.

Plotting in Bokeh using Custom Function?

does anyone know if/how one can use a "custom" function to plot in Bokeh using the Bokeh server? For example, I know you can use something like
plot = figure(toolbar_location=None)
plot.vbar(x='x', width=0.5, bottom=0, top='y', source=source)
But how can you plot using something like
def mplot(source):
p = pd.DataFrame()
p['aspects'] = source.data['x']
p['importance'] = source.data['y']
plot = Bar(p, values='importance', label='aspects', legend=False)
return plot
My current attempt is, here:
http://pastebin.com/7Zk9ampq
but it doesn't run. I'm not worried about getting the function "update_samples_or_dataset" working yet, just the initial plot to show. Any help would be much appreciated. Thanks!
Is this what you want? Note that I did not use the Bar function imported from bokeh.charts as this does not update upon updating the data source.
If you want to stick with using Bar from bokeh.charts you need to recreate the plot each time.
Note: to run this and have updating work - you need to execute bokeh serve --show plotfilename.py from the command line.
from bokeh.io import curdoc
from bokeh.layouts import layout
from bokeh.models.widgets import Button
from bokeh.plotting import ColumnDataSource, figure
import random
def bar_plot(fig, source):
fig.vbar(x='x', width=0.5, bottom=0,top='y',source=source, color="firebrick")
return fig
def update_data():
data = source.data
data['y'] = random.sample(range(0,10),len(data['y']))
source.data =data
button = Button(label="Press here to update data", button_type="success")
button.on_click(update_data)
data = {'x':[0,1,2,3],'y':[10,20,30,40]}
source = ColumnDataSource(data)
fig = figure(plot_width=650,
plot_height=500,
x_axis_label='x',
y_axis_label='y')
fig = bar_plot(fig, source)
layout = layout([[button,fig]])
curdoc().add_root(layout)
EDIT: See below a method that plots a bokeh plot but uses data from a dataframe as you wanted. It also will update the plot on each button press. Still you need to use the command bokeh serve --show plotfilename.py
from bokeh.io import curdoc
from bokeh.layouts import layout
from bokeh.models.widgets import Button
from bokeh.plotting import ColumnDataSource
from bokeh.charts import Bar
import random
import pandas as pd
def bar_plot(source):
df = pd.DataFrame(source.data)
fig = Bar(df, values='y', color="firebrick")
return fig
def update_data():
data = {'x':[0,1,2,3],'y':random.sample(range(0,10),4)}
source2 = ColumnDataSource(data)
newfig = bar_plot(source2)
layout.children[0].children[1] = newfig
button = Button(label="Press here to update data", button_type="success")
button.on_click(update_data)
data = {'x':[0,1,2,3],'y':[10,20,30,40]}
source = ColumnDataSource(data)
fig = bar_plot(source)
layout = layout([[button,fig]])
curdoc().add_root(layout)
I think you still have to attach your Bar instance to a Figure instance; a Figure is a set of plots, essentially, with niceties like the toolbar.

Categories