Browse Source

Refactor label processing in a ``_compute_x_labels`` and ``_compute_y_labels`` method. Handle both string and numbers for all charts. Create a ``Dual`` base chart for dual axis charts. (Fix #236)

pull/264/head
Florian Mounier 10 years ago
parent
commit
7748f847f3
  1. 4
      demo/moulinrouge/templates/svgs.jinja2
  2. 49
      demo/moulinrouge/tests.py
  3. 7
      docs/api/pygal.graph.dual.rst
  4. 1
      docs/api/pygal.graph.rst
  5. 1
      docs/changelog.rst
  6. 60
      docs/documentation/configuration/value.rst
  7. 11
      pygal/graph/bar.py
  8. 13
      pygal/graph/box.py
  9. 18
      pygal/graph/dot.py
  10. 53
      pygal/graph/dual.py
  11. 24
      pygal/graph/funnel.py
  12. 42
      pygal/graph/gauge.py
  13. 47
      pygal/graph/graph.py
  14. 17
      pygal/graph/histogram.py
  15. 25
      pygal/graph/line.py
  16. 6
      pygal/graph/map.py
  17. 6
      pygal/graph/pie.py
  18. 39
      pygal/graph/radar.py
  19. 9
      pygal/graph/stackedbar.py
  20. 27
      pygal/graph/xy.py
  21. 1
      pygal/test/test_line.py
  22. 9
      pygal/util.py

4
demo/moulinrouge/templates/svgs.jinja2

@ -1,10 +1,10 @@
{% extends '_layout.jinja2' %}
{% block section %}
{% for svg in svgs %}
{% for svg in svgs | sort(attribute='type') %}
<figure>
<embed src="{{ url_for('svg', type=svg.type, series=svg.series, config=svg.config) }}" type="image/svg+xml" width="{{ width }}" height="{{ height }}" />
<figcaption></figcaption>
<figcaption>{{ svg.type }}</figcaption>
</figure>
{% endfor %}
{% endblock section %}

49
demo/moulinrouge/tests.py

@ -180,6 +180,12 @@ def get_test_routes(app):
gauge.add('Need m', [-4])
gauge.add('Need z', [-10, 10.5])
gauge.add('No', [99, -99])
gauge.y_labels = [
{'label': 'X',
'value': 6},
{'label': '><',
'value': -6}
]
return gauge.render_response()
@app.route('/test/gauge/log')
@ -231,7 +237,7 @@ def get_test_routes(app):
graph.add('2', [7, -4, 10, None, 8, 3, 1])
graph.add('3', [7, -14, -10, None, 8, 3, 1])
graph.add('4', [7, 4, -10, None, 8, 3, 1])
graph.x_labels = ('a', 'b', 'c', 'd', 'e', 'f', 'g')
graph.x_labels = ('a', 'b', 'c', 'd')
graph.x_label_rotation = 90
return graph.render_response()
@ -341,13 +347,24 @@ def get_test_routes(app):
(1.5, 5, 10)
])
hist.add('2', [(2, 2, 8)])
hist.x_labels = [0, 3, 6, 9, 12]
return hist.render_response()
@app.route('/test/ylabels')
def test_ylabels():
chart = Line()
chart = Bar()
chart.x_labels = 'Red', 'Blue', 'Green'
chart.y_labels = .0001, .0003, .0004, .00045, .0005
chart.y_labels = [
{'value': .0001,
'label': 'LOL'},
{'value': .0003,
'label': 'ROFL'},
{'value': .0004,
'label': 'MAO'},
{'value': .00045,
'label': 'LMFAO'},
{'value': .0005,
'label': 'GMCB'}]
chart.add('line', [.0002, .0005, .00035])
return chart.render_response()
@ -398,6 +415,7 @@ def get_test_routes(app):
stacked = StackedBar(stack_from_top=True)
stacked.add('1', [1, 2, 3])
stacked.add('2', [4, 5, 6])
stacked.x_labels = ['a', 'b', 'c']
return stacked.render_response()
@app.route('/test/show_dots')
@ -603,9 +621,10 @@ def get_test_routes(app):
@app.route('/test/x_major_labels/<chart>')
def test_x_major_labels_for(chart):
chart = CHARTS_BY_NAME[chart]()
chart.add('test', range(12))
chart.x_labels = map(str, range(12))
chart.x_labels_major_count = 4
for i in range(12):
chart.add('test', range(12))
chart.x_labels = map(str, range(10))
# chart.x_labels_major_count = 4
# chart.x_labels_major = ['1', '5', '11', '1.0', '5.0', '11.0']
return chart.render_response()
@ -794,4 +813,22 @@ def get_test_routes(app):
line.add('_', [1, 32, 12, .4, .009])
return line.render_response()
@app.route('/test/legendlink/<chart>')
def test_legend_link_for(chart):
chart = CHARTS_BY_NAME[chart]()
# link on chart and label
chart.add({
'title': 'Red',
'xlink': {'href': 'http://en.wikipedia.org/wiki/Red'}
}, [{
'value': 2,
'label': 'This is red',
'xlink': {'href': 'http://en.wikipedia.org/wiki/Red'}}])
chart.add({'title': 'Yellow', 'xlink': {
'href': 'http://en.wikipedia.org/wiki/Yellow',
'target': '_blank'}}, 7)
return chart.render_response()
return list(sorted(filter(lambda x: x.startswith('test'), locals())))

