Skip to content

Instantly share code, notes, and snippets.

@tbnorth
Last active December 19, 2024 16:46
Show Gist options
  • Save tbnorth/878161e61f0cc3457d8292719428c41c to your computer and use it in GitHub Desktop.
Save tbnorth/878161e61f0cc3457d8292719428c41c to your computer and use it in GitHub Desktop.
drawsvg experiment, draw a fish timeline chart
"""Draw a simple SVG timeline chart with Python."""
import sys
import drawsvg as draw
w = 1200
h = 800
ox = -100
oy = -400
def create_drawing() -> draw.Drawing:
"""Create a drawing with a timeline."""
dw = draw.Drawing(w, h, origin=(ox, oy))
# white background
dw.append(draw.Rectangle(ox, oy, w, h, fill="#f0f0f0"))
# |--+--+--+--| timeline
# Q1 Q2 Q3 Q4
dw.append(draw.Lines(0, 0, 1000, 0, stroke="black", stroke_width=3))
for line in range(0, 1001, 250):
dw.append(draw.Lines(line, 10, line, -10, stroke="black", stroke_width=3))
dw.append(draw.Text("Q1", 25, x=125, y=25, text_anchor="middle"))
dw.append(draw.Text("Q2", 25, x=375, y=25, text_anchor="middle"))
dw.append(draw.Text("Q3", 25, x=595, y=25, text_anchor="middle"))
dw.append(draw.Text("Q4", 25, x=875, y=25, text_anchor="middle"))
return dw
def draw_task(
dw: draw.Drawing, # drawing context
text: str, # text to lable task
done: float, # fraction done, 0-1
pos: str, # position in year, 0-1000
dir: str = "up", # up: below line sloping up, "down", above sloping down
) -> None:
"""Draw a task on the timeline."""
slope = 0.5
x0 = pos - 300 * slope
y0 = 300
x1 = pos
y1 = 0
off = -1
if dir != "up":
y0 *= -1
off *= -1
p = draw.Lines( # done part of line, blue
x0,
y0,
(x1 - x0) * done + x0,
(y1 - y0) * done + y0,
stroke="blue",
stroke_width=3,
)
dw.append(p)
p = draw.Lines( # remaining part of line, red
(x1 - x0) * done + x0,
(y1 - y0) * done + y0,
x1,
y1,
stroke="red",
stroke_width=3,
)
dw.append(p)
# Couldn't find a way to offset text from line, so draw a second, invisible
# line to put the text on.
offs = 33 # offset for two lines of text
p = draw.Lines(x0 + offs * off, y0, x1 + offs * off, y1, stroke_width=0)
dw.append(p)
dw.append(draw.Text(text.split(r"\n"), 22, path=p))
def main() -> None:
"""Generate chart from inputs."""
dw = create_drawing()
with open(sys.argv[1]) as data:
title = data.readline()
tasks = list(i.split(None, 3) for i in data if i.strip())
dw.append(draw.Text(title, 32, x=500, y=-360, text_anchor="middle"))
for done_txt, pos_txt, dir, text in tasks:
done = float(done_txt)
pos = float(pos_txt)
draw_task(dw, text, done, pos, dir)
dw.save_svg(f"{sys.argv[2]}.svg")
dw.save_png(f"{sys.argv[2]}.png")
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment