Browse Source

Fix radar axis behaviour (#247)

pull/264/head
Florian Mounier 10 years ago
parent
commit
6e91e714eb
  1. 25
      demo/moulinrouge/tests.py
  2. 1
      docs/changelog.rst
  3. 2
      pygal/css/graph.css
  4. 24
      pygal/graph/base.py
  5. 3
      pygal/graph/dot.py
  6. 2
      pygal/graph/dual.py
  7. 6
      pygal/graph/funnel.py
  8. 2
      pygal/graph/gauge.py
  9. 2
      pygal/graph/graph.py
  10. 111
      pygal/graph/radar.py
  11. 5
      pygal/graph/stackedbar.py

25
demo/moulinrouge/tests.py

@ -3,7 +3,7 @@
from pygal import ( from pygal import (
Bar, Gauge, Pyramid, Funnel, Dot, StackedBar, StackedLine, XY, Bar, Gauge, Pyramid, Funnel, Dot, StackedBar, StackedLine, XY,
CHARTS_BY_NAME, Config, Line, Histogram, Box, CHARTS_BY_NAME, Config, Line, Histogram, Box,
Pie, Treemap, TimeLine, DateLine, Pie, Treemap, TimeLine, DateLine, Radar,
DateTimeLine) DateTimeLine)
try: try:
@ -675,6 +675,29 @@ def get_test_routes(app):
'lol6', 'lol7', 'lol8', 'lol9', 'lol10', 'lol11'] 'lol6', 'lol7', 'lol8', 'lol9', 'lol10', 'lol11']
return line.render_response() return line.render_response()
@app.route('/test/radar')
def test_radar():
radar = Radar()
for i in range(10):
radar.add(str(i), [i * j for j in range(8)])
radar.x_labels = range(8)
radar.x_label_rotation = 35
radar.y_label_rotation = 35
radar.y_labels = [{
'label': '500',
'value': 10
}, {
'label': '1000',
'value': 20
}, {
'label': '5000',
'value': 30
}, {
'label': '10000',
'value': 40
}]
return radar.render_response()
@app.route('/test/pie_serie_radius') @app.route('/test/pie_serie_radius')
def test_pie_serie_radius(): def test_pie_serie_radius():
pie = Pie() pie = Pie()

1
docs/changelog.rst