7
docs/api/pygal.graph.dual.rst

@ -0,0 +1,7 @@
pygal.graph.dual module
=======================
.. automodule:: pygal.graph.dual
:members:
:undoc-members:
:show-inheritance:

1
docs/api/pygal.graph.rst

@ -15,6 +15,7 @@ Submodules
pygal.graph.base
pygal.graph.box
pygal.graph.dot
pygal.graph.dual
pygal.graph.funnel
pygal.graph.gauge
pygal.graph.graph

1
docs/changelog.rst

@ -37,6 +37,7 @@ Changelog
* Add ``tooltip_fancy_mode`` to revert to old tooltips
* Add auto ``print_value`` color + a configurable ``value_colors`` list in style
* Add ``guide_stroke_dasharray`` and ``guide_stroke_dasharray`` in style to customize guides (#242) (thanks cbergmiller)
* Refactor label processing in a ``_compute_x_labels`` and ``_compute_y_labels`` method. Handle both string and numbers for all charts. Create a ``Dual`` base chart for dual axis charts. (#236)
1.7.0
=====

60
docs/documentation/configuration/value.rst

@ -142,3 +142,63 @@ You can specify a dictionary to xlink with all links attributes:
'target': '_self'}
}])
Legend
~~~~~~
Finally legends can be link with the same mechanism:
.. pygal-code::
chart = pygal.Bar()
chart.add({
'title': 'First',
'xlink': {'href': 'http://en.wikipedia.org/wiki/First'}
}, [{
'value': 2,
'label': 'This is the first',
'xlink': {'href': 'http://en.wikipedia.org/wiki/First'}
}])
chart.add({
'title': 'Second',
'xlink': {
'href': 'http://en.wikipedia.org/wiki/Second',
'target': '_top'
}
}, [{
'value': 4,
'label': 'This is the second',
'xlink': {
'href': 'http://en.wikipedia.org/wiki/Second',
'target': '_top'}
}])
chart.add('Third', 7)
chart.add({
'title': 'Fourth',
'xlink': {
'href': 'http://en.wikipedia.org/wiki/Fourth',
'target': '_blank'
}
}, [{
'value': 5,
'xlink': {
'href': 'http://en.wikipedia.org/wiki/Fourth',
'target': '_blank'}
}])
chart.add({
'title': 'Fifth',
'xlink': {
'href': 'http://en.wikipedia.org/wiki/Fifth',
'target': '_self'
}
}, [{
'value': 3,
'label': 'This is the fifth',
'xlink': {
'href': 'http://en.wikipedia.org/wiki/Fifth',
'target': '_self'}
}])

11
pygal/graph/bar.py

@ -24,7 +24,7 @@ proportional to the values that they represent.
from __future__ import division
from pygal.graph.graph import Graph
from pygal.util import swap, ident, compute_scale, decorate, alter
from pygal.util import swap, ident, decorate, alter
class Bar(Graph):
@ -104,15 +104,6 @@ class Bar(Graph):
self._points(x_pos)
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.y_labels else list(map(float, self.y_labels))
self._x_labels = self.x_labels and list(zip(self.x_labels, [
(i + .5) / self._len for i in range(self._len)]))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def _compute_secondary(self):
"""Compute parameters for secondary series rendering"""
if self.secondary_series:

13
pygal/graph/box.py

@ -76,20 +76,9 @@ class Box(Graph):
if self._max:
self._box.ymax = max(self._max, self.zero)
x_pos = [
x / self._len for x in range(self._len + 1)
] if self._len > 1 else [0, 1] # Center if only one value
self._points(x_pos)
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.y_labels else list(map(float, self.y_labels))
def _compute_x_labels(self):
self._x_labels = self.x_labels and list(zip(self.x_labels, [
(i + .5) / self._order for i in range(self._order)]))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def _plot(self):
"""Plot the series data"""

18
pygal/graph/dot.py

@ -76,17 +76,25 @@ class Dot(Graph):
self._box.xmax = x_len
self._box.ymax = y_len
x_pos = [n / 2 for n in range(1, 2 * x_len, 2)]
y_pos = [n / 2 for n in reversed(range(1, 2 * y_len, 2))]
self._x_pos = [n / 2 for n in range(1, 2 * x_len, 2)]
self._y_pos = [n / 2 for n in reversed(range(1, 2 * y_len, 2))]
for j, serie in enumerate(self.series):
serie.points = [
(x_pos[i], y_pos[j])
(self._x_pos[i], self._y_pos[j])
for i in range(x_len)]
self._x_labels = self.x_labels and list(zip(self.x_labels, x_pos))
def _compute_x_labels(self):
self._x_labels = self.x_labels and list(
zip(self.x_labels, self._x_pos))
def _compute_y_labels(self):
self._y_labels = list(zip(
self.y_labels or cut(self.series, 'title'), y_pos))
self.y_labels or [
serie.title['title']
if isinstance(serie.title, dict)
else serie.title for serie in self.series],
self._y_pos))
def _set_view(self):
"""Assign a view to current graph"""

53
pygal/graph/dual.py

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# This file is part of pygal
#
# A python svg graph plotting library
# Copyright © 2012-2015 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""Dual chart base. Dual means a chart with 2 scaled axis like xy"""
from pygal.util import cut, compute_scale
from pygal._compat import is_str
from pygal.graph.graph import Graph
class Dual(Graph):
_dual = True
def _compute_x_labels(self):
x_pos = compute_scale(
self._box.xmin, self._box.xmax, self.logarithmic,
self.order_min, self.min_scale, self.max_scale
)
if self.x_labels:
self._x_labels = []
for i, x_label in enumerate(self.x_labels):
if isinstance(x_label, dict):
pos = float(x_label.get('value'))
title = x_label.get('label', self._format(pos))
elif is_str(x_label):
pos = x_pos[i]
title = x_label
else:
pos = float(x_label)
title = self._x_format(x_label)
self._x_labels.append((title, pos))
self._box.xmin = min(self._box.xmin, min(cut(self._x_labels, 1)))
self._box.xmax = max(self._box.xmax, max(cut(self._x_labels, 1)))
else:
self._x_labels = list(zip(map(self._x_format, x_pos), x_pos))

24
pygal/graph/funnel.py

@ -54,21 +54,25 @@ class Funnel(Graph):
class_='funnel reactive tooltip-trigger'), metadata)
x, y = self.view((
self._x_labels[serie.index][1], # Poly center from label
# Poly center from label
self._center(self._x_pos[serie.index]),
sum([point[1] for point in poly]) / len(poly)))
self._tooltip_data(funnels, value, x, y, classes='centered')
self._static_value(serie_node, value, x, y)
def _center(self, x):
return x - 1 / (2 * self._order)
def _compute(self):
"""Compute y min and max and y scale and set labels"""
x_pos = [
self._x_pos = [
(x + 1) / self._order for x in range(self._order)
] if self._order != 1 else [.5] # Center if only one value
previous = [[self.zero, self.zero] for i in range(self._len)]
for i, serie in enumerate(self.series):
y_height = - sum(serie.safe_values) / 2
all_x_pos = [0] + x_pos
all_x_pos = [0] + self._x_pos
serie.points = []
for j, value in enumerate(serie.values):
poly = []
@ -84,15 +88,13 @@ class Funnel(Graph):
self._box.ymin = -val_max
self._box.ymax = val_max
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.y_labels else list(map(float, self.y_labels))
def _compute_x_labels(self):
self._x_labels = list(
zip(cut(self.series, 'title'),
map(lambda x: x - 1 / (2 * self._order), x_pos)))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
zip(self.x_labels or [
serie.title['title']
if isinstance(serie.title, dict)
else serie.title for serie in self.series],
map(self._center, self._x_pos)))
def _plot(self):
"""Plot the funnel"""

