From 4157a2f330e3be1b7fbcd4e7a8349c7a3700c837 Mon Sep 17 00:00:00 2001 From: qins Date: Sun, 25 Sep 2016 15:44:24 +0800 Subject: [PATCH] Solve issue #309, no new failure --- pygal/graph/line.py~ | 215 ---------------------- pygal/view.py | 2 +- pygal/view.py~ | 423 ------------------------------------------- 3 files changed, 1 insertion(+), 639 deletions(-) delete mode 100644 pygal/graph/line.py~ delete mode 100644 pygal/view.py~ diff --git a/pygal/graph/line.py~ b/pygal/graph/line.py~ deleted file mode 100644 index 3544d20..0000000 --- a/pygal/graph/line.py~ +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of pygal -# -# A python svg graph plotting library -# Copyright © 2012-2016 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 . - -""" -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 alter, cached_property, decorate - - -class 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 - for val in (serie.interpolated - if self.interpolate else serie.points) - if val[1] is not None and (not self.logarithmic or val[1] > 0)] - - @cached_property - def _secondary_values(self): - """Getter for secondary series values (flattened)""" - return [ - val[1] - for serie in self.secondary_series - for val in (serie.interpolated - if self.interpolate else serie.points) - if val[1] is not None and (not self.logarithmic or val[1] > 0)] - - def _fill(self, values): - """Add extra values to fill the line""" - zero = self.view.y(min(max(self.zero, self._box.ymin), self._box.ymax)) - - # Check to see if the data has been padded with "none's" - # Fill doesn't work correctly otherwise - end = len(values) - 1 - while end > 0: - x, y = values[end] - if self.missing_value_fill_truncation == "either": - if x is not None and y is not None: - break - elif self.missing_value_fill_truncation == "x": - if x is not None: - break - elif self.missing_value_fill_truncation == "y": - if y is not None: - break - else: - raise ValueError( - "Invalid value ({}) for config key " - "'missing_value_fill_truncation';" - " Use 'x', 'y' or 'either'".format( - self.missing_value_fill_truncation)) - end -= 1 - - return ([(values[0][0], zero)] + - values + - [(values[end][0], zero)]) - - def line(self, serie, rescale=False): - """Draw the line serie""" - serie_node = self.svg.serie(serie) - if rescale and self.secondary_series: - points = self._rescale(serie.points) - else: - points = serie.points - view_values = list(map(self.view, points)) - if serie.show_dots: - for i, (x, y) in enumerate(view_values): - if None in (x, y): - continue - - #modified - if self.logarithmic: - if points[i][1] == None or points[i][1] <= 0: - continue - - if (serie.show_only_major_dots and - self.x_labels and i < len(self.x_labels) and - self.x_labels[i] not in self._x_labels_major): - continue - - metadata = serie.metadata.get(i) - classes = [] - if x > self.view.width / 2: - classes.append('left') - if y > self.view.height / 2: - classes.append('top') - classes = ' '.join(classes) - - self._confidence_interval( - serie_node['overlay'], x, y, serie.values[i], metadata) - - dots = decorate( - self.svg, - self.svg.node(serie_node['overlay'], class_="dots"), - metadata) - - val = self._format(serie, i) - alter(self.svg.transposable_node( - dots, 'circle', cx=x, cy=y, r=serie.dots_size, - class_='dot reactive tooltip-trigger'), metadata) - self._tooltip_data( - dots, val, x, y, - xlabel=self._get_x_label(i)) - self._static_value( - serie_node, val, - x + self.style.value_font_size, - y + self.style.value_font_size, - metadata) - - if serie.stroke: - if self.interpolate: - points = serie.interpolated - if rescale and self.secondary_series: - points = self._rescale(points) - view_values = list(map(self.view, points)) - if serie.fill: - view_values = self._fill(view_values) - - if serie.allow_interruptions: - # view_values are in form [(x1, y1), (x2, y2)]. We - # need to split that into multiple sequences if a - # None is present here - - sequences = [] - cur_sequence = [] - for x, y in view_values: - if y is None and len(cur_sequence) > 0: - # emit current subsequence - sequences.append(cur_sequence) - cur_sequence = [] - elif y is None: # just discard - continue - else: - cur_sequence.append((x, y)) # append the element - - if len(cur_sequence) > 0: # emit last possible sequence - sequences.append(cur_sequence) - else: - # plain vanilla rendering - sequences = [view_values] - - #modified - if self.logarithmic: - for seq in sequences: - for ele in seq[::-1]: - if points[seq.index(ele)][1]==None or points[seq.index(ele)][1] <= 0: - del seq[seq.index(ele)] - - for seq in sequences: - self.svg.line( - serie_node['plot'], seq, close=self._self_close, - 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 - if self.horizontal: - self._x_pos = [ - x / (self._len - 1) for x in range(self._len) - ][::-1] if self._len != 1 else [.5] # Center if only one value - else: - self._x_pos = [ - x / (self._len - 1) for x in range(self._len) - ] if self._len != 1 else [.5] # Center if only one value - - self._points(self._x_pos) - - if self.include_x_axis: - # Y Label - self._box.ymin = min(self._min or 0, 0) - self._box.ymax = max(self._max or 0, 0) - else: - self._box.ymin = self._min - self._box.ymax = self._max - - def _plot(self): - """Plot the serie lines and secondary serie lines""" - for serie in self.series: - self.line(serie) - - for serie in self.secondary_series: - self.line(serie, True) diff --git a/pygal/view.py b/pygal/view.py index 04f7503..0faa108 100644 --- a/pygal/view.py +++ b/pygal/view.py @@ -343,7 +343,7 @@ class LogView(View): def y(self, y): """Project y""" if y is None or y <= 0 or self.log10_ymax - self.log10_ymin == 0: - return 0.00001 + return 0 return (self.height - self.height * (log10(y) - self.log10_ymin) / ( self.log10_ymax - self.log10_ymin)) diff --git a/pygal/view.py~ b/pygal/view.py~ deleted file mode 100644 index f9887db..0000000 --- a/pygal/view.py~ +++ /dev/null @@ -1,423 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of pygal -# -# A python svg graph plotting library -# Copyright © 2012-2016 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 . - -"""Projection and bounding helpers""" - -from __future__ import division - -from math import cos, log10, pi, sin - - -class Margin(object): - - """Class reprensenting a margin (top, right, left, bottom)""" - - def __init__(self, top, right, bottom, left): - """Create the margin object from the top, right, left, bottom margin""" - self.top = top - self.right = right - self.bottom = bottom - self.left = left - - @property - def x(self): - """Helper for total x margin""" - return self.left + self.right - - @property - def y(self): - """Helper for total y margin""" - return self.top + self.bottom - - -class Box(object): - - """Chart boundings""" - - margin = .02 - - def __init__(self, xmin=0, ymin=0, xmax=1, ymax=1): - """ - Create the chart bounds with min max horizontal - and vertical values - """ - self._xmin = xmin - self._ymin = ymin - self._xmax = xmax - self._ymax = ymax - - def set_polar_box(self, rmin=0, rmax=1, tmin=0, tmax=2 * pi): - """Helper for polar charts""" - self._rmin = rmin - self._rmax = rmax - self._tmin = tmin - self._tmax = tmax - self.xmin = self.ymin = rmin - rmax - self.xmax = self.ymax = rmax - rmin - - @property - def xmin(self): - """X minimum getter""" - return self._xmin - - @xmin.setter - def xmin(self, value): - """X minimum setter""" - if value is not None: - self._xmin = value - - @property - def ymin(self): - """Y minimum getter""" - return self._ymin - - @ymin.setter - def ymin(self, value): - """Y minimum setter""" - if value is not None: - self._ymin = value - - @property - def xmax(self): - """X maximum getter""" - return self._xmax - - @xmax.setter - def xmax(self, value): - """X maximum setter""" - if value is not None: - self._xmax = value - - @property - def ymax(self): - """Y maximum getter""" - return self._ymax - - @ymax.setter - def ymax(self, value): - """Y maximum setter""" - if value or self.ymin: - self._ymax = value - - @property - def width(self): - """Helper for box width""" - return self.xmax - self.xmin - - @property - def height(self): - """Helper for box height""" - return self.ymax - self.ymin - - def swap(self): - """Return the box (for horizontal graphs)""" - self.xmin, self.ymin = self.ymin, self.xmin - self.xmax, self.ymax = self.ymax, self.xmax - - def fix(self, with_margin=True): - """Correct box when no values and take margin in account""" - if not self.width: - self.xmax = self.xmin + 1 - if not self.height: - self.ymin /= 2 - self.ymax += self.ymin - xmargin = self.margin * self.width - self.xmin -= xmargin - self.xmax += xmargin - if with_margin: - ymargin = self.margin * self.height - self.ymin -= ymargin - self.ymax += ymargin - - -class View(object): - - """Projection base class""" - - def __init__(self, width, height, box): - """Create the view with a width an height and a box bounds""" - self.width = width - self.height = height - self.box = box - self.box.fix() - - def x(self, x): - """Project x""" - if x is None: - return None - return self.width * (x - self.box.xmin) / self.box.width - - def y(self, y): - """Project y""" - if y is None: - return None - return (self.height - self.height * - (y - self.box.ymin) / self.box.height) - - def __call__(self, xy): - """Project x and y""" - x, y = xy - return (self.x(x), self.y(y)) - - -class ReverseView(View): - - """Same as view but reversed vertically""" - - def y(self, y): - """Project reversed y""" - if y is None: - return None - return (self.height * (y - self.box.ymin) / self.box.height) - - -class HorizontalView(View): - - """Same as view but transposed""" - - def __init__(self, width, height, box): - """Create the view with a width an height and a box bounds""" - self._force_vertical = None - self.width = width - self.height = height - - self.box = box - self.box.fix() - self.box.swap() - - def x(self, x): - """Project x as y""" - if x is None: - return None - if self._force_vertical: - return super(HorizontalView, self).x(x) - return super(HorizontalView, self).y(x) - - def y(self, y): - """Project y as x""" - if y is None: - return None - if self._force_vertical: - return super(HorizontalView, self).y(y) - return super(HorizontalView, self).x(y) - - -class PolarView(View): - - """Polar projection for pie like graphs""" - - def __call__(self, rhotheta): - """Project rho and theta""" - if None in rhotheta: - return None, None - rho, theta = rhotheta - return super(PolarView, self).__call__( - (rho * cos(theta), rho * sin(theta))) - - -class PolarLogView(View): - - """Logarithmic polar projection""" - - def __init__(self, width, height, box): - """Create the view with a width an height and a box bounds""" - super(PolarLogView, self).__init__(width, height, box) - if not hasattr(box, '_rmin') or not hasattr(box, '_rmax'): - raise Exception( - 'Box must be set with set_polar_box for polar charts') - - self.log10_rmax = log10(self.box._rmax) - self.log10_rmin = log10(self.box._rmin) - if self.log10_rmin == self.log10_rmax: - self.log10_rmax = self.log10_rmin + 1 - - def __call__(self, rhotheta): - """Project rho and theta""" - if None in rhotheta: - return None, None - rho, theta = rhotheta - # Center case - if rho == 0: - return super(PolarLogView, self).__call__((0, 0)) - rho = (self.box._rmax - self.box._rmin) * ( - log10(rho) - self.log10_rmin) / ( - self.log10_rmax - self.log10_rmin) - return super(PolarLogView, self).__call__( - (rho * cos(theta), rho * sin(theta))) - - -class PolarThetaView(View): - - """Logarithmic polar projection""" - - def __init__(self, width, height, box, aperture=pi / 3): - """Create the view with a width an height and a box bounds""" - super(PolarThetaView, self).__init__(width, height, box) - self.aperture = aperture - if not hasattr(box, '_tmin') or not hasattr(box, '_tmax'): - raise Exception( - 'Box must be set with set_polar_box for polar charts') - - def __call__(self, rhotheta): - """Project rho and theta""" - if None in rhotheta: - return None, None - rho, theta = rhotheta - start = 3 * pi / 2 + self.aperture / 2 - theta = start + (2 * pi - self.aperture) * ( - theta - self.box._tmin) / ( - self.box._tmax - self.box._tmin) - return super(PolarThetaView, self).__call__( - (rho * cos(theta), rho * sin(theta))) - - -class PolarThetaLogView(View): - - """Logarithmic polar projection""" - - def __init__(self, width, height, box, aperture=pi / 3): - """Create the view with a width an height and a box bounds""" - super(PolarThetaLogView, self).__init__(width, height, box) - self.aperture = aperture - if not hasattr(box, '_tmin') or not hasattr(box, '_tmax'): - raise Exception( - 'Box must be set with set_polar_box for polar charts') - self.log10_tmax = log10(self.box._tmax) if self.box._tmax > 0 else 0 - self.log10_tmin = log10(self.box._tmin) if self.box._tmin > 0 else 0 - if self.log10_tmin == self.log10_tmax: - self.log10_tmax = self.log10_tmin + 1 - - def __call__(self, rhotheta): - """Project rho and theta""" - if None in rhotheta: - return None, None - rho, theta = rhotheta - # Center case - if theta == 0: - return super(PolarThetaLogView, self).__call__((0, 0)) - theta = self.box._tmin + (self.box._tmax - self.box._tmin) * ( - log10(theta) - self.log10_tmin) / ( - self.log10_tmax - self.log10_tmin) - - start = 3 * pi / 2 + self.aperture / 2 - theta = start + (2 * pi - self.aperture) * ( - theta - self.box._tmin) / ( - self.box._tmax - self.box._tmin) - - return super(PolarThetaLogView, self).__call__( - (rho * cos(theta), rho * sin(theta))) - - -class LogView(View): - - """Y Logarithmic projection""" - - # Do not want to call the parent here - def __init__(self, width, height, box): - """Create the view with a width an height and a box bounds""" - self.width = width - self.height = height - self.box = box - self.log10_ymax = log10(self.box.ymax) if self.box.ymax > 0 else 0 - self.log10_ymin = log10(self.box.ymin) if self.box.ymin > 0 else 0 - if self.log10_ymin == self.log10_ymax: - self.log10_ymax = self.log10_ymin + 1 - self.box.fix(False) - - def y(self, y): - """Project y""" - if y is None or y <= 0 or self.log10_ymax - self.log10_ymin == 0: - return 0.000001 - return (self.height - self.height * - (log10(y) - self.log10_ymin) / ( - self.log10_ymax - self.log10_ymin)) - - -class XLogView(View): - - """X logarithmic projection""" - - # Do not want to call the parent here - def __init__(self, width, height, box): - """Create the view with a width an height and a box bounds""" - self.width = width - self.height = height - self.box = box - self.log10_xmax = log10(self.box.xmax) if self.box.xmax > 0 else 0 - self.log10_xmin = log10(self.box.xmin) if self.box.xmin > 0 else 0 - self.box.fix(False) - - def x(self, x): - """Project x""" - if x is None or x <= 0 or self.log10_xmax - self.log10_xmin == 0: - return None - return (self.width * - (log10(x) - self.log10_xmin) / - (self.log10_xmax - self.log10_xmin)) - - -class XYLogView(XLogView, LogView): - - """X and Y logarithmic projection""" - - def __init__(self, width, height, box): - """Create the view with a width an height and a box bounds""" - self.width = width - self.height = height - self.box = box - self.log10_ymax = log10(self.box.ymax) if self.box.ymax > 0 else 0 - self.log10_ymin = log10(self.box.ymin) if self.box.ymin > 0 else 0 - self.log10_xmax = log10(self.box.xmax) if self.box.xmax > 0 else 0 - self.log10_xmin = log10(self.box.xmin) if self.box.xmin > 0 else 0 - self.box.fix(False) - - -class HorizontalLogView(XLogView): - - """Transposed Logarithmic projection""" - - # Do not want to call the parent here - def __init__(self, width, height, box): - """Create the view with a width an height and a box bounds""" - self._force_vertical = None - self.width = width - self.height = height - self.box = box - self.log10_xmax = log10(self.box.ymax) if self.box.ymax > 0 else 0 - self.log10_xmin = log10(self.box.ymin) if self.box.ymin > 0 else 0 - if self.log10_xmin == self.log10_xmax: - self.log10_xmax = self.log10_xmin + 1 - self.box.fix(False) - self.box.swap() - - def x(self, x): - """Project x as y""" - if x is None: - return None - if self._force_vertical: - return super(HorizontalLogView, self).x(x) - return super(XLogView, self).y(x) - - def y(self, y): - """Project y as x""" - if y is None: - return None - if self._force_vertical: - return super(XLogView, self).y(y) - return super(HorizontalLogView, self).x(y)