Paul Engstler
Initial commit
92f0e98
from xml.etree import ElementTree as et
def make_svg_bargraph(labels, heights, categories=None, palette=None,
barheight=100, barwidth=12, show_labels=True, file_header=False,
data_url=False):
if palette is None:
palette = default_bargraph_palette
if categories is None:
categories = [('', len(labels))]
unitheight = float(barheight) / max(max(heights, default=1), 1)
textheight = barheight if show_labels else 0
labelsize = float(barwidth)
gap = float(barwidth) / 4
# textsize = barwidth + gap
textsize = barwidth + gap / 2
rollup = max(heights, default=1)
textmargin = float(labelsize) * 2 / 3
leftmargin = 32
rightmargin = 8
svgwidth = len(heights) * (barwidth + gap) + 2 * leftmargin + rightmargin
svgheight = barheight + textheight
# create an SVG XML element
svg = et.Element('svg', width=str(svgwidth), height=str(svgheight),
version='1.1', xmlns='http://www.w3.org/2000/svg')
# Draw the bar graph
basey = svgheight - textheight
x = leftmargin
# Add units scale on left
if len(heights):
for h in [1, (max(heights) + 1) // 2, max(heights)]:
et.SubElement(svg, 'text', x='0', y='0',
style=('font-family:sans-serif;font-size:%dpx;' +
'text-anchor:end;alignment-baseline:hanging;' +
'transform:translate(%dpx, %dpx);') %
(textsize, x - gap, basey - h * unitheight)).text = str(h)
et.SubElement(svg, 'text', x='0', y='0',
style=('font-family:sans-serif;font-size:%dpx;' +
'text-anchor:middle;' +
'transform:translate(%dpx, %dpx) rotate(-90deg)') %
(textsize, x - gap - textsize, basey - h * unitheight / 2)
).text = 'units'
# Draw big category background rectangles
for catindex, (cat, catcount) in enumerate(categories):
if not catcount:
continue
et.SubElement(svg, 'rect', x=str(x), y=str(basey - rollup * unitheight),
width=(str((barwidth + gap) * catcount - gap)),
height = str(rollup*unitheight),
fill=palette[catindex % len(palette)][1])
x += (barwidth + gap) * catcount
# Draw small bars as well as 45degree text labels
x = leftmargin
catindex = -1
catcount = 0
for label, height in zip(labels, heights):
while not catcount and catindex <= len(categories):
catindex += 1
catcount = categories[catindex][1]
color = palette[catindex % len(palette)][0]
et.SubElement(svg, 'rect', x=str(x), y=str(basey-(height * unitheight)),
width=str(barwidth), height=str(height * unitheight),
fill=color)
x += barwidth
if show_labels:
et.SubElement(svg, 'text', x='0', y='0',
style=('font-family:sans-serif;font-size:%dpx;text-anchor:end;'+
'transform:translate(%dpx, %dpx) rotate(-45deg);') %
(labelsize, x, basey + textmargin)).text = label
x += gap
catcount -= 1
# Text labels for each category
x = leftmargin
for cat, catcount in categories:
if not catcount:
continue
et.SubElement(svg, 'text', x='0', y='0',
style=('font-family:sans-serif;font-size:%dpx;text-anchor:end;'+
'transform:translate(%dpx, %dpx) rotate(-90deg);') %
(textsize, x + (barwidth + gap) * catcount - gap,
basey - rollup * unitheight + gap)).text = '%d %s' % (
catcount, cat + ('s' if catcount != 1 else ''))
x += (barwidth + gap) * catcount
# Output - this is the bare svg.
result = et.tostring(svg).decode('utf-8')
if file_header or data_url:
result = ''.join([
'<?xml version=\"1.0\" standalone=\"no\"?>\n',
'<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n',
'\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n',
result])
if data_url:
import base64
result = 'data:image/svg+xml;base64,' + base64.b64encode(
result.encode('utf-8')).decode('utf-8')
return result
default_bargraph_palette = [
('#4B4CBF', '#B6B6F2'),
('#55B05B', '#B6F2BA'),
('#50BDAC', '#A5E5DB'),
('#81C679', '#C0FF9B'),
('#F0883B', '#F2CFB6'),
('#D4CF24', '#F2F1B6'),
('#D92E2B', '#F2B6B6'),
('#AB6BC6', '#CFAAFF'),
]