Skip to content

Instantly share code, notes, and snippets.

@a-poor
Last active April 28, 2022 23:53
Show Gist options
  • Save a-poor/173f0d190634065bfd7ad73eb882e637 to your computer and use it in GitHub Desktop.
Save a-poor/173f0d190634065bfd7ad73eb882e637 to your computer and use it in GitHub Desktop.
import jinja2
from IPython.display import display_svg
data = {
"title":"Time-Price Comparison",
"subtitle":"Scatter plot of time vs price.",
"data":[
{"time":2,"price":1,"callout":False},
{"time":3,"price":2,"callout":False},
{"time":4,"price":3,"callout":True},
{"time":5,"price":4,"callout":True},
{"time":6,"price":5,"callout":False}
],
"xlabel":"Time (PM)",
"ylabel":"Price",
"caption":"Note: Data made up from my imagination and therefore not real. [2020]"
}
layout = {
"data": {
"time_min":1,
"time_max":7,
"price_min":0,
"price_max":6
},
"plot": {
"xmin": 80,
"ymin": 110,
"xmax": 565,
"ymax": 300,
"pad": 10,
"point_radius": 5
},
"color": {
"color_on": "hsl(230,70%,60%)",
"color_off": "hsl(0,0%,50%)",
"axis": "hsl(0,0%,30%)",
"title": "hsl(0,0%,0%)",
"subtitle": "hsl(0,0%,50%)",
"caption": "hsl(0,0%,50%)",
"background": "hsl(0,0%,95%)"
}
}
def make_scale(dmin,dmax,rmin,rmax):
dwidth = dmax - dmin
rwidth = rmax - rmin
map_ = rwidth / dwidth
def scale(n):
return (n - dmin) * map_ + rmin
return scale
xscale = make_scale(
layout['data']['time_min'],
layout['data']['time_max'],
layout['plot']['xmin'],
layout['plot']['xmax']
)
yscale = make_scale(
layout['data']['price_min'],
layout['data']['price_max'],
layout['plot']['ymax'],
layout['plot']['ymin']
)
timerange = range(
layout['data']['time_min'],
layout['data']['time_max']+1
)
xticks = [{"text":f"{i}:00","pos":xscale(i)} for i in timerange]
pricerange = range(
layout['data']['price_min'],
layout['data']['price_max']+1
)
yticks = [{"text":f"${i}","pos":yscale(i)} for i in pricerange]
plot_template = """
<svg
xmlns="http://www.w3.org/2000/svg"
xml:lang="en"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 600 400"
width="600">
<!-- Set the background color -->
<rect
id="background"
x="0"
y="0"
width="600"
height="400"
fill="{{ layout.color.background }}"
/>
<!-- Create the Scatterplot -->
<g id="plot-layer">
{%- for p in data.data %}
<circle
cx="{{ xscale(p.time) }}"
cy="{{ yscale(p.price) }}"
r="{{ layout.plot.point_radius }}"
{% if p.callout -%}
fill="{{ layout.color.color_on }}"
{% else -%}
fill="{{ layout.color.color_off }}"
{% endif -%}
/>
{%- endfor %}
</g>
<!-- Draw the Axis, Ticks, & Labels -->
<g id="axis-layer">
<!-- Draw the axis -->
<polyline
points="
{{ layout.plot.xmin }} {{ layout.plot.ymin }}
{{ layout.plot.xmin }} {{ layout.plot.ymax }}
{{ layout.plot.xmax }} {{ layout.plot.ymax }}"
stroke="{{ layout.color.axis }}"
stroke-linecap="round"
fill="transparent"
/>
<!-- Draw the yaxis label (rotated) -->
<g transform="translate(0,20)">
<text
x="30"
y="176.5"
fill="{{ layout.color.axis }}"
font-family="helvetica"
text-align="center"
transform="rotate(-90,30,176.5)"
>
{{ data.ylabel }}
</text>
</g>
<!-- Draw the yticks -->
<g id="yticks">
{% for t in yticks %}
<line
x1="{{ layout.plot.xmin }}"
y1="{{ t.pos }}"
x2="{{ layout.plot.xmin - layout.plot.pad }}"
y2="{{ t.pos }}"
stroke="{{ layout.color.axis }}"
stroke-linecap="round"
/>
<text
x="{{ layout.plot.xmin }}"
y="{{ t.pos }}"
fill="{{ layout.color.axis }}"
font-family="helvetica"
text-anchor="end"
dx="-15"
dy="5"
font-weight="lighter"
>
{{ t.text }}
</text>
{%- endfor %}
</g>
<!-- Draw the x axis label -->
<text
x="322.5"
y="350"
fill="{{ layout.color.axis }}"
font-family="helvetica"
text-anchor="middle"
>
{{ data.xlabel }}
</text>
<!-- Draw the xticks -->
<g id="xticks">
{%- for t in xticks -%}
<line
x1="{{ t.pos }}"
y1="{{ layout.plot.ymax }}"
x2="{{ t.pos }}"
y2="{{ layout.plot.ymax + layout.plot.pad }}"
stroke="{{ layout.color.axis }}"
stroke-linecap="round"
/>
<text
x="{{ t.pos }}"
y="{{ layout.plot.ymax }}"
fill="{{ layout.color.axis }}"
font-family="helvetica"
text-anchor="middle"
dx="0"
dy="25"
font-weight="lighter"
>
{{ t.text }}
</text>
{% endfor -%}
</g>
</g>
<!-- Add the title, subtitle, & description -->
<g id="text-layer">
<text
x="15"
y="40"
style="font-family: helvetica; font-size: 28px; font-weight: normal;"
fill="{{ layout.color.title }}"
>
{{ data.title }}
</text>
<text
x="15"
y="70"
style="font-family: helvetica; font-size: 18px; font-weight: normal;"
fill="{{ layout.color.subtitle }}">
{{ data.subtitle }}
</text>
<text
x="15"
y="385"
style="font-family: helvetica; font-size: 10px; font-weight: normal;"
fill="{{ layout.color.caption }}">
{{ data.caption }}
</text>
</g>
</svg>"""
# Use the string to create a jinja template
jinja_template = jinja2.Template(
plot_template
)
# Render it by passing the data
# to the template
svg_string = jinja_template.render(
data=data,
layout=layout,
xticks=xticks,
yticks=yticks,
xscale=xscale,
yscale=yscale
)
# The result is just a string
# with the rendered plot
type(svg_string) == str
# Now, optionally, if you want
# to display the SVG in Jupyter
# you can do this...
display_svg(
svg_string,
raw=True
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment