Browse Source

Fix dot with log scale. Fix #201

pull/242/head
Florian Mounier 10 years ago
parent
commit
412a212c27
  1. 2
      CHANGELOG
  2. 8
      demo/moulinrouge/tests.py
  3. 4
      pygal/css/graph.css
  4. 53
      pygal/graph/dot.py
  5. 4
      pygal/view.py

2
CHANGELOG

@ -10,6 +10,8 @@ V 2.0.0 UNRELEASED
Add new Box plot modes and outliers and set extremes as default (#226 #121 #149) (thanks djezar) Add new Box plot modes and outliers and set extremes as default (#226 #121 #149) (thanks djezar)
Add secondary_range option to set range for secondary values. (#203) Add secondary_range option to set range for secondary values. (#203)
Maps are now plugins, they are removed from pygal core and moved to packages (pygal_maps_world, pygal_maps_fr, pygal_maps_ch, ...) (#225) Maps are now plugins, they are removed from pygal core and moved to packages (pygal_maps_world, pygal_maps_fr, pygal_maps_ch, ...) (#225)
Dot now supports negative values
Fix dot with log scale (#201)
V 1.7.0 V 1.7.0
Remove DateY and replace it by real XY datetime, date, time and timedelta support. (#188) Remove DateY and replace it by real XY datetime, date, time and timedelta support. (#188)

8
demo/moulinrouge/tests.py

@ -186,12 +186,12 @@ def get_test_routes(app):
@app.route('/test/dot') @app.route('/test/dot')
def test_dot(): def test_dot():
dot = Dot() dot = Dot(logarithmic=True)
dot.x_labels = map(str, range(4)) dot.x_labels = map(str, range(4))
dot.add('a', [1, lnk(3, 'Foo'), 5, 3]) dot.add('a', [1, lnk(3, 'Foo'), 5, 3])
dot.add('b', [2, 2, 0, 2, .1]) dot.add('b', [2, -2, 0, 2, .1])
dot.add('c', [5, 1, 5, lnk(3, 'Bar')]) dot.add('c', [5, 1, 50, lnk(3, 'Bar')])
dot.add('d', [5, 5, lnk(0, 'Babar'), 3]) dot.add('d', [-5, 5, lnk(0, 'Babar'), 3])
return dot.render_response() return dot.render_response()

4
pygal/css/graph.css

@ -107,6 +107,10 @@
stroke-width: 5px; stroke-width: 5px;
} }
{{ id }}.dot.negative {
fill: transparent;
}
{{ id }}.series text { {{ id }}.series text {
stroke: none; stroke: none;
} }

53
pygal/graph/dot.py

@ -22,32 +22,47 @@ Dot chart
""" """
from __future__ import division from __future__ import division
from pygal.util import decorate, cut, safe_enumerate from pygal.util import decorate, cut, safe_enumerate, cached_property
from pygal.adapters import positive
from pygal.graph.graph import Graph from pygal.graph.graph import Graph
from pygal.view import View, ReverseView
from math import log10
class Dot(Graph): class Dot(Graph):
"""Dot graph""" """Dot graph"""
_adapters = [positive]
def dot(self, serie, r_max): def dot(self, serie, r_max):
"""Draw a dot line""" """Draw a dot line"""
serie_node = self.svg.serie(serie) serie_node = self.svg.serie(serie)
view_values = list(map(self.view, serie.points)) view_values = list(map(self.view, serie.points))
for i, value in safe_enumerate(serie.values): for i, value in safe_enumerate(serie.values):
x, y = view_values[i] x, y = view_values[i]
size = r_max * value
value = self._format(value) if self.logarithmic:
log10min = log10(self._min) - 1
log10max = log10(self._max or 1)
if value != 0:
size = r_max * (
(log10(abs(value)) - log10min) /
(log10max - log10min)
)
else:
size = 0
else:
size = r_max * (abs(value) / (self._max or 1))
metadata = serie.metadata.get(i) metadata = serie.metadata.get(i)
dots = decorate( dots = decorate(
self.svg, self.svg,
self.svg.node(serie_node['plot'], class_="dots"), self.svg.node(serie_node['plot'], class_="dots"),
metadata) metadata)
self.svg.node(dots, 'circle', cx=x, cy=y, r=size, self.svg.node(dots, 'circle',
class_='dot reactive tooltip-trigger') cx=x, cy=y, r=size,
class_='dot reactive tooltip-trigger' + (
' negative' if value < 0 else ''))
value = self._format(value)
self._tooltip_data(dots, value, x, y, classes='centered') self._tooltip_data(dots, value, x, y, classes='centered')
self._static_value(serie_node, value, x, y) self._static_value(serie_node, value, x, y)
@ -69,10 +84,30 @@ class Dot(Graph):
self._y_labels = list(zip( self._y_labels = list(zip(
self.y_labels or cut(self.series, 'title'), y_pos)) self.y_labels or cut(self.series, 'title'), y_pos))
def _set_view(self):
"""Assign a view to current graph"""
view_class = ReverseView if self.inverse_y_axis else View
self.view = view_class(
self.width - self.margin_box.x,
self.height - self.margin_box.y,
self._box)
@cached_property
def _values(self):
"""Getter for series values (flattened)"""
return [abs(val) for val in super()._values if val != 0]
@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(map(abs, self._values)) if self._values else None))
def _plot(self): def _plot(self):
r_max = min( r_max = min(
self.view.x(1) - self.view.x(0), self.view.x(1) - self.view.x(0),
(self.view.y(0) or 0) - self.view.y(1)) / ( (self.view.y(0) or 0) - self.view.y(1)) / (
2 * (self._max or 1) * 1.05) 2 * 1.05)
for serie in self.series: for serie in self.series:
self.dot(serie, r_max) self.dot(serie, r_max)

4
pygal/view.py

@ -314,8 +314,8 @@ class LogView(View):
if y is None or y <= 0 or self.log10_ymax - self.log10_ymin == 0: if y is None or y <= 0 or self.log10_ymax - self.log10_ymin == 0:
return 0 return 0
return (self.height - self.height * return (self.height - self.height *
(log10(y) - self.log10_ymin) (log10(y) - self.log10_ymin) / (
/ (self.log10_ymax - self.log10_ymin)) self.log10_ymax - self.log10_ymin))
class XLogView(View): class XLogView(View):

Loading…
Cancel
Save