Skip to content

Instantly share code, notes, and snippets.

@galvanic
Last active March 3, 2021 23:12
Show Gist options
  • Save galvanic/71a44709da1da605d712 to your computer and use it in GitHub Desktop.
Save galvanic/71a44709da1da605d712 to your computer and use it in GitHub Desktop.
Plot a calendar in python using matplotlib.
import numpy as np
import brewer2mpl
import matplotlib.colorbar as cbar
from matplotlib import rcParams
import palettable
from dateutil import rrule
import matplotlib
import calendar
from random import random
def set_matplotlib_params():
"""
Set matplotlib defaults to nicer values
"""
rcParams['mathtext.default'] ='regular'
#rcParams['axes.labelsize'] = 11
#rcParams['xtick.labelsize'] = 11
#rcParams['ytick.labelsize'] = 11
#rcParams['legend.fontsize'] = 11
rcParams['font.family'] = 'sans-serif'
rcParams['font.serif'] = ['Helvetica']
#rcParams['figure.figsize'] = 16, 40 ## depends on number of years
def get_colors():
"""
Get palettable colors, which are nicer
"""
bmap = palettable.colorbrewer.sequential.BuPu_9.mpl_colors
return bmap
def draw_calendar(ax, df, horizontal, column_name):
'''
Draws one calendar year
'''
max_val = max(df[column_name])
min_val = min(df[column_name])
color_list = get_colors()
color_len = len(color_list)
for i in range(len(df.index)):
if(df[column_name][i] > 0):
cur_wk = dt.date(df.year[i], df.month[i], df.day[i]).isocalendar()[1]
cur_yr = dt.date(df.year[i], df.month[i], df.day[i]).isocalendar()[0]
if((cur_yr < df.year[i]) and (df.day[i]<7)):
cur_wk = 0
if(cur_yr>df.year[i]):
cur_wk = 53
day_of_week = df.index[i].weekday()
#normalise each data point to val - note added a very small amount
#to data range, so that we never get exactly 1.0
val = float((df[column_name][i]-min_val)/float(max_val-min_val + 0.000001))
if horizontal:
rect = matplotlib.patches.Rectangle((cur_wk,day_of_week), 1, 1, color = color_list[int(val*color_len)])
else:
rect = matplotlib.patches.Rectangle((day_of_week,cur_wk), 1, 1, color = color_list[int(val*color_len)],label='a')
ax.add_patch(rect)
return
def draw_day_boundary(ax, num_weeks):
'''
Draws calendar grid, seperating the days and weeks in a month
'''
line_color = 'white'
line_width = 0.5
line_style = '-'
## lines between weeks
for i in range(num_weeks):
ax.plot([0, 7], [i, i],
color=line_color, linestyle=line_style, lw=line_width)
## lines between days
for j in range(7):
ax.plot([j, j], [0, num_weeks],
color=line_color, linestyle=line_style, lw=line_width)
return
def draw_month_boundary(ax, df, horizontal):
line_color = 'black'
line_width = 1.25
line_style = '-'
month_seq = rrule.rrule(rrule.MONTHLY, dtstart=df.index[0], until=df.index[-1])
for mon in month_seq:
num_days = calendar.monthrange(mon.year,mon.month)[1]
current_week = dt.date(mon.year, mon.month, num_days).isocalendar()[1]
current_year = dt.date(mon.year, mon.month, num_days).isocalendar()[0]
day_of_week = dt.date(mon.year, mon.month, num_days).weekday()
if current_year == mon.year and mon.month <> 12:
if horizontal:
ax.plot([current_week+1, current_week+1], [0, day_of_week+1],
color=line_color, linestyle=line_style, lw=line_width)
else:
ax.plot([0, day_of_week+1], [current_week+1, current_week+1],
color=line_color, linestyle=line_style, lw=line_width)
if day_of_week != 6:
if horizontal:
ax.plot([current_week+1, current_week], [day_of_week+1, day_of_week+1],
color=line_color, linestyle=line_style, lw=line_width) # Parallel to X-Axis
ax.plot([current_week, current_week], [day_of_week+1, 7],
color=line_color, linestyle=line_style, lw=line_width)
else:
ax.plot([day_of_week+1, day_of_week+1], [current_week+1, current_week],
color=line_color, linestyle=line_style, lw=line_width) # Parallel to Y-axis
ax.plot([day_of_week+1, 7], [current_week, current_week],
color=line_color, linestyle=line_style, lw=line_width)
return
def draw_month_labels(horizontal):
'''
'''
pos_from_edge = 3
for idx in range(1,13):
month_name = dt.date(1900, idx, 1).strftime('%b')
if horizontal:
plt.text(pos_from_edge, 8, month_name, fontsize=14)
else:
plt.text(8, pos_from_edge, month_name, fontsize=14)
pos_from_edge += int(random()+4.2) ## bias for 4
return
def plot_calendars(data, column_name, orientation='v'):
'''
data: already processed to have year, month and day columns, plus a numeric column,
with data that will be shown along a colour axis
'''
df = data[['year','month','day', column_name]]
## global parameters
horizontal = True if orientation == 'h' else False
num_yrs = len(df['year'].unique())
max_val = max(df[column_name])
min_val = min(df[column_name])
figscale = 0.4
longueur = num_yrs*52*figscale
largeur = num_yrs*7*figscale
figsize = (longueur, largeur) if horizontal else (largeur, longueur)
# Draw blank figure
fig = plt.figure(figsize=figsize)
set_matplotlib_params()
plt.subplots_adjust(hspace=0.3)
plt.axis('off')
plt.axes().set_aspect('equal')
for idx, year in enumerate(df['year'].unique()):
sub_df = df[df['year'] == year]
if horizontal:
ax = plt.subplot2grid((num_yrs, 2), (idx, 1), rowspan=1, colspan=2)
else:
ax = plt.subplot2grid((2, num_yrs), (1, idx), rowspan=2, colspan=1)
ax.xaxis.tick_top()
ax.axes.get_xaxis().set_ticks([])
ax.axes.get_yaxis().set_ticks([])
ax.axis('off')
ax.set_title(str(year), fontsize=18)
period = np.timedelta64(sub_df.index[-1] - sub_df.index[0])
num_weeks = int(np.ceil( period/(np.timedelta64(1,'W')) )+2)
if horizontal:
plt.xlim(0, num_weeks)
plt.ylim(0, 7)
else:
plt.xlim(0, 7)
plt.ylim(num_weeks, 0)
draw_calendar(ax, sub_df, horizontal, column_name)
draw_day_boundary(ax, num_weeks)
draw_month_boundary(ax, sub_df, horizontal)
if year == min(df['year'].unique()) and horizontal:
draw_month_labels(horizontal)
elif year == max(df['year'].unique()) and not horizontal:
draw_month_labels(horizontal)
def draw_legend():
# plot an overall colorbar type legend
ax_colorbar = plt.subplot2grid((4, num_yrs), (3,0), rowspan=1, colspan=num_yrs)
mappableObject = matplotlib.cm.ScalarMappable(cmap = palettable.colorbrewer.sequential.BuPu_9.mpl_colormap)
mappableObject.set_array(np.array(df[column_name]))
col_bar = fig.colorbar(mappableObject, cax=ax_colorbar, orientation='horizontal',
boundaries = np.arange(min_val, max_val, (max_val-min_val)/10))
# You can change the boundaries kwarg to either make the scale look less boxy (increase 10)
# or to get different values on the tick marks, or even omit it altogether to let
col_bar.set_label(column_name)
ax_colorbar.set_title(column_name + ' color mapping')
def draw_bar_plot():
# draw the top overall graph
ax0 = plt.subplot2grid((4, num_yrs), (0,0), rowspan=1, colspan=num_yrs)
x_axis = np.arange(0.325, num_yrs+1,1.1)
bar_val = df[column_name].groupby(df['year']).mean()
err_val = df[column_name].groupby(df['year']).std()
ax0.bar(x_axis, bar_val, yerr=err_val,
linewidth=0, width=0.25, color='g',
error_kw=dict(ecolor='gray', lw=1))
ax0.axes.get_xaxis().set_ticks([])
ax0.spines['top'].set_visible(False)
ax0.spines['right'].set_visible(False)
plt.ylabel(column_name)
ax0.axes.get_yaxis().set_ticks([])
return
plt.tight_layout()
plt.show()
return
plot_calendars(calendar_df, 'size', 'h')
def plot_calendar_year(data, column_name, orientation='h'):
horizontal = True if orientation == 'h' else False
figscale = 0.4
longueur = 52*figscale
largeur = 7*figscale
figsize = (longueur, largeur) if horizontal else (largeur, longueur)
fig = plt.figure(figsize=figsize)
ax = fig.add_subplot(111)
plt.axis('off')
if horizontal:
plt.xlim(0, 52)
plt.ylim(0, 7)
else:
plt.xlim(0, 7)
plt.ylim(52, 0)
draw_calendar(ax, data, horizontal, column_name)
draw_day_boundary(ax, 52)
draw_month_boundary(ax, data, horizontal)
plt.show()
return
#plot_calendar_year(calendar_df[calendar_df.year == 2015], 'size', 'h')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment