mirror of
https://github.com/SqrtMinusOne/dotfiles.git
synced 2025-12-11 19:45:25 +03:00
256 lines
7 KiB
Org Mode
256 lines
7 KiB
Org Mode
#+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]]
|