Bokeh: saved svg different from what is displayed in jupyter notebook - svg

I first render the figure in notebook, then I save it in svg form. The figure display in notebook is correct, but the saved svg is missing some markers.
#!/usr/bin/python3
import pandas as pd
import numpy as np
import math
from bokeh.plotting import figure, show, ColumnDataSource, save, output_file, reset_output
from bokeh.models import HoverTool, Legend
from bokeh.layouts import gridplot
import colorsys # needed for generating N equally extinguishable colors
from itertools import cycle
d = {'Sex': ['male', 'male','male','male', 'male','male','female','female','female','female','female','female'], 'age': [20, 20,20, 25,25,25,20, 20,20,25,25,25], 'working_hours': [20,30,40,20,30,40,20,30,40,20,30,40],'income': [1000, 2000,3000,1500, 2500,3500,1100, 2100,3100,1300, 2300,3300] }
values = pd.DataFrame(data=d)
x_var = 'working_hours'
x_var_dimension = 'H'
y_var = 'income'
y_var_dimension = 'Dollars'
hover = HoverTool(tooltips=[("data (x,y)", "(#x, #y)")])
TOOLS=[hover]
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension))
nr_expressions_row_col=9
figs_array_row_col = []
figs_row_row_col=[]
legend_its_row_col = []
legend_its_row_col_renderer = []
loop_count = 0;
markers = ['circle', 'square', 'triangle', 'asterisk', 'circle_x', 'square_x', 'inverted_triangle', 'x', 'circle_cross', 'square_cross', 'diamond', 'cross']
pool = cycle(markers)
for key, group in values.groupby(['Sex']):
for key_sub1, group_sub1 in group.groupby(['age']):
loop_count+=1
x_data = group_sub1[x_var].values;
y_data = group_sub1[y_var].values
(color_r,color_g,color_b) = colorsys.hsv_to_rgb(loop_count*1.0/nr_expressions_row_col, 1, 1)
plot_row_col_line = p.line(x_data, y_data,line_color=(int(255*color_r),int(255*color_g),int(255*color_b)))
plot_row_col_glyph = p.scatter(x_data, y_data, color=(int(255*color_r),int(255*color_g),int(255*color_b)), size=10, marker=next(pool))
legend_its_row_col.append(("%s %s"%(key,key_sub1), [plot_row_col_line, plot_row_col_glyph]))
legend_row_col = Legend(items = legend_its_row_col, location=(0,0))
legend_row_col.click_policy = 'hide'
legend_row_col.background_fill_alpha = 0
p.add_layout(legend_row_col, 'left')
figs_row_row_col.append(p)
figs_array_row_col.append(figs_row_row_col)
grid_row_col = gridplot(figs_array_row_col)
reset_output()
output_notebook()
show(grid_row_col)
p.output_backend = "svg"
export_svgs(grid_row_col, filename="%s/"%'.' + "_" +"stackoverflow.svg")
Here is what I see in notebook, which is what I expected:
And here is what I see when opening '_stackoverflow.svg'
The legend color for 'female 25' and 'male 20' is partially black. (the marker part) and 'female 20' is missing both marker and its legend.

The reason you are seeing different colors in the notebook and the exported image is that they use two different backends. You set the backend to SVG just before exporting it in the last line. Therefore, though the exported image uses SVG backend, the image on the notebook uses the default backend, which is canvas.
The backend can be set with two options: one you used and one where you add output_backend="svg" argument when you call figure. That is, replacing
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension))
with
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension), output_backend="svg").
Now you will see the same issue you see in the exported image on you notebook image. It seems like this is a bug in the SVG backend, and most likely they are working on that. Unfortunately there is no expected date for the bug fix.

Related

Changing the values of a dict in lowercase ( values are code colors ) to be accepted as a color parametrer in plotly.graph.object

