Browse Source

Move ``font_size`` config to style. Add ``font_family`` for various elements in style. Add ``googlefont:font`` support for style fonts.

pull/242/head
Florian Mounier 10 years ago
parent
commit
adc03ef2fb
  1. 26
      demo/moulinrouge/tests.py
  2. 3
      docs/changelog.rst
  3. 5
      pygal/_compat.py
  4. 18
      pygal/config.py
  5. 32
      pygal/css/base.css
  6. 8
      pygal/css/graph.css
  7. 20
      pygal/css/style.css
  8. 54
      pygal/graph/graph.py
  9. 4
      pygal/graph/line.py
  10. 1
      pygal/graph/public.py
  11. 33
      pygal/style.py
  12. 31
      pygal/svg.py

26
demo/moulinrouge/tests.py

@ -48,14 +48,19 @@ def get_test_routes(app):
@app.route('/test/bar_links')
def test_bar_links():
bar = Bar(style=styles['neon'])
bar = Bar(style=styles['default'](
font_family='googlefont:Raleway'))
bar.js = ('http://l:2343/2.0.x/pygal-tooltips.js',)
bar.add('1234', [
bar.title = 'Wow ! Such Chart !'
bar.x_title = 'Many x labels'
bar.y_title = 'Much y labels'
bar.add('Red serie', [
{'value': 10,
'label': 'Ten',
'xlink': 'http://google.com?q=10'},
{'value': 20,
'tooltip': 'Twenty',
{'value': 25,
'label': 'Twenty is a good number yada yda yda yada yada',
'xlink': 'http://google.com?q=20'},
30,
{'value': 40,
@ -63,14 +68,15 @@ def get_test_routes(app):
'xlink': 'http://google.com?q=40'}
])
bar.add('4321', [40, {
bar.add('Blue serie', [40, {
'value': 30,
'label': 'Thirty',
'xlink': 'http://google.com?q=30'
}, 20, 10])
bar.x_labels = map(str, range(1, 5))
bar.x_labels = ['Yesterday', 'Today or any other day',
'Tomorrow', 'Someday']
bar.logarithmic = True
bar.zero = 1
# bar.zero = 1
return bar.render_response()
@app.route('/test/xy_links')
@ -395,8 +401,8 @@ def get_test_routes(app):
js = ['http://l:2343/2.0.x/pygal-tooltips.js']
stacked = StackedBar(LolConfig())
stacked.add('1', [1, 2, 3])
stacked.add('2', [4, 5, 6])
stacked.add('', [1, 2, 3])
stacked.add('My beautiful serie of 2019', [4, 5, 6])
return stacked.render_response()
@app.route('/test/dateline')
@ -628,7 +634,7 @@ def get_test_routes(app):
@app.route('/test/half_pie')
def test_half_pie():
pie = Pie(half_pie=True)
for i in range(100):
for i in range(20):
pie.add(str(i), i, inner_radius=.1)
pie.legend_at_bottom = True
pie.legend_at_bottom_columns = 4

3
docs/changelog.rst

@ -31,6 +31,9 @@ Changelog
* Rename in Style foreground_light as foreground_strong
* Rename in Style foreground_dark as foreground_subtle
* Add a ``render_data_uri`` method (#237)
* Move ``font_size`` config to style
* Add ``font_family`` for various elements in style
* Add ``googlefont:font`` support for style fonts
1.7.0
=====

5
pygal/_compat.py

@ -80,3 +80,8 @@ def timestamp(x):
return x.timestamp()
else:
return time.mktime(x.utctimetuple())
try:
from urllib import quote_plus
except ImportError:
from urllib.parse import quote_plus

18
pygal/config.py

@ -301,6 +301,10 @@ class Config(CommonConfig):
tooltip_border_radius = Key(0, int, "Look", "Tooltip border radius")
tooltip_fancy_mode = Key(
True, bool, "Look", "Fancy tooltips",
"Print legend, x label in tooltip and use serie color for value.")
inner_radius = Key(
0, float, "Look", "Piechart inner radius (donut), must be <.9")
@ -441,20 +445,6 @@ class Config(CommonConfig):
no_data_text = Key(
"No data", str, "Text", "Text to display when no data is given")
label_font_size = Key(10, int, "Text", "Label font size")
major_label_font_size = Key(10, int, "Text", "Major label font size")
value_font_size = Key(8, int, "Text", "Value font size")
tooltip_font_size = Key(16, int, "Text", "Tooltip font size")
title_font_size = Key(16, int, "Text", "Title font size")
legend_font_size = Key(14, int, "Text", "Legend font size")
no_data_font_size = Key(64, int, "Text", "No data text font size")
print_values = Key(
False, bool,
"Text", "Print values when graph is in non interactive mode")

32
pygal/css/base.css

@ -22,41 +22,43 @@
* Font-sizes from config, override with care
*/
{{ id }}.graph {
{{ id }} {
-webkit-user-select: none;
-webkit-font-smoothing: antialiased;
font-family: {{ style.font_family }};
}
{{ id }}.title {
font-family: {{ style.font_family }};
font-size: {{ font_sizes.title }};
font-family: {{ style.title_font_family }};
font-size: {{ style.title_font_size }}px;
}
{{ id }}.legends .legend text {
font-family: {{ style.font_family }};
font-size: {{ font_sizes.legend }};
font-family: {{ style.legend_font_family }};
font-size: {{ style.legend_font_size }}px;
}
{{ id }}.axis text {
font-family: {{ style.font_family }};
font-size: {{ font_sizes.label }};
font-family: {{ style.label_font_family }};
font-size: {{ style.label_font_size }}px;
}
{{ id }}.axis text.major {
font-family: {{ style.font_family }};
font-size: {{ font_sizes.major_label }};
font-family: {{ style.major_label_font_family }};
font-size: {{ style.major_label_font_size }}px;
}
{{ id }}.series text {
font-family: {{ style.font_family }};
font-size: {{ font_sizes.value }};
font-family: {{ style.value_font_family }};
font-size: {{ style.value_font_size }}px;
}
{{ id }}.tooltip text {
font-family: {{ style.font_family }};
font-size: {{ font_sizes.tooltip }};
{{ id }}.tooltip {
font-family: {{ style.tooltip_font_family }};
font-size: {{ style.tooltip_font_size }}px;
}
{{ id }}text.no_data {
font-size: {{ font_sizes.no_data }};
font-family: {{ style.no_data_font_family }};
font-size: {{ style.no_data_font_size }}px;
}

8
pygal/css/graph.css

@ -103,8 +103,8 @@
fill: transparent;
}
{{ id }} text {
stroke: none;
{{ id }} text, {{ id }} tspan {
stroke: none !important;
}
{{ id }}.series text.active {
@ -118,7 +118,3 @@
{{ id }}.tooltip text {
fill-opacity: 1;
}
{{ id }}.tooltip text tspan.label {
fill-opacity: .8;
}

20
pygal/css/style.css

@ -112,10 +112,28 @@
transition: opacity {{ style.transition }};
}
{{ id }}.tooltip text {
{{ id }}.tooltip .label {
fill: {{ style.foreground }};
}
{{ id }}.tooltip .label {
fill: {{ style.foreground }};
}
{{ id }}.tooltip .legend {
font-size: .8em;
fill: {{ style.foreground_subtle }};
}
{{ id }}.tooltip .x_label {
font-size: .6em;
fill: {{ style.foreground_strong }};
}
{{ id }}.tooltip .value {
font-size: 2em;
}
{{ id }}.map-element {
fill: {{ style.foreground }};
stroke: {{ style.foreground_subtle }} !important;

54
pygal/graph/graph.py

@ -108,15 +108,12 @@ class Graph(PublicApi):
style="opacity: 0",
**{'class': 'tooltip'})
a = self.svg.node(self.nodes['tooltip'], 'a')
self.svg.node(a, 'rect',
self.svg.node(self.nodes['tooltip'], 'rect',
rx=self.tooltip_border_radius,
ry=self.tooltip_border_radius,
width=0, height=0,
**{'class': 'tooltip-box'})
text = self.svg.node(a, 'text', class_='text')
self.svg.node(text, 'tspan', class_='label')
self.svg.node(text, 'tspan', class_='value')
self.svg.node(self.nodes['tooltip'], 'g', class_='text')
def _x_axis(self):
"""Make the x axis: labels and guides"""
@ -136,7 +133,7 @@ class Graph(PublicApi):
last_label_position - first_label_position) / (
len(self._x_labels) - 1)
truncation = reverse_text_len(
available_space, self.label_font_size)
available_space, self.style.label_font_size)
truncation = max(truncation, 1)
if 0 not in [label[1] for label in self._x_labels]:
@ -160,7 +157,7 @@ class Graph(PublicApi):
'axis ' if label == "0" else '',
'major ' if major else '',
'guide ' if position != 0 and not last_guide else ''))
y += .5 * self.label_font_size + 5
y += .5 * self.style.label_font_size + 5
text = self.svg.node(
guides, 'text',
x=x,
@ -241,7 +238,7 @@ class Graph(PublicApi):
text = self.svg.node(
guides, 'text',
x=x,
y=y + .35 * self.label_font_size,
y=y + .35 * self.style.label_font_size,
class_='major' if major else ''
)
@ -267,7 +264,7 @@ class Graph(PublicApi):
text = self.svg.node(
guides, 'text',
x=x,
y=y + .35 * self.label_font_size,
y=y + .35 * self.style.label_font_size,
class_='major' if major else ''
)
text.text = label
@ -292,7 +289,7 @@ class Graph(PublicApi):
available_space = self.view.width / cols - (
self.legend_box_size + 5)
truncation = reverse_text_len(
available_space, self.legend_font_size)
available_space, self.style.legend_font_size)
else:
x = self.spacing
y = self.margin_box.top + self.spacing
@ -304,7 +301,7 @@ class Graph(PublicApi):
self.nodes['graph'], class_='legends',
transform='translate(%d, %d)' % (x, y))
h = max(self.legend_box_size, self.legend_font_size)
h = max(self.legend_box_size, self.style.legend_font_size)
x_step = self.view.width / cols
if self.legend_at_bottom:
# if legends at the bottom, we dont split the windows
@ -326,7 +323,7 @@ class Graph(PublicApi):
x = self.margin_box.left + self.view.width + self.spacing
if self._y_2nd_labels:
h, w = get_texts_box(
cut(self._y_2nd_labels), self.label_font_size)
cut(self._y_2nd_labels), self.style.label_font_size)
x += self.spacing + max(w * cos(rad(self.y_label_rotation)), h)
y = self.margin_box.top + self.spacing
@ -348,8 +345,8 @@ class Graph(PublicApi):
legend, 'rect',
x=col * x_step,
y=1.5 * row * h + (
self.legend_font_size - self.legend_box_size
if self.legend_font_size > self.legend_box_size else 0
self.style.legend_font_size - self.legend_box_size
if self.style.legend_font_size > self.legend_box_size else 0
) / 2,
width=self.legend_box_size,
height=self.legend_box_size,
@ -367,7 +364,7 @@ class Graph(PublicApi):
self.svg.node(
node, 'text',
x=col * x_step + self.legend_box_size + 5,
y=1.5 * row * h + .5 * h + .3 * self.legend_font_size
y=1.5 * row * h + .5 * h + .3 * self.style.legend_font_size
).text = truncated
if truncated != title:
@ -380,7 +377,7 @@ class Graph(PublicApi):
self.svg.node(
self.nodes['title'], 'text', class_='title plot_title',
x=self.width / 2,
y=i * (self.title_font_size + self.spacing)
y=i * (self.style.title_font_size + self.spacing)
).text = title_line
def _make_x_title(self):
@ -392,7 +389,7 @@ class Graph(PublicApi):
text = self.svg.node(
self.nodes['title'], 'text', class_='title',
x=self.margin_box.left + self.view.width / 2,
y=y + i * (self.title_font_size + self.spacing)
y=y + i * (self.style.title_font_size + self.spacing)
)
text.text = title_line
@ -404,7 +401,7 @@ class Graph(PublicApi):
text = self.svg.node(
self.nodes['title'], 'text', class_='title',
x=self._legend_at_left_width,
y=i * (self.title_font_size + self.spacing) + yc
y=i * (self.style.title_font_size + self.spacing) + yc
)
text.attrib['transform'] = "rotate(%d %f %f)" % (
-90, self._legend_at_left_width, yc)
@ -455,7 +452,7 @@ class Graph(PublicApi):
serie_node['text_overlay'], 'text',
class_='centered',
x=x,
y=y + self.value_font_size / 3
y=y + self.style.value_font_size / 3
).text = value if self.print_zeroes or value != '0' else ''
def _get_value(self, values, i):
@ -530,7 +527,7 @@ class Graph(PublicApi):
h, w = get_texts_box(
map(lambda x: truncate(x, self.truncate_legend or 15),
cut(series_group, 'title')),
self.legend_font_size)
self.style.legend_font_size)
if self.legend_at_bottom:
h_max = max(h, self.legend_box_size)
cols = (self._order // self.legend_at_bottom_columns
@ -554,7 +551,7 @@ class Graph(PublicApi):
h, w = get_texts_box(
map(lambda x: truncate(x, self.truncate_label or 25),
cut(xlabels)),
self.label_font_size)
self.style.label_font_size)
self._x_labels_height = self.spacing + max(
w * sin(rad(self.x_label_rotation)), h)
if xlabels is self._x_labels:
@ -570,7 +567,7 @@ class Graph(PublicApi):
for ylabels in (self._y_labels, self._y_2nd_labels):
if ylabels:
h, w = get_texts_box(
cut(ylabels), self.label_font_size)
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)
@ -579,29 +576,30 @@ class Graph(PublicApi):
w * cos(rad(self.y_label_rotation)), h)
self._title = split_title(
self.title, self.width, self.title_font_size)
self.title, self.width, self.style.title_font_size)
if self.title:
h, _ = get_text_box(self._title[0], self.title_font_size)
h, _ = get_text_box(self._title[0], self.style.title_font_size)
self.margin_box.top += len(self._title) * (self.spacing + h)
self._x_title = split_title(
self.x_title, self.width - self.margin_box.x, self.title_font_size)
self.x_title, self.width - self.margin_box.x,
self.style.title_font_size)
self._x_title_height = 0
if self._x_title:
h, _ = get_text_box(self._x_title[0], self.title_font_size)
h, _ = get_text_box(self._x_title[0], self.style.title_font_size)
height = len(self._x_title) * (self.spacing + h)
self.margin_box.bottom += height
self._x_title_height = height + self.spacing
self._y_title = split_title(
self.y_title, self.height - self.margin_box.y,
self.title_font_size)
self.style.title_font_size)
self._y_title_height = 0
if self._y_title:
h, _ = get_text_box(self._y_title[0], self.title_font_size)
h, _ = get_text_box(self._y_title[0], self.style.title_font_size)
height = len(self._y_title) * (self.spacing + h)
self.margin_box.left += height
self._y_title_height = height + self.spacing

