|
from typing import Tuple |
|
import drawSvg |
|
import json |
|
import datetime |
|
import math |
|
import colormath.color_objects |
|
import colormath.color_conversions |
|
import calendar |
|
|
|
with open('contributions.json', 'r') as file: |
|
data = json.load(file) |
|
|
|
transpose = False |
|
|
|
######## Sizes |
|
padding = 24 |
|
block_size = 16 |
|
text_left_margin = 8 |
|
text_size = 16 |
|
text_width = 50 |
|
line_height = 1.4 |
|
block_margin = 4 |
|
|
|
######## Colors |
|
color_a = 27 |
|
color_b = -29 |
|
color_a_alt = 80 |
|
color_b_alt = 77 |
|
color_max_l = 100 |
|
color_min_l = 17 |
|
|
|
color_empty = '#33044b' |
|
color_font = '#BCA37F' |
|
color_bg = '#25033E' |
|
#============= |
|
|
|
if not transpose: |
|
width = padding * 2 + block_size * 7 + block_margin * 7 + text_left_margin + text_width |
|
height = padding * 2 + block_size * 53 + block_margin * 52 |
|
else: |
|
width = padding * 2 + block_size * 53 + block_margin * 52 |
|
height = padding * 2 + block_size * 7 + block_margin * 7 + text_size * line_height |
|
|
|
svg = drawSvg.Drawing(width, height, origin=(0, -height)) |
|
|
|
from_time = datetime.date(2020, 1, 1) |
|
to_time = datetime.date(2020, 12, 31) |
|
|
|
max_contrib = 1 |
|
|
|
|
|
def should_draw(date: datetime.date): |
|
return date >= from_time and date <= to_time |
|
|
|
|
|
def week_day(date: datetime.date) -> Tuple[int, int]: |
|
day = date.weekday() |
|
date_ordinal = date.toordinal() |
|
first_day = date.replace(month=1, day=1) |
|
first_ordinal = first_day.toordinal() |
|
week = math.floor((date_ordinal - first_ordinal + first_day.weekday()) / 7) |
|
return (week, day) |
|
|
|
|
|
def position(wd: Tuple[int, int]) -> Tuple[int, int]: |
|
if not transpose: |
|
return ( |
|
wd[1] * (block_size + block_margin) + padding, |
|
-((wd[0] + 1) * (block_size + block_margin) + padding), |
|
) |
|
else: |
|
return ( |
|
((wd[0]) * (block_size + block_margin) + padding), |
|
-((wd[1] + 1) * (block_size + block_margin) + padding), |
|
) |
|
|
|
|
|
def lerp(x, a, b): |
|
return x * (b - a) + a |
|
|
|
|
|
def color(count: int) -> str: |
|
if count == 0: return color_empty |
|
percentage = count / max_contrib |
|
percentage **= 0.6 |
|
l = lerp(percentage, color_min_l, color_max_l) |
|
a = lerp(percentage, color_a, color_a_alt) |
|
b = lerp(percentage, color_b, color_b_alt) |
|
|
|
fill_color = colormath.color_objects.LabColor(l, a, b, illuminant='d65') |
|
rgb_fill = convert_lab_to_rgb(fill_color) |
|
return rgb_fill.get_rgb_hex() |
|
|
|
|
|
def convert_lab_to_rgb( |
|
lab: colormath.color_objects.LabColor |
|
) -> colormath.color_objects.sRGBColor: |
|
rgb_fill = colormath.color_conversions.convert_color( |
|
lab, colormath.color_objects.sRGBColor) |
|
rgb_fill = colormath.color_objects.sRGBColor(rgb_fill.clamped_rgb_r, |
|
rgb_fill.clamped_rgb_g, |
|
rgb_fill.clamped_rgb_b) |
|
return rgb_fill |
|
|
|
|
|
def position_line_end(pos: Tuple[int, int]) -> Tuple[int, int]: |
|
if not transpose: |
|
return ( |
|
padding + (block_margin + block_size) * 7 + text_left_margin, |
|
pos[1], |
|
) |
|
else: |
|
return ( |
|
pos[0], |
|
-(padding + |
|
(block_margin + block_size) * 7 + text_size + text_left_margin), |
|
) |
|
|
|
|
|
def draw_item(item): |
|
date = datetime.date.fromisoformat(item['date']) |
|
if (should_draw(date)): |
|
draw_position = position(week_day(date)) |
|
|
|
svg.append( |
|
drawSvg.Rectangle(draw_position[0], |
|
draw_position[1], |
|
block_size, |
|
block_size, |
|
fill=color(item['count']))) |
|
if date.day == 1: |
|
pos = position_line_end(draw_position) |
|
svg.append( |
|
drawSvg.Text( |
|
calendar.month_abbr[date.month], |
|
text_size, |
|
pos[0], |
|
pos[1], |
|
font_family='IBM Plex Mono', |
|
font_weight=800, |
|
fill=color_font, |
|
)) |
|
|
|
|
|
for contribution in data['contributions']: |
|
date = datetime.date.fromisoformat(contribution['date']) |
|
if should_draw(date): |
|
if contribution['count'] > max_contrib: |
|
max_contrib = contribution['count'] |
|
|
|
svg.append(drawSvg.Rectangle(0, -height, width, height, fill=color_bg)) |
|
|
|
for contribution in data['contributions']: |
|
draw_item(contribution) |
|
|
|
svg.saveSvg("result.svg") |
|
svg.setPixelScale(3) |
|
svg.savePng("result.png") |