Skip to content

Commit

Permalink
Fix labels rotation > 180 (Fix #257) Fix secondary axis
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed Nov 2, 2015
1 parent 7102567 commit bdcc0ed
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 43 deletions.
18 changes: 11 additions & 7 deletions demo/moulinrouge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def _random(data, order):
def _random_series(type, data, order):
max = 10 ** order
min = 10 ** random.randrange(0, order)

with_2nd = bool(random.randint(0, 1))
series = []
for i in range(random.randrange(1, 10)):
if type == 'Pie':
Expand All @@ -89,7 +89,10 @@ def _random_series(type, data, order):
else:
values = [random_value((-max, min)[random.randrange(1, 2)],
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

from .tests import get_test_routes
Expand All @@ -107,15 +110,15 @@ def index():
def svg(type, series, config):
graph = get(type)(
pickle.loads(b64decode(str(config))))
for title, values in pickle.loads(b64decode(str(series))):
graph.add(title, values)
for title, values, is_2nd in pickle.loads(b64decode(str(series))):
graph.add(title, values, secondary=is_2nd)
return graph.render_response()

@app.route("/table/<type>/<series>/<config>")
def table(type, series, config):
graph = get(type)(pickle.loads(b64decode(str(config))))
for title, values in pickle.loads(b64decode(str(series))):
graph.add(title, values)
for title, values, is_2nd in pickle.loads(b64decode(str(series))):
graph.add(title, values, secondary=is_2nd)
return graph.render_table()

@app.route("/sparkline/<style>")
Expand Down Expand Up @@ -235,10 +238,11 @@ def rotation():
labels = [random_label() for i in range(data)]
svgs = []
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.x_labels = labels
config.x_label_rotation = angle
config.y_label_rotation = angle
svgs.append({'type': 'pygal.Bar',
'series': series,
'config': b64encode(pickle.dumps(config))})
Expand Down
21 changes: 20 additions & 1 deletion demo/moulinrouge/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ class LolConfig(Config):

@app.route('/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 = [
date(2013, 1, 1),
date(2013, 7, 1),
Expand All @@ -470,7 +470,9 @@ def test_dateline():
dateline.add("Serie", [
(date(2013, 1, 2), 213),
(date(2013, 8, 2), 281),
(date(2013, 5, 31), 281),
(date(2014, 12, 7), 198),
(date(2014, 9, 6), 198),
(date(2015, 3, 21), 120)
])
return dateline.render_response()
Expand Down Expand Up @@ -852,6 +854,23 @@ def test_only_zeroes():
line.add('zeroes 2', [0])
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')
def test_datetimeline():
line = DateTimeLine()
Expand Down
3 changes: 2 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Changelog
* Add `dynamic_print_values` to show print_values on legend hover. (Fix #279)
* Fix unparse_color for python 3.5+ compatibility (thanks felixonmars, sjourdois)
* Process major labels as labels. (Fix #263)

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

2.0.8
=====
Expand Down
13 changes: 13 additions & 0 deletions pygal/css/graph.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,26 @@
text-anchor: start;
}

{{ id }}.axis.x:not(.web) text[transform].backwards {
text-anchor: end;
}

{{ id }}.axis.y text {
text-anchor: end;
}

{{ id }}.axis.y text[transform].backwards {
text-anchor: start;
}

{{ id }}.axis.y2 text {
text-anchor: start;
}

{{ id }}.axis.y2 text[transform].backwards {
text-anchor: end;
}

{{ id }}.axis .guide.line {
stroke-dasharray: {{ style.guide_stroke_dasharray }};
}
Expand Down
25 changes: 0 additions & 25 deletions pygal/graph/bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,31 +108,6 @@ def _compute(self):

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):
"""Draw bars for series and secondary series"""
for serie in self.series:
Expand Down
6 changes: 6 additions & 0 deletions pygal/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ def prepare_values(self, raw, offset=0):
if self.zero == 0 and isinstance(self, BaseMap):
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'):
if getattr(self, key):
setattr(self, key, list(getattr(self, key)))
Expand Down
49 changes: 40 additions & 9 deletions pygal/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ def _x_axis(self):
available_space, self.style.label_font_size)
truncation = max(truncation, 1)

lastlabel = self._x_labels[-1][0]
if 0 not in [label[1] for label in self._x_labels]:
self.svg.node(axis, 'path',
d='M%f %f v%f' % (0, 0, self.view.height),
class_='line')
lastlabel = self._x_labels[-1][0]
lastlabel = None

for label, position in self._x_labels:
if self.horizontal:
Expand Down Expand Up @@ -184,6 +185,17 @@ def _x_axis(self):
if self.x_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % (
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:
secondary_ax = self.svg.node(
Expand All @@ -208,6 +220,11 @@ def _x_axis(self):
if self.x_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % (
-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):
"""Make the y axis: labels and guides"""
Expand Down Expand Up @@ -261,7 +278,10 @@ def _y_axis(self):
if self.y_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % (
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(
guides, 'title',
).text = self._format(position)
Expand All @@ -287,6 +307,11 @@ def _y_axis(self):
if self.y_label_rotation:
text.attrib['transform'] = "rotate(%d %f %f)" % (
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):
"""Make the legend box"""
Expand Down Expand Up @@ -340,7 +365,8 @@ def _legend(self):
if self._y_2nd_labels:
h, w = get_texts_box(
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

Expand Down Expand Up @@ -594,15 +620,20 @@ def _compute_margin(self):
cut(xlabels)),
self.style.label_font_size)
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:
self.margin_box.bottom += self._x_labels_height
else:
self.margin_box.top += self._x_labels_height
if self.x_label_rotation:
self.margin_box.right = max(
w * cos(rad(self.x_label_rotation)),
self.margin_box.right)
if self.x_label_rotation % 180 < 90:
self.margin_box.right = max(
w * abs(cos(rad(self.x_label_rotation))),
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:
for ylabels in (self._y_labels, self._y_2nd_labels):
Expand All @@ -611,10 +642,10 @@ def _compute_margin(self):
cut(ylabels), self.style.label_font_size)
if ylabels is self._y_labels:
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:
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, self.width, self.style.title_font_size)
Expand Down

0 comments on commit bdcc0ed

Please sign in to comment.