Browse Source

Fix horizontal and fix margin for 2ndary labels

pull/26/merge
Florian Mounier 12 years ago
parent
commit
829b100776
  1. 21
      demo/moulinrouge/tests.py
  2. 1
      pygal/css/graph.css
  3. 13
      pygal/graph/bar.py
  4. 42
      pygal/graph/base.py
  5. 49
      pygal/graph/graph.py
  6. 5
      pygal/graph/horizontal.py
  7. 19
      pygal/graph/line.py
  8. 6
      pygal/graph/xy.py

21
demo/moulinrouge/tests.py

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# This file is part of pygal
from pygal import (
Bar, Gauge, Pyramid, Funnel, Dot, StackedBar,
Bar, Gauge, Pyramid, Funnel, Dot, StackedBar, XY,
CHARTS_BY_NAME, Config, Line)
from pygal.style import styles
@ -183,11 +183,22 @@ def get_test_routes(app):
@app.route('/test/secondary/<chart>')
def test_secondary_for(chart):
chart = CHARTS_BY_NAME[chart]()
chart.add(10 * '1', [30, 20, 25])
chart.add(10 * '1b', [4, 5, 6], secondary=True)
chart = CHARTS_BY_NAME[chart](fill=True)
chart.x_label_rotation = 25
chart.y_label_rotation = 50
chart.add('1', [30, 20, 25])
chart.add(10 * '1b', [4000000, 5, 6], secondary=True)
chart.add(10 * '2b', [3, 0, 12], secondary=True)
chart.add(10 * '2', [8, 21, 5])
chart.add('2', [8, 21, 5])
return chart.render_response()
@app.route('/test/secondary_xy')
def test_secondary_xy():
chart = XY()
chart.add(10 * '1', [(30, 5), (20, 12), (25, 4)])
chart.add(10 * '1b', [(4, 12), (5, 8), (6, 4)], secondary=True)
chart.add(10 * '2b', [(3, 24), (0, 17), (12, 9)], secondary=True)
chart.add(10 * '2', [(8, 23), (21, 1), (5, 0)])
return chart.render_response()
@app.route('/test/stacked')

1
pygal/css/graph.css

@ -124,3 +124,4 @@ text.no_data {
a:visited {
fill: none;
}

13
pygal/graph/bar.py

@ -57,10 +57,17 @@ class Bar(Graph):
transpose = swap if self.horizontal else ident
return transpose((x + width / 2, y + height / 2))
def bar(self, serie_node, serie, index):
def bar(self, serie_node, serie, index, rescale=False):
"""Draw a bar graph for a serie"""
bars = self.svg.node(serie_node['plot'], class_="bars")
for i, (x, y) in enumerate(serie.points):
if rescale and self.secondary_series:
points = [
(x, self._scale_diff + (y - self._scale_min_2nd) * self._scale)
for x, y in serie.points]
else:
points = serie.points
for i, (x, y) in enumerate(points):
if None in (x, y) or (self.logarithmic and y <= 0):
continue
metadata = serie.metadata.get(i)
@ -100,3 +107,5 @@ class Bar(Graph):
def _plot(self):
for index, serie in enumerate(self.series):
self.bar(self._serie(index), serie, index)
for index, serie in enumerate(self.secondary_series, len(self.series)):
self.bar(self._serie(index), serie, index, True)

42
pygal/graph/base.py

@ -28,7 +28,6 @@ from pygal.util import (
from pygal.svg import Svg
from pygal.util import cached_property, reverse_text_len
from math import sin, cos, sqrt
from itertools import chain
class BaseGraph(object):
@ -45,6 +44,7 @@ class BaseGraph(object):
self.svg = Svg(self)
self._x_labels = None
self._y_labels = None
self._x_2nd_labels = None
self._y_2nd_labels = None
self.nodes = {}
self.margin = Margin(*([20] * 4))
@ -82,7 +82,7 @@ class BaseGraph(object):
@property
def all_series(self):
return chain(self.series, self.secondary_series)
return self.series + self.secondary_series
@property
def _format(self):
@ -98,27 +98,19 @@ class BaseGraph(object):
def _compute_margin(self):
"""Compute graph margins from set texts"""
if self.show_legend and self.series:
for series_group in (self.series, self.secondary_series):
if self.show_legend and series_group:
h, w = get_texts_box(
map(lambda x: truncate(x, self.truncate_legend or 15),
cut(self.series, 'title')),
cut(series_group, 'title')),
self.legend_font_size)
if self.legend_at_bottom:
h_max = max(h, self.legend_box_size)
self.margin.bottom += 10 + h_max * round(
sqrt(self._order) - 1) * 1.5 + h_max
else:
if series_group is self.series:
self.margin.left += 10 + w + self.legend_box_size
if self.show_legend and self.secondary_series:
h, w = get_texts_box(
map(lambda x: truncate(x, self.truncate_legend or 15),
cut(self.secondary_series, 'title')),
self.legend_font_size)
if self.legend_at_bottom:
h_max = max(h, self.legend_box_size)
self.margin.bottom += 10 + h_max * round(
sqrt(self._order) - 1) * 1.5 + h_max
else:
self.margin.right += 10 + w + self.legend_box_size
@ -126,23 +118,33 @@ class BaseGraph(object):
h, _ = get_text_box(self.title[0], self.title_font_size)
self.margin.top += len(self.title) * (10 + h)
if self._x_labels:
for xlabels in (self._x_labels, self._x_2nd_labels):
if xlabels:
h, w = get_texts_box(
cut(self._x_labels), self.label_font_size)
cut(xlabels), self.label_font_size)
self._x_labels_height = 10 + max(
w * sin(rad(self.x_label_rotation)), h)
if xlabels is self._x_labels:
self.margin.bottom += self._x_labels_height
else:
self.margin.top += self._x_labels_height
if self.x_label_rotation:
self.margin.right = max(
w * cos(rad(self.x_label_rotation)),
self.margin.right)
else:
if not self._x_labels:
self._x_labels_height = 0
if self._y_labels:
for ylabels in (self._y_labels, self._y_2nd_labels):
if ylabels:
h, w = get_texts_box(
cut(self._y_labels), self.label_font_size)
cut(ylabels), self.label_font_size)
if ylabels is self._y_labels:
self.margin.left += 10 + max(
w * cos(rad(self.y_label_rotation)), h)
else:
self.margin.right += 10 + max(
w * cos(rad(self.y_label_rotation)), h)
@cached_property
def _legends(self):
@ -209,6 +211,8 @@ class BaseGraph(object):
def _draw(self):
"""Draw all the things"""
self._compute()
self._compute_secondary()
self._post_compute()
self._split_title()
self._compute_margin()
self._decorate()