So, I'm trying to get the colors from the dictionary 'Disaster_type' to draw the markers in geoscatters depending of the type of disaster.
Basically, I want to reprensent in the graphic the natural diasasters with it's color code. eg; it's is a volcanic activity paint it 'orange'. I want to change the size of the marker as well depending of the magnitude of the disaster, but that's for another day.
here's the link of the dataset: https://www.kaggle.com/datasets/brsdincer/all-natural-disasters-19002021-eosdis
import plotly.graph_objects as go
import pandas as pd
import plotly as plt
df = pd.read_csv('1900_2021_DISASTERS - main.csv')
df.head()
df.tail()
disaster_set = {disaster for disaster in df['Disaster Type']}
disaster_type = {'Storm':'aliceblue',
'Volcanic activity':'orange',
'Flood':'royalblue',
'Mass movement (dry)':'darkorange',
'Landslide':'#C76114',
'Extreme temperature':'#FF0000',
'Animal accident':'gray55',
'Glacial lake outburst':'#7D9EC0',
'Earthquake':'#CD8C95',
'Insect infestation':'#EEE8AA',
'Wildfire':' #FFFF00',
'Fog':'#00E5EE',
'Drought':'#FFEFD5',
'Epidemic':'#00CD66 ',
'Impact':'#FF6347'}
# disaster_type_lower = {(k, v.lower()) for k, v in disaster_type.items()}
# print(disaster_type_lower)
# for values in disaster_type.values():
# disaster_type[values] = disaster_type.lowercase()
fig = go.Figure(data=go.Scattergeo(
lon = df['Longitude'],
lat = df['Latitude'],
text = df['Country'],
mode = 'markers',
marker_color = disaster_type_.values()
)
)
fig.show()
I cant figure how, I've left in comments after the dict how I tried to do that.
It changes them to lowercase, but know I dont know hot to get them...My brain is completly melted
it's a simple case of pandas map
found data that appears same as yours on kaggle so have used that
one type is unmapped Extreme temperature so used a fillna("red") to remove any errors
gray55 gave me an error so replaced it with RGB equivalent
import kaggle.cli
import sys
import pandas as pd
from zipfile import ZipFile
import urllib
import plotly.graph_objects as go
# fmt: off
# download data set
url = "https://www.kaggle.com/brsdincer/all-natural-disasters-19002021-eosdis"
sys.argv = [sys.argv[0]] + f"datasets download {urllib.parse.urlparse(url).path[1:]}".split(" ")
kaggle.cli.main()
zfile = ZipFile(f'{urllib.parse.urlparse(url).path.split("/")[-1]}.zip')
dfs = {f.filename: pd.read_csv(zfile.open(f)) for f in zfile.infolist()}
# fmt: on
df = dfs["DISASTERS/1970-2021_DISASTERS.xlsx - emdat data.csv"]
disaster_type = {
"Storm": "aliceblue",
"Volcanic activity": "orange",
"Flood": "royalblue",
"Mass movement (dry)": "darkorange",
"Landslide": "#C76114",
"Extreme temperature": "#FF0000",
"Animal accident": "#8c8c8c", # gray55
"Glacial lake outburst": "#7D9EC0",
"Earthquake": "#CD8C95",
"Insect infestation": "#EEE8AA",
"Wildfire": " #FFFF00",
"Fog": "#00E5EE",
"Drought": "#FFEFD5",
"Epidemic": "#00CD66 ",
"Impact": "#FF6347",
}
fig = go.Figure(
data=go.Scattergeo(
lon=df["Longitude"],
lat=df["Latitude"],
text=df["Country"],
mode="markers",
marker_color=df["Disaster Type"].map(disaster_type).fillna("red"),
)
)
fig.show()

export svgs in bokeh for vbar_stack with hatch patterns

