95 lines
2.5 KiB
Python
95 lines
2.5 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
import xml.etree.ElementTree as ET
|
||
|
|
||
|
import six
|
||
|
|
||
|
from leather.data_types import Text
|
||
|
from leather.series import CategorySeries
|
||
|
from leather.shapes.base import Shape
|
||
|
from leather import theme
|
||
|
from leather.utils import X, Y
|
||
|
|
||
|
|
||
|
class Line(Shape):
|
||
|
"""
|
||
|
Render a series of data as a line.
|
||
|
|
||
|
:param stroke_color:
|
||
|
The color to stroke the lines. If not provided, default chart colors
|
||
|
will be used.
|
||
|
:param width:
|
||
|
The width of the lines. Defaults to :data:`.theme.default_line_width`.
|
||
|
"""
|
||
|
def __init__(self, stroke_color=None, width=None):
|
||
|
self._stroke_color = stroke_color
|
||
|
self._width = width or theme.default_line_width
|
||
|
|
||
|
def validate_series(self, series):
|
||
|
"""
|
||
|
Verify this shape can be used to render a given series.
|
||
|
"""
|
||
|
if isinstance(series, CategorySeries):
|
||
|
raise ValueError('Line can not be used to render CategorySeries.')
|
||
|
|
||
|
if series.data_type(X) is Text or series.data_type(Y) is Text:
|
||
|
raise ValueError('Line does not support Text values.')
|
||
|
|
||
|
def _new_path(self, stroke_color):
|
||
|
"""
|
||
|
Start a new path.
|
||
|
"""
|
||
|
path = ET.Element('path',
|
||
|
stroke=stroke_color,
|
||
|
fill='none'
|
||
|
)
|
||
|
path.set('stroke-width', six.text_type(self._width))
|
||
|
|
||
|
return path
|
||
|
|
||
|
def to_svg(self, width, height, x_scale, y_scale, series, palette):
|
||
|
"""
|
||
|
Render lines to SVG elements.
|
||
|
"""
|
||
|
group = ET.Element('g')
|
||
|
group.set('class', 'series lines')
|
||
|
|
||
|
if self._stroke_color:
|
||
|
stroke_color = self._stroke_color
|
||
|
else:
|
||
|
stroke_color = next(palette)
|
||
|
|
||
|
path = self._new_path(stroke_color)
|
||
|
path_d = []
|
||
|
|
||
|
for d in series.data():
|
||
|
if d.x is None or d.y is None:
|
||
|
if path_d:
|
||
|
path.set('d', ' '.join(path_d))
|
||
|
group.append(path)
|
||
|
|
||
|
path_d = []
|
||
|
path = self._new_path(stroke_color)
|
||
|
|
||
|
continue
|
||
|
|
||
|
proj_x = x_scale.project(d.x, 0, width)
|
||
|
proj_y = y_scale.project(d.y, height, 0)
|
||
|
|
||
|
if not path_d:
|
||
|
command = 'M'
|
||
|
else:
|
||
|
command = 'L'
|
||
|
|
||
|
path_d.extend([
|
||
|
command,
|
||
|
six.text_type(proj_x),
|
||
|
six.text_type(proj_y)
|
||
|
])
|
||
|
|
||
|
if path_d:
|
||
|
path.set('d', ' '.join(path_d))
|
||
|
group.append(path)
|
||
|
|
||
|
return group
|