#+TITLE: Program Usage History #+PROPERTY: header-args:python :session *history* #+PROPERTY: header-args:python+ :exports both #+PROPERTY: header-args:python+ :tangle yes #+PROPERTY: header-args:python+ :async yes #+begin_src elisp :exports none (setq-local org-image-actual-width '(1024)) #+end_src #+RESULTS: | 1024 | * Init #+begin_src python from matplotlib import pyplot as plt from matplotlib import dates as mdates from pprint import pprint from datetime import date, datetime import copy import json import matplotlib as mpl import numpy as np import pandas as pd mpl.rcParams['figure.dpi'] = 125 mpl.rcParams['figure.facecolor'] = '0.1' mpl.rcParams['hatch.linewidth'] = 4.0 #+end_src #+RESULTS: #+begin_src python plt.style.use('./palenight.mplstyle') #+end_src #+RESULTS: ** Colors #+begin_src python COLORS = { 'k': '#292d3e', 'r': '#f07178', 'g': '#c3e88d', 'y': '#ffcb6b', 'b': '#82aaff', 'm': '#c792ea', 'c': '#89ddff', 'w': '#d0d0d0', 'k-l': '#434758', 'r-l': '#ff8b92', 'g-l': '#ddffa7', 'y-l': '#ffe585', 'b-l': '#9cc4ff', 'm-l': '#e1acff', 'c-l': '#a3f7ff', 'w-l': '#ffffff' } #+end_src #+RESULTS: * Load data ** Read file #+begin_src python DATA = './history.json' with open(DATA, 'r') as f: data_o = json.load(f) # pprint(data) #+end_src #+RESULTS: ** Some preprocessing #+begin_src python def preprocess_data(data): data = copy.deepcopy(data) for category in data: for elem in category['elements']: for state in elem['states']: if 'TODO' in state['startDate']: print(f'TODO in {elem["name"]} (startDate)') state['startDate'] = state['startDate'].replace('TODO', '').strip() state['startDate'] = datetime.strptime(state['startDate'], '%Y-%m-%d') try: if 'TODO' in state['endDate']: print(f'TODO in {elem["name"]} (endDate)') state['endDate'] = state['endDate'].replace('TODO', '').strip() state['endDate'] = datetime.strptime(state['endDate'], '%Y-%m-%d') except KeyError: state['endDate'] = datetime.today() try: state['hatchColor'] = COLORS.get(state['hatchColor'], state['hatchColor']) except KeyError: pass elem['states'].sort(key=lambda k: k['startDate']) try: elem['color'] = COLORS.get(elem['color'], elem['color']) except KeyError: pass category['elements'].sort(key=lambda k: k['states'][0]['startDate']) return data data = preprocess_data(data_o) #+end_src #+RESULTS: : TODO in Linux Mint (startDate) : TODO in Windows (startDate) : TODO in Windows (endDate) : TODO in bash (startDate) : TODO in Mailspring (startDate) ** Crop data #+begin_src python def crop_data(data, start_date): data = copy.deepcopy(data) for category in data: for elem in category['elements']: elem['states'] = [ state for state in elem['states'] if state['endDate'] >= start_date ] for state in elem['states']: if state['startDate'] <= start_date: state['startDate'] = start_date category['elements'] = [ element for element in category['elements'] if len(element['states']) > 0 ] data = [ category for category in data if len(category['elements']) > 0 ] return data data = crop_data(data, datetime(2017, 1, 1)) #+end_src #+RESULTS: * Plot one category #+begin_src python CAT_W = 0.3 def process_category(category, ax, is_last=True, notes=None): ticks = list(range(0, -len(category['elements']), -1)) if notes is None: notes = [] min_start_date = 10**9 for elem, k in zip(category['elements'], ticks): for state in elem['states']: start_date, end_date = mdates.date2num(state['startDate']), mdates.date2num(state['endDate']) min_start_date = min(min_start_date, start_date) kwargs = {} kwargs['color'] = elem.get('color', None) if state.get('state', None) == 'dashed': kwargs['hatch'] = '//' kwargs['edgecolor'] = state.get('hatchColor', 'y') kwargs['lw'] = 0 note = state.get('note', None) if note is not None: notes.append(note) stars = '*' * len(notes) ax.text(end_date, k + CAT_W * 0.7, stars, size=15) bars = ax.broken_barh( [(start_date, end_date - start_date)], (k - CAT_W, CAT_W * 2), ,**kwargs ) ax.set_yticks(ticks) ax.set_yticklabels([elem['name'] for elem in category['elements']]) ax.set_axisbelow(True) ax.grid(True, alpha=0.25) if not is_last: ax.tick_params(axis='x', which='both', labelbottom=False, length=0) else: ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m')) return notes def plot_notes(fig, ax, notes, x=0.9, y = 0.03): if len(notes) > 0: notes_text = '' for i, note in enumerate(notes): notes_text += '*' * (i + 1) + ' ' + note + '\n' ax.text(x, y, notes_text, transform=fig.transFigure, va='top', ha='right') fig, ax = plt.subplots(figsize=(12, 3)) notes = process_category(data[0], ax) ax.set_title(data[0]['title']) plot_notes(fig, ax, notes) notes #+end_src #+RESULTS: :RESULTS: | Dual boot, rarely used | [[file:./.ob-jupyter/c1be31055726c333300f950d82c2c9d15f9b0205.png]] :END: * Plot all separately #+begin_src python :display plain def plot_category(category): fig, ax = plt.subplots(figsize=(12, len(category['elements']) * 0.7 + 1)) notes = process_category(category, ax) ax.set_title(category['title']) plot_notes(fig, ax, notes) return fig def plot_separate(data): for category in data: fig = plot_category(category) # fig.tight_layout() fig.savefig(f'./img/{category["title"].replace("/", "-")}.png') # plot_separate(data) #+end_src #+RESULTS: * Plot all in one chart #+begin_src python :file img/all.png def plot_all(data): fig, axes = plt.subplots( len(data), gridspec_kw={ 'height_ratios': [len(datum['elements']) for datum in data] }, figsize=(14, 11), sharex=True ) notes = [] for i, [datum, ax] in enumerate(zip(data, axes)): is_last = i == len(data) - 1 notes = process_category(datum, ax, is_last=is_last, notes=notes) ax.yaxis.set_label_position("right") ax.set_ylabel(datum['title'], labelpad=16, rotation=270) plot_notes(fig, ax, notes, y=0.09) # fig.tight_layout() fig.subplots_adjust(hspace=0.15) ax.text(0.075, 0.08, f'upd. {datetime.now().strftime("%Y-%m-%d")}', transform=fig.transFigure, va='top', ha='left') plot_all(data) #+end_src #+RESULTS: [[file:img/all.png]]