I wanted to export svg and it is executing but it gives no output after waiting a long time. Can you please help me out?
I wanted to have hatched vbar stack as output as svg. I have found it takes that much time for exporting svg.
Links: https://github.com/bokeh/bokeh/issues/8192
Now I'm a bit confused whether it won't work for vbar plot or not?
from bokeh.plotting import figure, output_file, show
from bokeh.models import Title
from bokeh.core.properties import value
from bokeh.layouts import row
from bokeh.models import Legend
from bokeh.io import export_svgs
output_file('stacked.html')
capacity = ['CO2_Cap', 'Min_RES_Quota', 'CO2_Tax', 'FIT']
types = ['Lignite', 'Coal', 'CCGT', 'OCGT','Wind Onshore', 'Wind Offshore', 'Solar']
colors = ["#9a7b5c", "#414141", "#c7dada", "#c7dada", "#5a67ff",'#00b8f2', '#fff340']
maxload = [90.03467675, 90.03467675, 90.03467675, 90.03467675]
Total_demand = [540.7686148, 540.7686148, 540.7686148, 540.7686148]
data = {'capacity' : capacity,
'Lignite' : [0,22.28330605,0,25.76575924],
'Coal' : [0, 0, 0,0],
'CCGT' : [52.12309276, 0.209356135, 52.12598273, 6.416971663],
'OCGT' : [10.1753325,20.35167058,10.1729694,22.18142626],
'Wind Onshore': [90.66714923, 77.28982927, 90.6875821, 86.3713515],
'Wind Offshore': [24.26075426, 53.92876756, 24.25201174, 60.265434],
'Solar' : [90.1174063, 159.3567357, 90.0774928, 178.0811123]
}
s1 = figure(x_range=capacity, plot_height=450,plot_width=655)
s1.vbar_stack(types, x='capacity', width=0.3, color=colors, source=data,
legend=[value(x) for x in types], hatch_pattern=['dot', 'spiral', 'vertical_wave', 'right_diagonal_line', 'vertical_dash', 'left_diagonal_line', 'ring'])
s1.line(x = capacity, y = maxload, color="red", line_width=3, legend_label = "max_load")
s1.legend.location = "bottom_right"
s1.legend.click_policy="hide"
#s1.add_layout(Title(text="Total CO2 Emissions (in 10^6t) ", align="center"), "left")
show(s1)
from bokeh.io import export_svgs
s1.output_backend = "svg"
export_svgs(s1, filename="plot11111.svg")
As of Bokeh 2.1.1 hatch patterns are not supported on the SVG backend. With those removed, your code above functions as expected.

How to use Bokeh hover tool in for loop with checkbox widget

Python 3.6
Bokeh 12.15
I have tried to implement the bokeh example line_on_off.py, but in a for loop with a hover tool and data of varying length. What happens though is that when a line is turned off it turns off the tool tip of any line created after it. For example if I turn off line 1, line 2,3,4 tool tips are disabled, or if I turn off line 3 line 4's tool tip is disabled.
Can I use a hover tool and checkbox widget in a for loop like this? I have seen this multiline example, but my data is of varying length and I do not want to resample because I would like to see if there is bad or missing data.
Code
from bokeh.plotting import figure
from bokeh.models import CheckboxGroup, CustomJS
from bokeh.models import ColumnDataSource
import pandas as pd
from bokeh.models import HoverTool
def create_plot(df_list):
p = figure(x_axis_type = 'datetime')
glyph_dict = {}
labels = []
active = []
items = []
names = 'abcdefghijklmnopqrstuvwxyz'
callback_string = '{}.visible = {} in checkbox.active;'
code_string = ''
i = 0
sources = []
for df in df_list:
legend = df.columns[0]
series = df.iloc[:,0]
labels.append(legend)
x = series.index
y = series.values
source =ColumnDataSource(data = {'x':x,'y':y, 'date': [str(x) for x in x]})
sources.append(source)
line = p.line('x', 'y', source = sources[i])
items.append((legend, [line]))
name = names[i]
line.name = name
code_string += callback_string.format(name, str(i))
glyph_dict.update({name:line})
active.append(i)
i+=1
hover = HoverTool(tooltips=[('date', '#date'),('y', '#y')])
p.add_tools(hover)
checkbox = CheckboxGroup(labels=labels, active=active, width=200)
glyph_dict.update({'checkbox':checkbox})
checkbox.callback = CustomJS.from_coffeescript(args=glyph_dict, code=code_string)
return checkbox, p
Minimal example
import numpy as np
from datetime import datetime, timedelta
from bokeh.layouts import row
from bokeh.plotting import show
df_list = []
start = datetime(2017, 4,1)
end = datetime(2017,5,1)
for i in range(1,5):
date = pd.date_range(start, end, freq = '1w')
shape = len(date)
df = pd.DataFrame(index = date, data = np.random.randn(shape,1))
name = 'df'+ str(i)
df.columns = [name]
end = end + timedelta(weeks = 1)
df_list.append(df)
c,p = create_plot(df_list)
r=row([c,p])
show(r)
In a situation like this, you should probably create a new, separate hover tool for each line, by restricting the renderers property of each hover tool. So, in relation to your code, move the hover tool creation inside the loop, and have it set renderers each time:
line = p.line('x', 'y', source = sources[i])
hover = HoverTool(tooltips=[('date', '#date'),('y', '#y')]
renderers=[line])
p.add_tools(hover)

