Browse Source

Fix a lot of logarithmic things

pull/8/head
Florian Mounier 12 years ago
parent
commit
8d85ad3a0d
  1. 4
      demo/moulinrouge/tests.py
  2. 41
      pygal/graph/gauge.py
  3. 30
      pygal/graph/radar.py
  4. 9
      pygal/util.py
  5. 103
      pygal/view.py

4
demo/moulinrouge/tests.py

@ -152,8 +152,8 @@ def get_test_routes(app):
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()

41
pygal/graph/gauge.py

@ -23,36 +23,28 @@ Gauge chart
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 @@ class Gauge(Graph):
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 @@ class Gauge(Graph):
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 @@ class Gauge(Graph):
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
)

30
pygal/graph/radar.py

@ -24,8 +24,8 @@ Radar chart
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 @@ class Radar(Line):
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 @@ class Radar(Line):
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 @@ class Radar(Line):
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)

9
pygal/util.py

@ -117,7 +117,7 @@ swap = lambda tuple_: tuple(reversed(tuple_))
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

103
pygal/view.py

@ -21,7 +21,7 @@ Projection and bounding helpers
"""
from __future__ import division
from math import sin, cos, log10
from math import sin, cos, log10, pi
class Margin(object):
@ -53,6 +53,14 @@ class Box(object):
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 @@ class PolarView(View):
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

Loading…
Cancel
Save