Hi all,
I found a interesting github-like activity tracker chart, it is implemented using matplotlib.
https://pythonhosted.org/calmap
my question is: can i embed the calmap chart into my dash app?
Hi all,
I found a interesting github-like activity tracker chart, it is implemented using matplotlib.
https://pythonhosted.org/calmap
my question is: can i embed the calmap chart into my dash app?
Is your matplotlib chart technically just a picture? If so this might help:
No. I would like to embed a real interactive chart. I guess we can create one using plotly heatmap but it is not trivial for a beginner like me
I am currently working on using a plotly heatmap as a calendar to select/deselect days of production in some analysis I am doing for my company.
I will return when I have a rough idea about how to make a good calendar with plotly heatmap…
So far I have something like the following, but I have yet to test how well it works.
year = datetime.datetime.now().year
d1 = datetime.date(year, 1, 1)
d2 = datetime.date(year, 12, 31)
delta = d2 - d1
dates_in_year = [d1 + datetime.timedelta(i) for i in range(delta.days+1)]
weekdays_in_year = [i.weekday() for i in dates_in_year]
weeknumber_of_dates = [int(i.strftime("%V")) for i in dates_in_year]
z = [0]*len(dates_in_year)
text = [str(i) for i in dates_in_year]
data = [
go.Heatmap(
x = weekdays_in_year,
y = weeknumber_of_dates,
z = z
)
]
layout = go.Layout(
title='test'
)
fig = go.Figure(data=data, layout=layout)
I have looked alot at the Github Commits per Day example in https://plot.ly/python/heatmaps/ to get where I am right now, if you need some inspiration.
Okay, so now I know I have something which works… I will add a few comments if you need 'em.
It creates a calendar with weeks along the y-axis and weekdays along the x-axis. When you hover a “cell” the hoverinfo is a string with the given date…
(Yes, I am too lazy to translate my danish strings into english…)
def holidays():
year = datetime.datetime.now().year
d1 = datetime.date(year, 1, 1)
d2 = datetime.date(year, 12, 31)
delta = d2 - d1
dates_in_year = [d1 + datetime.timedelta(i) for i in range(delta.days+1)] #gives me a list with datetimes for each day a year
weekdays_in_year = [i.weekday() for i in dates_in_year] #gives [0,1,2,3,4,5,6,0,1,2,3,4,5,6,...] (ticktext in xaxis dict translates this to weekdays
weeknumber_of_dates = [int(i.strftime("%V")) for i in dates_in_year] #gives [1,1,1,1,1,1,1,2,2,2,2,2,2,2,...] name is self-explanatory
z = np.random.uniform(low=0.0, high=1.0, size=(len(dates_in_year,))) #random numbers to give some mad colorz
text = [str(i) for i in dates_in_year] #gives something like list of strings like '2018-01-25' for each date. Used in data trace to make good hovertext.
data = [
go.Heatmap(
x = weekdays_in_year,
y = weeknumber_of_dates,
z = data_days,
text=text,
hoverinfo="text",
xgap=3, # this
ygap=3, # and this is used to make the grid-like apperance
showscale=False
)
]
layout = go.Layout(
title='Ferielukket Kalender',
height=1000,
xaxis=dict(
showline=True, #draw axis option (maybe I should remove this?)
tickmode="array",
ticktext=["Man", "Tirs", "Ons", "Tors", "Fre", "Lør", "Søn"],
tickvals=[0,1,2,3,4,5,6],
title="Ugedag"
),
yaxis=dict(
showline=True,
title="Uge Nr."
),
plot_bgcolor=('rgb(0,0,0)') #making grid appear black
)
fig = go.Figure(data=data, layout=layout)
return fig
awesome! it is pretty close to what i need, i will give it a try. may be need to tune the grid a bit so that it become a square grid with white border line.
To tune the grid, I think you need to do something smart™ with ticks, I will pass on this one.
If you want a white border you just need to change the plot_bgcolor option in the go.Layout
plot_bgcolor=('rgb(0,0,0)') #making grid appear black
plot_bgcolor=('rgb(255,255,255)') #making grid appear white
Looks good! Just to add some ideas to help refine the layout a little more. You can:
remove all plot/grid lines:
xaxis = dict(
showline = False,
showgrid = False,
zeroline = False
)
yaxis = dict(
showline = False,
showgrid = False,
zeroline = False
)
set the global font color:
font = dict(color = 'rgb(255, 255, 255)')
and you can use margins to make it skinny:
margin = dict(l=80,r=80,t=400,b=300)
this is the changes i make it more github-chart -look-alike (there is still some flaw with the grid dimension which has dependency on the period of my chart.) i wish i could remove the ‘-’ from the axis label to make the chart neater, but i couldn’t find a way.
code:
import datetime
import plotly.graph_objs as go
import numpy as np
import dash_core_components as dcc
import dash_html_components as html
import dashdef holidays():
year = datetime.datetime.now().yeard1 = datetime.date(year, 8, 1)
d2 = datetime.date(year+1, 7, 15)delta = d2 - d1
dates_in_year = [d1 + datetime.timedelta(i) for i in range(delta.days+1)] #gives me a list with datetimes for each day a year
weekdays_in_year = [i.weekday() for i in dates_in_year] #gives [0,1,2,3,4,5,6,0,1,2,3,4,5,6,…] (ticktext in xaxis dict translates this to weekdays
weeknumber_of_dates = [i.strftime(“%Gww%V”)[2:] for i in dates_in_year] #gives [1,1,1,1,1,1,1,2,2,2,2,2,2,2,…] name is self-explanatory
z = np.random.randint(2, size=(len(dates_in_year)))
text = [str(i) for i in dates_in_year] #gives something like list of strings like ‘2018-01-25’ for each date. Used in data trace to make good hovertext.
#4cc417 green #347c17 dark green
colorscale=[[False, ‘#eeeeee’], [True, ‘#76cf63’]]data = [
go.Heatmap(
x = weeknumber_of_dates,
y = weekdays_in_year,
z = z,
text=text,
hoverinfo=“text”,
xgap=3, # this
ygap=3, # and this is used to make the grid-like apperance
showscale=False,
colorscale=colorscale
)
]
layout = go.Layout(
title=‘activity chart’,
height=280,
yaxis=dict(
showline = False, showgrid = False, zeroline = False,
tickmode=“array”,
ticktext=[“Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”, “Sun”],
tickvals=[0,1,2,3,4,5,6],
),
xaxis=dict(
showline = False, showgrid = False, zeroline = False,
),
font={‘size’:‘10’, ‘color’:‘#9e9e9e’},
plot_bgcolor=(‘#fff’),
margin = dict(t=40),
)fig = go.Figure(data=data, layout=layout)
return figapp = dash.Dash()
app.layout = html.Div([
dcc.Graph(id=‘heatmap-test’, figure=holidays(), config={‘displayModeBar’: False})
])
outcome:
Hey @zhsee
You can remove the ticks using ticks = ' '
in your xaxis and yaxis dict https://plot.ly/python/axes/#toggling-axes-lines-ticks-labels-and-autorange
cool! now it become neater i am run into another issue here, instead of two state my ‘z’ value now include another state (0, 1, 2) and i wish to use discrete color scale with green for 1 and dark green for 2 but it turns out giving me red and orange.
colorscale=[[0, ‘rgb(238, 238, 238)’], [1, ‘rgb(48, 137, 29)’], [2, ‘rgb(118, 207, 99)’]]
Colorscale maps between 0 (lowest) and 1 (highest) https://plot.ly/python/reference/#heatmap-colorscale Thus, try something like https://plot.ly/python/colorscales/#custom-discretized-heatmap-colorscale
i love this community so much
Also see Auto generated Heat map Calendar
Another solution would be to use the conditional formatting options in the DataTable: https://dash.plotly.com/datatable/conditional-formatting
I took this a little further with month labels, month separator lines, and multiple years:
import datetime
import plotly.graph_objs as go
import numpy as np
import dash_core_components as dcc
import dash_html_components as html
import dash
def display_year(z,
year: int = None,
month_lines: bool = True,
fig=None,
row: int = None):
if year is None:
year = datetime.datetime.now().year
data = np.ones(365) * np.nan
data[:len(z)] = z
d1 = datetime.date(year, 1, 1)
d2 = datetime.date(year, 12, 31)
delta = d2 - d1
month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
month_positions = (np.cumsum(month_days) - 15)/7
dates_in_year = [d1 + datetime.timedelta(i) for i in range(delta.days+1)] #gives me a list with datetimes for each day a year
weekdays_in_year = [i.weekday() for i in dates_in_year] #gives [0,1,2,3,4,5,6,0,1,2,3,4,5,6,…] (ticktext in xaxis dict translates this to weekdays
weeknumber_of_dates = [int(i.strftime("%V")) if not (int(i.strftime("%V")) == 1 and i.month == 12) else 53
for i in dates_in_year] #gives [1,1,1,1,1,1,1,2,2,2,2,2,2,2,…] name is self-explanatory
text = [str(i) for i in dates_in_year] #gives something like list of strings like ‘2018-01-25’ for each date. Used in data trace to make good hovertext.
#4cc417 green #347c17 dark green
colorscale=[[False, '#eeeeee'], [True, '#76cf63']]
# handle end of year
data = [
go.Heatmap(
x=weeknumber_of_dates,
y=weekdays_in_year,
z=data,
text=text,
hoverinfo='text',
xgap=3, # this
ygap=3, # and this is used to make the grid-like apperance
showscale=False,
colorscale=colorscale
)
]
if month_lines:
kwargs = dict(
mode='lines',
line=dict(
color='#9e9e9e',
width=1
),
hoverinfo='skip'
)
for date, dow, wkn in zip(dates_in_year,
weekdays_in_year,
weeknumber_of_dates):
if date.day == 1:
data += [
go.Scatter(
x=[wkn-.5, wkn-.5],
y=[dow-.5, 6.5],
**kwargs
)
]
if dow:
data += [
go.Scatter(
x=[wkn-.5, wkn+.5],
y=[dow-.5, dow - .5],
**kwargs
),
go.Scatter(
x=[wkn+.5, wkn+.5],
y=[dow-.5, -.5],
**kwargs
)
]
layout = go.Layout(
title='activity chart',
height=250,
yaxis=dict(
showline=False, showgrid=False, zeroline=False,
tickmode='array',
ticktext=['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
tickvals=[0, 1, 2, 3, 4, 5, 6],
autorange="reversed"
),
xaxis=dict(
showline=False, showgrid=False, zeroline=False,
tickmode='array',
ticktext=month_names,
tickvals=month_positions
),
font={'size':10, 'color':'#9e9e9e'},
plot_bgcolor=('#fff'),
margin = dict(t=40),
showlegend=False
)
if fig is None:
fig = go.Figure(data=data, layout=layout)
else:
fig.add_traces(data, rows=[(row+1)]*len(data), cols=[1]*len(data))
fig.update_layout(layout)
fig.update_xaxes(layout['xaxis'])
fig.update_yaxes(layout['yaxis'])
return fig
def display_years(z, years):
fig = make_subplots(rows=len(years), cols=1, subplot_titles=years)
for i, year in enumerate(years):
data = z[i*365 : (i+1)*365]
display_year(data, year=year, fig=fig, row=i)
fig.update_layout(height=250*len(years))
return fig
z = np.random.randint(2, size=(500,))
display_years(z, (2019, 2020))
SO nice @bendichter !
Hello Ben,
Thank you for sharing the code. It is very helpful.
When you generate the chart for 2022,
The expected behaviour would be that the 01-01-2022 and 02-01-2022 are at the beginning of the chart, 2022-12-31 shows up inside of December 2022 month, 2023-01-01 is not on the chart.
I am not sharing the code as I used copy-paste of your code
Please see screenshots attached.
Another questions is how to make the colour scale discrete? z-values in my case are 0,1,2 and I would like to give them fixed colours. How do I do that?
I will appreciate you help.
Thank you
Emil
@emilmajkowski thanks for pointing out these issues. I I made some corrections in the gist. Does it work for you now?
Hello Ben,
Thank you for your reply. Yes, it works perfectly fine now. Thanks a lot!
Btw. I fixed the colours to 0,1,2 values by providing zmin and zmax in the go.Heatmap
Best,
Emil