4
pygal/graph/line.py

@ -122,8 +122,8 @@ class Line(Graph):
dots, val, x, y)
self._static_value(
serie_node, val,
x + self.value_font_size,
y + self.value_font_size)
x + self.style.value_font_size,
y + self.style.value_font_size)
if serie.stroke:
if self.interpolate:

1
pygal/graph/public.py

@ -80,6 +80,7 @@ class PublicApi(BaseGraph):
from lxml.html import open_in_browser
except ImportError:
raise ImportError('You must install lxml to use render in browser')
kwargs.setdefault('force_uri_protocol', 'https')
open_in_browser(self.render_tree(**kwargs), encoding='utf-8')
def render_response(self, **kwargs):

33
pygal/style.py

@ -36,6 +36,24 @@ class Style(object):
# Monospaced font is highly encouraged
font_family = 'Consolas, "Liberation Mono", Menlo, Courier, '
'monospace'
label_font_family = None
major_label_font_family = None
value_font_family = None
tooltip_font_family = None
title_font_family = None
legend_font_family = None
no_data_font_family = None
label_font_size = 10
major_label_font_size = 10
value_font_size = 8
tooltip_font_size = 16
title_font_size = 16
legend_font_size = 14
no_data_font_size = 64
opacity = '.7'
opacity_hover = '.8'
transition = '150ms'
@ -64,6 +82,19 @@ class Style(object):
def __init__(self, **kwargs):
"""Create the style"""
self.__dict__.update(kwargs)
self._google_fonts = set()
if self.font_family.startswith('googlefont:'):
self.font_family = self.font_family.replace('googlefont:', '')
self._google_fonts.add(self.font_family.split(',')[0].strip())
for name in dir(self):
if name.endswith('_font_family'):
fn = getattr(self, name)
if fn is None:
setattr(self, name, self.font_family)
elif fn.startswith('googlefont:'):
setattr(self, name, fn.replace('googlefont:', ''))
self._google_fonts.add(getattr(self, name).split(',')[0].strip())
def get_colors(self, prefix, len_):
"""Get the css color list"""
@ -96,7 +127,7 @@ class Style(object):
"""Convert instance to a serializable mapping."""
config = {}
for attr in dir(self):
if not attr.startswith('__'):
if not attr.startswith('_'):
value = getattr(self, attr)
if not hasattr(value, '__call__'):
config[attr] = value

