diff --git a/pygal/__init__.py b/pygal/__init__.py new file mode 100644 index 0000000..8378559 --- /dev/null +++ b/pygal/__init__.py @@ -0,0 +1,3 @@ +from collections import namedtuple + +Serie = namedtuple('Serie', ('title', 'values')) diff --git a/pygal/base.py b/pygal/base.py new file mode 100644 index 0000000..deca0eb --- /dev/null +++ b/pygal/base.py @@ -0,0 +1,13 @@ + + +class BaseGraph(object): + """Graphs commons""" + + def render(self): + self.draw() + return self.svg.render() + + def _in_browser(self): + from lxml.html import open_in_browser + self.draw() + open_in_browser(self.svg.root, encoding='utf-8') diff --git a/pygal/line.py b/pygal/line.py new file mode 100644 index 0000000..a30f5f3 --- /dev/null +++ b/pygal/line.py @@ -0,0 +1,26 @@ +from pygal import Serie +from pygal.svg import Svg +from pygal.base import BaseGraph + + +class Line(BaseGraph): + """Line graph""" + + def __init__(self, width, height): + self.width = width + self.height = height + self.svg = Svg(width, height) + self.series = [] + + def add(self, title, values): + self.series.append( + Serie(title, values)) + + def draw(self): + self.svg.graph() + for serie in self.series: + n_values = len(serie.values) + x_spacing = self.width / n_values + self.svg.line([ + (i * x_spacing, v) + for i, v in enumerate(serie.values)]) diff --git a/pygal/svg.py b/pygal/svg.py new file mode 100644 index 0000000..4f3374b --- /dev/null +++ b/pygal/svg.py @@ -0,0 +1,52 @@ +from lxml import etree + + +class Svg(object): + """Svg object""" + ns = 'http://www.w3.org/2000/svg' + + def __init__(self, width, height): + self.width = width + self.height = height + self.root = etree.Element( + "{%s}svg" % self.ns, + attrib={ + 'viewBox': '0 0 %d %d' % (width, height) + }, + nsmap={ + None: self.ns, + 'xlink': 'http://www.w3.org/1999/xlink', + }) + + def node(self, parent=None, tag='g', attrib=None, **extras): + if parent is None: + parent = self.root + attrib = attrib or {} + attrib.update(extras) + for key, value in attrib.items(): + if not isinstance(value, basestring): + attrib[key] = str(value) + if key == 'class_': + attrib['class'] = attrib['class_'] + del attrib['class_'] + + return etree.SubElement(parent, tag, attrib) + + def format_coords(self, xy): + return '%f %f' % xy + + def graph(self): + self.graph = self.node(id='graph') + self.node(self.graph, 'rect', id='graph_background', + x=0, y=0, width=self.width, height=self.height) + + def line(self, values, origin=None): + origin = self.format_coords(origin or values[0]) + values = ' '.join(map(self.format_coords, values)) + self.node(self.graph, 'path', + d='M%s L%s' % (origin, values)) + + def render(self): + return etree.tostring( + self.root, pretty_print=True, + xml_declaration=True, encoding='utf-8') diff --git a/pygal/test/__init__.py b/pygal/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pygal/test/test_line.py b/pygal/test/test_line.py new file mode 100644 index 0000000..2b21e97 --- /dev/null +++ b/pygal/test/test_line.py @@ -0,0 +1,7 @@ +from pygal.line import Line + + +def test_simple_line(): + line = Line(800, 600) + line.add('test', [10, 20, 5, 17]) + line._in_browser() diff --git a/pygal/test/test_svg.py b/pygal/test/test_svg.py new file mode 100644 index 0000000..d17774c --- /dev/null +++ b/pygal/test/test_svg.py @@ -0,0 +1,11 @@ +from pygal.svg import Svg + + +def test_root(): + svg = Svg(800, 600) + assert svg.render() == ('\n'.join(( + '', + '', + ''))) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f20d6aa --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Public Domain +from setuptools import setup, find_packages + + +setup( + name="pygal", + packages=find_packages(), + tests_require=["pytest"], + install_requires=['lxml'])