[Python-modules-commits] [python-leather] 01/03: New upstream version 0.3.3

Ghislain Vaillant ghisvail-guest at moszumanska.debian.org
Fri Mar 10 18:12:50 UTC 2017


This is an automated email from the git hooks/post-receive script.

ghisvail-guest pushed a commit to branch master
in repository python-leather.

commit 4d009cfca13002e8a3fd8ecf236308628fff0972
Author: Ghislain Antony Vaillant <ghisvail at gmail.com>
Date:   Fri Mar 10 14:31:24 2017 +0000

    New upstream version 0.3.3
---
 PKG-INFO                              |  61 ++++++
 README.rst                            |  31 +++
 leather.egg-info/PKG-INFO             |  61 ++++++
 leather.egg-info/SOURCES.txt          |  35 +++
 leather.egg-info/dependency_links.txt |   1 +
 leather.egg-info/requires.txt         |   1 +
 leather.egg-info/top_level.txt        |   1 +
 leather/__init__.py                   |  12 ++
 leather/axis.py                       | 193 +++++++++++++++++
 leather/chart.py                      | 396 ++++++++++++++++++++++++++++++++++
 leather/data_types.py                 |  47 ++++
 leather/grid.py                       | 120 +++++++++++
 leather/lattice.py                    | 172 +++++++++++++++
 leather/scales/__init__.py            |   6 +
 leather/scales/base.py                | 140 ++++++++++++
 leather/scales/linear.py              |  61 ++++++
 leather/scales/ordinal.py             |  63 ++++++
 leather/scales/temporal.py            |  76 +++++++
 leather/series/__init__.py            |   4 +
 leather/series/base.py                | 138 ++++++++++++
 leather/series/category.py            |  88 ++++++++
 leather/shapes/__init__.py            |   7 +
 leather/shapes/bars.py                |  79 +++++++
 leather/shapes/base.py                | 101 +++++++++
 leather/shapes/columns.py             |  78 +++++++
 leather/shapes/dots.py                |  90 ++++++++
 leather/shapes/line.py                |  94 ++++++++
 leather/svg.py                        |  43 ++++
 leather/testcase.py                   |  39 ++++
 leather/theme.py                      | 128 +++++++++++
 leather/ticks/__init__.py             |   5 +
 leather/ticks/base.py                 |  18 ++
 leather/ticks/score.py                | 174 +++++++++++++++
 leather/ticks/score_time.py           | 168 +++++++++++++++
 leather/utils.py                      | 188 ++++++++++++++++
 setup.cfg                             |   5 +
 setup.py                              |  48 +++++
 37 files changed, 2972 insertions(+)

diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..20d0421
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,61 @@
+Metadata-Version: 1.1
+Name: leather
+Version: 0.3.3
+Summary: Python charting for 80% of humans.
+Home-page: http://leather.readthedocs.io/
+Author: Christopher Groskopf
+Author-email: chrisgroskopf at gmail.com
+License: MIT
+Description: .. image:: https://travis-ci.org/wireservice/leather.png
+            :target: https://travis-ci.org/wireservice/leather
+            :alt: Build status
+        
+        .. image:: https://img.shields.io/pypi/dw/leather.svg
+            :target: https://pypi.python.org/pypi/leather
+            :alt: PyPI downloads
+        
+        .. image:: https://img.shields.io/pypi/v/leather.svg
+            :target: https://pypi.python.org/pypi/leather
+            :alt: Version
+        
+        .. image:: https://img.shields.io/pypi/l/leather.svg
+            :target: https://pypi.python.org/pypi/leather
+            :alt: License
+        
+        .. image:: https://img.shields.io/pypi/pyversions/leather.svg
+            :target: https://pypi.python.org/pypi/leather
+            :alt: Support Python versions
+        
+        Leather is the Python charting library for those who need charts *now* and don't care if they're perfect.
+        
+        Leather isn't picky. It's rough. It gets dirty. It looks sexy just hanging on the back of a chair. Leather doesn't need your accessories. Leather is how Snake Plissken would make charts.
+        
+        Get it?
+        
+        Important links:
+        
+        * Documentation:    http://leather.rtfd.io
+        * Repository:       https://github.com/wireservice/leather
+        * Issues:           https://github.com/wireservice/leather/issues
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Framework :: IPython
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Science/Research
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Multimedia :: Graphics
+Classifier: Natural Language :: English
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Scientific/Engineering :: Information Analysis
+Classifier: Topic :: Scientific/Engineering :: Visualization
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..de947cf
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,31 @@
+.. image:: https://travis-ci.org/wireservice/leather.png
+    :target: https://travis-ci.org/wireservice/leather
+    :alt: Build status
+
+.. image:: https://img.shields.io/pypi/dw/leather.svg
+    :target: https://pypi.python.org/pypi/leather
+    :alt: PyPI downloads
+
+.. image:: https://img.shields.io/pypi/v/leather.svg
+    :target: https://pypi.python.org/pypi/leather
+    :alt: Version
+
+.. image:: https://img.shields.io/pypi/l/leather.svg
+    :target: https://pypi.python.org/pypi/leather
+    :alt: License
+
+.. image:: https://img.shields.io/pypi/pyversions/leather.svg
+    :target: https://pypi.python.org/pypi/leather
+    :alt: Support Python versions
+
+Leather is the Python charting library for those who need charts *now* and don't care if they're perfect.
+
+Leather isn't picky. It's rough. It gets dirty. It looks sexy just hanging on the back of a chair. Leather doesn't need your accessories. Leather is how Snake Plissken would make charts.
+
+Get it?
+
+Important links:
+
+* Documentation:    http://leather.rtfd.io
+* Repository:       https://github.com/wireservice/leather
+* Issues:           https://github.com/wireservice/leather/issues
diff --git a/leather.egg-info/PKG-INFO b/leather.egg-info/PKG-INFO
new file mode 100644
index 0000000..20d0421
--- /dev/null
+++ b/leather.egg-info/PKG-INFO
@@ -0,0 +1,61 @@
+Metadata-Version: 1.1
+Name: leather
+Version: 0.3.3
+Summary: Python charting for 80% of humans.
+Home-page: http://leather.readthedocs.io/
+Author: Christopher Groskopf
+Author-email: chrisgroskopf at gmail.com
+License: MIT
+Description: .. image:: https://travis-ci.org/wireservice/leather.png
+            :target: https://travis-ci.org/wireservice/leather
+            :alt: Build status
+        
+        .. image:: https://img.shields.io/pypi/dw/leather.svg
+            :target: https://pypi.python.org/pypi/leather
+            :alt: PyPI downloads
+        
+        .. image:: https://img.shields.io/pypi/v/leather.svg
+            :target: https://pypi.python.org/pypi/leather
+            :alt: Version
+        
+        .. image:: https://img.shields.io/pypi/l/leather.svg
+            :target: https://pypi.python.org/pypi/leather
+            :alt: License
+        
+        .. image:: https://img.shields.io/pypi/pyversions/leather.svg
+            :target: https://pypi.python.org/pypi/leather
+            :alt: Support Python versions
+        
+        Leather is the Python charting library for those who need charts *now* and don't care if they're perfect.
+        
+        Leather isn't picky. It's rough. It gets dirty. It looks sexy just hanging on the back of a chair. Leather doesn't need your accessories. Leather is how Snake Plissken would make charts.
+        
+        Get it?
+        
+        Important links:
+        
+        * Documentation:    http://leather.rtfd.io
+        * Repository:       https://github.com/wireservice/leather
+        * Issues:           https://github.com/wireservice/leather/issues
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Framework :: IPython
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Science/Research
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Multimedia :: Graphics
+Classifier: Natural Language :: English
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Scientific/Engineering :: Information Analysis
+Classifier: Topic :: Scientific/Engineering :: Visualization
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/leather.egg-info/SOURCES.txt b/leather.egg-info/SOURCES.txt
new file mode 100644
index 0000000..11dfca0
--- /dev/null
+++ b/leather.egg-info/SOURCES.txt
@@ -0,0 +1,35 @@
+README.rst
+setup.py
+leather/__init__.py
+leather/axis.py
+leather/chart.py
+leather/data_types.py
+leather/grid.py
+leather/lattice.py
+leather/svg.py
+leather/testcase.py
+leather/theme.py
+leather/utils.py
+leather.egg-info/PKG-INFO
+leather.egg-info/SOURCES.txt
+leather.egg-info/dependency_links.txt
+leather.egg-info/requires.txt
+leather.egg-info/top_level.txt
+leather/scales/__init__.py
+leather/scales/base.py
+leather/scales/linear.py
+leather/scales/ordinal.py
+leather/scales/temporal.py
+leather/series/__init__.py
+leather/series/base.py
+leather/series/category.py
+leather/shapes/__init__.py
+leather/shapes/bars.py
+leather/shapes/base.py
+leather/shapes/columns.py
+leather/shapes/dots.py
+leather/shapes/line.py
+leather/ticks/__init__.py
+leather/ticks/base.py
+leather/ticks/score.py
+leather/ticks/score_time.py
\ No newline at end of file
diff --git a/leather.egg-info/dependency_links.txt b/leather.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/leather.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/leather.egg-info/requires.txt b/leather.egg-info/requires.txt
new file mode 100644
index 0000000..980b4bc
--- /dev/null
+++ b/leather.egg-info/requires.txt
@@ -0,0 +1 @@
+six>=1.6.1
diff --git a/leather.egg-info/top_level.txt b/leather.egg-info/top_level.txt
new file mode 100644
index 0000000..e954dad
--- /dev/null
+++ b/leather.egg-info/top_level.txt
@@ -0,0 +1 @@
+leather
diff --git a/leather/__init__.py b/leather/__init__.py
new file mode 100644
index 0000000..6a9e46e
--- /dev/null
+++ b/leather/__init__.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+from leather.axis import Axis
+from leather.data_types import Number, Text
+from leather.chart import Chart
+from leather.grid import Grid
+from leather.lattice import Lattice
+from leather.scales import Scale, Linear, Ordinal, Temporal
+from leather.series import Series, CategorySeries, key_function
+from leather.shapes import Shape, Bars, Columns, Dots, Line, style_function
+from leather.testcase import LeatherTestCase
+from leather import theme
diff --git a/leather/axis.py b/leather/axis.py
new file mode 100644
index 0000000..5b3aff1
--- /dev/null
+++ b/leather/axis.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+
+import xml.etree.ElementTree as ET
+
+import six
+
+from leather import svg
+from leather import theme
+
+
+class Axis(object):
+    """
+    A horizontal or vertical chart axis.
+
+    :param ticks:
+        Instead of inferring tick values from the data, use exactly this
+        sequence of ticks values. These will still be passed to the
+        :code:`tick_formatter`.
+    :param tick_formatter:
+        An optional :func:`.tick_format_function`.
+    """
+    def __init__(self, ticks=None, tick_formatter=None, name=None):
+        self._ticks = ticks
+        self._tick_formatter = tick_formatter
+        self._name = six.text_type(name) if name is not None else None
+
+    def _estimate_left_tick_width(self, scale):
+        """
+        Estimate the y axis space used by tick labels.
+        """
+        tick_values = self._ticks or scale.ticks()
+        tick_count = len(tick_values)
+        tick_formatter = self._tick_formatter or scale.format_tick
+        max_len = 0
+
+        for i, value in enumerate(tick_values):
+            max_len = max(max_len, len(tick_formatter(value, i, tick_count)))
+
+        return max_len * theme.tick_font_char_width
+
+    def estimate_label_margin(self, scale, orient):
+        """
+        Estimate the space needed for the tick labels.
+        """
+        margin = 0
+
+        if orient == 'left':
+            margin += self._estimate_left_tick_width(scale) + (theme.tick_size * 2)
+        elif orient == 'bottom':
+            margin += theme.tick_font_char_height + (theme.tick_size * 2)
+
+        if self._name:
+            margin += theme.axis_title_font_char_height + theme.axis_title_gap
+
+        return margin
+
+    def to_svg(self, width, height, scale, orient):
+        """
+        Render this axis to SVG elements.
+        """
+        group = ET.Element('g')
+        group.set('class', 'axis ' + orient)
+
+        # Axis title
+        if self._name is not None:
+            if orient == 'left':
+                title_x = -(self._estimate_left_tick_width(scale) + theme.axis_title_gap)
+                title_y = height / 2
+                dy=''
+                transform = svg.rotate(270, title_x, title_y)
+            elif orient == 'bottom':
+                title_x = width / 2
+                title_y = height + theme.tick_font_char_height + (theme.tick_size * 2) + theme.axis_title_gap
+                dy='1em'
+                transform = ''
+
+            title = ET.Element('text',
+                x=six.text_type(title_x),
+                y=six.text_type(title_y),
+                dy=dy,
+                fill=theme.axis_title_color,
+                transform=transform
+            )
+            title.set('text-anchor', 'middle')
+            title.set('font-family', theme.axis_title_font_family)
+            title.text = self._name
+
+            group.append(title)
+
+        # Ticks
+        if orient == 'left':
+            label_x = -(theme.tick_size * 2)
+            x1 = -theme.tick_size
+            x2 = width
+            range_min = height
+            range_max = 0
+        elif orient == 'bottom':
+            label_y = height + (theme.tick_size * 2)
+            y1 = 0
+            y2 = height + theme.tick_size
+            range_min = 0
+            range_max = width
+
+        tick_values = self._ticks or scale.ticks()
+        tick_count = len(tick_values)
+        tick_formatter = self._tick_formatter or scale.format_tick
+
+        zero_tick_group = None
+
+        for i, value in enumerate(tick_values):
+            # Tick group
+            tick_group = ET.Element('g')
+            tick_group.set('class', 'tick')
+
+            if value == 0:
+                zero_tick_group = tick_group
+            else:
+                group.append(tick_group)
+
+            # Tick line
+            projected_value = scale.project(value, range_min, range_max)
+
+            if value == 0:
+                tick_color = theme.zero_color
+            else:
+                tick_color = theme.tick_color
+
+            if orient == 'left':
+                y1 = projected_value
+                y2 = projected_value
+
+            elif orient == 'bottom':
+                x1 = projected_value
+                x2 = projected_value
+
+            tick = ET.Element('line',
+                x1=six.text_type(x1),
+                y1=six.text_type(y1),
+                x2=six.text_type(x2),
+                y2=six.text_type(y2),
+                stroke=tick_color
+            )
+            tick.set('stroke-width', six.text_type(theme.tick_width))
+
+            tick_group.append(tick)
+
+            # Tick label
+            if orient == 'left':
+                x = label_x
+                y = projected_value
+                dy = '0.32em'
+                text_anchor = 'end'
+            elif orient == 'bottom':
+                x = projected_value
+                y = label_y
+                dy = '1em'
+                text_anchor = 'middle'
+
+            label = ET.Element('text',
+                x=six.text_type(x),
+                y=six.text_type(y),
+                dy=dy,
+                fill=theme.label_color
+            )
+            label.set('text-anchor', text_anchor)
+            label.set('font-family', theme.tick_font_family)
+
+            value = tick_formatter(value, i, tick_count)
+            label.text = six.text_type(value)
+
+            tick_group.append(label)
+
+        if zero_tick_group is not None:
+            group.append(zero_tick_group)
+
+        return group
+
+
+def tick_format_function(value, index, tick_count):
+    """
+    This example shows how to define a function to format tick values for
+    display.
+
+    :param x:
+        The value to be formatted.
+    :param index:
+        The index of the tick.
+    :param tick_count:
+        The total number of ticks being displayed.
+    :returns:
+        A stringified tick value for display.
+    """
+    return six.text_type(value)
diff --git a/leather/chart.py b/leather/chart.py
new file mode 100644
index 0000000..e272183
--- /dev/null
+++ b/leather/chart.py
@@ -0,0 +1,396 @@
+#!/usr/bin/env python
+
+from copy import copy
+import os
+import xml.etree.ElementTree as ET
+
+import six
+
+from leather.axis import Axis
+from leather.data_types import Date, DateTime
+from leather.scales import Scale, Linear, Temporal
+from leather.series import Series, CategorySeries
+from leather.shapes import Bars, Columns, Dots, Line
+import leather.svg as svg
+from leather import theme
+from leather.utils import X, Y, DIMENSION_NAMES, Box, IPythonSVG, warn
+
+
+class Chart(object):
+    """
+    Container for all chart types.
+
+    :param title:
+        An optional title that will be rendered at the top of the chart.
+    """
+    def __init__(self, title=None):
+        self._title = title
+        self._series_colors = theme.default_series_colors
+
+        self._layers = []
+        self._types = [None, None]
+        self._scales = [None, None]
+        self._axes = [None, None]
+
+    def _palette(self):
+        """
+        Return a generator for series colors.
+        """
+        return (color for color in self._series_colors)
+
+    def set_x_scale(self, scale):
+        """
+        Set the X :class:`.Scale` for this chart.
+        """
+        self._scales[X] = scale
+
+    def set_y_scale(self, scale):
+        """
+        See :meth:`.Chart.set_x_scale`.
+        """
+        self._scales[Y] = scale
+
+    def add_x_scale(self, domain_min, domain_max):
+        """
+        Create and add a :class:`.Scale`.
+
+        If the provided domain values are :class:`date` or :class:`datetime`
+        then a :class:`.Temporal` scale will be created, otherwise it will
+        :class:`.Linear`.
+
+        If you want to set a custom scale class use :meth:`.Chart.set_x_scale`
+        instead.
+        """
+        scale_type = Linear
+
+        if isinstance(domain_min, Date.types) or isinstance(domain_min, DateTime.types):
+            scale_type = Temporal
+
+        self.set_x_scale(scale_type(domain_min, domain_max))
+
+    def add_y_scale(self, domain_min, domain_max):
+        """
+        See :meth:`.Chart.add_x_scale`.
+        """
+        scale_type = Linear
+
+        if isinstance(domain_min, Date.types) or isinstance(domain_min, DateTime.types):
+            scale_type = Temporal
+
+        self.set_y_scale(scale_type(domain_min, domain_max))
+
+    def set_x_axis(self, axis):
+        """
+        Set an :class:`.Axis` class for this chart.
+        """
+        self._axes[X] = axis
+
+    def set_y_axis(self, axis):
+        """
+        See :meth:`.Chart.set_x_axis`.
+        """
+        self._axes[Y] = axis
+
+    def add_x_axis(self, ticks=None, tick_formatter=None, name=None):
+        """
+        Create and add an X :class:`.Axis`.
+
+        If you want to set a custom axis class use :meth:`.Chart.set_x_axis`
+        instead.
+        """
+        self._axes[X] = Axis(ticks, tick_formatter, name)
+
+    def add_y_axis(self, ticks=None, tick_formatter=None, name=None):
+        """
+        See :meth:`.Chart.add_x_axis`.
+        """
+        self._axes[Y] = Axis(ticks, tick_formatter, name)
+
+    def add_series(self, series, shape):
+        """
+        Add a data :class:`.Series` to the chart. The data types of the new
+        series must be consistent with any series that have already been added.
+
+        There are several shortcuts for adding different types of data series.
+        See :meth:`.Chart.add_bars`, :meth:`.Chart.add_columns`,
+        :meth:`.Chart.add_dots`, and :meth:`.Chart.add_line`.
+        """
+        if self._layers and isinstance(self._layers[0][0], CategorySeries):
+            raise RuntimeError('Additional series can not be added to a chart with a CategorySeries.')
+
+        if isinstance(series, CategorySeries):
+            self._types = series._types
+        else:
+            for dim in [X, Y]:
+                if not self._types[dim]:
+                    self._types[dim] = series._types[dim]
+                elif series._types[dim] is not self._types[dim]:
+                    raise TypeError('Can\'t mix axis-data types: %s and %s' % (series._types[dim], self._types[dim]))
+
+        shape.validate_series(series)
+
+        self._layers.append((
+            series,
+            shape
+        ))
+
+    def add_bars(self, data, x=None, y=None, name=None, fill_color=None):
+        """
+        Create and add a :class:`.Series` rendered with :class:`.Bars`.
+
+        Note that when creating bars in this way the order of the series data
+        will be reversed so that the first item in the series is displayed
+        as the top-most bar in the graphic. If you don't want this to happen
+        use :meth:`.Chart.add_series` instead.
+        """
+        self.add_series(
+            Series(list(reversed(data)), x=x, y=y, name=name),
+            Bars(fill_color)
+        )
+
+    def add_columns(self, data, x=None, y=None, name=None, fill_color=None):
+        """
+        Create and add a :class:`.Series` rendered with :class:`.Columns`.
+        """
+        self.add_series(
+            Series(data, x=x, y=y, name=name),
+            Columns(fill_color)
+        )
+
+    def add_dots(self, data, x=None, y=None, name=None, fill_color=None, radius=None):
+        """
+        Create and add a :class:`.Series` rendered with :class:`.Dots`.
+        """
+        self.add_series(
+            Series(data, x=x, y=y, name=name),
+            Dots(fill_color, radius)
+        )
+
+    def add_line(self, data, x=None, y=None, name=None, stroke_color=None, width=None):
+        """
+        Create and add a :class:`.Series` rendered with :class:`.Line`.
+        """
+        self.add_series(
+            Series(data, x=x, y=y, name=name),
+            Line(stroke_color, width)
+        )
+
+    def _validate_dimension(self, dimension):
+        """
+        Validates that the given scale and axis are valid for the data that
+        has been added to this chart. If a scale or axis has not been set,
+        generates automated ones.
+        """
+        scale = self._scales[dimension]
+        axis = self._axes[dimension]
+
+        if not scale:
+            scale = Scale.infer(self._layers, dimension, self._types[dimension])
+        else:
+            for series, shape in self._layers:
+                if not scale.contains(series.min(dimension)) or not scale.contains(series.max(dimension)):
+                    d = DIMENSION_NAMES[dimension]
+                    warn('Data contains values outside %s scale domain. All data points may not be visible on the chart.' % d)
+
+                    # Only display once per axis
+                    break
+
+        if not axis:
+            axis = Axis()
+
+        return (scale, axis)
+
+    def to_svg_group(self, width=None, height=None):
+        """
+        Render this chart to an SVG group element.
+
+        This can then be placed inside an :code:`<svg>` tag to make a complete
+        SVG graphic.
+
+        See :meth:`.Chart.to_svg` for arguments.
+        """
+        width = width or theme.default_width
+        height = height or theme.default_height
+
+        if not self._layers:
+            raise ValueError('You must add at least one series to the chart before rendering.')
+
+        if isinstance(theme.margin, float):
+            default_margin = width * theme.margin
+
+            margin = Box(
+                top=default_margin,
+                right=default_margin,
+                bottom=default_margin,
+                left=default_margin
+            )
+        elif isinstance(margin, int):
+            margin = Box(margin, margin, margin, margin)
+        elif not isinstance(margin, Box):
+            margin = Box(*margin)
+
+        # 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
+        ))
+
+        # Margins
+        margin_group = ET.Element('g')
+        margin_group.set('transform', svg.translate(margin.left, margin.top))
+
+        margin_width = width - (margin.left + margin.right)
+        margin_height = height - (margin.top + margin.bottom)
+
+        root_group.append(margin_group)
+
+        # Header
+        header_group = ET.Element('g')
+
+        header_margin = 0
+
+        if self._title:
+            label = ET.Element('text',
+                x=six.text_type(0),
+                y=six.text_type(0),
+                fill=theme.title_color
+            )
+            label.set('font-family', theme.title_font_family)
+            label.set('font-size', six.text_type(theme.title_font_size))
+            label.text = six.text_type(self._title)
+
+            header_group.append(label)
+            header_margin += theme.title_font_char_height + theme.title_gap
+
+        # Legend
+        if len(self._layers) > 1 or isinstance(self._layers[0][0], CategorySeries):
+            legend_group = ET.Element('g')
+            legend_group.set('transform', svg.translate(0, header_margin))
+
+            indent = 0
+            rows = 1
+            palette = self._palette()
+
+            for series, shape in self._layers:
+                for item_group, item_width in shape.legend_to_svg(series, palette):
+                    if indent + item_width > width:
+                        indent = 0
+                        rows += 1
+
+                    y = (rows - 1) * (theme.legend_font_char_height + theme.legend_gap)
+                    item_group.set('transform', svg.translate(indent, y))
+
+                    indent += item_width
+
+                    legend_group.append(item_group)
+
+            legend_height = rows * (theme.legend_font_char_height + theme.legend_gap)
+
+            header_margin += legend_height
+            header_group.append(legend_group)
+
+        margin_group.append(header_group)
+
+        # Body
+        body_group = ET.Element('g')
+        body_group.set('transform', svg.translate(0, header_margin))
+
+        body_width = margin_width
+        body_height = margin_height - header_margin
+
+        margin_group.append(body_group)
+
+        # Axes
+        x_scale, x_axis = self._validate_dimension(X)
+        y_scale, y_axis = self._validate_dimension(Y)
+
+        bottom_margin = x_axis.estimate_label_margin(x_scale, 'bottom')
+        left_margin = y_axis.estimate_label_margin(y_scale, 'left')
+
+        canvas_width = body_width - left_margin
+        canvas_height = body_height - bottom_margin
+
+        axes_group = ET.Element('g')
+        axes_group.set('transform', svg.translate(left_margin, 0))
+
+        axes_group.append(x_axis.to_svg(canvas_width, canvas_height, x_scale, 'bottom'))
+        axes_group.append(y_axis.to_svg(canvas_width, canvas_height, y_scale, 'left'))
+
+        header_group.set('transform', svg.translate(left_margin, 0))
+
+        body_group.append(axes_group)
+
+        # Series
+        series_group = ET.Element('g')
+
+        palette = self._palette()
+
+        for series, shape in self._layers:
+            series_group.append(shape.to_svg(canvas_width, canvas_height, x_scale, y_scale, series, palette))
+
+        axes_group.append(series_group)
+
+        return root_group
+
+    def to_svg(self, path=None, width=None, height=None):
+        """
+        Render this chart to an SVG document.
+
+        The :code:`width` and :code:`height` are specified in SVG's
+        "unitless" units, however, it is usually convenient to specify them
+        as though they were pixels.
+
+        :param path:
+            Filepath or file-like object to write to. If omitted then the SVG
+            will be returned as a string. If running within IPython, then this
+            will return a SVG object to be displayed.
+        :param width:
+            The output width, in SVG user units. Defaults to
+            :data:`.theme.default_chart_width`.
+        :param height:
+            The output height, in SVG user units. Defaults to
+            :data:`.theme.default_chart_height`.
+        """
+        width = width or theme.default_chart_width
+        height = height or 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'
+        )
+
+        group = self.to_svg_group(width, height)
+        root.append(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)
diff --git a/leather/data_types.py b/leather/data_types.py
new file mode 100644
index 0000000..f5e0b0d
--- /dev/null
+++ b/leather/data_types.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+from datetime import date, datetime
+from decimal import Decimal
+
+import six
+
+
+class DataType(object):
+    """
+    Base class for :class:`.Series` data types.
+    """
+    @classmethod
+    def infer(cls, v):
+        for t in [DateTime, Date, Number, Text]:
+            if isinstance(v, t.types):
+                return t
+
+        raise TypeError('No data type available for %s' % type(v))
+
+
+class Date(DataType):
+    """
+    Data representing dates.
+    """
+    types = (date,)
+
+
+class DateTime(DataType):
+    """
+    Data representing dates with times.
+    """
+    types = (datetime,)
+
+
+class Number(DataType):
+    """
+    Data representing numbers.
+    """
+    types = (int, float, Decimal)
+
+
+class Text(DataType):
+    """
+    Data representing text/strings.
+    """
+    types = six.string_types
diff --git a/leather/grid.py b/leather/grid.py
new file mode 100644
index 0000000..9693682
--- /dev/null
+++ b/leather/grid.py
@@ -0,0 +1,120 @@
+#!/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.
+        """
... 2240 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-leather.git



More information about the Python-modules-commits mailing list