42
pygal/graph/gauge.py

@ -20,7 +20,8 @@
"""Gauge chart representing values as needles on a polar scale"""
from __future__ import division
from pygal.util import decorate, compute_scale, alter
from pygal._compat import is_str
from pygal.util import decorate, compute_scale, alter, cut
from pygal.view import PolarThetaView, PolarThetaLogView
from pygal.graph.graph import Graph
@ -90,7 +91,7 @@ class Gauge(Graph):
def _y_axis(self, draw_axes=True):
"""Override y axis to plot a polar axis"""
axis = self.svg.node(self.nodes['plot'], class_="axis y x gauge")
axis = self.svg.node(self.nodes['plot'], class_="axis x gauge")
for i, (label, theta) in enumerate(self._y_labels):
guides = self.svg.node(axis, class_='guides')
@ -114,9 +115,13 @@ class Gauge(Graph):
y=y
).text = label
self.svg.node(
guides, 'title',
).text = self._format(theta)
def _x_axis(self, draw_axes=True):
"""Override x axis to put a center circle in center"""
axis = self.svg.node(self.nodes['plot'], class_="axis x gauge")
axis = self.svg.node(self.nodes['plot'], class_="axis y gauge")
x, y = self.view((0, 0))
self.svg.node(axis, 'circle', cx=x, cy=y, r=4)
@ -132,12 +137,33 @@ class Gauge(Graph):
0, 1,
self.min_,
self.max_)
y_pos = compute_scale(
self.min_, self.max_, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.y_labels else list(map(float, self.y_labels))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def _compute_y_labels(self):
y_pos = compute_scale(
self.min_, self.max_, self.logarithmic,
self.order_min, self.min_scale, self.max_scale
)
if self.y_labels:
self._y_labels = []
for i, y_label in enumerate(self.y_labels):
if isinstance(y_label, dict):
pos = float(y_label.get('value'))
title = y_label.get('label', self._format(pos))
elif is_str(y_label):
pos = y_pos[i]
title = y_label
else:
pos = float(y_label)
title = self._format(y_label)
self._y_labels.append((title, pos))
self.min_ = min(self.min_, min(cut(self._y_labels, 1)))
self.max_ = max(self.max_, max(cut(self._y_labels, 1)))
self._box.set_polar_box(
0, 1,
self.min_,
self.max_)
else:
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def _plot(self):
"""Plot all needles"""

47
pygal/graph/graph.py

@ -19,12 +19,12 @@
"""Chart properties and drawing"""
from __future__ import division
from pygal._compat import is_list_like
from pygal._compat import is_list_like, is_str
from pygal.interpolate import INTERPOLATIONS
from pygal.graph.public import PublicApi
from pygal.view import View, LogView, XYLogView, ReverseView
from pygal.util import (
cached_property, majorize, humanize, split_title,
cached_property, majorize, humanize, split_title, compute_scale,
truncate, reverse_text_len, get_text_box, get_texts_box, cut, rad,
decorate)
from math import sqrt, ceil, cos, sin
@ -165,12 +165,10 @@ class Graph(PublicApi):
class_='major' if major else ''
)
if isinstance(label, dict):
label = label['title']
text.text = truncate(label, truncation)
if text.text != label:
self.svg.node(guides, 'title').text = label
if self.x_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % (
self.x_label_rotation, x, y)
@ -242,14 +240,16 @@ class Graph(PublicApi):
class_='major' if major else ''
)
if isinstance(label, dict):
label = label['title']
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(position)
if self._y_2nd_labels:
secondary_ax = self.svg.node(
self.nodes['plot'], class_="axis y2")
@ -526,7 +526,9 @@ class Graph(PublicApi):
if self.show_legend and series_group:
h, w = get_texts_box(
map(lambda x: truncate(x, self.truncate_legend or 15),
cut(series_group, 'title')),
[serie.title['title']
if isinstance(serie.title, dict)
else serie.title for serie in series_group]),
self.style.legend_font_size)
if self.legend_at_bottom:
h_max = max(h, self.legend_box_size)
@ -713,9 +715,38 @@ class Graph(PublicApi):
cut(self._y_labels, 1)
)
def _compute_x_labels(self):
self._x_labels = self.x_labels and list(zip(self.x_labels, [
(i + .5) / self._len for i in range(self._len)]))
def _compute_y_labels(self):
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic,
self.order_min, self.min_scale, self.max_scale
)
if self.y_labels:
self._y_labels = []
for i, y_label in enumerate(self.y_labels):
if isinstance(y_label, dict):
pos = float(y_label.get('value'))
title = y_label.get('label', self._format(pos))
elif is_str(y_label):
pos = y_pos[i]
title = y_label
else:
pos = float(y_label)
title = self._format(y_label)
self._y_labels.append((title, pos))
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)))
else:
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def _draw(self):
"""Draw all the things"""
self._compute()
self._compute_x_labels()
self._compute_y_labels()
self._compute_secondary()
self._post_compute()
self._compute_margin()

