Browse Source

Document pygal.*

pull/242/head
Florian Mounier 9 years ago
parent
commit
6f59ff14a9
  1. 21
      pygal/__init__.py
  2. 19
      pygal/_compat.py
  3. 5
      pygal/adapters.py
  4. 9
      pygal/colors.py
  5. 24
      pygal/config.py
  6. 3
      pygal/etree.py
  7. 1
      pygal/interpolate.py
  8. 11
      pygal/serie.py
  9. 12
      pygal/state.py
  10. 23
      pygal/style.py
  11. 14
      pygal/svg.py
  12. 19
      pygal/table.py
  13. 18
      pygal/util.py
  14. 68
      pygal/view.py

21
pygal/__init__.py

@ -17,7 +17,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Main pygal package
Main pygal package.
This package holds all available charts in pygal, the Config class
and the maps extensions namespace module.
@ -76,16 +76,17 @@ CHARTS = list(CHARTS_BY_NAME.values())
class PluginImportFixer(object):
"""Allow external map plugins to be imported from pygal.maps package.
It is a ``sys.meta_path`` loader.
"""
Allow external map plugins to be imported from pygal.maps package.
def __init__(self):
pass
It is a ``sys.meta_path`` loader.
"""
def find_module(self, fullname, path=None):
"""This method says if the module to load can be loaded by
the load_module function, that is here if it is a ``pygal.maps.*``
"""
Tell if the module to load can be loaded by
the load_module function, ie: if it is a ``pygal.maps.*``
module.
"""
if fullname.startswith('pygal.maps.') and hasattr(
@ -94,8 +95,10 @@ class PluginImportFixer(object):
return None
def load_module(self, name):
"""Loads the ``pygal.maps.name`` module from
the previously loaded plugin"""
"""
Load the ``pygal.maps.name`` module from the previously
loaded plugin
"""
if name not in sys.modules:
sys.modules[name] = getattr(maps, name.split('.')[2])
return sys.modules[name]

19
pygal/_compat.py

@ -16,9 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Various hacks for transparent python 2 / python 3 support
"""
"""Various hacks for transparent python 2 / python 3 support"""
import sys
from collections import Iterable
@ -34,32 +32,38 @@ else:
def is_list_like(value):
"""Return whether value is an iterable but not a mapping / string"""
return isinstance(value, Iterable) and not isinstance(value, (base, dict))
def is_str(string):
"""Return whether value is a string or a byte list"""
return isinstance(string, base)
def to_str(string):
if not is_str(string):
return coerce(string)
return string
def to_str(obj):
"""Cast obj to unicode string"""
if not is_str(obj):
return coerce(obj)
return obj
def to_unicode(string):
"""Force stirng to be a string in python 3 or a unicode in python 2"""
if not isinstance(string, coerce):
return string.decode('utf-8')
return string
def u(s):
"""Emulate u'str' in python 2, do nothing in python 3"""
if sys.version_info[0] == 2:
return s.decode('utf-8')
return s
def total_seconds(td):
"""Backport of `timedelta.total_second` function for python 2.6"""
if sys.version_info[:2] == (2, 6):
return (
(td.days * 86400 + td.seconds) * 10 ** 6 + td.microseconds
@ -68,6 +72,7 @@ def total_seconds(td):
def timestamp(x):
"""Get a timestamp from a date in python 3 and python 2"""
if hasattr(x, 'timestamp'):
from datetime import timezone
if x.tzinfo is None:

5
pygal/adapters.py

@ -16,10 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Value adapters to use when a chart doesn't accept all value types
"""
"""Value adapters to use when a chart doesn't accept all value types"""
from decimal import Decimal

9
pygal/colors.py

@ -119,9 +119,10 @@ def parse_color(color):
def unparse_color(r, g, b, a, type):
"""Take the r, g, b, a color values and give back
a type css color string. This is the inverse function of parse_color"""
"""
Take the r, g, b, a color values and give back
a type css color string. This is the inverse function of parse_color
"""
if type == '#rgb':
# Don't lose precision on rgb shortcut
if r % 17 == 0 and g % 17 == 0 and b % 17 == 0:
@ -168,7 +169,7 @@ def adjust(color, attribute, percent):
def rotate(color, percent):
"""Rotates a color by changing its hue value by percent"""
"""Rotate a color by changing its hue value by percent"""
return adjust(color, 0, percent)

24
pygal/config.py

@ -16,10 +16,8 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""Config module holding all options and their default values."""
"""
Config module holding all options and their default values.
"""
from copy import deepcopy
from pygal.style import Style, DefaultStyle
from pygal.interpolate import INTERPOLATIONS
@ -29,6 +27,7 @@ CONFIG_ITEMS = []
class Key(object):
"""
Represents a config parameter.
@ -45,7 +44,7 @@ class Key(object):
def __init__(
self, default_value, type_, category, doc,
subdoc="", subtype=None):
"""Create a configuration key"""
self.value = default_value
self.type = type_
self.doc = doc
@ -59,6 +58,10 @@ class Key(object):
CONFIG_ITEMS.append(self)
def __repr__(self):
"""
Make a documentation repr.
This is a hack to generate doc from inner doc
"""
return """
Type: %s%s     
Default: %r     
@ -120,10 +123,11 @@ class Key(object):
class MetaConfig(type):
"""
Metaclass for configs. Used to get the key name and set it on the value.
"""
"""Config metaclass. Used to get the key name and set it on the value."""
def __new__(mcs, classname, bases, classdict):
"""Get the name of the key and set it on the key"""
for k, v in classdict.items():
if isinstance(v, Key):
v.name = k
@ -132,6 +136,7 @@ class MetaConfig(type):
class BaseConfig(MetaConfig('ConfigBase', (object,), {})):
"""
This class holds the common method for configs.
@ -159,7 +164,7 @@ class BaseConfig(MetaConfig('ConfigBase', (object,), {})):
self._update(kwargs)
def _update(self, kwargs):
"""Updates the config with the given dictionary"""
"""Update the config with the given dictionary"""
self.__dict__.update(
dict([(k, v) for (k, v) in kwargs.items()
if not k.startswith('_') and k in dir(self)]))
@ -182,6 +187,7 @@ class BaseConfig(MetaConfig('ConfigBase', (object,), {})):
class CommonConfig(BaseConfig):
"""Class holding options used in both chart and serie configuration"""
stroke = Key(
@ -213,6 +219,7 @@ class CommonConfig(BaseConfig):
class Config(CommonConfig):
"""Class holding config values"""
style = Key(
@ -496,6 +503,7 @@ class Config(CommonConfig):
class SerieConfig(CommonConfig):
"""Class holding serie config values"""
secondary = Key(

3
pygal/etree.py

@ -25,9 +25,11 @@ import os
class Etree(object):
"""Etree wrapper using lxml.etree or standard xml.etree"""
def __init__(self):
"""Create the wrapper"""
from xml.etree import ElementTree as _py_etree
self._py_etree = _py_etree
try:
@ -43,6 +45,7 @@ class Etree(object):
self.lxml = self._etree is self._lxml_etree
def __getattribute__(self, attr):
"""Retrieve attr from current active etree implementation"""
if (attr not in object.__getattribute__(self, '__dict__') and
attr not in Etree.__dict__):
return object.__getattribute__(self._etree, attr)

1
pygal/interpolate.py

@ -33,7 +33,6 @@ def quadratic_interpolate(x, y, precision=250, **kwargs):
Interpolate x, y using a quadratic algorithm
https://en.wikipedia.org/wiki/Spline_(mathematics)
"""
n = len(x) - 1
delta_x = [x2 - x1 for x1, x2 in zip(x, x[1:])]
delta_y = [y2 - y1 for y1, y2 in zip(y, y[1:])]

11
pygal/serie.py

@ -16,16 +16,16 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Little helpers for series
"""
"""Serie property holder"""
from pygal.util import cached_property
class Serie(object):
"""Serie containing title, values and the graph serie index"""
"""Serie class containing title, values and the graph serie index"""
def __init__(self, index, title, values, config, metadata=None):
"""Create the serie with its options"""
self.index = index
self.title = title
self.values = values
@ -35,4 +35,5 @@ class Serie(object):
@cached_property
def safe_values(self):
"""Property containing all values that are not None"""
return list(filter(lambda x: x is not None, self.values))

12
pygal/state.py

@ -16,14 +16,20 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Class holding state during render
"""
"""Class holding state during render"""
class State(object):
"""
Class containing config values
overriden by chart values
overriden by keyword args
"""
def __init__(self, graph, **kwargs):
"""Create the transient state"""
self.__dict__.update(**graph.config.__dict__)
self.__dict__.update(**graph.__dict__)
self.__dict__.update(**kwargs)

23
pygal/style.py

@ -16,9 +16,8 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Charts styling
"""
"""Charts styling classes"""
from __future__ import division
from pygal.util import cycle_fill
from pygal import colors
@ -27,7 +26,9 @@ import sys
class Style(object):
"""Styling class containing colors for the css generation"""
def __init__(
self,
background='black',
@ -45,6 +46,7 @@ class Style(object):
'#899ca1', '#f8f8f2', '#bf4646', '#516083', '#f92672',
'#82b414', '#fd971f', '#56c2d6', '#808384', '#8c54fe',
'#465457')):
"""Create the style"""
self.background = background
self.plot_background = plot_background
self.foreground = foreground
@ -58,7 +60,6 @@ class Style(object):
def get_colors(self, prefix, len_):
"""Get the css color list"""
def color(tupl):
"""Make a color css"""
return ((
@ -75,6 +76,7 @@ class Style(object):
return '\n'.join(map(color, enumerate(colors)))
def to_dict(self):
"""Convert instance to a serializable mapping."""
config = {}
for attr in dir(self):
if not attr.startswith('__'):
@ -296,10 +298,23 @@ for op in ('lighten', 'darken', 'saturate', 'desaturate', 'rotate'):
name = op.capitalize() + 'Style'
def get_style_for(op_name):
"""
Return a callable that returns a Style instance
for the given operation
"""
operation = getattr(colors, op_name)
def parametric_style(color, step=10, max_=None, base_style=None,
**kwargs):
"""
Generate a parametric Style instance of the parametric operation
This takes several parameters:
* a `step` which correspond on how many colors will be needed
* a `max_` which defines the maximum amplitude of the color effect
* a `base_style` which will be taken as default for everything
except colors
* any keyword arguments setting other style parameters
"""
if max_ is None:
violency = {
'darken': 50,

14
pygal/svg.py

@ -16,10 +16,8 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Svg helper
"""
"""Svg helper"""
from __future__ import division
from pygal._compat import to_str, u
@ -35,11 +33,14 @@ from pygal import __version__
class Svg(object):
"""Svg object"""
"""Svg related methods"""
ns = 'http://www.w3.org/2000/svg'
xlink_ns = 'http://www.w3.org/1999/xlink'
def __init__(self, graph):
"""Create the svg helper with the chart instance"""
self.graph = graph
if not graph.no_prefix:
self.id = '#chart-%s ' % graph.uuid
@ -98,7 +99,9 @@ class Svg(object):
os.path.dirname(__file__), 'css', css)
class FontSizes(object):
"""Container for font sizes"""
fs = FontSizes()
for name in dir(self.graph.state):
if name.endswith('_font_size'):
@ -286,6 +289,7 @@ class Svg(object):
self.root.set('height', str(self.graph.height))
def draw_no_data(self):
"""Write the no data text to the svg"""
no_data = self.node(self.graph.nodes['text_overlay'], 'text',
x=self.graph.view.width / 2,
y=self.graph.view.height / 2,
@ -314,7 +318,9 @@ class Svg(object):
return svg
def get_strokes(self):
"""Return a css snippet containing all stroke style options"""
def stroke_dict_to_css(stroke, i=None):
"""Return a css style for the given option"""
css = ['%s.series%s {\n' % (
self.id, '.serie-%d' % i if i is not None else '')]
for key in ('width', 'linejoin', 'linecap',

19
pygal/table.py

@ -17,8 +17,9 @@
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Table maker
HTML Table maker.
This class is used to render an html table from a chart data.
"""
from pygal.util import template
@ -27,18 +28,32 @@ import uuid
class HTML(object):
"""Lower case adapter of lxml builder"""
def __getattribute__(self, attr):
"""Get the uppercase builder attribute"""
return getattr(builder, attr.upper())
class Table(object):
"""Table generator class"""
_dual = None
def __init__(self, chart):
"Init the table"
"""Init the table"""
self.chart = chart
def render(self, total=False, transpose=False, style=False):
"""Render the HTMTL table of the chart.
`total` can be specified to include data sums
`transpose` make labels becomes columns
`style` include scoped style for the table
"""
self.chart.setup()
ln = self.chart._len
fmt = self.chart._format

18
pygal/util.py

@ -16,10 +16,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Various utils
"""
"""Various utility functions"""
from __future__ import division
from pygal._compat import u, is_list_like, to_unicode
import re
@ -250,6 +249,7 @@ def decorate(svg, node, metadata):
def alter(node, metadata):
"""Override nodes attributes from metadata node mapping"""
if node is not None and metadata and 'node' in metadata:
node.attrib.update(
dict((k, str(v)) for k, v in metadata['node'].items()))
@ -273,14 +273,21 @@ def truncate(string, index):
# # Stolen partly from brownie http://packages.python.org/Brownie/
class cached_property(object):
"""Optimize a static property"""
"""Memoize a property"""
def __init__(self, getter, doc=None):
"""Initialize the decorator"""
self.getter = getter
self.__module__ = getter.__module__
self.__name__ = getter.__name__
self.__doc__ = doc or getter.__doc__
def __get__(self, obj, type_=None):
"""
Get descriptor calling the property function and replacing it with
its value or on state if we are in the transient state.
"""
if obj is None:
return self
value = self.getter(obj)
@ -294,6 +301,7 @@ css_comments = re.compile(r'/\*.*?\*/', re.MULTILINE | re.DOTALL)
def minify_css(css):
"""Little css minifier"""
# Inspired by slimmer by Peter Bengtsson
remove_next_comment = 1
for css_comment in css_comments.findall(css):
@ -328,12 +336,14 @@ def compose(f, g):
def safe_enumerate(iterable):
"""Enumerate which does not yield None values"""
for i, v in enumerate(iterable):
if v is not None:
yield i, v
def split_title(title, width, title_fs):
"""Split a string for a specified width and font size"""
titles = []
if not title:
return titles

68
pygal/view.py

@ -16,17 +16,19 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Projection and bounding helpers
"""
"""Projection and bounding helpers"""
from __future__ import division
from math import sin, cos, log10, pi
class Margin(object):
"""Graph margin"""
"""Class reprensenting a margin (top, right, left, bottom)"""
def __init__(self, top, right, bottom, left):
"""Create the margin object from the top, right, left, bottom margin"""
self.top = top
self.right = right
self.bottom = bottom
@ -44,16 +46,23 @@ class Margin(object):
class Box(object):
"""Chart boundings"""
margin = .02
def __init__(self, xmin=0, ymin=0, xmax=1, ymax=1):
"""
Create the chart bounds with min max horizontal
and vertical values
"""
self._xmin = xmin
self._ymin = ymin
self._xmax = xmax
self._ymax = ymax
def set_polar_box(self, rmin=0, rmax=1, tmin=0, tmax=2 * pi):
"""Helper for polar charts"""
self._rmin = rmin
self._rmax = rmax
self._tmin = tmin
@ -63,37 +72,45 @@ class Box(object):
@property
def xmin(self):
"""X minimum getter"""
return self._xmin
@xmin.setter
def xmin(self, value):
"""X minimum setter"""
if value:
self._xmin = value
@property
def ymin(self):
"""Y minimum getter"""
return self._ymin
@ymin.setter
def ymin(self, value):
"""Y minimum setter"""
if value:
self._ymin = value
@property
def xmax(self):
"""X maximum getter"""
return self._xmax
@xmax.setter
def xmax(self, value):
"""X maximum setter"""
if value:
self._xmax = value
@property
def ymax(self):
"""Y maximum getter"""
return self._ymax
@ymax.setter
def ymax(self, value):
"""Y maximum setter"""
if value:
self._ymax = value
@ -129,8 +146,11 @@ class Box(object):
class View(object):
"""Projection base class"""
def __init__(self, width, height, box):
"""Create the view with a width an height and a box bounds"""
self.width = width
self.height = height
self.box = box
@ -156,14 +176,22 @@ class View(object):
class ReverseView(View):
"""Same as view but reversed vertically"""
def y(self, y):
"""Project reversed y"""
if y is None:
return None
return (self.height * (y - self.box.ymin) / self.box.height)
class HorizontalView(View):
"""Same as view but transposed"""
def __init__(self, width, height, box):
"""Create the view with a width an height and a box bounds"""
self._force_vertical = None
self.width = width
self.height = height
@ -173,7 +201,7 @@ class HorizontalView(View):
self.box.swap()
def x(self, x):
"""Project x"""
"""Project x as y"""
if x is None:
return None
if self._force_vertical:
@ -181,6 +209,7 @@ class HorizontalView(View):
return super(HorizontalView, self).y(x)
def y(self, y):
"""Project y as x"""
if y is None:
return None
if self._force_vertical:
@ -189,6 +218,7 @@ class HorizontalView(View):
class PolarView(View):
"""Polar projection for pie like graphs"""
def __call__(self, rhotheta):
@ -201,9 +231,11 @@ class PolarView(View):
class PolarLogView(View):
"""Logarithmic polar projection"""
def __init__(self, width, height, box):
"""Create the view with a width an height and a box bounds"""
super(PolarLogView, self).__init__(width, height, box)
if not hasattr(box, '_rmin') or not hasattr(box, '_rmax'):
raise Exception(
@ -230,9 +262,11 @@ class PolarLogView(View):
class PolarThetaView(View):
"""Logarithmic polar projection"""
def __init__(self, width, height, box, aperture=pi / 3):
"""Create the view with a width an height and a box bounds"""
super(PolarThetaView, self).__init__(width, height, box)
self.aperture = aperture
if not hasattr(box, '_tmin') or not hasattr(box, '_tmax'):
@ -253,9 +287,11 @@ class PolarThetaView(View):
class PolarThetaLogView(View):
"""Logarithmic polar projection"""
def __init__(self, width, height, box, aperture=pi / 3):
"""Create the view with a width an height and a box bounds"""
super(PolarThetaLogView, self).__init__(width, height, box)
self.aperture = aperture
if not hasattr(box, '_tmin') or not hasattr(box, '_tmax'):
@ -288,9 +324,12 @@ class PolarThetaLogView(View):
class LogView(View):
"""Logarithmic projection """
"""Y Logarithmic projection"""
# Do not want to call the parent here
def __init__(self, width, height, box):
"""Create the view with a width an height and a box bounds"""
self.width = width
self.height = height
self.box = box
@ -310,9 +349,12 @@ class LogView(View):
class XLogView(View):
"""Logarithmic projection """
"""X logarithmic projection"""
# Do not want to call the parent here
def __init__(self, width, height, box):
"""Create the view with a width an height and a box bounds"""
self.width = width
self.height = height
self.box = box
@ -330,7 +372,11 @@ class XLogView(View):
class XYLogView(XLogView, LogView):
"""X and Y logarithmic projection"""
def __init__(self, width, height, box):
"""Create the view with a width an height and a box bounds"""
self.width = width
self.height = height
self.box = box
@ -342,9 +388,12 @@ class XYLogView(XLogView, LogView):
class HorizontalLogView(XLogView):
"""Logarithmic projection """
"""Transposed Logarithmic projection"""
# Do not want to call the parent here
def __init__(self, width, height, box):
"""Create the view with a width an height and a box bounds"""
self._force_vertical = None
self.width = width
self.height = height
@ -357,7 +406,7 @@ class HorizontalLogView(XLogView):
self.box.swap()
def x(self, x):
"""Project x"""
"""Project x as y"""
if x is None:
return None
if self._force_vertical:
@ -365,6 +414,7 @@ class HorizontalLogView(XLogView):
return super(XLogView, self).y(x)
def y(self, y):
"""Project y as x"""
if y is None:
return None
if self._force_vertical:

Loading…
Cancel
Save