Browse Source

Fix x/y title margin computation + split if too long + position with legends

pull/35/merge
Florian Mounier 12 years ago
parent
commit
64c1b7640c
  1. 15
      demo/moulinrouge/tests.py
  2. 4
      pygal/config.py
  3. 57
      pygal/graph/base.py
  4. 50
      pygal/graph/graph.py
  5. 2
      pygal/svg.py
  6. 17
      pygal/util.py

15
demo/moulinrouge/tests.py

@ -143,6 +143,21 @@ def get_test_routes(app):
graph.x_labels = 'a',
return graph.render_response()
@app.route('/test/xytitles/<chart>')
def test_xy_titles_for(chart):
graph = CHARTS_BY_NAME[chart]()
graph.title = 'My global title'
graph.x_title = 'My X title'
graph.y_title = 'My Y title'
graph.add('My number 1 serie', [1, 3, 12])
graph.add('My number 2 serie', [7, -4, 10])
graph.add('A', [17, -14, 11], secondary=True)
graph.x_label_rotation = 25
graph.legend_at_bottom = not True
graph.x_labels = (
'First point', 'Second point', 'Third point')
return graph.render_response()
@app.route('/test/no_data/<chart>')
def test_no_data_for(chart):
graph = CHARTS_BY_NAME[chart]()

4
pygal/config.py

