How to add one legend that controlls multiple bokeh figures? - python

how can I create one legend to control multiple bokeh figures? Or how can I customize an exported html file created with bokeh to add legend with similar functionality?
Here is the scenario. I create an html file with 4 different figures. Each figure has a legend with labels/names for individual lines that are shown in the particular figure. Each of the four legend is clickable to toggle the lines separately in each figure.
Even though each of the four figures has one legend, the lines are related, so they each line describes one thing.
I now want to create a legend for all figures combined in one, to toggle each line in all four figures.
Maybe there is a way to add this kind of functionality to the exported html file in some way?
I thought someone with more experience has an idea how to achieve that.
Thanks in advance!
Kind regards

Legends are not (yet?) 'standalone' bokeh models, they need to be attached to a figure. For now to have an external legend for multiple figures, and place it wherever in a layout, some workaround is needed.
I typically do it like below, with an 'invisible' figure that holds the shared legend. You then have to define the legend items manually and assign to each their label and list of renderers.
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.models import LegendItem, Legend
from numpy.random import random, choice
from bokeh.layouts import gridplot
from webcolors import html4_names_to_hex
del html4_names_to_hex['white']
palette = list(html4_names_to_hex.keys())
fig_list = [figure(plot_width=300,plot_height=300) for i in range(4)]
renderer_list = []
color_list = []
for fig in fig_list:
for i in range(5):
color = choice(palette)
renderer = fig.line(range(10),random(10),line_width=2,color=color)
renderer_list += [renderer]
color_list += [color]
# Lines with the same color will share a same legend item
legend_items = [LegendItem(label=color,renderers=[renderer for renderer in renderer_list if renderer.glyph.line_color==color]) for color in set(color_list)]
## Use a dummy figure for the LEGEND
dum_fig = figure(plot_width=300,plot_height=600,outline_line_alpha=0,toolbar_location=None)
# set the components of the figure invisible
for fig_component in [dum_fig.grid[0],dum_fig.ygrid[0],dum_fig.xaxis[0],dum_fig.yaxis[0]]:
fig_component.visible = False
# The glyphs referred by the legend need to be present in the figure that holds the legend, so we must add them to the figure renderers
dum_fig.renderers += renderer_list
# set the figure range outside of the range of all glyphs
dum_fig.x_range.end = 1005
dum_fig.x_range.start = 1000
# add the legend
dum_fig.add_layout( Legend(click_policy='hide',location='top_left',border_line_alpha=0,items=legend_items) )
figrid = gridplot(fig_list,ncols=2,toolbar_location='left')
final = gridplot([[figrid,dum_fig]],toolbar_location=None)
show(final)

Related

Is there a way to plot lines over a datashader plot in Bokeh (Python)?

I am working with relatively large datasets (approximately 10x20.000.000 data point), for which Datashader is a useful visualisation tool. To give more information in these visualisations, I would like to add lines showing averages/standarddeviations on top of this datashade figure. Does anyone know how this would be possible?
My current code:
from bokeh.plotting import figure
from bokeh.io import show
x = 'xcol'
y= 'ycol'
data = dataframe
fig = figure(x_axis_label=x, y_axis_label=y)
points = hv.Points(data[[x, y]], label=('Title'))
hd.datashade(points, cmap='crest')
What I would like to do is for example add the following line to the figure generated with the code above:
fig.line([1,10,20], [0, 1000,2000], line_width=4)
Thanks in advance.

Bokeh, multi_line: how to select several lines with the box select?

I have a Bokeh plot created with the multi_line glyph. I can select a single line with the tap tool or multiple lines with the TapTool+Shift. Is there a way to select several lines at ones with the BoxSelectTool (like on the pic)? The density of the points along each line is quite high, so it's okay if selection works only when 1+ points of a line are inside the box selection area.
I'm looking for a stand-alone solution without a Python server. Writing of some CustomJS code is fine.
from bokeh.models import ColumnDataSource
from bokeh.layouts import column
import numpy as np
output_file('tst.html', mode="inline")
n = 8
t = np.linspace(0., 10., 80)
data = ColumnDataSource(dict(xx=[t for cnt in range(n)],
yy=[(10 + cnt/2*(-1)**cnt)*np.sin(t + cnt/3) for cnt in range(n)],
zz=[(10 - cnt/2*(-1)**cnt)*np.cos(t) for cnt in range(n)]))
f1 = figure(plot_width=800, plot_height=300, tools='tap,box_select,reset')
f2 = figure(plot_width=800, plot_height=300)
f1.multi_line(xs='xx', ys='yy', source=data)
f2.multi_line(xs='xx', ys='zz', source=data)
save(column(f1, f2))
Plot sample with the box selection
Thanks.
This functionality is not built-in, at least not in Bokeh 2.0.2. The MultiLine glyph defines only _hit_point (used by the tap, hover, and edit tools) and _hit_span (used by the hover tool) methods. For the box select tool to work, there needs to be a _hit_rect method defined. It's not that hard to do it yourself, but you will have to create a custom Bokeh model and write some TypeScript for it.
With that being said, please feel free to create a feature request on Bokeh's GitHub!

How can i erase/ hide label names in Bio.phylo.draw?

ive got problem with label names, at pic below i will show you what im mean.
e.g cacatua_moluccensis
Im using Phylo.draw() function to draw a phylogenetic tree and everything is fine, but one thing is unaesthetic for me, label lines didnt show out of axes at fig field but i dont know why label names did (look at cacatua_moluccensis at pic). Here is code i used:
from Bio import Phylo
import matplotlib
import matplotlib.pyplot as plt
fig = plt.figure(frameon=False)
ax=plt.gca()
tree = Phylo.read("simple1.dnd", "newick")
tree.rooted = True
Phylo.draw(tree, show_confidence=True, axes=ax)
I was able to change some things in axes, so i think change displaying of label names its possible.

