121 lines
3.1 KiB
Python
121 lines
3.1 KiB
Python
#!/usr/bin/env python
|
|
|
|
import math
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
|
|
import six
|
|
|
|
import leather.svg as svg
|
|
from leather import theme
|
|
from leather.utils import IPythonSVG
|
|
|
|
|
|
class Grid(object):
|
|
"""
|
|
A container for a set of :class:`.Chart` instances that are rendered in a
|
|
grid layout.
|
|
"""
|
|
def __init__(self):
|
|
self._charts = []
|
|
|
|
def add_one(self, chart):
|
|
"""
|
|
Add a :class:`.Chart` to the grid.
|
|
"""
|
|
self._charts.append(chart)
|
|
|
|
def add_many(self, charts):
|
|
"""
|
|
Add a sequence of charts to this grid.
|
|
"""
|
|
self._charts.extend(charts)
|
|
|
|
def to_svg(self, path=None, width=None, height=None):
|
|
"""
|
|
Render the grid to an SVG.
|
|
|
|
The :code:`width` and :code:`height` arguments refer to the size of the
|
|
entire grid. The size of individual charts will be inferred
|
|
automatically.
|
|
|
|
See :meth:`.Chart.to_svg` for arguments.
|
|
"""
|
|
if not width or not height:
|
|
count = len(self._charts)
|
|
|
|
columns = math.ceil(math.sqrt(count))
|
|
rows = math.ceil(count / columns)
|
|
|
|
width = columns * theme.default_chart_width
|
|
height = rows * theme.default_chart_height
|
|
|
|
root = ET.Element('svg',
|
|
width=six.text_type(width),
|
|
height=six.text_type(height),
|
|
version='1.1',
|
|
xmlns='http://www.w3.org/2000/svg'
|
|
)
|
|
|
|
# Root / background
|
|
root_group = ET.Element('g')
|
|
|
|
root_group.append(ET.Element('rect',
|
|
x=six.text_type(0),
|
|
y=six.text_type(0),
|
|
width=six.text_type(width),
|
|
height=six.text_type(height),
|
|
fill=theme.background_color
|
|
))
|
|
|
|
root.append(root_group)
|
|
|
|
# Charts
|
|
grid_group = ET.Element('g')
|
|
|
|
chart_count = len(self._charts)
|
|
grid_width = math.ceil(math.sqrt(chart_count))
|
|
grid_height = math.ceil(chart_count / grid_width)
|
|
chart_width = width / grid_width
|
|
chart_height = height / grid_height
|
|
|
|
for i, chart in enumerate(self._charts):
|
|
x = (i % grid_width) * chart_width
|
|
y = math.floor(i / grid_width) * chart_height
|
|
|
|
group = ET.Element('g')
|
|
group.set('transform', svg.translate(x, y))
|
|
|
|
chart = chart.to_svg_group(chart_width, chart_height)
|
|
group.append(chart)
|
|
|
|
grid_group.append(group)
|
|
|
|
root_group.append(grid_group)
|
|
|
|
svg_text = svg.stringify(root)
|
|
close = True
|
|
|
|
if path:
|
|
f = None
|
|
|
|
try:
|
|
if hasattr(path, 'write'):
|
|
f = path
|
|
close = False
|
|
else:
|
|
dirpath = os.path.dirname(path)
|
|
|
|
if dirpath and not os.path.exists(dirpath):
|
|
os.makedirs(dirpath)
|
|
|
|
f = open(path, 'w')
|
|
|
|
f.write(svg.HEADER)
|
|
f.write(svg_text)
|
|
finally:
|
|
if close and f is not None:
|
|
f.close()
|
|
else:
|
|
return IPythonSVG(svg_text)
|