diff --git a/pygal/__init__.py b/pygal/__init__.py index 87121f4..f9ad508 100644 --- a/pygal/__init__.py +++ b/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 . """ -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] diff --git a/pygal/_compat.py b/pygal/_compat.py index 2dc9e08..d5ce470 100644 --- a/pygal/_compat.py +++ b/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 . -""" -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: diff --git a/pygal/adapters.py b/pygal/adapters.py index 95ef55a..3720c63 100644 --- a/pygal/adapters.py +++ b/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 . -""" -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 diff --git a/pygal/colors.py b/pygal/colors.py index fa47e37..9f6df7a 100644 --- a/pygal/colors.py +++ b/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) diff --git a/pygal/config.py b/pygal/config.py index bf0a7db..87a86b8 100644 --- a/pygal/config.py +++ b/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 . +"""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( diff --git a/pygal/etree.py b/pygal/etree.py index aa5a46f..721a0a7 100644 --- a/pygal/etree.py +++ b/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) diff --git a/pygal/interpolate.py b/pygal/interpolate.py index 975a6aa..241f0b9 100644 --- a/pygal/interpolate.py +++ b/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:])] diff --git a/pygal/serie.py b/pygal/serie.py index 156b46d..e5b105c 100644 --- a/pygal/serie.py +++ b/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 . -""" -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)) diff --git a/pygal/state.py b/pygal/state.py index eafcc7a..96ce666 100644 --- a/pygal/state.py +++ b/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 . -""" -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) diff --git a/pygal/style.py b/pygal/style.py index 0d33641..102da10 100644 --- a/pygal/style.py +++ b/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 . -""" -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, diff --git a/pygal/svg.py b/pygal/svg.py index b65443b..06c3bd8 100644 --- a/pygal/svg.py +++ b/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 . -""" -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', diff --git a/pygal/table.py b/pygal/table.py index ed9a05e..c32168d 100644 --- a/pygal/table.py +++ b/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 . """ -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 diff --git a/pygal/util.py b/pygal/util.py index 94f749f..1fa96f0 100644 --- a/pygal/util.py +++ b/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 . -""" -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 diff --git a/pygal/view.py b/pygal/view.py index 07f5e28..a1c74b8 100644 --- a/pygal/view.py +++ b/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 . -""" -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: