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([ '\n', '\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'), ]