Permalink
Browse files

Fix a lot of logarithmic things

  • Loading branch information...
1 parent 542624f commit 8d85ad3a0d9b8d6ea46ce5ed13e368d8073ecaee @paradoxxxzero paradoxxxzero committed Oct 4, 2012
Showing with 146 additions and 41 deletions.
  1. +2 −2 demo/moulinrouge/tests.py
  2. +17 −24 pygal/graph/gauge.py
  3. +21 −9 pygal/graph/radar.py
  4. +5 −4 pygal/util.py
  5. +101 −2 pygal/view.py
@@ -152,8 +152,8 @@ def test_logarithmic_for(chart):
graph.add('xy', [
(.1, .234), (10, 243), (.001, 2), (1000000, 1231)])
else:
- graph.add('1', [.1, 10, .001, 1000000])
- graph.add('2', [.234, 243, 2, 2981379, 1231])
+ graph.add('1', [.1, 10, .01, 10000])
+ graph.add('2', [.234, 243, 2, 2379, 1231])
graph.x_labels = ('a', 'b', 'c', 'd', 'e')
graph.x_label_rotation = 90
return graph.render_response()
View
@@ -23,36 +23,28 @@
from __future__ import division
from pygal.util import decorate, compute_scale
-from pygal.view import PolarView
+from pygal.view import PolarThetaView, PolarThetaLogView
from pygal.graph.graph import Graph
-from math import pi
class Gauge(Graph):
"""Gauge graph"""
def _set_view(self):
- self.view = PolarView(
+ if self.logarithmic:
+ view_class = PolarThetaLogView
+ else:
+ view_class = PolarThetaView
+
+ self.view = view_class(
self.width - self.margin.x,
self.height - self.margin.y,
self._box)
- def arc_pos(self, value):
- aperture = pi / 3
- if value > self.max_:
- return (3 * pi - aperture / 2) / 2
- if value < self.min_:
- return (3 * pi + aperture / 2) / 2
- start = 3 * pi / 2 + aperture / 2
- return start + (2 * pi - aperture) * (
- value - self.min_) / (self.max_ - self.min_)
-
def needle(self, serie_node, serie):
- thickness = .05
- for i, value in enumerate(serie.values):
- if value is None:
+ for i, theta in enumerate(serie.values):
+ if theta is None:
continue
- theta = self.arc_pos(value)
fmt = lambda x: '%f %f' % x
value = self._format(serie.values[i])
metadata = serie.metadata.get(i)
@@ -64,9 +56,9 @@ def needle(self, serie_node, serie):
self.svg.node(
gauges, 'polygon', points=' '.join([
fmt(self.view((0, 0))),
- fmt(self.view((.75, theta + thickness))),
+ fmt(self.view((.75, theta))),
fmt(self.view((.8, theta))),
- fmt(self.view((.75, theta - thickness)))]),
+ fmt(self.view((.75, theta)))]),
class_='line reactive tooltip-trigger')
x, y = self.view((.75, theta))
@@ -79,9 +71,9 @@ def _x_axis(self, draw_axes=True):
axis = self.svg.node(self.nodes['plot'], class_="axis x gauge")
- for i, (label, pos) in enumerate(self._x_labels):
+ for i, (label, theta) in enumerate(self._x_labels):
guides = self.svg.node(axis, class_='guides')
- theta = self.arc_pos(pos)
+
self.svg.line(
guides, [self.view((.95, theta)), self.view((1, theta))],
close=True,
@@ -107,15 +99,16 @@ def _y_axis(self, draw_axes=True):
self.svg.node(axis, 'circle', cx=x, cy=y, r=4)
def _compute(self):
- self._box.xmin = -1
- self._box.ymin = -1
-
self.min_ = self._min or 0
self.max_ = self._max or 0
if self.max_ - self.min_ == 0:
self.min_ -= 1
self.max_ += 1
+ self._box.set_polar_box(
+ 0, 1,
+ self.min_,
+ self.max_)
x_pos = compute_scale(
self.min_, self.max_, self.logarithmic, self.order_min
)
View
@@ -24,8 +24,8 @@
from __future__ import division
from pygal.graph.line import Line
from pygal.adapters import positive, none_to_zero
-from pygal.view import PolarView
-from pygal.util import deg, cached_property, compute_scale
+from pygal.view import PolarView, PolarLogView
+from pygal.util import deg, cached_property, compute_scale, is_major
from math import cos, pi
@@ -54,7 +54,12 @@ def _values(self):
return super(Line, self)._values
def _set_view(self):
- self.view = PolarView(
+ if self.logarithmic:
+ view_class = PolarLogView
+ else:
+ view_class = PolarView
+
+ self.view = view_class(
self.width - self.margin.x,
self.height - self.margin.y,
self._box)
@@ -94,16 +99,20 @@ def _y_axis(self, draw_axes=True):
axis = self.svg.node(self.nodes['plot'], class_="axis y web")
for label, r in reversed(self._y_labels):
+ major = is_major(r)
guides = self.svg.node(axis, class_='guides')
self.svg.line(
guides, [self.view((r, theta)) for theta in self.x_pos],
close=True,
- class_='guide line')
+ class_='%sguide line' % (
+ 'major ' if major else ''))
x, y = self.view((r, self.x_pos[0]))
self.svg.node(
guides, 'text',
x=x - 5,
- y=y).text = label
+ y=y,
+ class_='major' if major else ''
+ ).text = label
def _compute(self):
delta = 2 * pi / self._len if self._len else 0
@@ -125,14 +134,17 @@ def _compute(self):
serie.interpolated = self._interpolate(
extended_vals, extended_x_pos, polar=True)
+ # x labels space
self._box.margin *= 2
- _max = self._max or 1
- self._box.xmin = self._box.ymin = - _max
- self._box.xmax = self._box.ymax = self._rmax = _max
+ self._rmin = self.zero
+ self._rmax = self._max or 1
+ self._box.set_polar_box(self._rmin, self._rmax)
y_pos = compute_scale(
- 0, self._box.ymax, self.logarithmic, self.order_min, max_scale=8
+ self._rmin, self._rmax, self.logarithmic, self.order_min,
+ max_scale=8
) if not self.y_labels else map(int, self.y_labels)
+
self._x_labels = self.x_labels and zip(self.x_labels, x_pos)
self._y_labels = zip(map(self._format, y_pos), y_pos)
View
@@ -117,7 +117,7 @@ def coord_format(xy):
ident = lambda x: x
-def compute_logarithmic_scale(min_, max_):
+def compute_logarithmic_scale(min_, max_, min_scale, max_scale):
"""Compute an optimal scale for logarithmic"""
if max_ <= 0 or min_ <= 0:
return []
@@ -128,9 +128,9 @@ def compute_logarithmic_scale(min_, max_):
if amplitude <= 1:
return []
detail = 10.
- while amplitude * detail < 20:
+ while amplitude * detail < min_scale * 5:
detail *= 2
- while amplitude * detail > 50:
+ while amplitude * detail > max_scale * 3:
detail /= 2
for order in range(min_order, max_order + 1):
for i in range(int(detail)):
@@ -150,7 +150,8 @@ def compute_scale(
if max_ - min_ == 0:
return [min_]
if logarithmic:
- log_scale = compute_logarithmic_scale(min_, max_)
+ log_scale = compute_logarithmic_scale(
+ min_, max_, min_scale, max_scale)
if log_scale:
return log_scale
# else we fallback to normal scalling
View
@@ -21,7 +21,7 @@
"""
from __future__ import division
-from math import sin, cos, log10
+from math import sin, cos, log10, pi
class Margin(object):
@@ -53,6 +53,14 @@ def __init__(self, xmin=0, ymin=0, xmax=1, ymax=1):
self._xmax = xmax
self._ymax = ymax
+ def set_polar_box(self, rmin=0, rmax=1, tmin=0, tmax=2 * pi):
+ 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):
return self._xmin
@@ -181,11 +189,102 @@ def __call__(self, rhotheta):
if None in rhotheta:
return None, None
rho, theta = rhotheta
- rho = max(rho, 0)
return super(PolarView, self).__call__(
(rho * cos(theta), rho * sin(theta)))
+class PolarLogView(View):
+ """Logarithmic polar projection"""
+
+ def __init__(self, width, height, box):
+ 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)
+
+ 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):
+ super(PolarThetaView, self).__init__(width, height, box)
+ 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
+ aperture = pi / 3
+ if theta > self.box._tmax:
+ theta = (3 * pi - aperture / 2) / 2
+ elif theta < self.box._tmin:
+ theta = (3 * pi + aperture / 2) / 2
+ else:
+ start = 3 * pi / 2 + aperture / 2
+ theta = start + (2 * pi - 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):
+ super(PolarThetaLogView, self).__init__(width, height, box)
+ 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)
+ self.log10_tmin = log10(self.box._tmin)
+
+ 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)
+ aperture = pi / 3
+ if theta > self.box._tmax:
+ theta = (3 * pi - aperture / 2) / 2
+ elif theta < self.box._tmin:
+ theta = (3 * pi + aperture / 2) / 2
+ else:
+ start = 3 * pi / 2 + aperture / 2
+ theta = start + (2 * pi - 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):
"""Logarithmic projection """
# Do not want to call the parent here

0 comments on commit 8d85ad3

Please sign in to comment.