Browse Source

Fix margin and bottom legend spacing + pretty_print off by default to avoid extra space in tooltip in firefox

pull/8/head
Florian Mounier 13 years ago
parent
commit
48bcf5cb1e
  1. 13
      demo/simple_test.py
  2. 2
      pygal/__init__.py
  3. 2
      pygal/config.py
  4. 7
      pygal/graph/base.py
  5. 15
      pygal/graph/graph.py
  6. 15
      pygal/svg.py
  7. 23
      pygal/test/test_util.py
  8. 30
      pygal/util.py

13
demo/simple_test.py

@ -178,18 +178,19 @@ pie = Pie(Config(style=NeonStyle))
# pie.add('test4', [20, lnk(18), 9]) # pie.add('test4', [20, lnk(18), 9])
# pie.add('test5', [17, 5, 10]) # pie.add('test5', [17, 5, 10])
# pie.add('test6', [None, None, 10]) # pie.add('test6', [None, None, 10])
for i in range(30): for i in range(10):
pie.add(str(i) + '!' * i, [i, 30 - i]) pie.add('P' + str(i) + '!' * i, [i, 30 - i])
# pie.included_js = [] # pie.js = [
# pie.external_js = [ # 'http://localhost:9898/svg.jquery.js',
# 'http://localhost:7575/svg.jquery.js', # 'http://localhost:9898/pygal-tooltips.js',
# 'http://localhost:7575/pygal.js',
# ] # ]
# pie.add('test', {'value': 11, 'xlink': 'javascript:alert("lol 11")'}) # pie.add('test', {'value': 11, 'xlink': 'javascript:alert("lol 11")'})
# pie.add('test2', 1) # pie.add('test2', 1)
# pie.add('test3', 5) # pie.add('test3', 5)
# pie.title = "Pie test" # pie.title = "Pie test"
pie.legend_at_bottom = True
pie.legend_box_size = 100
pie.render_to_file('out-pie.svg') pie.render_to_file('out-pie.svg')
config = Config() config = Config()

2
pygal/__init__.py

@ -21,7 +21,7 @@ Pygal - A python svg graph plotting library
""" """
__version__ = '0.10.0' __version__ = '0.10.1'
from pygal.config import Config from pygal.config import Config
from pygal.graph.bar import Bar from pygal.graph.bar import Bar

2
pygal/config.py

