Plot a calendar in python using matplotlib. The days are drawn on a 7*52 heatmap grid, with the months clearly delineated.
Slightly cleaned up from https://gist.github.com/rsnape/98bbf68e333b0948192e found in this Stack Overflow answer
Plot a calendar in python using matplotlib. The days are drawn on a 7*52 heatmap grid, with the months clearly delineated.
Slightly cleaned up from https://gist.github.com/rsnape/98bbf68e333b0948192e found in this Stack Overflow answer
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') |