From fcd3460089ee12480d79de7a28fba2b4d641ffd8 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Wed, 15 Jul 2015 15:44:16 +0200 Subject: [PATCH] Document pygal.graph package --- pygal/__init__.py | 3 +- pygal/graph/__init__.py | 4 +- pygal/graph/bar.py | 13 ++++-- pygal/graph/base.py | 11 +++-- pygal/graph/box.py | 17 ++++---- pygal/graph/dot.py | 10 +++-- pygal/graph/funnel.py | 16 ++++---- pygal/graph/gauge.py | 14 +++++-- pygal/graph/graph.py | 23 ++++++++--- pygal/graph/histogram.py | 20 ++++----- pygal/graph/horizontal.py | 14 ++++--- pygal/graph/horizontalbar.py | 7 ++-- pygal/graph/horizontalstackedbar.py | 6 +-- pygal/graph/line.py | 14 +++++-- pygal/graph/map.py | 13 +++++- pygal/graph/pie.py | 8 ++-- pygal/graph/public.py | 9 +++-- pygal/graph/pyramid.py | 56 +++++++++++++++++++++++-- pygal/graph/radar.py | 16 ++++++-- pygal/graph/stackedbar.py | 13 ++++-- pygal/graph/stackedline.py | 16 ++++++-- pygal/graph/time.py | 23 ++++++++++- pygal/graph/treemap.py | 7 ++-- pygal/graph/verticalpyramid.py | 63 ----------------------------- pygal/graph/xy.py | 22 +++++----- pygal/maps/__init__.py | 4 +- 26 files changed, 253 insertions(+), 169 deletions(-) delete mode 100644 pygal/graph/verticalpyramid.py diff --git a/pygal/__init__.py b/pygal/__init__.py index f9ad508..4eb6fd9 100644 --- a/pygal/__init__.py +++ b/pygal/__init__.py @@ -36,13 +36,12 @@ from pygal.graph.horizontalbar import HorizontalBar from pygal.graph.horizontalstackedbar import HorizontalStackedBar from pygal.graph.line import Line from pygal.graph.pie import Pie -from pygal.graph.pyramid import Pyramid +from pygal.graph.pyramid import Pyramid, VerticalPyramid from pygal.graph.radar import Radar from pygal.graph.stackedbar import StackedBar from pygal.graph.stackedline import StackedLine from pygal.graph.time import DateLine, DateTimeLine, TimeLine, TimeDeltaLine from pygal.graph.treemap import Treemap -from pygal.graph.verticalpyramid import VerticalPyramid from pygal.graph.xy import XY from pygal.graph.graph import Graph from pygal.config import Config diff --git a/pygal/graph/__init__.py b/pygal/graph/__init__.py index 0260a57..8f61de8 100644 --- a/pygal/graph/__init__.py +++ b/pygal/graph/__init__.py @@ -16,7 +16,5 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Graph modules -""" +"""Graph package containing all builtin charts""" diff --git a/pygal/graph/bar.py b/pygal/graph/bar.py index 665376d..ed03661 100644 --- a/pygal/graph/bar.py +++ b/pygal/graph/bar.py @@ -16,9 +16,10 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Bar chart +""" +Bar chart that presents grouped data with rectangular bars with lengths +proportional to the values that they represent. """ from __future__ import division @@ -27,16 +28,19 @@ from pygal.util import swap, ident, compute_scale, decorate, alter class Bar(Graph): - """Bar graph""" + + """Bar graph class""" _series_margin = .06 _serie_margin = .06 def __init__(self, *args, **kwargs): + """Bar chart creation""" self._x_ranges = None super(Bar, self).__init__(*args, **kwargs) def _bar(self, serie, parent, x, y, i, zero, secondary=False): + """Internal bar drawing function""" width = (self.view.x(1) - self.view.x(0)) / self._len x, y = self.view((x, y)) series_margin = width * self._series_margin @@ -88,6 +92,7 @@ class Bar(Graph): self._static_value(serie_node, val, x_center, y_center) def _compute(self): + """Compute y min and max and y scale and set labels""" if self._min: self._box.ymin = min(self._min, self.zero) if self._max: @@ -109,6 +114,7 @@ class Bar(Graph): self._y_labels = list(zip(map(self._format, y_pos), y_pos)) def _compute_secondary(self): + """Compute parameters for secondary series rendering""" if self.secondary_series: y_pos = list(zip(*self._y_labels))[1] ymin = self._secondary_min @@ -133,6 +139,7 @@ class Bar(Graph): for y in y_pos] def _plot(self): + """Draw bars for series and secondary series""" for serie in self.series: self.bar(serie) for serie in self.secondary_series: diff --git a/pygal/graph/base.py b/pygal/graph/base.py index 54add4e..2625fa9 100644 --- a/pygal/graph/base.py +++ b/pygal/graph/base.py @@ -16,10 +16,8 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Base for pygal charts -""" +"""Base for pygal charts""" from __future__ import division from pygal._compat import is_list_like @@ -38,11 +36,13 @@ import os class BaseGraph(object): + """Chart internal behaviour related functions""" _adapters = [] def __init__(self, config=None, **kwargs): + """Config preparation and various initialization""" if config: if isinstance(config, type): config = config() @@ -60,12 +60,14 @@ class BaseGraph(object): self.xml_filters = [] def __setattr__(self, name, value): + """Set an attribute on the class or in the state if there is one""" if name.startswith('__') or getattr(self, 'state', None) is None: super(BaseGraph, self).__setattr__(name, value) else: setattr(self.state, name, value) def __getattribute__(self, name): + """Get an attribute from the class or from the state if there is one""" if name.startswith('__') or name == 'state' or getattr( self, 'state', None ) is None or name not in self.state.__dict__: @@ -173,7 +175,7 @@ class BaseGraph(object): return series def setup(self, **kwargs): - """Init the graph""" + """Set up the transient state prior rendering""" # Keep labels in case of map if getattr(self, 'x_labels', None) is not None: self.x_labels = list(self.x_labels) @@ -212,6 +214,7 @@ class BaseGraph(object): self.svg.pre_render() def teardown(self): + """Remove the transient state after rendering""" if os.getenv('PYGAL_KEEP_STATE'): return diff --git a/pygal/graph/box.py b/pygal/graph/box.py index c7846d6..931d152 100644 --- a/pygal/graph/box.py +++ b/pygal/graph/box.py @@ -16,8 +16,10 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ -Box plot +Box plot: a convenient way to display series as box with whiskers and outliers +Different types are available throught the box_mode option """ from __future__ import division @@ -28,6 +30,7 @@ from bisect import bisect_left, bisect_right class Box(Graph): + """ Box plot For each series, shows the median value, the 25th and 75th percentiles, @@ -36,10 +39,8 @@ class Box(Graph): See http://en.wikipedia.org/wiki/Box_plot """ - _series_margin = .06 - def __init__(self, *args, **kwargs): - super(Box, self).__init__(*args, **kwargs) + _series_margin = .06 @property def _format(self): @@ -91,9 +92,7 @@ class Box(Graph): self._y_labels = list(zip(map(self._format, y_pos), y_pos)) def _plot(self): - """ - Plot the series data - """ + """Plot the series data""" for serie in self.series: self._boxf(serie) @@ -103,9 +102,7 @@ class Box(Graph): return 7 def _boxf(self, serie): - """ - For a specific series, draw the box plot. - """ + """For a specific series, draw the box plot.""" serie_node = self.svg.serie(serie) # Note: q0 and q4 do not literally mean the zero-th quartile # and the fourth quartile, but rather the distance from 1.5 times diff --git a/pygal/graph/dot.py b/pygal/graph/dot.py index f935088..6c82097 100644 --- a/pygal/graph/dot.py +++ b/pygal/graph/dot.py @@ -16,9 +16,10 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Dot chart +""" +Dot chart displaying values as a grid of dots, the bigger the value +the bigger the dot """ from __future__ import division @@ -29,7 +30,8 @@ from math import log10 class Dot(Graph): - """Dot graph""" + + """Dot graph class""" def dot(self, serie, r_max): """Draw a dot line""" @@ -68,6 +70,7 @@ class Dot(Graph): self._static_value(serie_node, value, x, y) def _compute(self): + """Compute y min and max and y scale and set labels""" x_len = self._len y_len = self._order self._box.xmax = x_len @@ -106,6 +109,7 @@ class Dot(Graph): else (max(map(abs, self._values)) if self._values else None)) def _plot(self): + """Plot all dots for series""" r_max = min( self.view.x(1) - self.view.x(0), (self.view.y(0) or 0) - self.view.y(1)) / ( diff --git a/pygal/graph/funnel.py b/pygal/graph/funnel.py index 2dcedc9..387b64a 100644 --- a/pygal/graph/funnel.py +++ b/pygal/graph/funnel.py @@ -16,10 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Funnel chart - -""" +"""Funnel chart: Represent values as a funnel""" from __future__ import division from pygal.util import decorate, cut, compute_scale, alter @@ -28,15 +25,18 @@ from pygal.graph.graph import Graph class Funnel(Graph): - """Funnel graph""" + + """Funnel graph class""" _adapters = [positive, none_to_zero] def _format(self, value): - return super(Funnel, self)._format(abs(value)) + """Return the value formatter for this graph here its absolute value""" + value = value and abs(value) + return super(Funnel, self)._format(value) def funnel(self, serie): - """Draw a dot line""" + """Draw a funnel slice""" serie_node = self.svg.serie(serie) fmt = lambda x: '%f %f' % x for i, poly in enumerate(serie.points): @@ -60,6 +60,7 @@ class Funnel(Graph): self._static_value(serie_node, value, x, y) def _compute(self): + """Compute y min and max and y scale and set labels""" x_pos = [ (x + 1) / self._order for x in range(self._order) ] if self._order != 1 else [.5] # Center if only one value @@ -94,5 +95,6 @@ class Funnel(Graph): self._y_labels = list(zip(map(self._format, y_pos), y_pos)) def _plot(self): + """Plot the funnel""" for serie in self.series: self.funnel(serie) diff --git a/pygal/graph/gauge.py b/pygal/graph/gauge.py index 3e4f7ac..a52f582 100644 --- a/pygal/graph/gauge.py +++ b/pygal/graph/gauge.py @@ -16,10 +16,8 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Gauge chart -""" +"""Gauge chart representing values as needles on a polar scale""" from __future__ import division from pygal.util import decorate, compute_scale, alter @@ -28,10 +26,13 @@ from pygal.graph.graph import Graph class Gauge(Graph): - """Gauge graph""" + + """Gauge graph class""" + needle_width = 1 / 20 def _set_view(self): + """Assign a view to current graph""" if self.logarithmic: view_class = PolarThetaLogView else: @@ -43,6 +44,7 @@ class Gauge(Graph): self._box) def needle(self, serie): + """Draw a needle for each value""" serie_node = self.svg.serie(serie) for i, theta in enumerate(serie.values): if theta is None: @@ -87,6 +89,7 @@ class Gauge(Graph): self._static_value(serie_node, value, x, y) def _y_axis(self, draw_axes=True): + """Override y axis to plot a polar axis""" axis = self.svg.node(self.nodes['plot'], class_="axis y x gauge") for i, (label, theta) in enumerate(self._y_labels): @@ -112,11 +115,13 @@ class Gauge(Graph): ).text = label def _x_axis(self, draw_axes=True): + """Override x axis to put a center circle in center""" axis = self.svg.node(self.nodes['plot'], class_="axis x gauge") x, y = self.view((0, 0)) self.svg.node(axis, 'circle', cx=x, cy=y, r=4) def _compute(self): + """Compute y min and max and y scale and set labels""" self.min_ = self._min or 0 self.max_ = self._max or 0 if self.max_ - self.min_ == 0: @@ -135,5 +140,6 @@ class Gauge(Graph): self._y_labels = list(zip(map(self._format, y_pos), y_pos)) def _plot(self): + """Plot all needles""" for serie in self.series: self.needle(serie) diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index 0997dcd..59649a6 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -16,10 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Commmon graphing functions - -""" +"""Chart properties and drawing""" from __future__ import division from pygal._compat import is_list_like @@ -35,7 +32,9 @@ from itertools import repeat, chain class Graph(PublicApi): + """Graph super class containing generic common functions""" + _dual = False def _decorate(self): @@ -427,12 +426,14 @@ class Graph(PublicApi): **self.interpolation_parameters)) def _rescale(self, points): + """Scale for secondary""" return [ (x, self._scale_diff + (y - self._scale_min_2nd) * self._scale if y is not None else None) for x, y in points] def _tooltip_data(self, node, value, x, y, classes=None): + """Insert in desc tags informations for the javascript tooltip""" self.svg.node(node, 'desc', class_="value").text = value if classes is None: classes = [] @@ -448,6 +449,7 @@ class Graph(PublicApi): class_="y " + classes).text = str(y) def _static_value(self, serie_node, value, x, y): + """Write the print value""" if self.print_values: self.svg.node( serie_node['text_overlay'], 'text', @@ -461,6 +463,10 @@ class Graph(PublicApi): return self._format(values[i][1]) def _points(self, x_pos): + """ + Convert given data values into drawable points (x, y) + and interpolated points if interpolate option is specified + """ for serie in self.all_series: serie.points = [ (x_pos[i], v) @@ -471,6 +477,7 @@ class Graph(PublicApi): serie.interpolated = [] def _compute_secondary(self): + """Compute secondary axis min max and label positions""" # secondary y axis support if self.secondary_series and self._y_labels: y_pos = list(zip(*self._y_labels))[1] @@ -492,10 +499,12 @@ class Graph(PublicApi): self._scale_min_2nd = ymin def _post_compute(self): + """Hook called after compute and before margin computations and plot""" pass @property def all_series(self): + """Getter for all series (nomal and secondary)""" return self.series + self.secondary_series @property @@ -721,7 +730,9 @@ class Graph(PublicApi): def _has_data(self): """Check if there is any data""" return any([ - len([v for v in ( - s[1] if is_list_like(s) else [s]) if v is not None]) + len([v for a in ( + s[1] if is_list_like(s) else [s]) + for v in (a if self._dual else [a]) + if v is not None]) for s in self.raw_series ]) diff --git a/pygal/graph/histogram.py b/pygal/graph/histogram.py index cc937b5..3d47bcd 100644 --- a/pygal/graph/histogram.py +++ b/pygal/graph/histogram.py @@ -17,18 +17,20 @@ # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . """ -Histogram chart - +Histogram chart: like a bar chart but with data plotted along a x axis +as bars of varying width. """ from __future__ import division +from pygal._compat import is_list_like from pygal.graph.graph import Graph from pygal.util import ( swap, ident, compute_scale, decorate, cached_property, alter) class Histogram(Graph): - """Histogram chart""" + + """Histogram chart class""" _dual = True _series_margin = 0 @@ -43,6 +45,7 @@ class Histogram(Graph): @cached_property def xvals(self): + """All x values""" return [val for serie in self.all_series for dval in serie.values @@ -51,19 +54,14 @@ class Histogram(Graph): @cached_property def yvals(self): + """All y values""" return [val[0] for serie in self.series for val in serie.values if val[0] is not None] - def _has_data(self): - """Check if there is any data""" - return sum( - map(len, map(lambda s: s.safe_values, self.series))) != 0 and any(( - sum(map(abs, self.xvals)) != 0, - sum(map(abs, self.yvals)) != 0)) - def _bar(self, serie, parent, x0, x1, y, i, zero, secondary=False): + """Internal bar drawing function""" x, y = self.view((x0, y)) x1, _ = self.view((x1, y)) width = x1 - x @@ -104,6 +102,7 @@ class Histogram(Graph): self._static_value(serie_node, val, x_center, y_center) def _compute(self): + """Compute x/y min and max and x/y scale and set labels""" if self.xvals: xmin = min(self.xvals) xmax = max(self.xvals) @@ -139,6 +138,7 @@ class Histogram(Graph): self._y_labels = list(zip(map(self._format, y_pos), y_pos)) def _plot(self): + """Draw bars for series and secondary series""" for serie in self.series: self.bar(serie) for serie in self.secondary_series: diff --git a/pygal/graph/horizontal.py b/pygal/graph/horizontal.py index 75096c6..d482481 100644 --- a/pygal/graph/horizontal.py +++ b/pygal/graph/horizontal.py @@ -16,32 +16,34 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Horizontal graph base - -""" +"""Horizontal graph mixin""" from pygal.graph.graph import Graph from pygal.view import HorizontalView, HorizontalLogView class HorizontalGraph(Graph): - """Horizontal graph""" + + """Horizontal graph mixin""" + def __init__(self, *args, **kwargs): + """Set the horizontal flag to True""" self.horizontal = True super(HorizontalGraph, self).__init__(*args, **kwargs) def _post_compute(self): + """After computations transpose labels""" self._x_labels, self._y_labels = self._y_labels, self._x_labels self._x_2nd_labels, self._y_2nd_labels = ( self._y_2nd_labels, self._x_2nd_labels) def _axes(self): + """Set the _force_vertical flag when rendering axes""" self.view._force_vertical = True super(HorizontalGraph, self)._axes() self.view._force_vertical = False def _set_view(self): - """Assign a view to current graph""" + """Assign a horizontal view to current graph""" if self.logarithmic: view_class = HorizontalLogView else: diff --git a/pygal/graph/horizontalbar.py b/pygal/graph/horizontalbar.py index 796c718..56ffeec 100644 --- a/pygal/graph/horizontalbar.py +++ b/pygal/graph/horizontalbar.py @@ -16,18 +16,19 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Horizontal bar graph -""" +"""Horizontal bar graph""" + from pygal.graph.horizontal import HorizontalGraph from pygal.graph.bar import Bar class HorizontalBar(HorizontalGraph, Bar): + """Horizontal Bar graph""" def _plot(self): + """Draw the bars in reverse order""" for serie in self.series[::-1]: self.bar(serie) for serie in self.secondary_series[::-1]: diff --git a/pygal/graph/horizontalstackedbar.py b/pygal/graph/horizontalstackedbar.py index 2da3ddb..80b7182 100644 --- a/pygal/graph/horizontalstackedbar.py +++ b/pygal/graph/horizontalstackedbar.py @@ -16,13 +16,13 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Horizontal stacked graph -""" +"""Horizontal stacked graph""" + from pygal.graph.horizontal import HorizontalGraph from pygal.graph.stackedbar import StackedBar class HorizontalStackedBar(HorizontalGraph, StackedBar): + """Horizontal Stacked Bar graph""" diff --git a/pygal/graph/line.py b/pygal/graph/line.py index b480477..7343bdb 100644 --- a/pygal/graph/line.py +++ b/pygal/graph/line.py @@ -16,24 +16,29 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Line chart """ +Line chart: Display series of data as markers (dots) +connected by straight segments +""" + from __future__ import division from pygal.graph.graph import Graph from pygal.util import cached_property, compute_scale, decorate, alter class Line(Graph): - """Line graph""" + + """Line graph class""" def __init__(self, *args, **kwargs): + """Set _self_close as False, it's True for Radar like Line""" self._self_close = False super(Line, self).__init__(*args, **kwargs) @cached_property def _values(self): + """Getter for series values (flattened)""" return [ val[1] for serie in self.series @@ -43,6 +48,7 @@ class Line(Graph): @cached_property def _secondary_values(self): + """Getter for secondary series values (flattened)""" return [ val[1] for serie in self.secondary_series @@ -132,6 +138,7 @@ class Line(Graph): class_='line reactive' + (' nofill' if not serie.fill else '')) def _compute(self): + """Compute y min and max and y scale and set labels""" # X Labels x_pos = [ x / (self._len - 1) for x in range(self._len) @@ -171,6 +178,7 @@ class Line(Graph): self._y_labels = list(zip(map(self._format, y_pos), y_pos)) def _plot(self): + """Plot the serie lines and secondary serie lines""" for serie in self.series: self.line(serie) diff --git a/pygal/graph/map.py b/pygal/graph/map.py index a6edbf8..19b40eb 100644 --- a/pygal/graph/map.py +++ b/pygal/graph/map.py @@ -17,6 +17,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . +""" +pygal contains no map but a base class to create extension +see the pygal_maps_world package to get an exemple. +https://github.com/Kozea/pygal_maps_world +""" + from __future__ import division from pygal.graph.graph import Graph from pygal.util import cut, cached_property, decorate @@ -24,7 +30,9 @@ from pygal.etree import etree class BaseMap(Graph): - """Base map.""" + + """Base class for maps""" + _dual = True @cached_property @@ -36,12 +44,15 @@ class BaseMap(Graph): if val[1] is not None] def enumerate_values(self, serie): + """Hook to replace default enumeration on values""" return enumerate(serie.values) def adapt_code(self, area_code): + """Hook to change the area code""" return area_code def _plot(self): + """Insert a map in the chart and apply data on it""" map = etree.fromstring(self.svg_map) map.set('width', str(self.view.width)) map.set('height', str(self.view.height)) diff --git a/pygal/graph/pie.py b/pygal/graph/pie.py index 1391369..3aff1a3 100644 --- a/pygal/graph/pie.py +++ b/pygal/graph/pie.py @@ -17,8 +17,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . """ -Pie chart - +Pie chart: A circular chart divided into slice to illustrate proportions +It can be made as a donut or a half pie. """ from __future__ import division @@ -29,7 +29,8 @@ from math import pi class Pie(Graph): - """Pie graph""" + + """Pie graph class""" _adapters = [positive, none_to_zero] @@ -94,6 +95,7 @@ class Pie(Graph): return serie_angle def _plot(self): + """Draw all the serie slices""" total = sum(map(sum, map(lambda x: x.values, self.series))) if total == 0: return diff --git a/pygal/graph/public.py b/pygal/graph/public.py index afc01e3..eb5066c 100644 --- a/pygal/graph/public.py +++ b/pygal/graph/public.py @@ -16,10 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -pygal public api functions - -""" +"""pygal public api functions""" import io from pygal._compat import u, is_list_like @@ -27,6 +24,7 @@ from pygal.graph.base import BaseGraph class PublicApi(BaseGraph): + """Chart public functions""" def add(self, title, values, **kwargs): @@ -40,6 +38,7 @@ class PublicApi(BaseGraph): return self def add_xml_filter(self, callback): + """Add an xml filter for in tree post processing""" self.xml_filters.append(callback) return self @@ -61,6 +60,7 @@ class PublicApi(BaseGraph): return svg def render_table(self, **kwargs): + """Render the data as a html table""" # Import here to avoid lxml import try: from pygal.table import Table @@ -130,6 +130,7 @@ class PublicApi(BaseGraph): return chart def render_sparkline(self, **kwargs): + """Render a sparkline""" spark_options = dict( width=200, height=50, diff --git a/pygal/graph/pyramid.py b/pygal/graph/pyramid.py index aa1a75d..0f37ed7 100644 --- a/pygal/graph/pyramid.py +++ b/pygal/graph/pyramid.py @@ -16,14 +16,62 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Pyramid chart +""" +Pyramid chart: Stacked bar chart containing only positive values divided by two +axes, generally gender for age pyramid. """ +from __future__ import division + +from pygal.adapters import positive from pygal.graph.horizontal import HorizontalGraph -from pygal.graph.verticalpyramid import VerticalPyramid +from pygal.graph.stackedbar import StackedBar + + +class VerticalPyramid(StackedBar): + + """Vertical Pyramid graph class""" + + _adapters = [positive] + + def _format(self, value): + """Return the value formatter for this graph here its absolute value""" + value = value and abs(value) + return super(VerticalPyramid, self)._format(value) + + def _get_separated_values(self, secondary=False): + """Separate values between odd and even series stacked""" + series = self.secondary_series if secondary else self.series + positive_vals = map(sum, zip( + *[serie.safe_values + for index, serie in enumerate(series) + if index % 2])) + negative_vals = map(sum, zip( + *[serie.safe_values + for index, serie in enumerate(series) + if not index % 2])) + return list(positive_vals), list(negative_vals) + + def _compute_box(self, positive_vals, negative_vals): + """Compute Y min and max""" + self._box.ymax = max(max(positive_vals or [self.zero]), + max(negative_vals or [self.zero])) + self._box.ymin = - self._box.ymax + + def _pre_compute_secondary(self, positive_vals, negative_vals): + """Compute secondary y min and max""" + self._secondary_max = max(max(positive_vals), max(negative_vals)) + self._secondary_min = - self._secondary_max + + def _bar(self, serie, parent, x, y, i, zero, secondary=False): + """Internal stacking bar drawing function""" + if serie.index % 2: + y = -y + return super(VerticalPyramid, self)._bar( + serie, parent, x, y, i, zero, secondary) class Pyramid(HorizontalGraph, VerticalPyramid): - """Horizontal Pyramid graph""" + + """Horizontal Pyramid graph class like the one used by age pyramid""" diff --git a/pygal/graph/radar.py b/pygal/graph/radar.py index 7b5e2ed..d135366 100644 --- a/pygal/graph/radar.py +++ b/pygal/graph/radar.py @@ -16,9 +16,10 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Radar chart +""" +Radar chart: As known as kiviat chart or spider chart is a polar line chart +useful for multivariate observation. """ from __future__ import division @@ -30,23 +31,28 @@ from math import cos, pi class Radar(Line): - """Kiviat graph""" + + """Rada graph class""" _adapters = [positive, none_to_zero] def __init__(self, *args, **kwargs): + """Init custom vars""" self.x_pos = None self._rmax = None super(Radar, self).__init__(*args, **kwargs) def _fill(self, values): + """Add extra values to fill the line""" return values def _get_value(self, values, i): + """Get the value formatted for tooltip""" return self._format(values[i][0]) @cached_property def _values(self): + """Getter for series values (flattened)""" if self.interpolate: return [val[0] for serie in self.series for val in serie.interpolated] @@ -54,6 +60,7 @@ class Radar(Line): return super(Line, self)._values def _set_view(self): + """Assign a view to current graph""" if self.logarithmic: view_class = PolarLogView else: @@ -65,6 +72,7 @@ class Radar(Line): self._box) def _x_axis(self, draw_axes=True): + """Override x axis to make it polar""" if not self._x_labels: return @@ -114,6 +122,7 @@ class Radar(Line): deg(angle), format_(pos_text)) def _y_axis(self, draw_axes=True): + """Override y axis to make it polar""" if not self._y_labels: return @@ -156,6 +165,7 @@ class Radar(Line): ).text = label def _compute(self): + """Compute r min max and labels position""" delta = 2 * pi / self._len if self._len else 0 x_pos = [.5 * pi + i * delta for i in range(self._len + 1)] for serie in self.all_series: diff --git a/pygal/graph/stackedbar.py b/pygal/graph/stackedbar.py index aff93a8..90ec28b 100644 --- a/pygal/graph/stackedbar.py +++ b/pygal/graph/stackedbar.py @@ -17,8 +17,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . """ -Stacked Bar chart - +Stacked Bar chart: Like a bar chart but with all series stacking +on top of the others instead of being displayed side by side. """ from __future__ import division @@ -28,11 +28,13 @@ from pygal.adapters import none_to_zero class StackedBar(Bar): - """Stacked Bar graph""" + + """Stacked Bar graph class""" _adapters = [none_to_zero] def _get_separated_values(self, secondary=False): + """Separate values between positives and negatives stacked""" series = self.secondary_series if secondary else self.series transposed = list(zip(*[serie.values for serie in series])) positive_vals = [sum([ @@ -47,10 +49,12 @@ class StackedBar(Bar): return positive_vals, negative_vals def _compute_box(self, positive_vals, negative_vals): + """Compute Y min and max""" self._box.ymin = negative_vals and min(min(negative_vals), self.zero) self._box.ymax = positive_vals and max(max(positive_vals), self.zero) def _compute(self): + """Compute y min and max and y scale and set labels""" positive_vals, negative_vals = self._get_separated_values() self._compute_box(positive_vals, negative_vals) @@ -88,12 +92,14 @@ class StackedBar(Bar): self._pre_compute_secondary(positive_vals, negative_vals) def _pre_compute_secondary(self, positive_vals, negative_vals): + """Compute secondary y min and max""" self._secondary_min = (negative_vals and min( min(negative_vals), self.zero)) or self.zero self._secondary_max = (positive_vals and max( max(positive_vals), self.zero)) or self.zero def _bar(self, serie, parent, x, y, i, zero, secondary=False): + """Internal stacking bar drawing function""" if secondary: cumulation = (self.secondary_negative_cumulation if y < self.zero else @@ -131,6 +137,7 @@ class StackedBar(Bar): return transpose((x + width / 2, y + height / 2)) def _plot(self): + """Draw bars for series and secondary series""" for serie in self.series[::-1 if self.stack_from_top else 1]: self.bar(serie) for serie in self.secondary_series[::-1 if self.stack_from_top else 1]: diff --git a/pygal/graph/stackedline.py b/pygal/graph/stackedline.py index eb4f85d..d48b233 100644 --- a/pygal/graph/stackedline.py +++ b/pygal/graph/stackedline.py @@ -16,25 +16,30 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Stacked Line chart """ +Stacked Line chart: Like a line chart but with all lines stacking +on top of the others. Used along fill=True option. +""" + from __future__ import division from pygal.graph.line import Line from pygal.adapters import none_to_zero class StackedLine(Line): - """Stacked Line graph""" + + """Stacked Line graph class""" _adapters = [none_to_zero] def __init__(self, *args, **kwargs): + """Custom variable initialization""" self._previous_line = None super(StackedLine, self).__init__(*args, **kwargs) def _fill(self, values): + """Add extra values to fill the line""" if not self._previous_line: self._previous_line = values return super(StackedLine, self)._fill(values) @@ -43,6 +48,10 @@ class StackedLine(Line): return new_values def _points(self, x_pos): + """ + Convert given data values into drawable points (x, y) + and interpolated points if interpolate option is specified + """ for series_group in (self.series, self.secondary_series): accumulation = [0] * self._len for serie in series_group[::-1 if self.stack_from_top else 1]: @@ -56,6 +65,7 @@ class StackedLine(Line): serie.interpolated = [] def _plot(self): + """Plot stacked serie lines and stacked secondary lines""" for serie in self.series[::-1 if self.stack_from_top else 1]: self.line(serie) for serie in self.secondary_series[::-1 if self.stack_from_top else 1]: diff --git a/pygal/graph/time.py b/pygal/graph/time.py index 3f25112..69054ca 100644 --- a/pygal/graph/time.py +++ b/pygal/graph/time.py @@ -16,9 +16,12 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ -Various datetime line plot +XY time extensions: handle convertion of date, time, datetime, timedelta +into float for xy plot and back to their type for display """ + from pygal.adapters import positive from pygal.graph.xy import XY from datetime import datetime, date, time, timedelta @@ -26,36 +29,42 @@ from pygal._compat import timestamp, total_seconds def datetime_to_timestamp(x): + """Convert a datetime into a utc float timestamp""" if isinstance(x, datetime): return timestamp(x) return x def datetime_to_time(x): + """Convert a datetime into a time""" if isinstance(x, datetime): return x.time() return x def date_to_datetime(x): + """Convert a date into a datetime""" if not isinstance(x, datetime) and isinstance(x, date): return datetime.combine(x, time()) return x def time_to_datetime(x): + """Convert a time into a datetime""" if isinstance(x, time): return datetime.combine(date(1970, 1, 1), x) return x def timedelta_to_seconds(x): + """Convert a timedelta into an amount of seconds""" if isinstance(x, timedelta): return total_seconds(x) return x def time_to_seconds(x): + """Convert a time in a seconds sum""" if isinstance(x, time): return (( ((x.hour * 60) + x.minute) * 60 + x.second @@ -64,6 +73,7 @@ def time_to_seconds(x): def seconds_to_time(x): + """Convert a number of second into a time""" t = int(x * 10 ** 6) ms = t % 10 ** 6 t = t // 10 ** 6 @@ -76,6 +86,9 @@ def seconds_to_time(x): class DateTimeLine(XY): + + """DateTime abscissa xy graph class""" + _x_adapters = [datetime_to_timestamp, date_to_datetime] @property @@ -91,6 +104,8 @@ class DateTimeLine(XY): class DateLine(DateTimeLine): + """Date abscissa xy graph class""" + @property def _x_format(self): """Return the value formatter for this graph""" @@ -103,6 +118,9 @@ class DateLine(DateTimeLine): class TimeLine(DateTimeLine): + + """Time abscissa xy graph class""" + _x_adapters = [positive, time_to_seconds, datetime_to_time] @property @@ -117,6 +135,9 @@ class TimeLine(DateTimeLine): class TimeDeltaLine(XY): + + """TimeDelta abscissa xy graph class""" + _x_adapters = [timedelta_to_seconds] @property diff --git a/pygal/graph/treemap.py b/pygal/graph/treemap.py index 80547c4..bab2615 100644 --- a/pygal/graph/treemap.py +++ b/pygal/graph/treemap.py @@ -16,10 +16,8 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Treemap chart -""" +"""Treemap chart: Visualize data using nested recangles""" from __future__ import division from pygal.util import decorate, cut, alter @@ -28,7 +26,8 @@ from pygal.adapters import positive, none_to_zero class Treemap(Graph): - """Treemap graph""" + + """Treemap graph class""" _adapters = [positive, none_to_zero] diff --git a/pygal/graph/verticalpyramid.py b/pygal/graph/verticalpyramid.py deleted file mode 100644 index 7b4412d..0000000 --- a/pygal/graph/verticalpyramid.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of pygal -# -# A python svg graph plotting library -# Copyright © 2012-2015 Kozea -# -# This library is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# This library is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with pygal. If not, see . -""" -Pyramid chart - -""" - -from __future__ import division -from pygal.adapters import positive -from pygal.graph.stackedbar import StackedBar - - -class VerticalPyramid(StackedBar): - """Pyramid graph""" - - _adapters = [positive] - - def _format(self, value): - value = value and abs(value) - return super(VerticalPyramid, self)._format(value) - - def _get_separated_values(self, secondary=False): - series = self.secondary_series if secondary else self.series - positive_vals = map(sum, zip( - *[serie.safe_values - for index, serie in enumerate(series) - if index % 2])) - negative_vals = map(sum, zip( - *[serie.safe_values - for index, serie in enumerate(series) - if not index % 2])) - return list(positive_vals), list(negative_vals) - - def _compute_box(self, positive_vals, negative_vals): - self._box.ymax = max(max(positive_vals or [self.zero]), - max(negative_vals or [self.zero])) - self._box.ymin = - self._box.ymax - - def _pre_compute_secondary(self, positive_vals, negative_vals): - self._secondary_max = max(max(positive_vals), max(negative_vals)) - self._secondary_min = - self._secondary_max - - def _bar(self, serie, parent, x, y, i, zero, secondary=False): - if serie.index % 2: - y = -y - return super(VerticalPyramid, self)._bar( - serie, parent, x, y, i, zero, secondary) diff --git a/pygal/graph/xy.py b/pygal/graph/xy.py index b0b7ccf..4e140c6 100644 --- a/pygal/graph/xy.py +++ b/pygal/graph/xy.py @@ -16,9 +16,10 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -XY Line graph +""" +XY Line graph: Plot a set of couple data points (x, y) connected by +straight segments. """ from __future__ import division @@ -28,16 +29,20 @@ from pygal.graph.line import Line class XY(Line): - """XY Line graph""" + + """XY Line graph class""" + _dual = True _x_adapters = [] def _get_value(self, values, i): + """Get the value formatted for tooltip""" vals = values[i] return '%s: %s' % (self._x_format(vals[0]), self._format(vals[1])) @cached_property def xvals(self): + """All x values""" return [val[0] for serie in self.all_series for val in serie.values @@ -45,6 +50,7 @@ class XY(Line): @cached_property def yvals(self): + """All y values""" return [val[1] for serie in self.series for val in serie.values @@ -52,22 +58,18 @@ class XY(Line): @cached_property def _min(self): + """Getter for the minimum series value""" return (self.range[0] if (self.range and self.range[0] is not None) else (min(self.yvals) if self.yvals else None)) @cached_property def _max(self): + """Getter for the maximum series value""" return (self.range[1] if (self.range and self.range[1] is not None) else (max(self.yvals) if self.yvals else None)) - def _has_data(self): - """Check if there is any data""" - return sum( - map(len, map(lambda s: s.safe_values, self.series))) != 0 and any(( - sum(map(abs, self.xvals)) != 0, - sum(map(abs, self.yvals)) != 0)) - def _compute(self): + """Compute x/y min and max and x/y scale and set labels""" if self.xvals: if self.xrange: x_adapter = reduce( diff --git a/pygal/maps/__init__.py b/pygal/maps/__init__.py index 0925440..ec331c9 100644 --- a/pygal/maps/__init__.py +++ b/pygal/maps/__init__.py @@ -16,7 +16,5 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . -""" -Maps extensions namespace module -""" +"""Maps extensions namespace module"""