@ -104,11 +104,11 @@ class Config(object):
None, str, "Look",
"Graph title.", "Leave it to None to disable title.")
xtitle = Key(
x_title = Key(
None, str, "Look",
"Graph X-Axis title.", "Leave it to None to disable X-Axis title.")
ytitle = Key(
y_title = Key(
None, str, "Look",
"Graph Y-Axis title.", "Leave it to None to disable Y-Axis title.")

57
pygal/graph/base.py

@ -24,9 +24,9 @@ Base for pygal charts
from __future__ import division
from pygal.view import Margin, Box
from pygal.util import (
get_text_box, get_texts_box, cut, rad, humanize, truncate)
get_text_box, get_texts_box, cut, rad, humanize, truncate, split_title)
from pygal.svg import Svg
from pygal.util import cached_property, reverse_text_len
from pygal.util import cached_property
from math import sin, cos, sqrt
@ -73,22 +73,6 @@ class BaseGraph(object):
return object.__getattribute__(self.config, attr)
return object.__getattribute__(self, attr)
def _split_title(self):
if not self.title:
self.title = []
return
size = reverse_text_len(self.width, self.title_font_size * 1.1)
title = self.title.strip()
self.title = []
while len(title) > size:
title_part = title[:size]
i = title_part.rfind(' ')
if i == -1:
i = len(title_part)
self.title.append(title_part[:i])
title = title[i:].strip()
self.title.append(title)
@property
def all_series(self):
return self.series + self.secondary_series
@ -107,6 +91,7 @@ class BaseGraph(object):
def _compute_margin(self):
"""Compute graph margins from set texts"""
self._legend_at_left_width = 0
for series_group in (self.series, self.secondary_series):
if self.show_legend and series_group:
h, w = get_texts_box(
@ -119,14 +104,12 @@ class BaseGraph(object):
sqrt(self._order) - 1) * 1.5 + h_max
else:
if series_group is self.series:
self.margin.left += 10 + w + self.legend_box_size
legend_width = 10 + w + self.legend_box_size
self.margin.left += legend_width
self._legend_at_left_width += legend_width
else:
self.margin.right += 10 + w + self.legend_box_size
if self.title:
h, _ = get_text_box(self.title[0], self.title_font_size)
self.margin.top += len(self.title) * (10 + h)
for xlabels in (self._x_labels, self._x_2nd_labels):
if xlabels:
h, w = get_texts_box(
@ -157,6 +140,33 @@ class BaseGraph(object):
self.margin.right += 10 + max(
w * cos(rad(self.y_label_rotation)), h)
self.title = split_title(
self.title, self.width, self.title_font_size)
if self.title:
h, _ = get_text_box(self.title[0], self.title_font_size)
self.margin.top += len(self.title) * (10 + h)
self.x_title = split_title(
self.x_title, self.width - self.margin.x, self.title_font_size)
self._x_title_height = 0
if self.x_title:
h, _ = get_text_box(self.x_title[0], self.title_font_size)
height = len(self.x_title) * (10 + h)
self.margin.bottom += height
self._x_title_height = height + 10
self.y_title = split_title(
self.y_title, self.height - self.margin.y, self.title_font_size)
self._y_title_height = 0
if self.y_title:
h, _ = get_text_box(self.y_title[0], self.title_font_size)
height = len(self.y_title) * (10 + h)
self.margin.left += height
self._y_title_height = height + 10
@cached_property
def _legends(self):
"""Getter for series title"""
@ -224,7 +234,6 @@ class BaseGraph(object):
self._compute()
self._compute_secondary()
self._post_compute()
self._split_title()
self._compute_margin()
self._decorate()
if self.series and self._has_data():

50
pygal/graph/graph.py

@ -86,6 +86,9 @@ class Graph(BaseGraph):
x=0, y=0,
width=self.view.width,
height=self.view.height)
self.nodes['title'] = self.svg.node(
self.nodes['graph'],
class_="titles")
self.nodes['overlay'] = self.svg.node(
self.nodes['graph'], class_="plot overlay",
transform="translate(%d, %d)" % (
@ -138,7 +141,7 @@ class Graph(BaseGraph):
if self.x_labels_major:
x_labels_major = self.x_labels_major
elif self.x_labels_major_every:
x_labels_major = [self._x_labels[i][0] for i in xrange(
x_labels_major = [self._x_labels[i][0] for i in range(
0, len(self._x_labels), self.x_labels_major_every)]
elif self.x_labels_major_count:
label_count = len(self._x_labels)
@ -148,7 +151,7 @@ class Graph(BaseGraph):
else:
x_labels_major = [self._x_labels[
int(i * (label_count - 1) / (major_count - 1))][0]
for i in xrange(major_count)]
for i in range(major_count)]
else:
x_labels_major = []
for label, position in self._x_labels:
@ -270,6 +273,7 @@ class Graph(BaseGraph):
if self.legend_at_bottom:
x = self.margin.left + 10
y = (self.margin.top + self.view.height +
self._x_title_height +
self._x_labels_height + 10)
cols = ceil(sqrt(self._order)) or 1
@ -355,43 +359,39 @@ class Graph(BaseGraph):
def _title(self):
"""Make the title"""
if self.title:
title_node = self.svg.node(
self.nodes['graph'],
class_="titles")
for i, title_line in enumerate(self.title, 1):
self.svg.node(
title_node, 'text', class_='title',
self.nodes['title'], 'text', class_='title',
x=self.width / 2,
y=i * (self.title_font_size + 10)
).text = title_line
def _x_title(self):
"""Make the X-Axis title"""
if self.xtitle:
title_node = self.svg.node(
self.nodes['graph'],
class_="titles")
self.svg.node(
title_node, 'text', class_='title',
x= (self.width / 2) + 80,
y= self.height + 10
).text = self.xtitle
y = (self.height - self.margin.bottom +
self._x_labels_height)
if self.x_title:
for i, title_line in enumerate(self.x_title, 1):
text = self.svg.node(
self.nodes['title'], 'text', class_='title',
x=self.margin.left + self.view.width / 2,
y=y + i * (self.title_font_size + 10)
)
text.text = title_line
def _y_title(self):
"""Make the Y-Axis title"""
if self.ytitle:
title_node = self.svg.node(
self.nodes['graph'],
class_="titles")
if self.y_title:
yc = self.margin.top + self.view.height / 2
for i, title_line in enumerate(self.y_title, 1):
text = self.svg.node(
title_node, 'text', class_='title',
x= 40,
y= self.height / 2,
self.nodes['title'], 'text', class_='title',
x=self._legend_at_left_width,
y=i * (self.title_font_size + 10) + yc
)
text.text = self.ytitle
text.attrib['transform'] = "rotate(%d %f %f)" % (
-90, 40, self.height / 2)
-90, self._legend_at_left_width, yc)
text.text = title_line
def _serie(self, serie):
"""Make serie node"""

2
pygal/svg.py

@ -192,7 +192,7 @@ class Svg(object):
self.add_styles()
self.add_scripts()
self.root.set(
'viewBox', '0 0 %d %d' % (self.graph.width, self.graph.height + 20))
'viewBox', '0 0 %d %d' % (self.graph.width, self.graph.height))
if self.graph.explicit_size:
self.root.set('width', str(self.graph.width))
self.root.set('height', str(self.graph.height))

17
pygal/util.py

@ -365,3 +365,20 @@ def prepare_values(raw, config, cls):
values.append(value)
series.append(Serie(title, values, metadata))
return series
def split_title(title, width, title_fs):
titles = []
if not title:
return titles
size = reverse_text_len(width, title_fs * 1.1)
title = title.strip()
while len(title) > size:
title_part = title[:size]
i = title_part.rfind(' ')
if i == -1:
i = len(title_part)
titles.append(title_part[:i])
title = title[i:].strip()
titles.append(title)
return titles

Loading…
Cancel
Save