Browse Source

Fix labels rotation > 180 (Fix #257) Fix secondary axis

pull/290/head
Florian Mounier 9 years ago
parent
commit
bdcc0ed9e8
  1. 18
      demo/moulinrouge/__init__.py
  2. 21
      demo/moulinrouge/tests.py
  3. 3
      docs/changelog.rst
  4. 13
      pygal/css/graph.css
  5. 25
      pygal/graph/bar.py
  6. 6
      pygal/graph/base.py
  7. 45
      pygal/graph/graph.py

18
demo/moulinrouge/__init__.py

@ -76,7 +76,7 @@ def create_app():
def _random_series(type, data, order): def _random_series(type, data, order):
max = 10 ** order max = 10 ** order
min = 10 ** random.randrange(0, order) min = 10 ** random.randrange(0, order)
with_2nd = bool(random.randint(0, 1))
series = [] series = []
for i in range(random.randrange(1, 10)): for i in range(random.randrange(1, 10)):
if type == 'Pie': if type == 'Pie':
@ -89,7 +89,10 @@ def create_app():
else: else:
values = [random_value((-max, min)[random.randrange(1, 2)], values = [random_value((-max, min)[random.randrange(1, 2)],
max) for i in range(data)] max) for i in range(data)]
series.append((random_label(), values)) is_2nd = False
if with_2nd:
is_2nd = bool(random.randint(0, 1))
series.append((random_label(), values, is_2nd))
return series return series
from .tests import get_test_routes from .tests import get_test_routes
@ -107,15 +110,15 @@ def create_app():
def svg(type, series, config): def svg(type, series, config):
graph = get(type)( graph = get(type)(
pickle.loads(b64decode(str(config)))) pickle.loads(b64decode(str(config))))
for title, values in pickle.loads(b64decode(str(series))): for title, values, is_2nd in pickle.loads(b64decode(str(series))):
graph.add(title, values) graph.add(title, values, secondary=is_2nd)
return graph.render_response() return graph.render_response()
@app.route("/table/<type>/<series>/<config>") @app.route("/table/<type>/<series>/<config>")
def table(type, series, config): def table(type, series, config):
graph = get(type)(pickle.loads(b64decode(str(config)))) graph = get(type)(pickle.loads(b64decode(str(config))))
for title, values in pickle.loads(b64decode(str(series))): for title, values, is_2nd in pickle.loads(b64decode(str(series))):
graph.add(title, values) graph.add(title, values, secondary=is_2nd)
return graph.render_table() return graph.render_table()
@app.route("/sparkline/<style>") @app.route("/sparkline/<style>")
@ -235,10 +238,11 @@ def create_app():
labels = [random_label() for i in range(data)] labels = [random_label() for i in range(data)]
svgs = [] svgs = []
config.show_legend = bool(random.randrange(0, 2)) config.show_legend = bool(random.randrange(0, 2))
for angle in range(0, 91, 5): for angle in range(0, 370, 10):
config.title = "%d rotation" % angle config.title = "%d rotation" % angle
config.x_labels = labels config.x_labels = labels
config.x_label_rotation = angle config.x_label_rotation = angle
config.y_label_rotation = angle
svgs.append({'type': 'pygal.Bar', svgs.append({'type': 'pygal.Bar',
'series': series, 'series': series,
'config': b64encode(pickle.dumps(config))}) 'config': b64encode(pickle.dumps(config))})

21
demo/moulinrouge/tests.py

@ -454,7 +454,7 @@ def get_test_routes(app):
@app.route('/test/dateline') @app.route('/test/dateline')
def test_dateline(): def test_dateline():
dateline = DateLine(x_label_rotation=25, show_minor_x_labels=False) dateline = DateLine(y_label_rotation=112)
dateline.x_labels = [ dateline.x_labels = [
date(2013, 1, 1), date(2013, 1, 1),
date(2013, 7, 1), date(2013, 7, 1),
@ -470,7 +470,9 @@ def get_test_routes(app):
dateline.add("Serie", [ dateline.add("Serie", [
(date(2013, 1, 2), 213), (date(2013, 1, 2), 213),
(date(2013, 8, 2), 281), (date(2013, 8, 2), 281),
(date(2013, 5, 31), 281),
(date(2014, 12, 7), 198), (date(2014, 12, 7), 198),
(date(2014, 9, 6), 198),
(date(2015, 3, 21), 120) (date(2015, 3, 21), 120)
]) ])
return dateline.render_response() return dateline.render_response()
@ -852,6 +854,23 @@ def get_test_routes(app):
line.add('zeroes 2', [0]) line.add('zeroes 2', [0])
return line.render_response() return line.render_response()
@app.route('/test/rotations/<chart>')
def test_rotations_for(chart):
graph = CHARTS_BY_NAME[chart]()
# graph.x_label_rotation = 290
# graph.y_label_rotation = 0
graph.add('lalalla al alallaa a 1',
[1, 3, 12, 3, 4, None, 9])
graph.add('lalalla al alallaa a 2',
[7, -4, 10, None, 8, 3, 1], secondary=True)
graph.add('lalalla al alallaa a 3',
[7, -14, -10, None, 8, 3, 1])
graph.add('lalalla al alallaa a 4',
[7, 4, -10, None, 8, 3, 1], secondary=True)
graph.x_labels = ('a', 'b', 'c', 'd', 'e', 'f', 'g')
# graph.legend_at_bottom = True
return graph.render_response()
@app.route('/test/datetimeline') @app.route('/test/datetimeline')
def test_datetimeline(): def test_datetimeline():
line = DateTimeLine() line = DateTimeLine()

3
docs/changelog.rst

@ -8,7 +8,8 @@ Changelog
* Add `dynamic_print_values` to show print_values on legend hover. (Fix #279) * Add `dynamic_print_values` to show print_values on legend hover. (Fix #279)
* Fix unparse_color for python 3.5+ compatibility (thanks felixonmars, sjourdois) * Fix unparse_color for python 3.5+ compatibility (thanks felixonmars, sjourdois)
* Process major labels as labels. (Fix #263) * Process major labels as labels. (Fix #263)
* Fix labels rotation > 180 (Fix #257)
* Fix secondary axis
2.0.8 2.0.8
===== =====

13
pygal/css/graph.css

@ -46,13 +46,26 @@
text-anchor: start; text-anchor: start;
} }
{{ id }}.axis.x:not(.web) text[transform].backwards {
text-anchor: end;
}
{{ id }}.axis.y text { {{ id }}.axis.y text {
text-anchor: end; text-anchor: end;
} }
{{ id }}.axis.y text[transform].backwards {
text-anchor: start;
}
{{ id }}.axis.y2 text { {{ id }}.axis.y2 text {
text-anchor: start; text-anchor: start;
} }
{{ id }}.axis.y2 text[transform].backwards {
text-anchor: end;
}
{{ id }}.axis .guide.line { {{ id }}.axis .guide.line {
stroke-dasharray: {{ style.guide_stroke_dasharray }}; stroke-dasharray: {{ style.guide_stroke_dasharray }};
} }

25
pygal/graph/bar.py

@ -108,31 +108,6 @@ class Bar(Graph):
self._x_pos = [(i + .5) / self._len for i in range(self._len)] self._x_pos = [(i + .5) / self._len for i in range(self._len)]
def _compute_secondary(self):
"""Compute parameters for secondary series rendering"""
if self.secondary_series:
y_pos = list(zip(*self._y_labels))[1]
ymin = self._secondary_min
ymax = self._secondary_max
min_0_ratio = (self.zero - self._box.ymin) / self._box.height or 1
max_0_ratio = (self._box.ymax - self.zero) / self._box.height or 1
if ymax > self._box.ymax:
ymin = -(ymax - self.zero) * (1 / max_0_ratio - 1)
else:
ymax = (self.zero - ymin) * (1 / min_0_ratio - 1)
left_range = abs(self._box.ymax - self._box.ymin)
right_range = abs(ymax - ymin) or 1
self._scale = left_range / right_range
self._scale_diff = self._box.ymin
self._scale_min_2nd = ymin
self._y_2nd_labels = [
(self._format(self._box.xmin + y * right_range / left_range),
y)
for y in y_pos]
def _plot(self): def _plot(self):
"""Draw bars for series and secondary series""" """Draw bars for series and secondary series"""
for serie in self.series: for serie in self.series:

6
pygal/graph/base.py

@ -82,6 +82,12 @@ class BaseGraph(object):
if self.zero == 0 and isinstance(self, BaseMap): if self.zero == 0 and isinstance(self, BaseMap):
self.zero = 1 self.zero = 1
if self.x_label_rotation:
self.x_label_rotation %= 360
if self.y_label_rotation:
self.y_label_rotation %= 360
for key in ('x_labels', 'y_labels'): for key in ('x_labels', 'y_labels'):
if getattr(self, key): if getattr(self, key):
setattr(self, key, list(getattr(self, key))) setattr(self, key, list(getattr(self, key)))

45
pygal/graph/graph.py

@ -139,11 +139,12 @@ class Graph(PublicApi):
available_space, self.style.label_font_size) available_space, self.style.label_font_size)
truncation = max(truncation, 1) truncation = max(truncation, 1)
lastlabel = self._x_labels[-1][0]
if 0 not in [label[1] for label in self._x_labels]: if 0 not in [label[1] for label in self._x_labels]:
self.svg.node(axis, 'path', self.svg.node(axis, 'path',
d='M%f %f v%f' % (0, 0, self.view.height), d='M%f %f v%f' % (0, 0, self.view.height),
class_='line') class_='line')
lastlabel = self._x_labels[-1][0] lastlabel = None
for label, position in self._x_labels: for label, position in self._x_labels:
if self.horizontal: if self.horizontal:
@ -184,6 +185,17 @@ class Graph(PublicApi):
if self.x_label_rotation: if self.x_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % ( text.attrib['transform'] = "rotate(%d %f %f)" % (
self.x_label_rotation, x, y) self.x_label_rotation, x, y)
if self.x_label_rotation >= 180:
text.attrib['class'] = ' '.join(
(text.attrib['class'] and text.attrib['class'].split(
' ') or []) + ['backwards'])
if self._y_2nd_labels and 0 not in [
label[1] for label in self._x_labels]:
self.svg.node(axis, 'path',
d='M%f %f v%f' % (
self.view.width, 0, self.view.height),
class_='line')
if self._x_2nd_labels: if self._x_2nd_labels:
secondary_ax = self.svg.node( secondary_ax = self.svg.node(
@ -208,6 +220,11 @@ class Graph(PublicApi):
if self.x_label_rotation: if self.x_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % ( text.attrib['transform'] = "rotate(%d %f %f)" % (
-self.x_label_rotation, x, y) -self.x_label_rotation, x, y)
if self.x_label_rotation >= 180:
text.attrib['class'] = ' '.join((
text.attrib['class'] and
text.attrib['class'].split(
' ') or []) + ['backwards'])
def _y_axis(self): def _y_axis(self):
"""Make the y axis: labels and guides""" """Make the y axis: labels and guides"""
@ -261,7 +278,10 @@ class Graph(PublicApi):
if self.y_label_rotation: if self.y_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % ( text.attrib['transform'] = "rotate(%d %f %f)" % (
self.y_label_rotation, x, y) self.y_label_rotation, x, y)
if 90 < self.y_label_rotation < 270:
text.attrib['class'] = ' '.join(
(text.attrib['class'] and text.attrib['class'].split(
' ') or []) + ['backwards'])
self.svg.node( self.svg.node(
guides, 'title', guides, 'title',
).text = self._format(position) ).text = self._format(position)
@ -287,6 +307,11 @@ class Graph(PublicApi):
if self.y_label_rotation: if self.y_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % ( text.attrib['transform'] = "rotate(%d %f %f)" % (
self.y_label_rotation, x, y) self.y_label_rotation, x, y)
if 90 < self.y_label_rotation < 270:
text.attrib['class'] = ' '.join(
(text.attrib['class'] and
text.attrib['class'].split(
' ') or []) + ['backwards'])
def _legend(self): def _legend(self):
"""Make the legend box""" """Make the legend box"""
@ -340,7 +365,8 @@ class Graph(PublicApi):
if self._y_2nd_labels: if self._y_2nd_labels:
h, w = get_texts_box( h, w = get_texts_box(
cut(self._y_2nd_labels), self.style.label_font_size) cut(self._y_2nd_labels), self.style.label_font_size)
x += self.spacing + max(w * cos(rad(self.y_label_rotation)), h) x += self.spacing + max(w * abs(cos(rad(
self.y_label_rotation))), h)
y = self.margin_box.top + self.spacing y = self.margin_box.top + self.spacing
@ -594,15 +620,20 @@ class Graph(PublicApi):
cut(xlabels)), cut(xlabels)),
self.style.label_font_size) self.style.label_font_size)
self._x_labels_height = self.spacing + max( self._x_labels_height = self.spacing + max(
w * sin(rad(self.x_label_rotation)), h) w * abs(sin(rad(self.x_label_rotation))), h)
if xlabels is self._x_labels: if xlabels is self._x_labels:
self.margin_box.bottom += self._x_labels_height self.margin_box.bottom += self._x_labels_height
else: else:
self.margin_box.top += self._x_labels_height self.margin_box.top += self._x_labels_height
if self.x_label_rotation: if self.x_label_rotation:
if self.x_label_rotation % 180 < 90:
self.margin_box.right = max( self.margin_box.right = max(
w * cos(rad(self.x_label_rotation)), w * abs(cos(rad(self.x_label_rotation))),
self.margin_box.right) self.margin_box.right)
else:
self.margin_box.left = max(
w * abs(cos(rad(self.x_label_rotation))),
self.margin_box.left)
if self.show_y_labels: if self.show_y_labels:
for ylabels in (self._y_labels, self._y_2nd_labels): for ylabels in (self._y_labels, self._y_2nd_labels):
@ -611,10 +642,10 @@ class Graph(PublicApi):
cut(ylabels), self.style.label_font_size) cut(ylabels), self.style.label_font_size)
if ylabels is self._y_labels: if ylabels is self._y_labels:
self.margin_box.left += self.spacing + max( self.margin_box.left += self.spacing + max(
w * cos(rad(self.y_label_rotation)), h) w * abs(cos(rad(self.y_label_rotation))), h)
else: else:
self.margin_box.right += self.spacing + max( self.margin_box.right += self.spacing + max(
w * cos(rad(self.y_label_rotation)), h) w * abs(cos(rad(self.y_label_rotation))), h)
self._title = split_title( self._title = split_title(
self.title, self.width, self.style.title_font_size) self.title, self.width, self.style.title_font_size)

Loading…
Cancel
Save