Related
The code generates a graph based on data from several dates, I want to filter the x axis, to show from year to year, but this problem is giving and I don't know how to solve it.
chart = AreaChart()
chart.style = 7
chart.x_axis.number_format = "yyyy"
chart.x_axis.scaling.min = 0
chart.x_axis.scaling.max = 350
chart.legend = None
chart.x_axis.auto = True
values = Reference(ws, range_string="Calculo!$E$2:$E$559")
categoria = Reference(ws, range_string="Calculo!$A$2:$A$559")
chart.add_data(values)
chart.set_categories(categoria)
chart2 = LineChart()
values2 = Reference(ws, range_string="Calculo!$F$2:$F$559")
categoria2 = Reference(ws, range_string="Calculo!$A$2:$A$559")
chart2.add_data(values2)
chart2.set_categories(categoria2)
s2 = chart2.series[0]
s2.marker.symbol = "dash"
s2.marker.graphicalProperties.solidFill = "FF0000" # Marker filling
s2.marker.graphicalProperties.line.solidFill = "FF0000" # Marker outline
chart += chart
wb["test"].add_chart(chart,"B6")
I want to get this result:
when I remove the code chart.x_axis.auto = True, it looks like this.
I'm trying to create a machine shop schedule that is color coded by parts that belong to the same assembly. I'm using plotly express timeline to create the Gantt. It is reading an excel file on my desktop to generate the schedule. I created a sample below. The goal is to have all the Chair parts be the same color, and all the Desk parts be the same color.
Here's the code to read the excel file and create the Gantt:
df = pd.read_excel(r"C:\Users\john.doe\Documents\Machine Durations - Sample.xlsx")
df['Start Shift'] = df['Start Shift'].astype(int)
df['Finish'] = df['Finish'].astype(int)
#display(df)
# create a slice if the df for the rank = 1
dfRank1 = df[df.Rank == 1]
# reindex it
dfRank1 = dfRank1.reset_index()
#display(dfRank1)
#Create the visual
df["Part"] = df["Part"].astype(str)
df["delta"] = df["Finish"]-df["Start Shift"]
fig = px.timeline(df,x_start ="Start Shift", x_end = "Finish", y = "Machine", hover_name ="Part",color = "Part", text = "Part", title = "Machine Shop Cycle", opacity = .75)
fig.update_yaxes(autorange="reversed")
fig.layout.xaxis.type = 'linear'
#fig.data[0].x = df.delta.tolist()
for d in fig.data:
filt = df['Part'] == d.name
d.x = df[filt]['delta'].tolist()
fig.update_traces(textposition='inside')
fig.show()
good practice is paste you data as text into a question
have made two changes
put Assembly into hover_data so that it is in customdata of each trace
loop through traces to update marker_color based on Assembly in customdata
# update colors to that of the assembly
cmap = {"Chair":"red", "Desk":"blue"}
fig.for_each_trace(lambda t: t.update({"marker":{"color":[cmap[a] for a in t["customdata"][:,0]]}}))
full code
import pandas as pd
import plotly.express as px
import io
df = pd.read_csv(
io.StringIO(
"""Part,Machine,Duration,Duration Shifts(6),Start Shift,Finish,Index,Assembly,Rank
Legs,Lathe,100,5,0,5,1,Chair,A
Seat,Mill,400,5,0,5,1,Chair,A
Back,Mill,200,3,5,8,1,Chair,A
Legs,Lathe,200,3,5,8,1,Desk,A
Table Top,Mill,200,3,8,11,1,Desk,A
Wheels,Mill-Turn,200,10,0,10,1,Desk,A"""
)
)
df["Start Shift"] = df["Start Shift"].astype(int)
df["Finish"] = df["Finish"].astype(int)
# display(df)
# create a slice if the df for the rank = 1
dfRank1 = df[df.Rank == 1]
# reindex it
dfRank1 = dfRank1.reset_index()
# display(dfRank1)
# Create the visual
df["Part"] = df["Part"].astype(str)
df["delta"] = df["Finish"] - df["Start Shift"]
fig = px.timeline(
df,
x_start="Start Shift",
x_end="Finish",
y="Machine",
hover_name="Part",
hover_data=["Assembly"], # want this for setting color
color="Part",
text="Part",
title="Machine Shop Cycle",
opacity=0.75,
)
fig.update_yaxes(autorange="reversed")
fig.layout.xaxis.type = "linear"
# fig.data[0].x = df.delta.tolist()
for d in fig.data:
filt = df["Part"] == d.name
d.x = df[filt]["delta"].tolist()
fig.update_traces(textposition="inside")
# update colors to that of the assembly
cmap = {"Chair":"red", "Desk":"blue"}
fig.for_each_trace(lambda t: t.update({"marker":{"color":[cmap[a] for a in t["customdata"][:,0]]}}))
output
I am new to Bokeh I am trying to replicate the line plots shown in https://www.worldometers.info/coronavirus/ using Bokeh.Here is my full code
from datetime import datetime, timedelta, date
import requests
import json
from bokeh.plotting import output_notebook, figure, show
from bokeh.models import ColumnDataSource, HoverTool, Title
def DateFormatter(x):
s = '01222020'
global given_date
given_date = datetime(month=int(s[:2]), day=int(s[2:4]), year=int(s[4:]))
given_date += timedelta(days=x)
final = str(given_date.strftime('%m{}%d{}%y').format('/','/'))
if int(final[0:2])<10 and int(final[3:5])<10:
final = final[1:]
final = final[0 : 2 : ] + final[3 : :]
elif int(final[0:2])<10:
final = final[1:]
elif int(final[3:5])<10:
final = final[0 : 2 : ] + "/" + final[4 : :]
return final
def DateFormatterForPlot(x):
s_plot = '01222020'
global given_date_plot
given_date_plot = datetime(month=int(s_plot[:2]), day=int(s_plot[2:4]), year=int(s_plot[4:]))
given_date_plot += timedelta(days=x-1)
final_plot1 = str(given_date_plot.strftime('%b %d'))
if int(final_plot1[4:6])<10:
final_plot1 = final_plot1[0 : 4 : ] + final_plot1[5 : :]
return final_plot1
finallist=[]
l2 = []
plot_list = []
country = "India"
chart_type = "cases"
base_site = f'https://disease.sh/v3/covid-19/historical/{country}?lastdays=all'
r = requests.get(base_site)
if r.status_code == 200:
packages_json = r.json()
today_date = str(date.today().strftime('%m{}%d{}%y').format('/','/'))
if int(today_date[0:2])<10 and int(today_date[3:5])<10:
today_date = today_date[1:]
today_date = today_date[0 : 2 : ] + today_date[3 : :]
elif int(today_date[0:2])<10:
today_date = today_date[1:]
elif int(today_date[3:5])<10:
today_date = today_date[0 : 2 : ] + "/" + today_date[4 : :]
for i in range(1,1000):
current_date = DateFormatter(i)
if current_date==today_date:
f_date = datetime(2020, 1, 22)
delta = given_date - f_date
break;
for i in range(delta.days):#Day 157 is 26th June
try:
a = DateFormatter(i)
packages_str = json.dumps(packages_json['timeline'][chart_type][a], indent=2)
finallist.append(int(packages_str))
except KeyError:
pass
count = 0
for i in finallist:
count = count + 1
for i in range(1,count+1):
l2.append(i)
for i in l2:
plot_list.append(DateFormatterForPlot(i))
else:
print("Not a valid country")
source = ColumnDataSource(data=dict(y=finallist, x=l2, desc=plot_list))
TOOLTIPS = """
<style>
.bk-tooltip>div:not(:first-child) {display:none;}
</style>
<b>X: </b> #desc <br>
<b>Y: </b> #y{0,0}
"""
plot = figure(background_fill_color='#fafafa', x_axis_label='Days', y_axis_label='Coronavirus {}'.format(chart_type.capitalize()), plot_width=1200, plot_height=400, toolbar_location=None)
plot.line('x', 'y', source=source, legend = 'Number of {}'.format(chart_type.capitalize()), line_width=2, color='gray')
cr = plot.circle('x', 'y', size=10, source=source, fill_color="grey", hover_fill_color="gainsboro", fill_alpha=0.1, line_color=None, hover_line_color="white", hover_fill_alpha=1)
plot.add_tools(HoverTool(tooltips=TOOLTIPS, renderers=[cr]))
plot.add_layout(Title(text="Coronavirus {} Example Graph".format(chart_type.capitalize()), align="center"), "above")
plot.legend.location = 'top_left'
plot.left[0].formatter.use_scientific = False #Used to disable scientific notation on y-axis
The problem that is arising here is that more than one circle glyphs are being shown when cursor is hovered on the area of the plot where the density of circle glyphs are high.
I think there is a CustomJS callback for Hovertool to allow only one circle glyph to be shown on hovering mouse over it but I am not able to implement it.
Output of the snippet can be seen here http://geetanshjindal.pythonanywhere.com/charts/
I am using the latest bokeh version 2.1.1 and python version 3.6
I think you have three options and all have to do with de-densifying your plot:
Reduce the size of the circle glyphs so that they don't overlap. size=5 seems to work OK for your plot width.
Increase plot width so that the circle glyphs are more spaced out. Of course, this will add a scroll bar so might not be the best user experience.
Don't remove the Bokeh toolbar and encourage the user to interact with the plot - zooming in to see all circle glyphs.
The approach of selectively showing tooltips using CSS is interesting, but it hides the decision-making ("show the tooltip only for the first circle of the overlapping two") from the user, potentially causing confusion.
One alternative approach would be to remove the tooltips altogether and add annotations in key places instead (passing a million of cases?), as well as putting a table with all dates and values as a side panel for reference.
For annotations, you can use a combination of a Label and Bezier curve models:
from bokeh.models import Label, Bezier
test_label = Label(x=50, y=550000, x_units='data', y_units='data',
text='Custom description of the datapoint', render_mode='css',
border_line_color='white', border_line_alpha=1.0,
border_line_width=3, background_fill_color='white',
background_fill_alpha=1.0)
# play around with control point values for best effect
curve_source = ColumnDataSource(dict(
x0=[150],
y0=[395048],
x1=[130],
y1=[600000],
cx0=[150],
cy0=[500000],
cx1=[150],
cy1=[600000]
)
)
test_curve = Bezier(
x0="x0", y0="y0", x1="x1", y1="y1",
cx0="cx0", cy0="cy0", cx1="cx1", cy1="cy1",
line_color="green", line_width=1)
plot.add_glyph(curve_source, test_curve)
plot.add_layout(test_label)
I have been trying to create a Geoscatter Plot with Plotly where the marker size should indicate the number of customers (row items) in one city (zip_city). I based my code on two templates from the Plotly documentation: United States Bubble Map and the aggregation part Mapping with Aggregates.
I managed to put together a code that does what I want, except for one drawback: when I hover over a bubble, I would like to see the name of the city plus number of customers (the result from the aggregation), so something like Aguadilla: 2. Can you help me on how to do this?
Here is my code (as a beginner with plotly, I am also open to code improvements):
import plotly.offline as pyo
import pandas as pd
df = pd.DataFrame.from_dict({'Customer': [111, 222, 555, 666],
'zip_city': ['Aguadilla', 'Aguadilla', 'Arecibo', 'Wrangell'],
'zip_latitude':[18.498987, 18.498987, 18.449732,56.409507],
'zip_longitude':[-67.13699,-67.13699,-66.69879,-132.33822]})
data = [dict(
type = 'scattergeo',
locationmode = 'USA-states',
lon = df['zip_longitude'],
lat = df['zip_latitude'],
text = df['Customer'],
marker = dict(
size = df['Customer'],
line = dict(width=0.5, color='rgb(40,40,40)'),
sizemode = 'area'
),
transforms = [dict(
type = 'aggregate',
groups = df['zip_city'],
aggregations = [dict(target = df['Customer'], func = 'count', enabled = True)]
)]
)]
layout = dict(title = 'Customers per US City')
fig = dict( data=data, layout=layout )
pyo.plot( fig, validate=False)
Update:
Can I access the result of the transforms argument directly in the data argument to show the number of customers per city?
You can create a list, that will contains what you want and then set text=list in data. Also do not forget specify hoverinfo='text'.
I am updated your code, so try this:
import pandas as pd
import plotly.offline as pyo
df = pd.DataFrame.from_dict({'Customer': [111, 222, 555, 666],
'zip_city': ['Aguadilla', 'Aguadilla', 'Arecibo', 'Wrangell'],
'zip_latitude':[18.498987, 18.498987, 18.449732,56.409507],
'zip_longitude':[-67.13699,-67.13699,-66.69879,-132.33822]})
customer = df['Customer'].tolist()
zipcity = df['zip_city'].tolist()
list = []
for i in range(len(customer)):
k = str(zipcity[i]) + ':' + str(customer[i])
list.append(k)
data = [dict(
type = 'scattergeo',
locationmode = 'USA-states',
lon = df['zip_longitude'],
lat = df['zip_latitude'],
text = list,
hoverinfo = 'text',
marker = dict(
size = df['Customer'],
line = dict(width=0.5, color='rgb(40,40,40)'),
sizemode = 'area'
),
transforms = [dict(
type = 'aggregate',
groups = df['zip_city'],
aggregations = [dict(target = df['Customer'], func = 'count', enabled = True)]
)]
)]
layout = dict(title = 'Customers per US City')
fig = dict(data=data, layout=layout)
pyo.plot(fig, validate=False)
I am trying to get a line plot via Bokeh in Python. I am new to Bokeh and I am trying to apply hover tool tips over the plot. The x-axis of the plot has Timestamp values which are converted into epoch string. I've reviewed some same problems here and tried to use the workaround for my case but it doesn't seem to work. On the plot it gives ??? where the time should show up.
Any suggestions for my code?
Timestamp is in format 2016-12-29 02:49:12
Also can someone tell how do I format x-axis ticks to show up vertically ?
p = figure(width=1100,height=300,tools='resize,pan,wheel_zoom,box_zoom,reset,previewsave,hover',logo=None)
p.title.text = "Time Series for Price in Euros"
p.grid.grid_line_alpha = 0
p.xaxis.axis_label = "Day"
p.yaxis.axis_label = "Euros"
p.ygrid.band_fill_color = "olive"
p.ygrid.band_fill_alpha = 0.1
p.circle(df['DateTime'],df['EuP'], size=4, legend='close',
color='darkgrey', alpha=0.2)
p.xaxis.formatter = DatetimeTickFormatter(formats=dict(
hours=["%d %B %Y"],
days=["%d %B %Y"],
months=["%d %B %Y"],
years=["%d %B %Y"],
))
source = ColumnDataSource(data=dict(time=[x.strftime("%Y-%m-%d %H:%M:%S")for x in df['DateTime']]))
hover = p.select(dict(type=HoverTool))
hover.tooltips = {"time":'#time', "y":"$y"}
hover.mode = 'mouse'
p.line(df['DateTime'],df['EuP'],legend='Price',color='navy',alpha=0.7)
Since this answer was originally posted, new work has gone into Bokeh to make things simpler. A datetime field can be formatted as a datetime directly by the hover tool, by specifying a formatter, e.g.:
HoverTool(tooltips=[('date', '#DateTime{%F}')],
formatters={'#DateTime': 'datetime'})
It is no longer necessary to pre-format date fields in the data source as below. For more information see Formatting Tooltip Fields
OLD ANSWER:
The problem with your tooltip is you created a source with the string representation of the dates, but the p.line() call is unaware of it. So you have to pass in a columndatasource that has the tooltip, the x and y values.
Here is a working variant of your code:
from bokeh.plotting import figure, show
from bokeh.models.formatters import DatetimeTickFormatter
from bokeh.models import ColumnDataSource
from bokeh.models.tools import HoverTool
import pandas as pd
import numpy as np
data = {
'DateTime' : pd.Series(
['2016-12-29 02:49:12',
'2016-12-30 02:49:12',
'2016-12-31 02:49:12'],
dtype='datetime64[ns]'),
'EuP' : [20,40,15]
}
df = pd.DataFrame(data)
df['tooltip'] = [x.strftime("%Y-%m-%d %H:%M:%S") for x in df['DateTime']]
p = figure(width=1100,height=300,tools='resize,pan,wheel_zoom,box_zoom,reset,previewsave,hover',logo=None)
p.title.text = "Time Series for Price in Euros"
p.grid.grid_line_alpha = 0
p.xaxis.axis_label = "Day"
p.yaxis.axis_label = "Euros"
p.ygrid.band_fill_color = "olive"
p.ygrid.band_fill_alpha = 0.1
p.circle(df['DateTime'],df['EuP'], size=4, legend='close',
color='darkgrey', alpha=0.2)
p.xaxis.formatter = DatetimeTickFormatter(formats=dict(
hours=["%d %B %Y"],
days=["%d %B %Y"],
months=["%d %B %Y"],
years=["%d %B %Y"],
))
hover = p.select(dict(type=HoverTool))
tips = [('when','#tooltip'), ('y','$y')]
hover.tooltips = tips
hover.mode = 'mouse'
p.line(x='DateTime', y='EuP', source=ColumnDataSource(df),
legend='Price',color='navy',alpha=0.7)
show(p)
Also note there is an open issue about the lack of formatting options in the bokeh tooltip. There might be an easier way to not have to format the datestrings as a separate column:
https://github.com/bokeh/bokeh/issues/1239
Also can someone tell how do I format x-axis ticks to show up vertically ?
They look fine to me, sorry I cannot help on that one.
Hope this helps!
PS it would be better next time if you posted a working script with import statements, and a mocked up dataframe to make it possible to test. It took some time to sort it all out. But I am learning Bokeh so that is fine :)
Sorry for not commenting, I don't have enough reputation for that.
The accepted answer by #Alex doesn't work for me (Bokeh 2.0.1), because it is lacking a simple #-sign in the formatter. The working code is this:
HoverTool(tooltips=[('date', '#DateTime{%F}')],
formatters={'#DateTime': 'datetime'})
I have created a wrapper for ScatterPlot in bokeh.
class Visualization():
WIDTH = 1000
TOOLS = "pan,wheel_zoom,box_zoom,reset,save"
class ScatterChart(Visualization):
def __init__(self, data, spec:Dict):
self.data = data
self.x_column = spec["x_axis"]
self.y_column = spec["y_axis"]
self.series_ = spec["series_column"]
self.xlabel = spec['xlabel']
self.ylabel = spec['ylabel']
self.title = spec['title']
def prepare_data(self):
# Get Axis Type
self.xtype = 'datetime' if self.data.dtypes[self.x_column].type is np.datetime64 else 'linear'
self.ytype = 'datetime' if self.data.dtypes[self.x_column].type is np.datetime64 else 'linear'
return self.data
def render(self):
df_ = self.prepare_data()
format_= {}
tool_tip=[]
# For axis
for col in [self.x_column, self.y_column , self.series_]:
if self.data.dtypes[col].type is np.datetime64:
format_['#' + str(col) ] = "datetime" # formatter
tool_tip.append(tuple([str(col) , '#' + str(col) + '{%F}'])) # tool-tip
else:
format_['#' + str(col) ] = "numeral" #
tool_tip.append(tuple([str(col) , '#' + str(col)]))
# print(format_)
# print(tool_tip)
# Add Hover parameters
hover = HoverTool(tooltips= tool_tip
, formatters=format_ )
p=figure(
width = super().WIDTH,
height = 500,
x_axis_label = self.xlabel,
x_axis_type=self.xtype,
y_axis_label = self.ylabel,
y_axis_type=self.ytype,
title = self.title,
tools = super().TOOLS
)
# Get Only Top 10 groups/series to display
for value, color in zip(islice(self.data.groupby(by=[self.series_]
)[self.series_].count().sort_values(ascending=False).rename('cnt').reset_index()[self.series_].tolist(), 10), Category10[10]):
p.scatter(
x=self.x_column,
y=self.y_column,
source=df_.loc[(df_[self.series_]==value)],
color=color,
legend_group=self.series_
)
p.add_tools(hover)
p.toolbar.logo = None
p.legend.location = "top_left"
p.legend.click_policy="hide"
return p
# end of ScatterChart
This is how I initialize this
from visualization import ScatterChart
sc = ScatterChart(
df,
{'x_axis' :'ts',
'y_axis': 'Discus',
'series_column': 'Competition',
'xlabel':'Discus',
'ylabel':'Javeline',
'title':'Discus Vs Javeline'
})
d = sc.render()
show(d)