49
pygal/graph/graph.py

@ -158,6 +158,26 @@ class Graph(BaseGraph):
text.attrib['transform'] = "rotate(%d %f %f)" % (
self.x_label_rotation, x, y)
if self._x_2nd_labels:
secondary_ax = self.svg.node(
self.nodes['plot'], class_="axis x x2")
for label, position in self._x_2nd_labels:
major = is_major(position)
# it is needed, to have the same structure as primary axis
guides = self.svg.node(secondary_ax, class_='guides')
x = self.view.x(position)
y = -5
text = self.svg.node(
guides, 'text',
x=x,
y=y,
class_='major' if major else ''
)
text.text = label
if self.x_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % (
-self.x_label_rotation, x, y)
def _y_axis(self, draw_axes=True):
"""Make the y axis: labels and guides"""
if not self._y_labels:
@ -265,7 +285,7 @@ class Graph(BaseGraph):
x = self.margin.left + self.view.width + 10
if self._y_2nd_labels:
h, w = get_texts_box(
cut(self._y_labels), self.label_font_size)
cut(self._y_2nd_labels), self.label_font_size)
x += 10 + max(w * cos(rad(self.y_label_rotation)), h)
y = self.margin.top + 10
@ -388,3 +408,30 @@ class Graph(BaseGraph):
for i, v in enumerate(serie.values)]
if self.interpolate:
serie.interpolated = self._interpolate(serie.values, x_pos)
def _compute_secondary(self):
# secondary y axis support
y_pos = zip(*self._y_labels)[1]
# secondary y axis support
if self.secondary_series:
if self.include_x_axis:
ymin = min(self._secondary_min, 0)
ymax = max(self._secondary_max, 0)
else:
ymin = self._secondary_min
ymax = self._secondary_max
steps = len(y_pos)
left_range = abs(y_pos[-1] - y_pos[0])
right_range = abs(ymax - ymin)
scale = right_range / (steps - 1)
self._y_2nd_labels = [(self._format(ymin + i * scale), pos)
for i, pos in enumerate(y_pos)]
min_2nd = float(self._y_2nd_labels[0][0])
self._scale = left_range / right_range
self._scale_diff = y_pos[0]
self._scale_min_2nd = min_2nd
def _post_compute(self):
pass

5
pygal/graph/horizontal.py

@ -30,9 +30,10 @@ class HorizontalGraph(Graph):
self.horizontal = True
super(HorizontalGraph, self).__init__(*args, **kwargs)
def _compute(self):
super(HorizontalGraph, self)._compute()
def _post_compute(self):
self._x_labels, self._y_labels = self._y_labels, self._x_labels
self._x_2nd_labels, self._y_2nd_labels = (
self._y_2nd_labels, self._x_2nd_labels)
def _axes(self):
self.view._force_vertical = True

19
pygal/graph/line.py

@ -137,25 +137,6 @@ class Line(Graph):
) if not self.y_labels else map(float, self.y_labels)
self._y_labels = zip(map(self._format, y_pos), y_pos)
# secondary y axis support
if self.secondary_series:
if self.include_x_axis:
ymin = min(self._secondary_min, 0)
ymax = max(self._secondary_max, 0)
else:
ymin = self._secondary_min
ymax = self._secondary_max
steps = len(y_pos)
left_range = abs(y_pos[-1] - y_pos[0])
right_range = abs(ymax - ymin)
scale = right_range / (steps - 1)
self._y_2nd_labels = [(self._format(ymin + i * scale), pos)
for i, pos in enumerate(y_pos)]
min_2nd = float(self._y_2nd_labels[0][0])
self._scale = left_range / right_range
self._scale_diff = y_pos[0]
self._scale_min_2nd = min_2nd
def _plot(self):
for index, serie in enumerate(self.series):

6
pygal/graph/xy.py

@ -34,7 +34,7 @@ class XY(Line):
def _compute(self):
xvals = [val[0]
for serie in self.series
for serie in self.all_series
for val in serie.values
if val[0] is not None]
yvals = [val[1]
@ -48,7 +48,7 @@ class XY(Line):
else:
rng = None
for serie in self.series:
for serie in self.all_series:
serie.points = serie.values
if self.interpolate and rng:
vals = zip(*sorted(
@ -59,7 +59,7 @@ class XY(Line):
if self.interpolate and rng:
xvals = [val[0]
for serie in self.series
for serie in self.all_series
for val in serie.interpolated]
yvals = [val[1]
for serie in self.series

Loading…
Cancel
Save