HOME/Articles/

matplotlib example plot calendar (snippet)

Article Outline

Python matplotlib example 'plot calendar'

Functions in program:

  • def plot_calendar_year(data, column_name, orientation='h'):
  • def plot_calendars(data, column_name, orientation='v'):
  • def draw_month_labels(horizontal):
  • def draw_month_boundary(ax, df, horizontal):
  • def draw_day_boundary(ax, num_weeks):
  • def draw_calendar(ax, df, horizontal, column_name):
  • def get_colors():
  • def set_matplotlib_params():

Modules used in program:

  • import calendar
  • import matplotlib
  • import palettable
  • import matplotlib.colorbar as cbar
  • import brewer2mpl
  • import numpy as np

python plot calendar

Python matplotlib example: plot calendar

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')