Add text annotations to each data point in Holoviews plot - python

In Bokeh I am able to add a text annotation to each point in my plot programmatically by using LabelSet. Below I give an example for a simple bar plot:
import numpy as np
import pandas as pd
# Make some data
dat = \
(pd.DataFrame({'team':['a','b','c'], 'n_people':[10,5,12]})
.assign(n_people_percent = lambda x: (x['n_people']/np.sum(x['n_people'])*100)
.round(1).astype('string') + '%')
)
dat
# Bar plot with text annotations for every bar
from bkcharts import show, Bar
from bkcharts.attributes import CatAttr
from bokeh.models import (ColumnDataSource, LabelSet)
source_labs = ColumnDataSource(data = dat)
p = Bar(data = dat, label = CatAttr(columns = 'team'), values = 'n_people')
labels = LabelSet(x = 'team', y = 'n_people',
text = 'n_people_percent', source = source_labs)
p.add_layout(labels)
show(p)
However I am not sure how to achieve the same thing with Holoviews. I can make the same bar plot without the annotations very easily:
import holoviews as hv
hv.extension('bokeh')
p = hv.Bars(dat, kdims=['team'], vdims=['n_people'])
p
I can add a single text label adding an Overlay with the hv.Text element
p * hv.Text('a', 11, '37.0%')
But I have no idea how I can label each bar without explicitly calling hv.Text separately for every data point (bar). The problem seems to be that hv.Text does not accept a data argument like other elements e.g. hv.Bars, instead just x and y coordinates. My intuition would be that I should be able to do something like
p * hv.Text(dat, kdims=['team'], vdims=['n_people_percent'])
Any help with this appreciated!

Looks like this commit adds vectorized labels to hv.Labels, so try:
import holoviews as hv
hv.extension('bokeh')
p = hv.Bars(dat, kdims=['team'], vdims=['n_people'])
p * hv.Labels(dat, kdims=['team', 'n_people'], vdims=['n_people_percent'])

Related

How to highlight a selected area in the graph with using Bokeh?

I plotted a line graphs with using bokeh on Python. I want to highlight and take the values (Max-Min-x, y coordinates) of the selected areas with "Box Select tool" like shown below. when I choose a certain section on the graph with "box select tool" the color of the selected part does not change. How to solve this problem?
Example
import numpy as np
import pandas as pd
from bokeh.plotting import figure,show,output_file
from bokeh.models import ColumnDataSource
output_file("PlottingTest.html")
dataset = pd.read_csv("data.csv")
data = dataset.iloc[:,3]
time = np.linspace(1, 500, num = 500)
TOOLS ="pan,wheel_zoom,reset,hover,poly_select,xbox_select,lasso_select"
s1 = ColumnDataSource(data=dict(x=time, y=data))
p = figure(title = 'Test',x_axis_label = 'time', y_axis_label='csv Data',plot_width=1000, plot_height=500,tools=TOOLS)
p.line ('date', 't1', source=s1, selection_color="orange")
p.line(time, data, legend_label="Current", line_width=1)
p.toolbar.autohide = True
show(p)

holoviews hv.save x label cutoff (trimmed)

I am trying to save a holoviews plot, to png, using the bokeh backend. But the saved png has its xlabel partially cutoff. How do I increase the whitespace margin at the bottom of the plot to avoid this?
I have tried to create a single column NdLayout, and then edit the plot afterwards, but each plot has its xlabel trimmed.
When rendering this in a jupyter notebook, the xlabel appears as expected.
import numpy as np
import holoviews as hv
hv.extension('bokeh')
a1 = np.random.normal(0,1,size=600)
a2 = np.random.normal(1,1,size=600)
b = np.arange(0,600)
xx = hv.Curve([(ii,jj) for ii,jj in zip(b,a1)],['predicted_sample'],['Value']).relabel('StandardNormal')
yy = hv.Curve([(ii,jj) for ii,jj in zip(b,a2)],['predicted_sample'],['Value']).relabel('ShiftedNormal')
hv.save(xx*yy,"plot.png")
Single:
hv.save(((xx*yy) + (xx*yy)).cols(1),'ndlayout_plots.png')
NdLayout:
For those that find this, I found a work around using hooks and setting the bokeh min_border_bottom value directly.
import numpy as np
import holoviews as hv
hv.extension('bokeh')
a1 = np.random.normal(0,1,size=600)
a2 = np.random.normal(1,1,size=600)
b = np.arange(0,600)
xx = hv.Curve([(ii,jj) for ii,jj in zip(b,a1)],['predicted_sample'],['Value']).relabel('StandardNormal')
yy = hv.Curve([(ii,jj) for ii,jj in zip(b,a2)],['predicted_sample'],['Value']).relabel('ShiftedNormal')
pp = xx*yy #create the overlay
def fixBottomMargin(plot,element):
plot.handles['plot'].min_border_bottom = 100
pp = pp.opts(opts.Curve(hooks=[fixBottomMargin])) #call the hooks on the curve element
hv.save(pp,filename='plot.png')
Information found by reading the bokeh documentation, Styling Visual Attributes

How to use RangetoolLink with holoviews in an Overlayed plot

I am trying to use the holoviews Rangetool link in a holoviews Overlayed plot. But unable to achieve the range linking to work. Is it possible to achieve this.?
Based on these links example 1 and example 2 I tried the options with an overlayed plot instead of a single curve plot. But this didn't work. Below I provided an example with a similar dummy data.
import pandas as pd
import holoviews as hv
from holoviews import opts
import numpy as np
from holoviews.plotting.links import RangeToolLink
hv.extension('bokeh')
# Genrate Random Data
def randomDataGenerator(noOfSampleDataSets):
for i in range(noOfSampleDataSets):
res = np.random.randn(1000).cumsum()
yield res
# Overlay Plots
overlaid_plot = hv.Overlay([hv.Curve(data)
.opts(width=800, height=600, axiswise=True, default_tools=[])
for data in randomDataGenerator(5)])
# Adjust Source Height
source = overlaid_plot.opts(height=200)
# adjust target plot attributes
target = source.opts(clone=True, width=800, labelled=['y'],)
# Link source and target
rtlink = RangeToolLink(source, target)
# Compose and plot.
(target + source).cols(1).opts(merge_tools=False)
I expect that the source plot will show up with a range tool as shown in the example and be able to select a range in it which should select the same data points in the target plot.
Following code works in my case. I slightly refactored the code. But the logic is still the same. So if we have a an overlaid plot, link one of the curves in the overlayed plot works fine with all the remaining curves.
Following code works in a jupyter notebook. Its not tested in other environment.
import holoviews as hv
import numpy as np
hv.extension('bokeh')
from holoviews.plotting.links import RangeToolLink
# Genrate Random Data
def randomDataGenerator(noOfSampleDataSets):
for i in range(noOfSampleDataSets):
res = np.random.randn(1000).cumsum()
yield res
#generate all curves
def getCurves(n):
for data in randomDataGenerator(n):
curve = hv.Curve(data)
yield curve
source_curves, target_curves = [], []
for curve in getCurves(10):
# Without relabel, the curve somehow shares the ranging properties. opts with clone=True doesn't help either.
src = curve.relabel('').opts(width=800, height=200, yaxis=None, default_tools=[])
tgt = curve.opts(width=800, labelled=['y'], toolbar='disable')
source_curves.append(src)
target_curves.append(tgt)
# link RangeTool for the first curves in the list.
RangeToolLink(source_curves[0],target_curves[0])
#Overlay the source and target curves
overlaid_plot_src = hv.Overlay(source_curves).relabel('Source')
overlaid_plot_tgt = hv.Overlay(target_curves).relabel('Target').opts(height=600)
# layout the plot and render
layout = (overlaid_plot_tgt + overlaid_plot_src).cols(1)
layout.opts(merge_tools=False,shared_axes=False)

Holoviews AdjointLayout with Bokeh Widgets