Big data multiple plots, multiple web pages Plotly

I am trying to work with big data plotting around 20 plots on plotly and embed them on web page. I can very well plot individual plots with username and one api_key that found in the profile.
The Problem comes is when: I have to rerun all the 20 plots with python program after interval of every 15 mins and every time I am getting new windows. Instead I need the same plot to update/redraw.
How do I get that? I tried reading the plot.ly document and also few tutorials outside. Cannot find how to get it done. Can anyone please help me with steps or refer me to some document where I can know how to work with multiple plots that will update at same time.
I am following the steps given in plotly tutorial not sure if I should use stream_ids ? Or can I create a new api_key for every plot ?Confused !!! Thanks in Advance for the suggestions.
Edit: I could make access tokens and Initiate the credentials from the following tutorial.
The code below works perfect: But now I am looking for required fixing in the below code by trying to minimize the code with annotations and where to include the streaming API Access Tokens while having sizable scatter plots ?
import plotly.plotly as py
import plotly.tools as tls
from plotly.graph_objs import *
import csv
import pandas as pd
import numpy as np
df = pd.read_csv('finally.csv')
df1=df[['NAME','COUNT']]
sizemode='area'
sizeref=df1['COUNT'].max()/1000
def Trace(X,PLACE,sizes):
return Scatter(
x=X['NAME'],
y=X['COUNT'].sum(),
name=PLACE,
mode='marker',
marker=Marker(
line=Line(width=0.9),
size=sizes,
sizeref=sizeref,
opacity=0.9,
)
)
data=Data()
for PLACE, X in df1.groupby('NAME'):
sizes=X['COUNT'].sum()/1000
data.append(Trace(X,PLACE,sizes))
title = "Fig 1.1 : All NAMES"
x_title = "Names".format()
y_title = "Count"
# Define a dictionary of axis style options
axis_style = dict(
zeroline=False, # remove thick zero line
gridcolor='#FFFFFF', # white grid lines
ticks='outside', # draw ticks outside axes
ticklen=8, # tick length
tickwidth=1.5 # and width
)
# Make layout object
layout = Layout(
title=title, # set plot title
plot_bgcolor='#EFECEA', # set plot color to grey
xaxis=XAxis(
axis_style, # add axis style dictionary
title=x_title, # x-axis title
),
yaxis=YAxis(
axis_style, # add axis style dictionary
title=y_title, # y-axis title
),
showlegend=False,
)
fig = Figure(data=data,layout=layout)
plot_url=py.plot(fig,filename=' plotting')
In plot/ iplot there is 'fileopt' option which should help you. For example, if you would want to add new traces to your existing data you can run
plot_url = py.plot(fig, filename='my-file', fileopt='append')
You're right it is not well documented yet. But if you run help(py.plot) you would get a small document on it as follow:
plot(figure_or_data, validate=True, **plot_options)
Create a unique url for this plot in Plotly and optionally open url.
plot_options keyword agruments:
filename (string) -- the name that will be associated with this figure
fileopt ('new' | 'overwrite' | 'extend' | 'append') -- 'new' creates a
'new': create a new, unique url for this plot
'overwrite': overwrite the file associated with `filename` with this
'extend': add additional numbers (data) to existing traces
'append': add additional traces to existing data lists
world_readable (default=True) -- make this figure private/public
auto_open (default=True) -- Toggle browser options
True: open this plot in a new browser tab
False: do not open plot in the browser, but do return the unique url

How can I automatically combine matplotlib graphs with Adobe Illustrator vector illustrations?

I'm currently writing a scientific paper and am generating most of the figures using matplotlib. I have a pipeline set up using a makefile that regenerates all of my plots whenever I update the data. My problem is that the figures are made up multiple panels, and some of those panels should contain vector illustrations which I've created using Adobe Illustrator. How can I automatically combine the graphs with the illustrations when I update my raw data? I could save the vector illustrations in a raster format and then display them using matplotlib's imshow function, but I want the output to be a vector to ensure the best possible print quality.
After some more extensive googling I found this old message on the matplotlib mailing list:
The thread suggests using the python library PyX, which works well for me.
I can save both the illustrator diagrams and the matplotlib plots as .eps files, and then combine them together like this:
import pyx
c = pyx.canvas.canvas()
c.insert(pyx.epsfile.epsfile(0, 0, "1.eps", align="tl"))
c.insert(pyx.epsfile.epsfile(0,0,"2.eps", align="tr"))
c.writeEPSfile("combined.eps")
I found this example in the svgutils documentation which outlines how to combine matplotlib-generated SVGs into a single plot.
Here's the example from that page:
import svgutils.transform as sg
import sys
#create new SVG figure
fig = sg.SVGFigure("16cm", "6.5cm")
# load matpotlib-generated figures
fig1 = sg.fromfile('sigmoid_fit.svg')
fig2 = sg.fromfile('anscombe.svg')
# get the plot objects
plot1 = fig1.getroot()
plot2 = fig2.getroot()
plot2.moveto(280, 0, scale=0.5)
# add text labels
txt1 = sg.TextElement(25,20, "A", size=12, weight="bold")
txt2 = sg.TextElement(305,20, "B", size=12, weight="bold")
# append plots and labels to figure
fig.append([plot1, plot2])
fig.append([txt1, txt2])
# save generated SVG files
fig.save("fig_final.svg")

Categories

Resources