|
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 / 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 |
|
|
|
|
|
svg = et.Element('svg', width=str(svgwidth), height=str(svgheight), |
|
version='1.1', xmlns='http://www.w3.org/2000/svg') |
|
|
|
|
|
basey = svgheight - textheight |
|
x = leftmargin |
|
|
|
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' |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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'), |
|
] |
|
|
|
|