Skip to content

Instantly share code, notes, and snippets.

@lynzrand
Last active December 8, 2020 17:44
Show Gist options
  • Save lynzrand/3f49256559f34de7d049d9d742ff9aec to your computer and use it in GitHub Desktop.
Save lynzrand/3f49256559f34de7d049d9d742ff9aec to your computer and use it in GitHub Desktop.
Github Contribution Renderer
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")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment