Browse Source

Adds errors in charts

113_errors
Jean-Marc Martins 11 years ago
parent
commit
3b42619c05
  1. 12
      pygal/graph/bar.py
  2. 8
      pygal/graph/line.py
  3. 32
      pygal/svg.py
  4. 33
      pygal/util.py

12
pygal/graph/bar.py

@ -82,6 +82,18 @@ class Bar(Graph):
x_center, y_center = self._bar(
bar, x, y, index, i, self.zero, secondary=rescale,
rounded=serie.rounded_bars)
errors = metadata.get('errors') if metadata else None
if errors:
error_node = self.svg.node(bar, class_='errors')
base_x = x_center
transpose = ident
if self.horizontal:
transpose = swap
base_x = y_center
self.svg.draw_errors(error_node, errors, base_x,
transpose=transpose)
self._tooltip_data(
bar, val, x_center, y_center, classes="centered")
self._static_value(serie_node, val, x_center, y_center)

8
pygal/graph/line.py

@ -103,7 +103,8 @@ class Line(Graph):
self.svg.node(serie_node['overlay'], class_="dots"),
metadata)
val = self._get_value(serie.points, i)
self.svg.node(dots, 'circle', cx=x, cy=y, r=serie.dots_size,
self.svg.node(dots, 'circle', cx=x, cy=y,
r=serie.dots_size,
class_='dot reactive tooltip-trigger')
self._tooltip_data(
dots, val, x, y)
@ -112,6 +113,11 @@ class Line(Graph):
x + self.value_font_size,
y + self.value_font_size)
errors = metadata.get('errors') if metadata else None
if errors:
error_node = self.svg.node(dots, class_='errors')
self.svg.draw_errors(error_node, errors, x)
if serie.stroke:
if self.interpolate:
view_values = list(map(self.view, serie.interpolated))

32
pygal/svg.py

@ -30,7 +30,7 @@ from datetime import date, datetime
from numbers import Number
from lxml import etree
from math import cos, sin, pi
from pygal.util import template, coord_format, minify_css
from pygal.util import template, coord_format, minify_css, ident
from pygal import __version__
@ -240,3 +240,33 @@ class Svg(object):
if self.graph.disable_xml_declaration or is_unicode:
svg = svg.decode('utf-8')
return svg
def draw_errors(self, node, errors, base_x, transpose=None):
"""Draws the confidence level for a given point."""
if not transpose:
transpose = ident
width = (self.graph.view.x(1) - self.graph.view.x(0)) / self.graph._len
series_margin = width * getattr(self.graph, '_series_margin', 0)
width -= 2 * series_margin
offset = (width - width / 2) / 2
lower_tip = transpose((base_x, self.graph.view.y(errors.get('min'))))
upper_tip = transpose((base_x, self.graph.view.y(errors.get('max'))))
lower_left_tip = transpose((base_x - offset,
self.graph.view.y(errors.get('min'))))
lower_right_tip = transpose((base_x + offset,
self.graph.view.y(errors.get('min'))))
upper_left_tip = transpose((base_x - offset,
self.graph.view.y(errors.get('max'))))
upper_right_tip = transpose((base_x + offset,
self.graph.view.y(errors.get('max'))))
# base line
self.line(node, coords=[lower_tip, upper_tip], class_='error')
# hat/foot
self.line(node, coords=[lower_left_tip, lower_right_tip],
class_='error')
self.line(node, coords=[upper_left_tip, upper_right_tip],
class_='error')

33
pygal/util.py

@ -24,7 +24,7 @@ from __future__ import division
from pygal._compat import to_str, u, is_list_like
import re
from decimal import Decimal
from math import floor, pi, log, log10, ceil
from math import floor, pi, log, log10, ceil, fsum, sqrt
from itertools import cycle
from functools import reduce
from pygal.adapters import not_zero, positive
@ -228,7 +228,7 @@ def get_texts_box(texts, fs):
def decorate(svg, node, metadata):
"""Add metedata next to a node"""
"""Add metadata next to a node"""
if not metadata:
return node
xlink = metadata.get('xlink')
@ -376,6 +376,13 @@ def prepare_values(raw, config, cls):
if isinstance(raw_value, dict):
raw_value = dict(raw_value)
value = raw_value.pop('value', None)
if not value:
stdd = raw_value.pop('stdd', None)
errors = raw_value.pop('errors', None)
if stdd and is_list_like(stdd):
value = mean(stdd)
if not errors:
raw_value.update(dict(errors=compute_errors(stdd)))
metadata[index] = raw_value
else:
value = raw_value
@ -423,3 +430,25 @@ def split_title(title, width, title_fs):
title_line = title_line[i:].strip()
titles.append(title_line)
return titles
def mean(values):
"""Return the mean of a serie of values."""
return fsum(values) / len(values)
def variance(values):
"""Return the variance of a serie of values."""
values_mean = mean(values)
return fsum((v-values_mean) ** 2 for v in values) / len(values)
def std_deviation(values):
"""Returns the standard deviation of values."""
return sqrt(variance(values))
def compute_errors(values):
m = mean(values)
stdd = std_deviation(values)
return dict(mean=m, min=m-stdd, max=m+stdd)

Loading…
Cancel
Save