mirror of https://github.com/Kozea/pygal.git
Florian Mounier
11 years ago
5 changed files with 174 additions and 8 deletions
@ -0,0 +1,148 @@
|
||||
# -*- coding: utf-8 -*- |
||||
# This file is part of pygal |
||||
# |
||||
# A python svg graph plotting library |
||||
# Copyright © 2012-2013 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/>. |
||||
""" |
||||
Histogram chart |
||||
|
||||
""" |
||||
|
||||
from __future__ import division |
||||
from pygal.graph.graph import Graph |
||||
from pygal.util import swap, ident, compute_scale, decorate, cached_property |
||||
|
||||
|
||||
class Histogram(Graph): |
||||
"""Histogram chart""" |
||||
|
||||
_dual = True |
||||
_series_margin = 0 |
||||
_serie_margin = 0 |
||||
|
||||
@cached_property |
||||
def _values(self): |
||||
"""Getter for secondary series values (flattened)""" |
||||
return [val[0] |
||||
for serie in self.series |
||||
for val in serie.values |
||||
if val[0] is not None] |
||||
|
||||
@cached_property |
||||
def _secondary_values(self): |
||||
"""Getter for secondary series values (flattened)""" |
||||
return [val[0] |
||||
for serie in self.secondary_series |
||||
for val in serie.values |
||||
if val[0] is not None] |
||||
|
||||
@cached_property |
||||
def xvals(self): |
||||
return [val |
||||
for serie in self.all_series |
||||
for dval in serie.values |
||||
for val in dval[1:3] |
||||
if val is not None] |
||||
|
||||
@cached_property |
||||
def yvals(self): |
||||
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, parent, x0, x1, y, index, i, zero, secondary=False): |
||||
x, y = self.view((x0, y)) |
||||
x1, _ = self.view((x1, y)) |
||||
width = x1 - x |
||||
height = self.view.y(zero) - y |
||||
series_margin = width * self._series_margin |
||||
x += series_margin |
||||
width -= 2 * series_margin |
||||
|
||||
r = self.rounded_bars * 1 if self.rounded_bars else 0 |
||||
self.svg.transposable_node( |
||||
parent, 'rect', |
||||
x=x, y=y, rx=r, ry=r, width=width, height=height, |
||||
class_='rect reactive tooltip-trigger') |
||||
transpose = swap if self.horizontal else ident |
||||
return transpose((x + width / 2, y + height / 2)) |
||||
|
||||
def bar(self, serie_node, serie, index, rescale=False): |
||||
"""Draw a bar graph for a serie""" |
||||
bars = self.svg.node(serie_node['plot'], class_="histbars") |
||||
points = serie.points |
||||
|
||||
for i, (y, x0, x1) in enumerate(points): |
||||
if None in (x0, x1, y) or (self.logarithmic and y <= 0): |
||||
continue |
||||
metadata = serie.metadata.get(i) |
||||
|
||||
bar = decorate( |
||||
self.svg, |
||||
self.svg.node(bars, class_='histbar'), |
||||
metadata) |
||||
val = self._format(serie.values[i][0]) |
||||
|
||||
x_center, y_center = self._bar( |
||||
bar, x0, x1, y, index, i, self.zero, secondary=rescale) |
||||
self._tooltip_data( |
||||
bar, val, x_center, y_center, classes="centered") |
||||
self._static_value(serie_node, val, x_center, y_center) |
||||
|
||||
def _compute(self): |
||||
if self.xvals: |
||||
xmin = min(self.xvals) |
||||
xmax = max(self.xvals) |
||||
xrng = (xmax - xmin) |
||||
else: |
||||
xrng = None |
||||
|
||||
if self.yvals: |
||||
ymin = min(min(self.yvals), self.zero) |
||||
ymax = max(max(self.yvals), self.zero) |
||||
yrng = (ymax - ymin) |
||||
else: |
||||
yrng = None |
||||
|
||||
for serie in self.all_series: |
||||
serie.points = serie.values |
||||
|
||||
if xrng: |
||||
self._box.xmin, self._box.xmax = xmin, xmax |
||||
if yrng: |
||||
self._box.ymin, self._box.ymax = ymin, ymax |
||||
|
||||
x_pos = compute_scale( |
||||
self._box.xmin, self._box.xmax, self.logarithmic, self.order_min) |
||||
y_pos = compute_scale( |
||||
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min) |
||||
|
||||
self._x_labels = list(zip(map(self._format, x_pos), x_pos)) |
||||
self._y_labels = list(zip(map(self._format, y_pos), y_pos)) |
||||
|
||||
def _plot(self): |
||||
for index, serie in enumerate(self.series): |
||||
self.bar(self._serie(index), serie, index) |
||||
for index, serie in enumerate(self.secondary_series, len(self.series)): |
||||
self.bar(self._serie(index), serie, index, True) |
Loading…
Reference in new issue