Python Holoviews responsive chart with sizing_mode property - python

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)

Related

plotly.graph_objects.table static table

I am using plotly.go to make a table and export to html, however the columns on the table are not static, they can be dragged and rearranged with the mouse. I'm trying to find a simple way to make the table static while still exporting to html.
import pandas as pd
import plotly.graph_objects as go
df = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
columns=['a', 'b', 'c'])
fig = go.Figure(data=[go.Table(
header=dict(
values=list(df),
line_color='darkslategray',
fill_color = 'lightskyblue',
align='center'),
cells=dict(
values=[df.a, df.b, df.c],
line_color='darkslategray',
fill_color='lightcyan',
align='center'))
])
fig.show()
fig.to_html('table.html')
I've tried using staticPlot: true, but it doesn't seem like that's an attribute in graph_objects.
I was able to get a static table by adding the following to the code:
fig.show()
fig.to_html('table.html', config={'staticPlot': True})
The one thing to keep in mind is that the mouse icon still changes to look like columns can be dragged, but the columns are static.

Stacked bar plot in python / plotly (express): grouping / ordering of bars

I have data in a dataframe that I want to plot with a stacked bar plot:
test_df = pd.DataFrame([[1, 5, 1, 'A'], [2, 10, 1, 'B'], [3, 3, 1, 'A']], columns = ('ID', 'Value', 'Bucket', 'Type'))
if I do the plot with Plotly Express I get bars stacked on each other and correctly ordered (based on the index):
fig = px.bar(test_df, x='Bucket', y='Value', barmode='stack')
However, I want to color the data based on Type, hence I go for
fig = px.bar(test_df, x='Bucket', y='Value', barmode='stack', color='Type')
This works, except now the ordering is messed up, because all bars are now grouped by Type. I looked through the docs of Plotly Express and couldn't find a way to specify the ordering of the bars independently. Any tips on how to do this?
I found this one here, but the scenario is a bit different and the options mentioned there don't seem to help me:
How to disable plotly express from grouping bars based on color?
Edit: This goes into the right direction, but not with using Plotly Express, but rather Plotly graph_objects:
import plotly.graph_objects as go
test_df = pd.DataFrame([[1, 5, 1, 'A', 'red'], [2, 10, 1, 'B', 'blue'], [3, 3, 1, 'A', 'red']], columns = ('ID', 'Value', 'Bucket', 'Type', 'Color'))
fig = go.Figure()
fig.add_trace(go.Bar(x=test_df["Bucket"], y=test_df["Value"], marker_color=test_df["Color"]))
Output:
Still, I'd prefer the Express version, because so many things are easier to handle there (Legend, Hover properties etc.).
The only way I can understand your question is that you don't want B to be stacked on top of A, but rather the opposite. If that's the case, then you can get what you want through:
fig.data = fig.data[::-1]
fig.layout.legend.traceorder = 'reversed'
Some details:
fig.data = fig.data[::-1] simply reverses the order that the traces appear in fig.data and ultimately in the plotted figure itself. This will however reverse the order of the legend as well. So without fig.layout.legend.traceorder = 'reversed' the result would be:
And so it follows that the complete work-around looks like this:
fig.data = fig.data[::-1]
fig.layout.legend.traceorder = 'reversed'
Complete code:
import pandas as px
import plotly.express as px
test_df = pd.DataFrame([[1, 5, 1, 'A'], [2, 10, 1, 'B'], [3, 3, 1, 'A']], columns = ('ID', 'Value', 'Bucket', 'Type'))
fig = px.bar(test_df, x='Bucket', y='Value', barmode='stack', color='Type')
fig.data = fig.data[::-1]
fig.layout.legend.traceorder = 'reversed'
fig.show()
Ok, sorry for the long delay on this, but I finally got around to solving this.
My solution is possibly not the most straight forward one, but it does work.
The basic idea is to use graph_objects instead of express and then iterate over the dataframe and add each bar as a separate trace. This way, each trace can get a name that can be grouped in a certain way (which is not possible if adding all bars in a single trace, or at least I could not find a way).
Unfortunately, the ordering of the legend is messed up (if you have more then 2 buckets) and there is no way in plotly currently to sort it. But that's a minor thing.
The main thing that bothers me is that this could've been so much easier if plotly.express allowed for manual ordering of the bars by a certain column.
Maybe I'll submit that as a suggestion.
import pandas as pd
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default = "browser"
test_df = pd.DataFrame(
[[1, 5, 1, 'B'], [3, 3, 1, 'A'], [5, 10, 1, 'B'],
[2, 8, 2, 'B'], [4, 5, 2, 'A'], [6, 3, 2, 'A']],
columns = ('ID', 'Value', 'Bucket', 'Type'))
# add named colors to the dataframe based on type
test_df.loc[test_df['Type'] == 'A', 'Color'] = 'Crimson'
test_df.loc[test_df['Type'] == 'B', 'Color'] = 'ForestGreen'
# ensure that the dataframe is sorted by the values
test_df.sort_values('ID', inplace=True)
fig = go.Figure()
# it's tedious to iterate over each item, but only this way we can ensure that everything is correctly ordered and labelled
# Set up legend_show_dict to check if an item should be shown or not. This should be only done for the first occurrence to avoid duplication.
legend_show_dict = {}
for i, row in test_df.iterrows():
if row['Type'] in legend_show_dict:
legend_show = legend_show_dict[row['Type']]
else:
legend_show = True
legend_show_dict[row['Type']] = False
fig.add_trace(
go.Bar(
x=[row['Bucket']],
y=[row['Value']],
marker_color=row['Color'],
name=row['Type'],
legendgroup=row['Type'],
showlegend=legend_show,
hovertemplate="<br>".join([
'ID: ' + str(row['ID']),
'Value: ' + str(row['Value']),
'Bucket: ' + str(row['Value']),
'Type: ' + row['Type'],
])
))
fig.update_layout(
xaxis={'categoryorder': 'category ascending', 'title': 'Bucket'},
yaxis={'title': 'Value'},
legend={'traceorder': 'normal'}
)
fig.update_layout(barmode='stack', font_size=20)
fig.show()
This is what it should look like then:

query bokeh multi_line with markers

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.

How do I extract data from a Bokeh ColumnDatasource

I was trying to avoid using a ColumnDataSource and instead of that I was passing pandas dataframe columns directly to Bokeh plots.
Soon though I had to implement a HoverTool which requires to have the data in a ColumnDataSource. So, I started using ColumnDataSource.
Now, I was creating a box annotation and I had to use the maximum value of a certain column from my data to define the top border of the box.
I can do that easily using pandas:
low_box = BoxAnnotation(
top=flowers['petal_width'][flowers['species']=='setosa'].max(),
fill_alpha=0.1, fill_color='red')
But I can't figure out how to extract the maximum from a ColumnDataSource.
Is there a way to extract a maximum value from it, or is my approach all wrong in the first place?
A ColumnDataSource object has an attribute data which will return the python dictionary used to create the object in the first place.
from bokeh.plotting import ColumnDataSource
# define ColumnDataSource
source = ColumnDataSource(
data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
desc=['A', 'b', 'C', 'd', 'E'],
)
)
# find max for variable 'x' from 'source'
print( max( source.data['x'] ))
If the source input is a Pandas DataFrame, you can use the Standard method:
source = ColumnDataSource(
data= pd.DataFrame( dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
desc=['A', 'b', 'C', 'd', 'E'],
))
)
print( source.data['x'].max() )

bokeh heatmap discrepancy

I'm learning Bokeh and ran the following example:
from bokeh.charts import HeatMap, output_file, show
import pandas as pd
output_file('heatmap.html')
df = pd.DataFrame(
dict(
apples=[4, 5, 8],
bananas=[1, 2, 4],
pears=[6, 5, 4],
),
index=['2012', '2013', '2014']
)
p = HeatMap(df, title='Fruits')
show(p)
It does not match what it is supposed to be:
http://docs.bokeh.org/en/0.9.3/docs/user_guide/charts.html#heatmap
Anybody have any ideas to fix the color? Thanks!

Categories