@ -110,6 +110,8 @@ class Config(object):
truncate_legend = None truncate_legend = None
#: Label string length truncation threshold (None = auto) #: Label string length truncation threshold (None = auto)
truncate_label = None truncate_label = None
#: Pretty print the svg
pretty_print = False
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Can be instanciated with config kwargs""" """Can be instanciated with config kwargs"""

7
pygal/graph/base.py

@ -90,7 +90,9 @@ class BaseGraph(object):
cut(self.series, 'title')), cut(self.series, 'title')),
self.legend_font_size) self.legend_font_size)
if self.legend_at_bottom: if self.legend_at_bottom:
self.margin.bottom += 10 + h * int(sqrt(len(self.series))) h_max = max(h, self.legend_box_size)
self.margin.bottom += 10 + h_max * round(
sqrt(len(self.series)) - 1) * 1.5 + h_max
else: else:
self.margin.right += 10 + w + self.legend_box_size self.margin.right += 10 + w + self.legend_box_size
@ -188,7 +190,8 @@ class BaseGraph(object):
def render(self, is_unicode=False): def render(self, is_unicode=False):
"""Render the graph, and return the svg string""" """Render the graph, and return the svg string"""
self._render() self._render()
return self.svg.render(is_unicode=is_unicode) return self.svg.render(
is_unicode=is_unicode, pretty_print=self.pretty_print)
def render_tree(self): def render_tree(self):
"""Render the graph, and return lxml tree""" """Render the graph, and return lxml tree"""

15
pygal/graph/graph.py

@ -187,8 +187,9 @@ class Graph(BaseGraph):
y = (self.margin.top + self.view.height + y = (self.margin.top + self.view.height +
self._x_labels_height + 10) self._x_labels_height + 10)
cols = ceil(sqrt(len(self.series))) cols = ceil(sqrt(len(self.series)))
if not truncation: if not truncation:
available_space = self.width / cols - ( available_space = self.view.width / cols - (
self.legend_box_size + 5) self.legend_box_size + 5)
truncation = int(reverse_text_len( truncation = int(reverse_text_len(
available_space, self.legend_font_size)) available_space, self.legend_font_size))
@ -203,7 +204,8 @@ class Graph(BaseGraph):
self.nodes['graph'], class_='legends', self.nodes['graph'], class_='legends',
transform='translate(%d, %d)' % (x, y)) transform='translate(%d, %d)' % (x, y))
x_step = self.width / cols h = max(self.legend_box_size, self.legend_font_size)
x_step = self.view.width / cols
for i, title in enumerate(self._legends): for i, title in enumerate(self._legends):
col = i % cols col = i % cols
row = i // cols row = i // cols
@ -214,7 +216,10 @@ class Graph(BaseGraph):
self.svg.node( self.svg.node(
legend, 'rect', legend, 'rect',
x=col * x_step, x=col * x_step,
y=1.5 * row * self.legend_box_size, y=1.5 * row * h + (
self.legend_font_size - self.legend_box_size
if self.legend_font_size > self.legend_box_size else 0
) / 2,
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 % 16) class_="color-%d reactive" % (i % 16)
@ -224,8 +229,8 @@ class Graph(BaseGraph):
self.svg.node( self.svg.node(
legend, 'text', legend, 'text',
x=col * x_step + self.legend_box_size + 5, x=col * x_step + self.legend_box_size + 5,
y=1.5 * row * self.legend_box_size y=1.5 * row * h
+ .5 * self.legend_box_size + .5 * h
+ .3 * self.legend_font_size + .3 * self.legend_font_size
).text = truncated ).text = truncated
if truncated != title: if truncated != title:

15
pygal/svg.py

@ -28,7 +28,7 @@ import json
from lxml import etree from lxml import etree
from math import cos, sin, pi from math import cos, sin, pi
from urlparse import urlparse from urlparse import urlparse
from pygal.util import template, coord_format from pygal.util import template, coord_format, minify_css
from pygal import __version__ from pygal import __version__
@ -68,12 +68,14 @@ class Svg(object):
css = os.path.join( css = os.path.join(
os.path.dirname(__file__), 'css', css) os.path.dirname(__file__), 'css', css)
with io.open(css, encoding='utf-8') as f: with io.open(css, encoding='utf-8') as f:
templ = template( css_text = template(
f.read(), f.read(),
style=self.graph.style, style=self.graph.style,
font_sizes=self.graph.font_sizes()) font_sizes=self.graph.font_sizes())
if not self.graph.pretty_print:
css_text = minify_css(css_text)
self.node( self.node(
self.defs, 'style', type='text/css').text = templ self.defs, 'style', type='text/css').text = css_text
def add_scripts(self): def add_scripts(self):
"""Add the js to the svg""" """Add the js to the svg"""
@ -184,15 +186,16 @@ class Svg(object):
class_='no_data') class_='no_data')
no_data.text = self.graph.no_data_text no_data.text = self.graph.no_data_text
def render(self, is_unicode=False): def render(self, is_unicode=False, pretty_print=False):
"""Last thing to do before rendering""" """Last thing to do before rendering"""
svg = etree.tostring( svg = etree.tostring(
self.root, pretty_print=True, self.root, pretty_print=pretty_print,
xml_declaration=False, xml_declaration=False,
encoding='utf-8') encoding='utf-8')
if not self.graph.disable_xml_declaration: if not self.graph.disable_xml_declaration:
svg = b'\n'.join( svg = b'\n'.join(
[etree.tostring(pi, encoding='utf-8', pretty_print=True) [etree.tostring(
pi, encoding='utf-8', pretty_print=pretty_print)
for pi in self.processing_instructions] for pi in self.processing_instructions]
) + b'\n' + svg ) + b'\n' + svg
if self.graph.disable_xml_declaration or is_unicode: if self.graph.disable_xml_declaration or is_unicode:

23
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, truncate) is_major, truncate, minify_css)
from pytest import raises from pytest import raises
@ -121,3 +121,24 @@ def test_truncate():
assert truncate('1234567890', 10) == '1234567890' assert truncate('1234567890', 10) == '1234567890'
assert truncate('1234567890', 0) == '1234567890' assert truncate('1234567890', 0) == '1234567890'
assert truncate('1234567890', -1) == '1234567890' assert truncate('1234567890', -1) == '1234567890'
def test_minify_css():
css = '''
/*
* Font-sizes from config, override with care
*/
.title {
font-family: sans;
font-size: 12 ;
}
.legends .legend text {
font-family: monospace;
font-size: 14 ;}
'''
assert minify_css(css) == (
'.title{font-family:sans;font-size:12}'
'.legends .legend text{font-family:monospace;font-size:14}')

30
pygal/util.py

@ -25,6 +25,7 @@ from __future__ import division
from decimal import Decimal from decimal import Decimal
from math import floor, pi, log, log10, ceil from math import floor, pi, log, log10, ceil
from itertools import cycle from itertools import cycle
import re
ORDERS = u"yzafpnµm kMGTPEZY" ORDERS = u"yzafpnµm kMGTPEZY"
@ -243,3 +244,32 @@ class cached_property(object):
return self return self
value = obj.__dict__[self.__name__] = self.getter(obj) value = obj.__dict__[self.__name__] = self.getter(obj)
return value return value
css_comments = re.compile(r'/\*.*?\*/', re.MULTILINE | re.DOTALL)
def minify_css(css):
# Inspired by slimmer by Peter Bengtsson
remove_next_comment = 1
for css_comment in css_comments.findall(css):
if css_comment[-3:] == '\*/':
remove_next_comment = 0
continue
if remove_next_comment:
css = css.replace(css_comment, '')
else:
remove_next_comment = 1
# >= 2 whitespace becomes one whitespace
css = re.sub(r'\s\s+', ' ', css)
# no whitespace before end of line
css = re.sub(r'\s+\n', '', css)
# Remove space before and after certain chars
for char in ('{', '}', ':', ';', ','):
css = re.sub(char + r'\s', char, css)
css = re.sub(r'\s' + char, char, css)
css = re.sub(r'}\s(#|\w)', r'}\1', css)
# no need for the ; before end of attributes
css = re.sub(r';}', r'}', css)
css = re.sub(r'}//-->', r'}\n//-->', css)
return css.strip()

Loading…
Cancel
Save