Bokeh return empty map

I am trying to create a map with bokeh to show the population in US_cities, but as soon as I run the code, it returns empty map, frame is there but map is not. I am trying to do something like this but for all US Cities.
Here is my code using "us_cities.json" file in bokeh data:
import pandas as pd
from bokeh.io import show
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper
)
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure
palette.reverse()
new_data = pd.read_json("/home/alvin/.bokeh/data/us_cities.json")
#Creating random data that I want to show on map
new_data['pop'] = ((new_data['lat'] * 100) - new_data["lon"])/ 800
#Converting pd series to array
xs = new_data['lat'].tolist()
ys = new_data['lon'].tolist()
pops = new_data['pop'].tolist()
#creating ColumnDataSource
source = ColumnDataSource(data=dict(
x=xs,
y=ys,
pop = pops,
))
TOOLS = "pan,wheel_zoom,reset,hover,save"
p = figure(
title="Just a US Map", tools=TOOLS,
x_axis_location=None, y_axis_location=None
)
color_mapper = LogColorMapper(palette=palette)
p.grid.grid_line_color = None
p.patches('x', 'y', source=source,
fill_color={'field': 'pop', 'transform': color_mapper},
fill_alpha=0.7, line_color="white", line_width=0.5)
hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
("population)", "#pop%"),
("(Long, Lat)", "($x, $y)"),
]
show(p)
What could be the problem here?
I am running python3 and bokeh 0.12.6
If I check my data it looks like this:
enter image description here
The US cities data does not contain glyphs, like the counties data. You can show the counties data, and overlay the cities data as a scatter plot on top:
import pandas as pd
from bokeh.io import show
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper
)
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure
from bokeh.sampledata.us_counties import data as counties
palette.reverse()
counties = {
code: county for code, county in counties.items() if county["state"] == "tx"
}
county_xs = [county["lons"] for county in counties.values()]
county_ys = [county["lats"] for county in counties.values()]
csource = ColumnDataSource(data=dict(
x=county_xs,
y=county_ys,
))
new_data = pd.read_json("/home/tc427/.bokeh/data/us_cities.json")[::100]
#Creating random data that I want to show on map
new_data['pop'] = ((new_data['lat'] * 100) - new_data["lon"])/ 800
#Converting pd series to array
xs = new_data['lon'].tolist()
ys = new_data['lat'].tolist()
pops = new_data['pop'].tolist()
#creating ColumnDataSource
source = ColumnDataSource(data=dict(
x=xs,
y=ys,
pop = pops,
))
TOOLS = "pan,wheel_zoom,reset,hover,save"
p = figure(
title="Just a US Map", tools=TOOLS,
)
color_mapper = LogColorMapper(palette=palette, low=0, high=10)
p.patches('x', 'y', source=csource)
#p.patches('x', 'y', source=csource,
# fill_color={'field': 'pop', 'transform': color_mapper},
# fill_alpha=0.7, color="red", line_width=0.5)
p.scatter('x', 'y', source=source, color={'field': 'pop', 'transform': color_mapper})
hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
("population)", "#pop%"),
("(Long, Lat)", "($x, $y)"),
]
show(p)
I assume you are using a jupyter notebook...
As i have faced the same problem recently, I would suggest you try following steps;
open your browser's JavaScript console and check for errors.
read the discussion in link It seems to be a cronical problem.
It might be caused by many reasons like internet connetction issues, API problems etc. You can find and solve the problem by following above steps.
Once you detect and solve your problem, restarting the kernel will not help since the bokehjs is still loaded in the page. You will need to reload the page.