@ -12,6 +12,7 @@ Changelog
* Default ``print_zeroes`` to True * Default ``print_zeroes`` to True
* (Re)Add xlink in desc to show on tooltip * (Re)Add xlink in desc to show on tooltip
* Activate element on tooltip hovering. (#106) * Activate element on tooltip hovering. (#106)
* Fix radar axis behaviour (#247)
2.0.0 2.0.0
===== =====

2
pygal/css/graph.css

@ -23,7 +23,7 @@
} }
{{ id }}.guide.line { {{ id }}.guide.line {
fill-opacity: 0; fill: none;
} }
{{ id }}.centered { {{ id }}.centered {

24
pygal/graph/base.py

@ -95,18 +95,12 @@ class BaseGraph(object):
adapters.remove(fun) adapters.remove(fun)
adapters = adapters + [positive, not_zero] adapters = adapters + [positive, not_zero]
adapters = adapters + [decimal_to_float] adapters = adapters + [decimal_to_float]
adapter = reduce(compose, adapters) if not self.strict else ident
if adapter and self.y_labels: self._adapt = reduce(compose, adapters) if not self.strict else ident
self.y_labels = list(map(adapter, self.y_labels)) self._x_adapt = reduce(
x_adapter = reduce(
compose, self._x_adapters) if not self.strict and getattr( compose, self._x_adapters) if not self.strict and getattr(
self, '_x_adapters', None) else ident self, '_x_adapters', None) else ident
if x_adapter and self.x_labels:
self.x_labels = list(map(x_adapter, self.x_labels))
series = [] series = []
raw = [( raw = [(
@ -151,20 +145,22 @@ class BaseGraph(object):
value = (value, self.zero, self.zero) value = (value, self.zero, self.zero)
elif len(value) == 2: elif len(value) == 2:
value = (1, value[0], value[1]) value = (1, value[0], value[1])
value = list(map(adapter, value)) value = list(map(self._adapt, value))
elif self._dual: elif self._dual:
if value is None: if value is None:
value = (None, None) value = (None, None)
elif not is_list_like(value): elif not is_list_like(value):
value = (value, self.zero) value = (value, self.zero)
if x_adapter: if self._x_adapt:
value = (x_adapter(value[0]), adapter(value[1])) value = (
self._x_adapt(value[0]),
self._adapt(value[1]))
if isinstance(self, BaseMap): if isinstance(self, BaseMap):
value = (adapter(value[0]), value[1]) value = (self._adapt(value[0]), value[1])
else: else:
value = list(map(adapter, value)) value = list(map(self._adapt, value))
else: else:
value = adapter(value) value = self._adapt(value)
values.append(value) values.append(value)
serie_config = SerieConfig() serie_config = SerieConfig()

3
pygal/graph/dot.py

@ -26,6 +26,7 @@ from __future__ import division
from math import log10 from math import log10
from pygal._compat import to_str
from pygal.graph.graph import Graph from pygal.graph.graph import Graph
from pygal.util import alter, cached_property, decorate, safe_enumerate from pygal.util import alter, cached_property, decorate, safe_enumerate
from pygal.view import ReverseView, View from pygal.view import ReverseView, View
@ -90,7 +91,7 @@ class Dot(Graph):
def _compute_y_labels(self): def _compute_y_labels(self):
self._y_labels = list(zip( self._y_labels = list(zip(
self.y_labels or [ self.y_labels and map(to_str, self.y_labels) or [
serie.title['title'] serie.title['title']
if isinstance(serie.title, dict) if isinstance(serie.title, dict)
else serie.title for serie in self.series], else serie.title for serie in self.series],

2
pygal/graph/dual.py

@ -42,7 +42,7 @@ class Dual(Graph):
pos = x_pos[i] pos = x_pos[i]
title = x_label title = x_label
else: else:
pos = float(x_label) pos = self._x_adapt(float(x_label))
title = self._x_format(x_label) title = self._x_format(x_label)
self._x_labels.append((title, pos)) self._x_labels.append((title, pos))

6
pygal/graph/funnel.py

@ -96,9 +96,9 @@ class Funnel(Graph):
self._x_labels = list( self._x_labels = list(
zip(self.x_labels and zip(self.x_labels and
map(to_str, self.x_labels) or [ map(to_str, self.x_labels) or [
serie.title['title'] serie.title['title']
if isinstance(serie.title, dict) if isinstance(serie.title, dict)
else serie.title for serie in self.series], else serie.title for serie in self.series],
map(self._center, self._x_pos))) map(self._center, self._x_pos)))
def _plot(self): def _plot(self):

2
pygal/graph/gauge.py

@ -161,7 +161,7 @@ class Gauge(Graph):
else: else:
pos = float(y_label) pos = float(y_label)
title = self._format(y_label) title = self._format(y_label)
self._y_labels.append((title, pos)) self._y_labels.append((title, self._adapt(pos)))
self.min_ = min(self.min_, min(cut(self._y_labels, 1))) self.min_ = min(self.min_, min(cut(self._y_labels, 1)))
self.max_ = max(self.max_, max(cut(self._y_labels, 1))) self.max_ = max(self.max_, max(cut(self._y_labels, 1)))
self._box.set_polar_box( self._box.set_polar_box(

2
pygal/graph/graph.py

@ -766,7 +766,7 @@ class Graph(PublicApi):
else: else:
pos = float(y_label) pos = float(y_label)
title = self._format(y_label) title = self._format(y_label)
self._y_labels.append((title, pos)) self._y_labels.append((title, self._adapt(pos)))
self._box.ymin = min(self._box.ymin, min(cut(self._y_labels, 1))) self._box.ymin = min(self._box.ymin, min(cut(self._y_labels, 1)))
self._box.ymax = max(self._box.ymax, max(cut(self._y_labels, 1))) self._box.ymax = max(self._box.ymax, max(cut(self._y_labels, 1)))
else: else:

111
pygal/graph/radar.py

@ -29,7 +29,9 @@ from math import cos, pi
from pygal._compat import is_str from pygal._compat import is_str
from pygal.adapters import none_to_zero, positive from pygal.adapters import none_to_zero, positive
from pygal.graph.line import Line from pygal.graph.line import Line
from pygal.util import cached_property, compute_scale, cut, deg, majorize from pygal.util import (
cached_property, compute_scale, cut, deg, truncate,
reverse_text_len)
from pygal.view import PolarLogView, PolarView from pygal.view import PolarLogView, PolarView
@ -75,40 +77,43 @@ class Radar(Line):
def _x_axis(self, draw_axes=True): def _x_axis(self, draw_axes=True):
"""Override x axis to make it polar""" """Override x axis to make it polar"""
if not self._x_labels: if not self._x_labels or not self.show_x_labels:
return return
axis = self.svg.node(self.nodes['plot'], class_="axis x web") axis = self.svg.node(self.nodes['plot'], class_="axis x web%s" % (
' always_show' if self.show_x_guides else ''
))
format_ = lambda x: '%f %f' % x format_ = lambda x: '%f %f' % x
center = self.view((0, 0)) center = self.view((0, 0))
r = self._rmax r = self._rmax
if self.x_labels_major: truncation = self.truncate_label
x_labels_major = self.x_labels_major if not truncation:
elif self.x_labels_major_every: if self.x_label_rotation or len(self._x_labels) <= 1:
x_labels_major = [self._x_labels[i][0] for i in range( truncation = 25
0, len(self._x_labels), self.x_labels_major_every)]
elif self.x_labels_major_count:
label_count = len(self._x_labels)
major_count = self.x_labels_major_count
if (major_count >= label_count):
x_labels_major = [label[0] for label in self._x_labels]
else: else:
x_labels_major = [self._x_labels[ first_label_position = self.view.x(self._x_labels[0][1]) or 0
int(i * label_count / major_count)][0] last_label_position = self.view.x(self._x_labels[-1][1]) or 0
for i in range(major_count)] available_space = (
else: last_label_position - first_label_position) / (
x_labels_major = [] len(self._x_labels) - 1)
truncation = reverse_text_len(
available_space, self.style.label_font_size)
truncation = max(truncation, 1)
for label, theta in self._x_labels: for label, theta in self._x_labels:
major = label in x_labels_major major = label in self._x_major_labels
if not (self.show_minor_x_labels or major): if not (self.show_minor_x_labels or major):
continue continue
guides = self.svg.node(axis, class_='guides') guides = self.svg.node(axis, class_='guides')
end = self.view((r, theta)) end = self.view((r, theta))
self.svg.node( self.svg.node(
guides, 'path', guides, 'path',
d='M%s L%s' % (format_(center), format_(end)), d='M%s L%s' % (format_(center), format_(end)),
class_='%sline' % ('major ' if major else '')) class_='%s%sline' % (
'axis ' if label == "0" else '',
'major ' if major else ''))
r_txt = (1 - self._box.__class__.margin) * self._box.ymax r_txt = (1 - self._box.__class__.margin) * self._box.ymax
pos_text = self.view((r_txt, theta)) pos_text = self.view((r_txt, theta))
text = self.svg.node( text = self.svg.node(
@ -116,55 +121,57 @@ class Radar(Line):
x=pos_text[0], x=pos_text[0],
y=pos_text[1], y=pos_text[1],
class_='major' if major else '') class_='major' if major else '')
text.text = label text.text = truncate(label, truncation)
if text.text != label:
self.svg.node(guides, 'title').text = label
else:
self.svg.node(
guides, 'title',
).text = self._x_format(theta)
angle = - theta + pi / 2 angle = - theta + pi / 2
if cos(angle) < 0: if cos(angle) < 0:
angle -= pi angle -= pi
text.attrib['transform'] = 'rotate(%f %s)' % ( text.attrib['transform'] = 'rotate(%f %s)' % (
deg(angle), format_(pos_text)) self.x_label_rotation or deg(angle), format_(pos_text))
def _y_axis(self, draw_axes=True): def _y_axis(self, draw_axes=True):
"""Override y axis to make it polar""" """Override y axis to make it polar"""
if not self._y_labels: if not self._y_labels or not self.show_y_labels:
return return
axis = self.svg.node(self.nodes['plot'], class_="axis y web") axis = self.svg.node(self.nodes['plot'], class_="axis y web")
if self.y_labels_major:
y_labels_major = self.y_labels_major
elif self.y_labels_major_every:
y_labels_major = [self._y_labels[i][1] for i in range(
0, len(self._y_labels), self.y_labels_major_every)]
elif self.y_labels_major_count:
label_count = len(self._y_labels)
major_count = self.y_labels_major_count
if (major_count >= label_count):
y_labels_major = [label[1] for label in self._y_labels]
else:
y_labels_major = [self._y_labels[
int(i * (label_count - 1) / (major_count - 1))][1]
for i in range(major_count)]
else:
y_labels_major = majorize(
cut(self._y_labels, 1)
)
for label, r in reversed(self._y_labels): for label, r in reversed(self._y_labels):
major = r in y_labels_major major = r in self._y_major_labels
if not (self.show_minor_y_labels or major): if not (self.show_minor_y_labels or major):
continue continue
guides = self.svg.node(axis, class_='guides') guides = self.svg.node(axis, class_='%sguides' % (
self.svg.line( 'logarithmic ' if self.logarithmic else ''
guides, [self.view((r, theta)) for theta in self._x_pos], ))
close=True, if self.show_y_guides:
class_='%sguide line' % ( self.svg.line(
'major ' if major else '')) guides, [self.view((r, theta)) for theta in self._x_pos],
close=True,
class_='%sguide line' % (
'major ' if major else ''))
x, y = self.view((r, self._x_pos[0])) x, y = self.view((r, self._x_pos[0]))
self.svg.node( x -= 5
text = self.svg.node(
guides, 'text', guides, 'text',
x=x - 5, x=x,
y=y, y=y,
class_='major' if major else '' class_='major' if major else ''
).text = label )
text.text = label
if self.y_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % (
self.y_label_rotation, x, y)
self.svg.node(
guides, 'title',
).text = self._format(r)
def _compute(self): def _compute(self):
"""Compute r min max and labels position""" """Compute r min max and labels position"""
@ -209,7 +216,7 @@ class Radar(Line):
else: else:
pos = float(y_label) pos = float(y_label)
title = self._format(y_label) title = self._format(y_label)
self._y_labels.append((title, pos)) self._y_labels.append((title, self._adapt(pos)))
self._rmin = min(self._rmin, min(cut(self._y_labels, 1))) self._rmin = min(self._rmin, min(cut(self._y_labels, 1)))
self._rmax = max(self._rmax, max(cut(self._y_labels, 1))) self._rmax = max(self._rmax, max(cut(self._y_labels, 1)))
self._box.set_polar_box(self._rmin, self._rmax) self._box.set_polar_box(self._rmin, self._rmax)

5
pygal/graph/stackedbar.py

@ -22,9 +22,10 @@ on top of the others instead of being displayed side by side.
""" """
from __future__ import division from __future__ import division
from pygal.graph.bar import Bar
from pygal.util import compute_scale, swap, ident
from pygal.adapters import none_to_zero from pygal.adapters import none_to_zero
from pygal.graph.bar import Bar
from pygal.util import ident, swap
class StackedBar(Bar): class StackedBar(Bar):

Loading…
Cancel
Save