How to work with ColumnDataSource and LegendItem together in bokeh? - python

I would like to create a categorial scatter plot with these functions using bokeh:
Hover Tooltips
Legend outside the plot area with click policy = "hide"
After numerous searches, I can implement the function #1 and #2 separately.
But I don't know how to make these 2 functions work at the same time.
Tooltips only needs one glyph object, but legend outside plot area needs a for-loop to create a list of glyph objects which is used to generate legend_items.
Thanks.
Code Example:
Hover Tooltips can be achieved by using ColumnDataSources
(https://docs.bokeh.org/en/latest/docs/user_guide/categorical.html#heat-maps)
import pandas as pd
from bokeh.sampledata.stocks import AAPL
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, output_file, show
df = pd.DataFrame(AAPL)
output_file("test.html")
p = figure(tools="hover")
source = ColumnDataSource(df)
p.scatter("high", "low", source=source)
p.hover.tooltips = [("Date","#date")]
show(p)
Legend outside the plot
(Position the legend outside the plot area with Bokeh)
import pandas as pd
from bokeh.models import Legend, LegendItem
from bokeh.palettes import Spectral4
from bokeh.plotting import figure, output_file, show
from bokeh.sampledata.stocks import AAPL, GOOG, IBM, MSFT
output_file("test2.html")
p = figure(x_axis_type="datetime")
p_list = []
for data, name, color in zip([AAPL, IBM, MSFT, GOOG], ["AAPL", "IBM", "MSFT", "GOOG"], Spectral4):
df = pd.DataFrame(data)
df['date'] = pd.to_datetime(df['date'])
p_list.append(p.line(df['date'], df['close'], line_width=2, color=color, alpha=0.8))
legend_items = [LegendItem(label=x, renderers=[p_list[i]]) for i, x in enumerate(["AAPL", "IBM", "MSFT", "GOOG"])]
legend_ = Legend(items=legend_items, click_policy="hide", location="center", label_text_font_size="12px")
p.add_layout(legend_, "right")
show(p)

import pandas as pd
from bokeh.models import Legend, LegendItem, ColumnDataSource
from bokeh.palettes import Spectral4
from bokeh.plotting import figure, show
from bokeh.sampledata.stocks import AAPL, GOOG, IBM, MSFT
p = figure(x_axis_type="datetime", tools='hover')
p.hover.tooltips = [("Date", "#date{%F}"),
("Open", "#open"),
("Close", "#close")]
p.hover.formatters = {'#date': 'datetime'}
legend_items = []
for data, name, color in zip([AAPL, IBM, MSFT, GOOG],
["AAPL", "IBM", "MSFT", "GOOG"],
Spectral4):
data['date'] = pd.to_datetime(data['date']).values
ds = ColumnDataSource(data)
renderer = p.line('date', 'close', source=ds,
line_width=2, color=color, alpha=0.8)
legend_items.append(LegendItem(label=name, renderers=[renderer]))
legend = Legend(items=legend_items, click_policy="hide",
location="center", label_text_font_size="12px")
p.add_layout(legend, "right")
show(p)
If you want to have different tooltips for different stocks, you can create one hover tool per each stock and specify them in the tools argument. The downside - there will be multiple hover tool buttons on the toolbar.

Related

Fix right-side of plot in place when zooming in/out

When I zoom out the following Bokeh plot:
A bunch of whitespace is added to both sides of the data:
How can I make it so that the right-hand side of the plot is fixed, so that zooming out will only create whitespace on the left-hand side? For example, this is my desired zoom-out look:
This is the code that initializes the plot:
from bokeh.plotting import figure, curdoc
from bokeh.driving import linear
from bokeh.models.tools import PanTool, WheelZoomTool
p = figure(sizing_mode="stretch_both", y_axis_location="right", x_axis_type="datetime")
pan_tool = p.select(dict(type=PanTool))
pan_tool.dimensions="width"
zoom_tool = p.select(dict(type=WheelZoomTool))
zoom_tool.dimensions="width"
This can be done with a CustomJS callback. Define a ColumnDataSource as source and look for the maximum x value on thof the source. If you have multiple renderers or multiple sources, you have to find the mximum of the maximums.
Minimal Example
from bokeh.plotting import show, figure, output_notebook
from bokeh.models import CustomJS, Line, ColumnDataSource
output_notebook()
p = figure(width=500, height=300)
source = ColumnDataSource(dict(x=data, y=data))
p.line(x='x', y='y', source=source)
callback = CustomJS(
args=dict(p=p, source=source),
code="""
p.x_range.end = Math.max(...source.data['x'])
p.change.emit();
"""
)
p.x_range.js_on_change("end", callback)
show(p)

Strange Labels on Bokeh Pie/Donut Chart

I'm following the response in this question: Adding labels in pie chart wedge in bokeh
I'm trying to add labels to my Bokeh chart so users can see data values. For some reason, the chart is rendering like this:
I'm not sure why this is happening. I tried commenting out the z variable to see if this made a difference.. it does not.
Here is my code:
import os
import pandas as pd
import pyodbc
from bokeh.plotting import figure, show
from bokeh.io import export_png
from bokeh.models import LabelSet, ColumnDataSource
from bokeh.palettes import Category20
import matplotlib as plt
from math import pi
from bokeh.transform import cumsum
lst = ['On_Time', 'All']
lst2 = [8, 85]
df = pd.DataFrame(list(zip(lst, lst2)),
columns =['Column', 'Value'])
df
df['angle'] = df['value']/df['value'].sum() * 2*pi
df['angle']
df['color'] = ['#084594', '#2171b5']
#z=110*(df['value']/df['value'].sum())
#df['value']=z
#df
p = figure(plot_height=350, title="", toolbar_location=None,
tools="", x_range=(-.5, .5))
p.annular_wedge(x=0, y=1, inner_radius=0.15, outer_radius=0.25, direction="anticlock",
start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
line_color="white", fill_color='color', legend='column', source=df)
df["value"] = df['value'].astype(str)
df["value"] = df["value"].str.pad(35, side = "left")
source = ColumnDataSource(df)
labels = LabelSet(x=0, y=1, text='value', level='glyph',
angle=cumsum('angle', include_zero=True), source=source, render_mode='canvas')
p.add_layout(labels)
p.axis.axis_label=None
p.axis.visible=False
p.grid.grid_line_color = None
show(p)
The labels are "underneath" because you have set
level='glyph'
in the call to LabelSet. There is generally not very many reasons to disturb the default render levels. If you remove this, the label will show up "on top" the way annotations such as LabelSet are intended to.
Also note the "str padding" that the other answer used to position the label alignment. That's slightly hacky but serviceable. Font differences between viewers' browser might make small differences. The alternative is to compute actual, exact x, y positions around the circle where you want the labels to go.

Bokeh chart with slider and checkbox?

I have data in format of given image.
I'm finding it difficult to add "Checkbox" of variables and "Slider" for index date range.
I have researched through StackOverflow a lot but since I have no knowledge on jQuery or js I couldn't figure out what to do.
My code :
from bokeh.palettes import Spectral11
from bokeh.plotting import figure, show, output_file
from bokeh.models import Legend, LegendItem
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.models import CustomJS, Slider
from bokeh.layouts import row, widgetbox
from bokeh.models import CustomJS, Slider
from bokeh.plotting import figure, output_file, show, ColumnDataSource
output_file('temp.html')
numlines=len(data.columns)
mypalette=Spectral11[0:numlines]
p = figure(width=1100, height=650, x_axis_type="datetime")
r =p.multi_line(xs=[data.index.values]*numlines,
ys=[data[name].values for name in data],
line_color=mypalette,
line_width=3)
legend = Legend(items=[
LegendItem(label=f"{X_t_list[:1]}", renderers=[r], index=0),
LegendItem(label=f"{X_t_list[1:2]}", renderers=[r], index=1),
LegendItem(label=f"{X_t_list[2:3]}", renderers=[r], index=2),
LegendItem(label=f"{X_t_list[3:4]}", renderers=[r], index=3),
LegendItem(label=f"{X_t_list[4:5]}", renderers=[r], index=4),
LegendItem(label=f"{X_t_list[5:6]}", renderers=[r], index=5),
LegendItem(label=f"{X_t_list[6:7]}", renderers=[r], index=6),
LegendItem(label=f"{X_t_list[7:8]}", renderers=[r], index=7),
LegendItem(label=f"{X_t_list[8:9]}", renderers=[r], index=8),
LegendItem(label=f"{X_t_list[9:10]}", renderers=[r], index=9),
LegendItem(label=f"{X_t_list[10:11]}", renderers=[r], index=10),])
p.add_layout(legend)
show(p)
Bokeh plot I'm able to plot:
Can someone help me with this?
Instead of adding checkboxes you could use the interactive legend. I added this in my example instead of checkboxes, saves some code/space. Lines should disappear when you click on a legend item. I also added a DateRangeSlider.
Since I'm using callback functions in this code you'll have to run it as a bokeh server: bokeh serve script.py --show.
import pandas as pd
from bokeh.palettes import Spectral11
from bokeh.plotting import figure, show, curdoc
from bokeh.models.widgets import DateRangeSlider
from bokeh.layouts import layout
df = pd.read_csv('Data.csv')
df['Index'] = pd.to_datetime(df['Index'])
tooltips = [
("Value", "$y"),
("Variable", "$name")
]
p = figure(width=1100, height=650, x_axis_type="datetime", tooltips=tooltips)
for column, color in zip(list(df)[1:], Spectral11):
p.line(x=df['Index'], y=df[column], color=color, legend=column, line_width=3, name=column)
def updateRange(attr, old, new):
p.x_range.start = new[0]
p.x_range.end = new[1]
slider = DateRangeSlider(title="Period", start=min(df['Index']), end=max(df['Index']), value=(min(df['Index']),max(df['Index'])), width=500)
slider.on_change('value', updateRange)
p.legend.click_policy="hide"
curdoc().add_root(layout(slider,p))

Bokeh graph doesn't plot properly

The following code doesn't generate a graph:
import pandas
import numpy as np
from bokeh.plotting import figure, show, output_file
from bokeh.io import output_notebook
from datetime import datetime
output_notebook()
TOOLS="hover,crosshair,pan,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,\
tap,save,box_select,poly_select,lasso_select,"
df = pandas.read_csv('./logs.csv')
df['datetime'] = pd.to_datetime(df['datetime'])
xvals = df['datetime'].dt.strftime('%Y-%m-%d')
yvals = df['datetime'].dt.strftime('%H:%M:%S')
p = figure(title="Test Title", width=500, height=500, \
x_axis_type="datetime", y_axis_type="datetime", \
x_range=(df.iloc[-1]['datetime'].strftime('%Y/%m/%d'),\
df.iloc[0]['datetime'].strftime('%Y/%m/%d')),\
y_range=('00:00:00','23:59:59'),\
tools=TOOLS)
p.scatter(xvals, yvals, alpha=0.5)
show(p)
This graph produced is a blank graph. What is the problem?
EDIT:
I updated the code with
xvals = df['datetime'].dt.date
yvals = df['datetime'].dt.time
p = figure(title="Activity history", width=800, height=500, \
x_axis_type='datetime', y_axis_type='datetime',\
x_axis_label="Date", y_axis_label="Time",\
tools=TOOLS)
p.scatter(xvals, yvals, alpha=0.3)
show(p)
And this produces a graph.
OK, as far as I can tell, this is what you want (using some project sample data, since you did not provide anything to run your code with):
from bokeh.plotting import figure, show
from bokeh.sampledata.commits import data
p = figure(x_axis_type="datetime", y_axis_type="datetime")
p.circle(x=data.index, y=data.index.time)
show(p)
The datetime axis type, as the name suggests, treats the timestamps as datetimes. I.e., these are interpreted as hours of the day in the first day of the first year of Epoch. That's why the axis starts and ends with 1/01 and 1/02. You might want to use customize the tick formatter to display just the hours.
For reference, data looks like this:

How do I make a line graph from a dataframe in bokeh?

I'm reading a .csv file in bokeh which has two columns: one for date and one for the values corresponding to that date. I'm trying to make a line graph with the dates on the x axis and the values on y, but it isn't working. Any ideas?
CODE:
import pandas as pd
from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource
from datetime import datetime
from bokeh.palettes import Spectral3
output_file('output.html')
df = pd.read_csv('speedgraphak29.csv')
p = figure(x_axis_type="datetime")
p.line(x=df.dates, y=df.windspeed, line_width=2)
show(p)
It's returning an empty graph. What should I do?
Since you didn't provide an example of the input data I had to make something up. You probably forgot to specify that the dates column should be interpreted as datetime values as bigreddot noted. Here is a working example:
import pandas as pd
from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource
from datetime import datetime
from bokeh.palettes import Spectral3
output_file('output.html')
df = pd.DataFrame.from_dict({'dates': ["1-1-2019", "2-1-2019", "3-1-2019", "4-1-2019", "5-1-2019", "6-1-2019", "7-1-2019", "8-1-2019", "9-1-2019", "10-1-2019"], 'windspeed': [10, 15, 20,30 , 25, 5, 15, 30, 35, 25]})
df['dates'] = pd.to_datetime(df['dates'])
source = ColumnDataSource(df)
p = figure(x_axis_type="datetime")
p.line(x='dates', y='windspeed', line_width=2, source=source)
show(p)
You could use this. Say you have a CSV called sample_data.csv with columns Date and Amount. Just to add on to what Jasper had.
import pandas as pd
from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource
output_file('output.html')
df = pd.read_csv('sample_data.csv', parse_dates=['Date'])
source = ColumnDataSource(df)
p = figure(x_axis_type="datetime")
p.line(x='Date', y='Amount', line_width=2, source=source)
show(p)
In this case, read the CSV with the column as a date format. Using ColumnDataSource allows you to use advanced features like hovering over a plot to see more details if needed.
You may alternatively also use lists directly which would look like.
p.line(x='my_list_of_dates', y='my_list_of_counts', line_width=2)
This would mean reading each column and making a list from it. All in all, using ColumnDataSource would allow you to directly call a column by its name.

Categories