Skip to content

Commit

Permalink
Fix a lot of logarithmic things
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed Oct 4, 2012
1 parent 542624f commit 8d85ad3
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 41 deletions.
4 changes: 2 additions & 2 deletions demo/moulinrouge/tests.py
Expand Up @@ -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()
Expand Down
41 changes: 17 additions & 24 deletions pygal/graph/gauge.py
Expand Up @@ -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)
Expand All @@ -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))
Expand All @@ -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,
Expand All @@ -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
)
Expand Down
30 changes: 21 additions & 9 deletions pygal/graph/radar.py
Expand Up @@ -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


Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down
9 changes: 5 additions & 4 deletions pygal/util.py
Expand Up @@ -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 []
Expand All @@ -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)):
Expand All @@ -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
Expand Down
103 changes: 101 additions & 2 deletions pygal/view.py
Expand Up @@ -21,7 +21,7 @@
"""

from __future__ import division
from math import sin, cos, log10
from math import sin, cos, log10, pi


class Margin(object):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 8d85ad3

Please sign in to comment.