From f285ac280921b49d7b1f88c5c9aae60937880eda Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Fri, 27 Apr 2012 12:18:43 +0200 Subject: [PATCH] Truncate long texts + config option --- demo/simple_test.py | 21 +++++++++++++++------ pygal/config.py | 4 ++++ pygal/graph/base.py | 9 ++++++--- pygal/graph/graph.py | 28 +++++++++++++++++++++++----- pygal/test/test_util.py | 12 +++++++++++- pygal/util.py | 16 ++++++++++++++-- 6 files changed, 73 insertions(+), 17 deletions(-) diff --git a/demo/simple_test.py b/demo/simple_test.py index d4c6ab7..a1e9ff5 100755 --- a/demo/simple_test.py +++ b/demo/simple_test.py @@ -18,10 +18,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . import time - +import sys +from math import cos, sin +from subprocess import call from pygal import * from pygal.style import * -from math import cos, sin + lnk = lambda v, l=None: {'value': v, 'xlink': 'javascript:alert("Test %s")' % v, 'label': l} t_start = time.time() @@ -151,12 +153,13 @@ line.interpolation_precision = 200 line.render_to_file('out-line.svg') stackedline = StackedLine(fill=True) -stackedline.add('test1', [1, 3, 2, None, 2, 13, 2, 5, 8]) +stackedline.add('test1u euirset uriets ur itseruie uriset uriest u', [1, 3, 2, None, 2, 13, 2, 5, 8]) stackedline.add('test2', [4, 1, 1, 3, 12, 3]) stackedline.add('test3', [9, 3, 2, lnk(10, '!'), 8, 2]) -stackedline.x_labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] -stackedline.title = "Stackedline test" -# stackedline.interpolate = "cubic" +stackedline.x_labels = ['aaisaruistarsitauritaria', 'bsruie trstie rusiet ruetsru', 'cuasitaruisrnauciuestuirue uiretsu iersut irasui', 'd', 'e', 'f', 'g'] +stackedline.title = "Stackedline ine uisernuis enuir esnue sunres nuise nuires uinsre auin ruist arusit arst ierustie ruiset uriest uirest test" +# stackedline.interpolate = "cubic"xb + stackedline.render_to_file('out-stackedline.svg') xy = XY(Config(fill=True, style=NeonStyle, interpolate='cubic')) @@ -201,4 +204,10 @@ radar.title = "Radar test" radar.render_to_file('out-radar.svg') +call(['wsreload', '--url', "file:///*/*/kozea/pygal/*"]) + print "Ok (%dms)" % (1000 * (time.time() - t_start)) + +if '-t' in sys.argv: + import pytest + pytest.main('') diff --git a/pygal/config.py b/pygal/config.py index ba130dc..b2ff60f 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -104,6 +104,10 @@ class Config(object): disable_xml_declaration = False #: Write width and height attributes explicit_size = False + #: Legend string length truncation threshold + truncate_legend = 15 + #: Label string length truncation threshold (None = auto) + truncate_label = None def __init__(self, **kwargs): """Can be instanciated with config kwargs""" diff --git a/pygal/graph/base.py b/pygal/graph/base.py index 02313fb..f7e3619 100644 --- a/pygal/graph/base.py +++ b/pygal/graph/base.py @@ -23,9 +23,10 @@ Base for pygal charts from __future__ import division import io -from pygal.serie import Serie, Value, PositiveValue +from pygal.serie import Serie, Value from pygal.view import Margin, Box -from pygal.util import get_text_box, get_texts_box, cut, rad, humanize +from pygal.util import ( + get_text_box, get_texts_box, cut, rad, humanize, truncate) from pygal.svg import Svg from pygal.config import Config from pygal.util import cached_property @@ -85,7 +86,9 @@ class BaseGraph(object): """Compute graph margins from set texts""" if self.show_legend: h, w = get_texts_box( - cut(self.series, 'title'), self.legend_font_size) + map(lambda x: truncate(x, self.truncate_legend), + cut(self.series, 'title')), + self.legend_font_size) self.margin.right += 10 + w + self.legend_box_size if self.title: diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index 3b07461..859fb20 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -25,7 +25,7 @@ from __future__ import division from pygal.interpolate import interpolation from pygal.graph.base import BaseGraph from pygal.view import View, LogView -from pygal.util import is_major +from pygal.util import is_major, truncate, reverse_text_len from math import isnan, pi @@ -104,6 +104,18 @@ class Graph(BaseGraph): return axis = self.svg.node(self.nodes['plot'], class_="axis x") + truncation = self.truncate_label + if not truncation: + if self.x_label_rotation: + truncation = 25 + else: + first_label_position = self.view.x(self._x_labels[0][1]) + last_label_position = self.view.x(self._x_labels[-1][1]) + available_space = ( + last_label_position - first_label_position) / ( + len(self._x_labels) - 1) + truncation = int( + reverse_text_len(available_space, self.label_font_size)) if 0 not in [label[1] for label in self._x_labels] and draw_axes: self.svg.node(axis, 'path', @@ -123,7 +135,9 @@ class Graph(BaseGraph): x=x, y=y + .5 * self.label_font_size + 5 ) - text.text = label + 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) @@ -183,7 +197,8 @@ class Graph(BaseGraph): width=self.legend_box_size, height=self.legend_box_size, class_="color-%d reactive" % i - ).text = title + ) + truncated = truncate(title, self.truncate_legend) # Serious magical numbers here self.svg.node( legend, 'text', @@ -191,7 +206,9 @@ class Graph(BaseGraph): y=1.5 * i * self.legend_box_size + .5 * self.legend_box_size + .3 * self.legend_font_size - ).text = title + ).text = truncated + if truncated != title: + self.svg.node(legend, 'title').text = title def _title(self): """Make the title""" @@ -199,7 +216,8 @@ class Graph(BaseGraph): self.svg.node(self.nodes['graph'], 'text', class_='title', x=self.margin.left + self.view.width / 2, y=self.title_font_size + 10 - ).text = self.title + ).text = truncate(self.title, int(reverse_text_len( + self.width, self.title_font_size))) def _serie(self, serie): """Make serie node""" diff --git a/pygal/test/test_util.py b/pygal/test/test_util.py index 8b8dbb1..5ffb282 100644 --- a/pygal/test/test_util.py +++ b/pygal/test/test_util.py @@ -18,7 +18,7 @@ # along with pygal. If not, see . from pygal.util import ( round_to_int, round_to_float, _swap_curly, template, humanize, - is_major) + is_major, truncate) from pytest import raises @@ -111,3 +111,13 @@ def test_is_major(): assert is_major(n) for n in (2, 10002., 100000.0003, -200, -0.0005): assert not is_major(n) + + +def test_truncate(): + assert truncate('1234567890', 50) == '1234567890' + assert truncate('1234567890', 5) == u'1234…' + assert truncate('1234567890', 1) == u'…' + assert truncate('1234567890', 9) == u'12345678…' + assert truncate('1234567890', 10) == '1234567890' + assert truncate('1234567890', 0) == '1234567890' + assert truncate('1234567890', -1) == '1234567890' diff --git a/pygal/util.py b/pygal/util.py index 3924cbc..0581df4 100644 --- a/pygal/util.py +++ b/pygal/util.py @@ -175,9 +175,14 @@ def compute_scale(min_, max_, logarithmic=False, min_scale=4, max_scale=20): return positions -def text_len(lenght, fs): +def text_len(length, fs): + """Approximation of text width""" + return length * 0.6 * fs + + +def reverse_text_len(width, fs): """Approximation of text length""" - return lenght * 0.6 * fs + return width / (0.6 * fs) def get_text_box(text, fs): @@ -217,6 +222,13 @@ def cycle_fill(short_list, max_len): return short_list +def truncate(string, index): + """Truncate a string at index and add ...""" + if len(string) > index and index > 0: + string = string[:index - 1] + u'…' + return string + + # Stolen from brownie http://packages.python.org/Brownie/ class cached_property(object): """Optimize a static property"""