31
pygal/svg.py

@ -20,7 +20,7 @@
"""Svg helper"""
from __future__ import division
from pygal._compat import to_str, u
from pygal._compat import to_str, u, quote_plus
from pygal.etree import etree
import io
import os
@ -85,7 +85,15 @@ class Svg(object):
colors = self.graph.style.get_colors(self.id, self.graph._order)
strokes = self.get_strokes()
all_css = []
for css in ['file://base.css'] + list(self.graph.css):
auto_css = ['file://base.css']
if self.graph.style._google_fonts:
auto_css.append(
'//fonts.googleapis.com/css?family=%s' % quote_plus(
'|'.join(self.graph.style._google_fonts))
)
for css in auto_css + list(self.graph.css):
css_text = None
if css.startswith('inline:'):
css_text = css[len('inline:'):]
@ -94,25 +102,12 @@ class Svg(object):
css = os.path.join(
os.path.dirname(__file__), 'css', css[len('file://'):])
class FontSizes(object):
"""Container for font sizes"""
fs = FontSizes()
for name in dir(self.graph.state):
if name.endswith('_font_size'):
setattr(
fs,
name.replace('_font_size', ''),
('%dpx' % getattr(self.graph, name)))
with io.open(css, encoding='utf-8') as f:
css_text = template(
f.read(),
style=self.graph.style,
colors=colors,
strokes=strokes,
font_sizes=fs,
id=self.id)
if css_text is not None:
@ -146,9 +141,13 @@ class Svg(object):
return o.to_dict()
return json.JSONEncoder().default(o)
dct = get_js_dict()
# Config adds
dct['legends'] = self.graph._legends
common_script.text = " = ".join(
("window.config", json.dumps(
get_js_dict(), default=json_default)))
dct, default=json_default)))
for js in self.graph.js:
if js.startswith('file://'):

Loading…
Cancel
Save