From 58330ff679ff4916a49a7cc4f3b001b71de19d00 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 13 May 2014 18:05:57 +0200 Subject: [PATCH] Include DateY fixes from #109 --- pygal/graph/datey.py | 73 +++++++++++++++++++++++++--------------- pygal/graph/xy.py | 18 +++++++--- pygal/svg.py | 9 +++-- pygal/test/test_date.py | 58 +++++++++++++++++++++++++++++++ pygal/test/test_graph.py | 1 + 5 files changed, 125 insertions(+), 34 deletions(-) create mode 100644 pygal/test/test_date.py diff --git a/pygal/graph/datey.py b/pygal/graph/datey.py index bda6d6e..51909d2 100644 --- a/pygal/graph/datey.py +++ b/pygal/graph/datey.py @@ -30,8 +30,12 @@ def jour(n) : x=(1,20,35,54,345,898) x=tuple(map(jour,x)) +x_label=(0,100,200,300,400,500,600,700,800,900,1000) +x_label=map(jour,x_label) y=(1,3,4,2,3,1) graph=pygal.DateY(x_label_rotation=20) +graph.x_label_format = "%Y-%m-%d" +graph.x_labels = x_label graph.add("graph1",list(zip(x,y))+[None,None]) graph.render_in_browser() """ @@ -75,27 +79,19 @@ class DateY(XY): for serie in self.all_series: serie.values = [(self._tonumber(v[0]), v[1]) for v in serie.values] - xvals = [val[0] - for serie in self.series - for val in serie.values - if val[0] is not None] - yvals = [val[1] - for serie in self.series - for val in serie.values - if val[1] is not None] - if xvals: - xmin = min(xvals) - xmax = max(xvals) + if self.xvals: + xmin = min(self.xvals) + xmax = max(self.xvals) rng = (xmax - xmin) else: rng = None - if yvals: - ymin = min(yvals) - ymax = max(yvals) + if self.yvals: + ymin = self._min + ymax = self._max if self.include_x_axis: - ymin = min(ymin or 0, 0) - ymax = max(ymax or 0, 0) + ymin = min(self._min or 0, 0) + ymax = max(self._max or 0, 0) for serie in self.all_series: serie.points = serie.values @@ -106,25 +102,46 @@ class DateY(XY): serie.interpolated = self._interpolate(vals[0], vals[1]) if self.interpolate and rng: - xvals = [val[0] - for serie in self.all_series - for val in serie.interpolated] - yvals = [val[1] - for serie in self.all_series - for val in serie.interpolated] - if xvals: - xmin = min(xvals) - xmax = max(xvals) + self.xvals = [val[0] + for serie in self.all_series + for val in serie.interpolated] + self.yvals = [val[1] + for serie in self.all_series + for val in serie.interpolated] + if self.xvals: + xmin = min(self.xvals) + xmax = max(self.xvals) rng = (xmax - xmin) else: rng = None - if rng: + # Calculate/prcoess the x_labels + if self.x_labels and all( + map(lambda x: isinstance( + x, (datetime.datetime, datetime.date)), self.x_labels)): + # Process the given x_labels + x_labels_num = [] + for label in self.x_labels: + x_labels_num.append(self._tonumber(label)) + x_pos = x_labels_num + + # Update the xmin/xmax to fit all of the x_labels and the data + xmin = min(xmin, min(x_pos)) + xmax = max(xmax, max(x_pos)) + self._box.xmin, self._box.xmax = xmin, xmax self._box.ymin, self._box.ymax = ymin, ymax + else: + # Automatically generate the x_labels + if rng: + self._box.xmin, self._box.xmax = xmin, xmax + self._box.ymin, self._box.ymax = ymin, ymax + + x_pos = compute_scale( + self._box.xmin, self._box.xmax, self.logarithmic, + self.order_min) - x_pos = compute_scale( - self._box.xmin, self._box.xmax, self.logarithmic, self.order_min) + # Always auto-generate the y labels y_pos = compute_scale( self._box.ymin, self._box.ymax, self.logarithmic, self.order_min) diff --git a/pygal/graph/xy.py b/pygal/graph/xy.py index 24272ac..292c3a5 100644 --- a/pygal/graph/xy.py +++ b/pygal/graph/xy.py @@ -44,12 +44,22 @@ class XY(Line): for val in serie.values if val[1] is not None] + @cached_property + def _min(self): + 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): + 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)) + sum(map(abs, self.xvals)) != 0, + sum(map(abs, self.yvals)) != 0)) def _compute(self): if self.xvals: @@ -60,8 +70,8 @@ class XY(Line): xrng = None if self.yvals: - ymin = min(self.yvals) - ymax = max(self.yvals) + ymin = self._min + ymax = self._max if self.include_x_axis: ymin = min(ymin or 0, 0) diff --git a/pygal/svg.py b/pygal/svg.py index 76956fe..f09bbff 100644 --- a/pygal/svg.py +++ b/pygal/svg.py @@ -26,7 +26,7 @@ from pygal._compat import to_str, u import io import os import json -from datetime import date +from datetime import date, datetime from numbers import Number from lxml import etree from math import cos, sin, pi @@ -97,7 +97,12 @@ class Svg(object): """Add the js to the svg""" common_script = self.node(self.defs, 'script', type='text/javascript') common_script.text = " = ".join( - ("window.config", json.dumps(self.graph.config.to_dict()))) + ("window.config", json.dumps( + self.graph.config.to_dict(), + default=lambda o: ( + o.isoformat() if isinstance(o, (datetime, date)) + else json.JSONEncoder().default(o)) + ))) for js in self.graph.js: if '://' in js: diff --git a/pygal/test/test_date.py b/pygal/test/test_date.py new file mode 100644 index 0000000..391a658 --- /dev/null +++ b/pygal/test/test_date.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# This file is part of pygal +# +# A python svg graph plotting library +# Copyright © 2012-2014 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 . +from pygal import DateY +from pygal.test.utils import texts +from datetime import datetime + + +def test_date(): + datey = DateY(truncate_label=1000) + datey.add('dates', [ + (datetime(2013, 1, 2), 300), + (datetime(2013, 1, 12), 412), + (datetime(2013, 2, 2), 823), + (datetime(2013, 2, 22), 672) + ]) + + q = datey.render_pyquery() + + assert list( + map(lambda t: t.split(' ')[0], + q(".axis.x text").map(texts))) == [ + '2013-01-02', + '2013-01-13', + '2013-01-25', + '2013-02-05', + '2013-02-17' + ] + + datey.x_labels = [ + datetime(2013, 1, 1), + datetime(2013, 2, 1), + datetime(2013, 3, 1) + ] + + q = datey.render_pyquery() + assert list( + map(lambda t: t.split(' ')[0], + q(".axis.x text").map(texts))) == [ + '2013-01-01', + '2013-02-01', + '2013-03-01' + ] diff --git a/pygal/test/test_graph.py b/pygal/test/test_graph.py index dbefd1f..30c0a4a 100644 --- a/pygal/test/test_graph.py +++ b/pygal/test/test_graph.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + import os import pygal import uuid