In Bokeh, how do I add tooltips to a Timeseries chart (hover tool)?

Is it possible to add Tooltips to a Timeseries chart?
In the simplified code example below, I want to see a single column name ('a','b' or 'c') when the mouse hovers over the relevant line.
Instead, a "???" is displayed and ALL three lines get a tool tip (rather than just the one im hovering over)
Per the documentation (
http://docs.bokeh.org/en/latest/docs/user_guide/tools.html#hovertool), field names starting with “#” are interpreted as columns on the data source.
How can I display the 'columns' from a pandas DataFrame in the tooltip?
Or, if the high level TimeSeries interface doesn't support this, any clues for using the lower level interfaces to do the same thing? (line? multi_line?) or convert the DataFrame into a different format (ColumnDataSource?)
For bonus credit, how should the "$x" be formatted to display the date as a date?
thanks in advance
import pandas as pd
import numpy as np
from bokeh.charts import TimeSeries
from bokeh.models import HoverTool
from bokeh.plotting import show
toy_df = pd.DataFrame(data=np.random.rand(5,3), columns = ('a', 'b' ,'c'), index = pd.DatetimeIndex(start='01-01-2015',periods=5, freq='d'))
p = TimeSeries(toy_df, tools='hover')
hover = p.select(dict(type=HoverTool))
hover.tooltips = [
("Series", "#columns"),
("Date", "$x"),
("Value", "$y"),
]
show(p)
Below is what I came up with.
Its not pretty but it works.
Im still new to Bokeh (& Python for that matter) so if anyone wants to suggest a better way to do this, please feel free.
import pandas as pd
import numpy as np
from bokeh.charts import TimeSeries
from bokeh.models import HoverTool
from bokeh.plotting import show
toy_df = pd.DataFrame(data=np.random.rand(5,3), columns = ('a', 'b' ,'c'), index = pd.DatetimeIndex(start='01-01-2015',periods=5, freq='d'))
_tools_to_show = 'box_zoom,pan,save,hover,resize,reset,tap,wheel_zoom'
p = figure(width=1200, height=900, x_axis_type="datetime", tools=_tools_to_show)
# FIRST plot ALL lines (This is a hack to get it working, why can't i pass in a dataframe to multi_line?)
# It's not pretty but it works.
# what I want to do!: p.multi_line(df)
ts_list_of_list = []
for i in range(0,len(toy_df.columns)):
ts_list_of_list.append(toy_df.index.T)
vals_list_of_list = toy_df.values.T.tolist()
# Define colors because otherwise multi_line will use blue for all lines...
cols_to_use = ['Black', 'Red', 'Lime']
p.multi_line(ts_list_of_list, vals_list_of_list, line_color=cols_to_use)
# THEN put scatter one at a time on top of each one to get tool tips (HACK! lines with tooltips not yet supported by Bokeh?)
for (name, series) in toy_df.iteritems():
# need to repmat the name to be same dimension as index
name_for_display = np.tile(name, [len(toy_df.index),1])
source = ColumnDataSource({'x': toy_df.index, 'y': series.values, 'series_name': name_for_display, 'Date': toy_df.index.format()})
# trouble formating x as datestring, so pre-formating and using an extra column. It's not pretty but it works.
p.scatter('x', 'y', source = source, fill_alpha=0, line_alpha=0.3, line_color="grey")
hover = p.select(dict(type=HoverTool))
hover.tooltips = [("Series", "#series_name"), ("Date", "#Date"), ("Value", "#y{0.00%}"),]
hover.mode = 'mouse'
show(p)
I’m not familiar with Pandas,I just use python list to show the very example of how to add tooltips to muti_lines, show series names ,and properly display date/time。Below is the result.
Thanks to #bs123's answer and #tterry's answer in Bokeh Plotting: Enable tooltips for only some glyphs
my result
# -*- coding: utf-8 -*-
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models import HoverTool
from datetime import datetime
dateX_str = ['2016-11-14','2016-11-15','2016-11-16']
#conver the string of datetime to python datetime object
dateX = [datetime.strptime(i, "%Y-%m-%d") for i in dateX_str]
v1= [10,13,5]
v2 = [8,4,14]
v3= [14,9,6]
v = [v1,v2,v3]
names = ['v1','v2','v3']
colors = ['red','blue','yellow']
output_file('example.html',title = 'example of add tooltips to multi_timeseries')
tools_to_show = 'hover,box_zoom,pan,save,resize,reset,wheel_zoom'
p = figure(x_axis_type="datetime", tools=tools_to_show)
#to show the tooltip for multi_lines,you need use the ColumnDataSource which define the data source of glyph
#the key is to use the same column name for each data source of the glyph
#so you don't have to add tooltip for each glyph,the tooltip is added to the figure
#plot each timeseries line glyph
for i in xrange(3):
# bokeh can't show datetime object in tooltip properly,so we use string instead
source = ColumnDataSource(data={
'dateX': dateX, # python datetime object as X axis
'v': v[i],
'dateX_str': dateX_str, #string of datetime for display in tooltip
'name': [names[i] for n in xrange(3)]
})
p.line('dateX', 'v',source=source,legend=names[i],color = colors[i])
circle = p.circle('dateX', 'v',source=source, fill_color="white", size=8, legend=names[i],color = colors[i])
#to avoid some strange behavior(as shown in the picture at the end), only add the circle glyph to the renders of hover tool
#so tooltip only takes effect on circle glyph
p.tools[0].renderers.append(circle)
# show the tooltip
hover = p.select(dict(type=HoverTool))
hover.tooltips = [("value", "#v"), ("name", "#name"), ("date", "#dateX_str")]
hover.mode = 'mouse'
show(p)
tooltips with some strange behavior,two tips displayed at the same time
Here is my solution. I inspected the glyph render data source to see what are the names on it. Then I use those names on the hoover tooltips. You can see the resulting plot here.
import numpy as np
from bokeh.charts import TimeSeries
from bokeh.models import HoverTool
from bokeh.plotting import show
toy_df = pd.DataFrame(data=np.random.rand(5,3), columns = ('a', 'b' ,'c'), index = pd.DatetimeIndex(start='01-01-2015',periods=5, freq='d'))
#Bockeh display dates as numbers so convert to string tu show correctly
toy_df.index = toy_df.index.astype(str)
p = TimeSeries(toy_df, tools='hover')
#Next 3 lines are to inspect how are names on gliph to call them with #name on hover
#glyph_renderers = p.select(dict(type=GlyphRenderer))
#bar_source = glyph_renderers[0].data_source
#print(bar_source.data) #Here we can inspect names to call on hover
hover = p.select(dict(type=HoverTool))
hover.tooltips = [
("Series", "#series"),
("Date", "#x_values"),
("Value", "#y_values"),
]
show(p)
The original poster's code doesn't work with the latest pandas (DatetimeIndex constructor has changed), but Hovertool now supports a formatters attribute that lets you specify a format as a strftime string. Something like
fig.add_tool(HoverTool(
tooltip=[
('time', '#index{%Y-%m-%d}')
],
formatters={
'#index': 'datetime'
}
))

Resources