Related
As per the Plotly website, in a simple line chart one can change the legend entry from the column name to a manually specified string of text. For example, this code results in the following chart:
import pandas as pd
import plotly.express as px
df = pd.DataFrame(dict(
x = [1, 2, 3, 4],
y = [2, 3, 4, 3]
))
fig = px.line(
df,
x="x",
y="y",
width=800, height=600,
labels={
"y": "Series"
},
)
fig.show()
label changed:
However, when one plots multiple columns to the line chart, this label specification no longer works. There is no error message, but the legend entries are simply not changed. See this example and output:
import pandas as pd
import plotly.express as px
df = pd.DataFrame(dict(
x = [1, 2, 3, 4],
y1 = [2, 3, 4, 3],
y2 = [2, 4, 6, 8]
))
fig = px.line(
df,
x="x",
y=["y1", "y2"],
width=800, height=600,
labels={
"y1": "Series 1",
"y2": "Series 2"
},
)
fig.show()
legend entries not changed:
Is this a bug, or am I missing something? Any idea how this can be fixed?
In case anybody read my previous post, I did some more digging and found the solution to this issue. At the heart, the labels one sees over on the right in the legend are attributes known as "names" and not "labels". Searching for how to revise those names, I came across another post about this issue with a solution Legend Label Update. Using that information, here is a revised version of your program.
import pandas as pd
import plotly.express as px
df = pd.DataFrame(dict(
x = [1, 2, 3, 4],
y1 = [2, 3, 4, 3],
y2 = [2, 4, 6, 8]
))
fig = px.line(df, x="x", y=["y1", "y2"], width=800, height=600)
fig.update_layout(legend_title_text='Variable', xaxis_title="X", yaxis_title="Series")
newnames = {'y1':'Series 1', 'y2': 'Series 2'} # From the other post
fig.for_each_trace(lambda t: t.update(name = newnames[t.name]))
fig.show()
Following is a sample graph.
Try that out to see if that addresses your situation.
Regards.
I have a stacked vbar chart in Bokeh, a simplified version of which can be reproduced with:
from bokeh.plotting import figure
from bokeh.io import show
months = ['JAN', 'FEB', 'MAR']
categories = ["cat1", "cat2", "cat3"]
data = {"month" : months,
"cat1" : [1, 4, 12],
"cat2" : [2, 5, 3],
"cat3" : [5, 6, 1]}
colors = ["#c9d9d3", "#718dbf", "#e84d60"]
p = figure(x_range=months, plot_height=250, title="Categories by month",
toolbar_location=None)
p.vbar_stack(categories, x='month', width=0.9, color=colors, source=data)
show(p)
I want to add a legend to the chart, but my real chart has a lot of categories in the stacks and therefore the legend would be very large, so I want it to be outside the plot area to the right.
There's a SO answer here which explains how to add a legend outside of the plot area, but in the example given each glyph rendered is assigned to a variable which is then labelled and added to a Legend object. I understand how to do that, but I believe the vbar_stack method creates mutliple glyphs in a single call, so I don't know how to label these and add them to a separate Legend object to place outside the chart area?
Alternatively, is there a simpler way to use the legend argument when calling vbar_stack and then locate the legend outside the chart area?
Any help much appreciated.
For anyone interested, have now fixed this using simple indexing of the vbar_stack glyphs. Solution below:
from bokeh.plotting import figure
from bokeh.io import show
from bokeh.models import Legend
months = ['JAN', 'FEB', 'MAR']
categories = ["cat1", "cat2", "cat3"]
data = {"month" : months,
"cat1" : [1, 4, 12],
"cat2" : [2, 5, 3],
"cat3" : [5, 6, 1]}
colors = ["#c9d9d3", "#718dbf", "#e84d60"]
p = figure(x_range=months, plot_height=250, title="Categories by month",
toolbar_location=None)
v = p.vbar_stack(categories, x='month', width=0.9, color=colors, source=data)
legend = Legend(items=[
("cat1", [v[0]]),
("cat2", [v[1]]),
("cat3", [v[2]]),
], location=(0, -30))
p.add_layout(legend, 'right')
show(p)
Thanks Toby Petty for your answer.
I have slightly improved your code so that it automatically graps the categories from the source data and assigns colors. I thought this might be handy as the categories are often not explicitly stored in a variable and have to be taken from the data.
from bokeh.plotting import figure
from bokeh.io import show
from bokeh.models import Legend
from bokeh.palettes import brewer
months = ['JAN', 'FEB', 'MAR']
data = {"month" : months,
"cat1" : [1, 4, 12],
"cat2" : [2, 5, 3],
"cat3" : [5, 6, 1],
"cat4" : [8, 2, 1],
"cat5" : [1, 1, 3]}
categories = list(data.keys())
categories.remove('month')
colors = brewer['YlGnBu'][len(categories)]
p = figure(x_range=months, plot_height=250, title="Categories by month",
toolbar_location=None)
v = p.vbar_stack(categories, x='month', width=0.9, color=colors, source=data)
legend = Legend(items=[(x, [v[i]]) for i, x in enumerate(categories)], location=(0, -30))
p.add_layout(legend, 'right')
show(p)
I'm using Holoviews and Bokeh and have an issue.
Using Bokeh I can specify the sizing_mode="scale_width" property, so my charts will be responsive.
And it works fine now.
But I couldn't find anything like this for Hovowiews.
boxwhisker = hv.BoxWhisker(df, ['cyl', 'origin'], 'mpg', label='')
boxwhisker.options(show_legend=False, height=200, sizing_mode='scale_width')
renderer = hv.renderer('bokeh')
boxChart = renderer.get_plot(boxwhisker).state
boxChart.name = 'boxChart'
curdoc().add_root(boxChart)
sizing_mode='scale_width' is second line is not working, so I have my chart size fixed, not responsive.
Is the any solution for it?
As of now you cannot do that directly via Holoviews, but there is a way through extracting a Bokeh plot from a Holoviews one. The idea is taken from this Holoviews page under 'Combining HoloViews and Bokeh Plots/Widgets'.
The above example could be made responsive as follows:
import holoviews as hv
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import layout
hv.extension('bokeh')
data = [[1, 'A', 5],
[1, 'A', 3],
[1, 'B', 10],
[1, 'B', 5],
[2, 'A', 5],
[2, 'A', 19],
[2, 'B', 7],
[2, 'B', 10]]
df = pd.DataFrame.from_records(data, columns=['cyl', 'origin', 'mpg'])
boxwhisker = hv.BoxWhisker(df, ['cyl', 'origin'], 'mpg', label='')
boxwhisker.options(show_legend=False, height=200)
renderer = hv.renderer('bokeh').instance(mode='server')
doc = curdoc()
box_chart = renderer.get_plot(boxwhisker, doc)
doc.name = 'boxChart'
plot_layout = layout(box_chart.state, sizing_mode='scale_width')
doc.add_root(plot_layout)
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.
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.