17
pygal/graph/histogram.py

@ -23,16 +23,15 @@ as bars of varying width.
from __future__ import division
from pygal._compat import is_list_like
from pygal.graph.graph import Graph
from pygal.graph.dual import Dual
from pygal.util import (
swap, ident, compute_scale, decorate, cached_property, alter)
class Histogram(Graph):
class Histogram(Dual):
"""Histogram chart class"""
_dual = True
_series_margin = 0
@cached_property
@ -125,18 +124,6 @@ class Histogram(Graph):
if yrng:
self._box.ymin, self._box.ymax = ymin, ymax
x_pos = compute_scale(
self._box.xmin, self._box.xmax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.x_labels else list(map(float, self.x_labels))
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.y_labels else list(map(float, self.y_labels))
self._x_labels = list(zip(map(self._format, x_pos), x_pos))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def _plot(self):
"""Draw bars for series and secondary series"""
for serie in self.series:

25
pygal/graph/line.py

@ -24,7 +24,7 @@ connected by straight segments
from __future__ import division
from pygal.graph.graph import Graph
from pygal.util import cached_property, compute_scale, decorate, alter
from pygal.util import cached_property, compute_scale, decorate, alter, cut
class Line(Graph):
@ -146,18 +146,6 @@ class Line(Graph):
self._points(x_pos)
if self.x_labels:
label_len = len(self.x_labels)
if label_len != self._len:
label_pos = [0.5] if label_len == 1 else [
x / (label_len - 1) for x in range(label_len)
]
self._x_labels = list(zip(self.x_labels, label_pos))
else:
self._x_labels = list(zip(self.x_labels, x_pos))
else:
self._x_labels = None
if self.include_x_axis:
# Y Label
self._box.ymin = min(self._min or 0, 0)
@ -166,17 +154,6 @@ class Line(Graph):
self._box.ymin = self._min
self._box.ymax = self._max
if self.y_labels:
self._box.ymin = min(self._box.ymin, min(self.y_labels))
self._box.ymax = max(self._box.ymax, max(self.y_labels))
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.y_labels else list(map(float, self.y_labels))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def _plot(self):
"""Plot the serie lines and secondary serie lines"""
for serie in self.series:

6
pygal/graph/map.py

@ -117,3 +117,9 @@ class BaseMap(Graph):
self.area_names[area_code], self._format(value))
self.nodes['plot'].append(map)
def _compute_x_labels(self):
pass
def _compute_y_labels(self):
pass

6
pygal/graph/pie.py

@ -94,6 +94,12 @@ class Pie(Graph):
original_start_angle, center, val)
return serie_angle
def _compute_x_labels(self):
pass
def _compute_y_labels(self):
pass
def _plot(self):
"""Draw all the serie slices"""
total = sum(map(sum, map(lambda x: x.values, self.series)))

39
pygal/graph/radar.py

