Browse Source

Document pygal.graph package

pull/242/head
Florian Mounier 10 years ago
parent
commit
fcd3460089
  1. 3
      pygal/__init__.py
  2. 4
      pygal/graph/__init__.py
  3. 13
      pygal/graph/bar.py
  4. 11
      pygal/graph/base.py
  5. 17
      pygal/graph/box.py
  6. 10
      pygal/graph/dot.py
  7. 16
      pygal/graph/funnel.py
  8. 14
      pygal/graph/gauge.py
  9. 23
      pygal/graph/graph.py
  10. 20
      pygal/graph/histogram.py
  11. 14
      pygal/graph/horizontal.py
  12. 7
      pygal/graph/horizontalbar.py
  13. 6
      pygal/graph/horizontalstackedbar.py
  14. 14
      pygal/graph/line.py
  15. 13
      pygal/graph/map.py
  16. 8
      pygal/graph/pie.py
  17. 9
      pygal/graph/public.py
  18. 56
      pygal/graph/pyramid.py
  19. 16
      pygal/graph/radar.py
  20. 13
      pygal/graph/stackedbar.py
  21. 16
      pygal/graph/stackedline.py
  22. 23
      pygal/graph/time.py
  23. 7
      pygal/graph/treemap.py
  24. 63
      pygal/graph/verticalpyramid.py
  25. 22
      pygal/graph/xy.py
  26. 4
      pygal/maps/__init__.py

3
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

4
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 <http://www.gnu.org/licenses/>.
"""
Graph modules
"""
"""Graph package containing all builtin charts"""

13
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 <http://www.gnu.org/licenses/>.
"""
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:

11
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 <http://www.gnu.org/licenses/>.
"""
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

17
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 <http://www.gnu.org/licenses/>.
"""
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

10
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 <http://www.gnu.org/licenses/>.
"""
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)) / (

16
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 <http://www.gnu.org/licenses/>.
"""
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)

14
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 <http://www.gnu.org/licenses/>.
"""
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)

23
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 <http://www.gnu.org/licenses/>.
"""
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
])

20
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 <http://www.gnu.org/licenses/>.
"""
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:

14
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 <http://www.gnu.org/licenses/>.
"""
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:

7
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 <http://www.gnu.org/licenses/>.
"""
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]:

6
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 <http://www.gnu.org/licenses/>.
"""
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"""

14
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 <http://www.gnu.org/licenses/>.
"""
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)

13
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 <http://www.gnu.org/licenses/>.
"""
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))

8
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 <http://www.gnu.org/licenses/>.
"""
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

9
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 <http://www.gnu.org/licenses/>.
"""
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,

56
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 <http://www.gnu.org/licenses/>.
"""
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"""

16
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 <http://www.gnu.org/licenses/>.
"""
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:

13
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 <http://www.gnu.org/licenses/>.
"""
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]:

16
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 <http://www.gnu.org/licenses/>.
"""
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]:

23
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 <http://www.gnu.org/licenses/>.
"""
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

7
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 <http://www.gnu.org/licenses/>.
"""
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]

63
pygal/graph/verticalpyramid.py

@ -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 <http://www.gnu.org/licenses/>.
"""
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)

22
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 <http://www.gnu.org/licenses/>.
"""
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(

4
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 <http://www.gnu.org/licenses/>.
"""
Maps extensions namespace module
"""
"""Maps extensions namespace module"""

Loading…
Cancel
Save