Browse Source

Truncate long texts + config option

pull/8/head
Florian Mounier 13 years ago
parent
commit
f285ac2809
  1. 21
      demo/simple_test.py
  2. 4
      pygal/config.py
  3. 9
      pygal/graph/base.py
  4. 28
      pygal/graph/graph.py
  5. 12
      pygal/test/test_util.py
  6. 16
      pygal/util.py

21
demo/simple_test.py

@ -18,10 +18,12 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>. # along with pygal. If not, see <http://www.gnu.org/licenses/>.
import time import time
import sys
from math import cos, sin
from subprocess import call
from pygal import * from pygal import *
from pygal.style 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} lnk = lambda v, l=None: {'value': v, 'xlink': 'javascript:alert("Test %s")' % v, 'label': l}
t_start = time.time() t_start = time.time()
@ -151,12 +153,13 @@ line.interpolation_precision = 200
line.render_to_file('out-line.svg') line.render_to_file('out-line.svg')
stackedline = StackedLine(fill=True) 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('test2', [4, 1, 1, 3, 12, 3])
stackedline.add('test3', [9, 3, 2, lnk(10, '!'), 8, 2]) stackedline.add('test3', [9, 3, 2, lnk(10, '!'), 8, 2])
stackedline.x_labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] stackedline.x_labels = ['aaisaruistarsitauritaria', 'bsruie trstie rusiet ruetsru', 'cuasitaruisrnauciuestuirue uiretsu iersut irasui', 'd', 'e', 'f', 'g']
stackedline.title = "Stackedline test" stackedline.title = "Stackedline ine uisernuis enuir esnue sunres nuise nuires uinsre auin ruist arusit arst ierustie ruiset uriest uirest test"
# stackedline.interpolate = "cubic" # stackedline.interpolate = "cubic"xb
stackedline.render_to_file('out-stackedline.svg') stackedline.render_to_file('out-stackedline.svg')
xy = XY(Config(fill=True, style=NeonStyle, interpolate='cubic')) xy = XY(Config(fill=True, style=NeonStyle, interpolate='cubic'))
@ -201,4 +204,10 @@ radar.title = "Radar test"
radar.render_to_file('out-radar.svg') radar.render_to_file('out-radar.svg')
call(['wsreload', '--url', "file:///*/*/kozea/pygal/*"])
print "Ok (%dms)" % (1000 * (time.time() - t_start)) print "Ok (%dms)" % (1000 * (time.time() - t_start))
if '-t' in sys.argv:
import pytest
pytest.main('')

4
pygal/config.py

@ -104,6 +104,10 @@ class Config(object):
disable_xml_declaration = False disable_xml_declaration = False
#: Write width and height attributes #: Write width and height attributes
explicit_size = False 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): def __init__(self, **kwargs):
"""Can be instanciated with config kwargs""" """Can be instanciated with config kwargs"""

9
pygal/graph/base.py

@ -23,9 +23,10 @@ Base for pygal charts
from __future__ import division from __future__ import division
import io import io
from pygal.serie import Serie, Value, PositiveValue from pygal.serie import Serie, Value
from pygal.view import Margin, Box 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.svg import Svg
from pygal.config import Config from pygal.config import Config
from pygal.util import cached_property from pygal.util import cached_property
@ -85,7 +86,9 @@ class BaseGraph(object):
"""Compute graph margins from set texts""" """Compute graph margins from set texts"""
if self.show_legend: if self.show_legend:
h, w = get_texts_box( 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 self.margin.right += 10 + w + self.legend_box_size
if self.title: if self.title:

28
pygal/graph/graph.py

@ -25,7 +25,7 @@ from __future__ import division
from pygal.interpolate import interpolation from pygal.interpolate import interpolation
from pygal.graph.base import BaseGraph from pygal.graph.base import BaseGraph
from pygal.view import View, LogView 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 from math import isnan, pi
@ -104,6 +104,18 @@ class Graph(BaseGraph):
return return
axis = self.svg.node(self.nodes['plot'], class_="axis x") 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: if 0 not in [label[1] for label in self._x_labels] and draw_axes:
self.svg.node(axis, 'path', self.svg.node(axis, 'path',
@ -123,7 +135,9 @@ class Graph(BaseGraph):
x=x, x=x,
y=y + .5 * self.label_font_size + 5 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: 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)
@ -183,7 +197,8 @@ class Graph(BaseGraph):
width=self.legend_box_size, width=self.legend_box_size,
height=self.legend_box_size, height=self.legend_box_size,
class_="color-%d reactive" % i class_="color-%d reactive" % i
).text = title )
truncated = truncate(title, self.truncate_legend)
# Serious magical numbers here # Serious magical numbers here
self.svg.node( self.svg.node(
legend, 'text', legend, 'text',
@ -191,7 +206,9 @@ class Graph(BaseGraph):
y=1.5 * i * self.legend_box_size y=1.5 * i * self.legend_box_size
+ .5 * self.legend_box_size + .5 * self.legend_box_size
+ .3 * self.legend_font_size + .3 * self.legend_font_size
).text = title ).text = truncated
if truncated != title:
self.svg.node(legend, 'title').text = title
def _title(self): def _title(self):
"""Make the title""" """Make the title"""
@ -199,7 +216,8 @@ class Graph(BaseGraph):
self.svg.node(self.nodes['graph'], 'text', class_='title', self.svg.node(self.nodes['graph'], 'text', class_='title',
x=self.margin.left + self.view.width / 2, x=self.margin.left + self.view.width / 2,
y=self.title_font_size + 10 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): def _serie(self, serie):
"""Make serie node""" """Make serie node"""

12
pygal/test/test_util.py

@ -18,7 +18,7 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>. # along with pygal. If not, see <http://www.gnu.org/licenses/>.
from pygal.util import ( from pygal.util import (
round_to_int, round_to_float, _swap_curly, template, humanize, round_to_int, round_to_float, _swap_curly, template, humanize,
is_major) is_major, truncate)
from pytest import raises from pytest import raises
@ -111,3 +111,13 @@ def test_is_major():
assert is_major(n) assert is_major(n)
for n in (2, 10002., 100000.0003, -200, -0.0005): for n in (2, 10002., 100000.0003, -200, -0.0005):
assert not is_major(n) 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'

16
pygal/util.py

@ -175,9 +175,14 @@ def compute_scale(min_, max_, logarithmic=False, min_scale=4, max_scale=20):
return positions 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""" """Approximation of text length"""
return lenght * 0.6 * fs return width / (0.6 * fs)
def get_text_box(text, fs): def get_text_box(text, fs):
@ -217,6 +222,13 @@ def cycle_fill(short_list, max_len):
return short_list 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/ # Stolen from brownie http://packages.python.org/Brownie/
class cached_property(object): class cached_property(object):
"""Optimize a static property""" """Optimize a static property"""

Loading…
Cancel
Save