@ -27,6 +27,7 @@ from pygal.graph.line import Line
from pygal.adapters import positive, none_to_zero
from pygal.view import PolarView, PolarLogView
from pygal.util import deg, cached_property, compute_scale, majorize, cut
from pygal._compat import is_str
from math import cos, pi
@ -38,7 +39,7 @@ class Radar(Line):
def __init__(self, *args, **kwargs):
"""Init custom vars"""
self.x_pos = None
self._x_pos = None
self._rmax = None
super(Radar, self).__init__(*args, **kwargs)
@ -152,11 +153,11 @@ class Radar(Line):
continue
guides = self.svg.node(axis, class_='guides')
self.svg.line(
guides, [self.view((r, theta)) for theta in self.x_pos],
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(
guides, 'text',
x=x - 5,
@ -188,14 +189,34 @@ class Radar(Line):
self._rmin = self.zero
self._rmax = self._max or 1
self._box.set_polar_box(self._rmin, self._rmax)
self._self_close = True
self._x_pos = x_pos
def _compute_x_labels(self):
self._x_labels = self.x_labels and list(
zip(self.x_labels, self._x_pos))
def _compute_y_labels(self):
y_pos = compute_scale(
self._rmin, self._rmax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale / 2
) if not self.y_labels else list(map(int, self.y_labels))
self._x_labels = self.x_labels and list(zip(self.x_labels, x_pos))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
)
if self.y_labels:
self._y_labels = []
for i, y_label in enumerate(self.y_labels):
if isinstance(y_label, dict):
pos = float(y_label.get('value'))
title = y_label.get('label', self._format(pos))
elif is_str(y_label):
pos = y_pos[i]
title = y_label
else:
pos = float(y_label)
title = self._format(y_label)
self._y_labels.append((title, pos))
self._rmin = min(self._rmin, min(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.x_pos = x_pos
self._self_close = True
else:
self._y_labels = list(zip(map(self._format, y_pos), y_pos))

9
pygal/graph/stackedbar.py

@ -70,15 +70,6 @@ class StackedBar(Bar):
] if self._len > 1 else [0, 1] # Center if only one value
self._points(x_pos)
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.y_labels else list(map(float, self.y_labels))
self._x_ranges = zip(x_pos, x_pos[1:])
self._x_labels = self.x_labels and list(zip(self.x_labels, [
sum(x_range) / 2 for x_range in self._x_ranges]))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))
self.negative_cumulation = [0] * self._len
self.positive_cumulation = [0] * self._len

27
pygal/graph/xy.py

@ -24,15 +24,15 @@ straight segments.
from __future__ import division
from functools import reduce
from pygal.util import compute_scale, cached_property, compose, ident
from pygal.util import compute_scale, cached_property, compose, ident, cut
from pygal.graph.line import Line
from pygal.graph.dual import Dual
class XY(Line):
class XY(Line, Dual):
"""XY Line graph class"""
_dual = True
_x_adapters = []
def _get_value(self, values, i):
@ -123,26 +123,5 @@ class XY(Line):
if xrng:
self._box.xmin, self._box.xmax = xmin, xmax
if self.x_labels:
self._box.xmin = min(self.x_labels)
self._box.xmax = max(self.x_labels)
if yrng:
self._box.ymin, self._box.ymax = ymin, ymax
if self.y_labels:
self._box.ymin = min(self.y_labels)
self._box.ymax = max(self.y_labels)
x_pos = compute_scale(
self._box.xmin, self._box.xmax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.x_labels else list(map(float, self.x_labels))
y_pos = compute_scale(
self._box.ymin, self._box.ymax, self.logarithmic, self.order_min,
self.min_scale, self.max_scale
) if not self.y_labels else list(map(float, self.y_labels))
self._x_labels = list(zip(map(self._x_format, x_pos), x_pos))
self._y_labels = list(zip(map(self._format, y_pos), y_pos))

1
pygal/test/test_line.py

@ -95,6 +95,7 @@ def test_not_equal_x_labels():
"""Test x_labels"""
line = Line()
line.add('test1', range(100))
line.truncate_label = -1
line.x_labels = map(str, range(11))
q = line.render_pyquery()
assert len(q(".dots")) == 100

9
pygal/util.py

@ -24,7 +24,6 @@ from pygal._compat import u, is_list_like, to_unicode
import re
from decimal import Decimal
from math import floor, pi, log, log10, ceil
from itertools import cycle
ORDERS = u("yzafpnµm kMGTPEZY")
@ -215,13 +214,7 @@ def get_text_box(text, fs):
def get_texts_box(texts, fs):
"""Approximation of multiple texts bounds"""
def get_text_title(texts):
for text in texts:
if isinstance(text, dict):
yield text['title']
else:
yield text
max_len = max(map(len, get_text_title(texts)))
max_len = max(map(len, texts))
return (fs, text_len(max_len, fs))

Loading…
Cancel
Save