I am trying to append an AdjointLayout of a Scatter plot with two supporting histograms to a Bokeh dashboard. However, whenever trying to incorporate the two in a single row, the Bokeh widgets encounter display issues and the AdjointLayout never scales. Is this the current expected behavior or is here a different approach I need to take to currently accomplish this?
Minimal Example of the problem:
import numpy as np
import pandas as pd
import holoviews as hv
from bokeh.layouts import layout
from bokeh.models import Select
from bokeh.io import curdoc
renderer = hv.renderer('bokeh').instance(mode='server')
np.random.seed(10)
data = np.random.rand(100,4)
opts = {}
opts['color_index'] = 2
opts['size_index'] = 3
opts['scaling_factor'] = 50
points = hv.Points(data, vdims=['z', 'size']).opts(plot=opts)
fields = ['berry', 'cherry', 'dairy']
x = Select(title='X-Axis:', value=fields[0], options=fields)
y = Select(title='Y-Axis:', value=fields[1], options=fields)
dashboard = points + points[0.3:0.7, 0.3:0.7].hist()
app = renderer.get_plot(dashboard).state
dashboard = layout([
[[x, y], app],
])
curdoc().add_root(dashboard)
Using Bokeh 0.13.0 and Holoviews 1.10.5

Changing bokeh grid lines position

I am trying to plot a few points on a graph, similarly to a heat map.
Sample code (adapted from the heat map section here):
import pandas as pd
from bokeh.io import output_notebook, show
from bokeh.models import BasicTicker, ColorBar, ColumnDataSource, LinearColorMapper, PrintfTickFormatter
from bokeh.plotting import figure
from bokeh.transform import transform
import numpy as np
# change this if you don't run it on a Jupyter Notebook
output_notebook()
testx = np.random.randint(0,10,10)
testy = np.random.randint(0,10,10)
npdata = np.stack((testx,testy), axis = 1)
hist, bins = np.histogramdd(npdata, normed = False, bins = (10,10), range=((0,10),(0,10)))
data = pd.DataFrame(hist, columns = [str(x) for x in range(10)])
data.columns.name = 'y'
data['x'] = [str(x) for x in range(10)]
data = data.set_index('x')
df = pd.DataFrame(data.stack(), columns=['present']).reset_index()
source = ColumnDataSource(df)
colors = ['lightblue', "yellow"]
mapper = LinearColorMapper(palette=colors, low=df.present.min(), high=df.present.max())
p = figure(plot_width=400, plot_height=400, title="test circle map",
x_range=list(data.index), y_range=list((data.columns)),
toolbar_location=None, tools="", x_axis_location="below")
p.circle(x="x", y="y", size=20, source=source,
line_color=None, fill_color=transform('present', mapper))
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = "10pt"
p.axis.major_label_standoff = 10
p.xaxis.major_label_orientation = 0
show(p)
That returns:
Now, as you can see, the grid lines are centered on the points(circles), and I would like, instead to have the circles enclosed in a square created by the lines.
I went through this to see if I could find information on how to offset the grid lines by 0.5 (that would have worked), but I was not able to.
There's nothing built into Bokeh to accomplish this kind of offsetting of categorical ticks, but you can write a custom extension to do it:
CS_CODE = """
import {CategoricalTicker} from "models/tickers/categorical_ticker"
export class MyTicker extends CategoricalTicker
type: "MyTicker"
get_ticks: (start, end, range, cross_loc) ->
ticks = super(start, end, range, cross_loc)
# shift the default tick locations by half a categorical bin width
ticks.major = ([x, 0.5] for x in ticks.major)
return ticks
"""
class MyTicker(CategoricalTicker):
__implementation__ = CS_CODE
p.xgrid.ticker = MyTicker()
p.ygrid.ticker = MyTicker()
Note that Bokeh assumes CoffeeScript by default when the code is just a string, but it's possible to use pure JS or TypeScript as well. Adding this to your code yields:
Please note the comment about output_notebook you must call it (possibly again, if you have called it previously) after the custom model is defined, due to #6107

Categories

Resources