Browse Source

Implement new call API. Ref #323

pull/333/head
Florian Mounier 9 years ago
parent
commit
6a597e2953
  1. 33
      demo/moulinrouge/tests.py
  2. 6
      docs/changelog.rst
  3. 4
      pygal/config.py
  4. 16
      pygal/graph/base.py
  5. 2
      pygal/graph/dot.py
  6. 2
      pygal/graph/funnel.py
  7. 95
      pygal/graph/graph.py
  8. 15
      pygal/graph/public.py
  9. 3
      pygal/serie.py
  10. 3
      pygal/test/test_config.py

33
demo/moulinrouge/tests.py

@ -279,6 +279,17 @@ def get_test_routes(app):
graph.x_label_rotation = 90
return graph.render_response()
@app.route('/test/<chart>')
def test_call_api_for(chart):
graph = CHARTS_BY_NAME[chart]()
graph(1, 3, 12, 3, 4, None, 9, title='1')
graph(7, -4, 10, None, 8, 3, 1, title='2')
graph(7, -14, -10, None, 8, 3, 1, title='3')
graph(7, 4, -10, None, 8, 3, 1, title='4')
graph.x_labels = ('a', 'b', 'c', 'd')
graph.x_label_rotation = 90
return graph.render_response()
@app.route('/test/one/<chart>')
def test_one_for(chart):
graph = CHARTS_BY_NAME[chart]()
@ -684,7 +695,7 @@ def get_test_routes(app):
colors = [rotate('#ff0000', i * 360 / n) for i in range(n)]
pie = Pie(style=Style(colors=colors))
for i in range(n):
pie.add(str(i), 1)
pie(1, title=str(i) if i % 5 == 1 else None)
return pie.render_response()
@app.route('/test/major_dots')
@ -1027,15 +1038,17 @@ def get_test_routes(app):
def test_legend_link_for(chart):
chart = CHARTS_BY_NAME[chart]()
# link on chart and label
chart.add({
'title': 'Red',
'tooltip': 'Cramoisi',
'xlink': {'href': 'http://en.wikipedia.org/wiki/Red'}
}, [{
'value': 2,
'label': 'This is red',
'tooltip': 'LOOLLOLOLO',
'xlink': {'href': 'http://en.wikipedia.org/wiki/Red'}}])
chart.add(
[{
'value': 2,
'label': 'This is red',
'tooltip': 'LOOLLOLOLO',
'xlink': {'href': 'http://en.wikipedia.org/wiki/Red'}}],
title={
'title': 'Red',
'tooltip': 'Cramoisi',
'xlink': {'href': 'http://en.wikipedia.org/wiki/Red'}
})
chart.add({'title': 'Yellow', 'xlink': {
'href': 'http://en.wikipedia.org/wiki/Yellow',

6
docs/changelog.rst

@ -2,6 +2,12 @@
Changelog
=========
2.3.0 UNRELEASED
================
* New call API: `chart = Line(fill=True); chart.add('title', [1, 3, 12]); chart.render()` can now be replaced with `Line(fill=True)(1, 3, 12, title='title').render()`
2.2.3
=====

4
pygal/config.py

@ -537,6 +537,10 @@ class SerieConfig(CommonConfig):
"""Class holding serie config values"""
title = Key(
None, str, "Look",
"Serie title.", "Leave it to None to disable title.")
secondary = Key(
False, bool, "Misc",
"Set it to put the serie in a second axis")

16
pygal/graph/base.py

@ -56,7 +56,6 @@ class BaseGraph(object):
self.state = None
self.uuid = str(uuid4())
self.raw_series = []
self.raw_series2 = []
self.xml_filters = []
def __setattr__(self, name, value):
@ -110,16 +109,15 @@ class BaseGraph(object):
series = []
raw = [(
title,
list(raw_values) if not isinstance(
raw_values, dict) else raw_values,
serie_config_kwargs
) for title, raw_values, serie_config_kwargs in raw]
) for raw_values, serie_config_kwargs in raw]
width = max([len(values) for _, values, _ in raw] +
width = max([len(values) for values, _ in raw] +
[len(self.x_labels or [])])
for title, raw_values, serie_config_kwargs in raw:
for raw_values, serie_config_kwargs in raw:
metadata = {}
values = []
if isinstance(raw_values, dict):
@ -174,8 +172,7 @@ class BaseGraph(object):
if k in dir(serie_config)))
serie_config(**serie_config_kwargs)
series.append(
Serie(offset + len(series),
title, values, serie_config, metadata))
Serie(offset + len(series), values, serie_config, metadata))
return series
def setup(self, **kwargs):
@ -189,9 +186,10 @@ class BaseGraph(object):
if isinstance(self.style, type):
self.style = self.style()
self.series = self.prepare_values(
self.raw_series) or []
[rs for rs in self.raw_series if not rs[1].get('secondary')]) or []
self.secondary_series = self.prepare_values(
self.raw_series2, len(self.series)) or []
[rs for rs in self.raw_series if rs[1].get('secondary')],
len(self.series)) or []
self.horizontal = getattr(self, 'horizontal', False)
self.svg = Svg(self)
self._x_labels = None

2
pygal/graph/dot.py

@ -94,7 +94,7 @@ class Dot(Graph):
self.y_labels and map(to_str, self.y_labels) or [
serie.title['title']
if isinstance(serie.title, dict)
else serie.title for serie in self.series],
else serie.title or '' for serie in self.series],
self._y_pos))
def _set_view(self):

2
pygal/graph/funnel.py

@ -102,7 +102,7 @@ class Funnel(Graph):
map(self._x_format, self.x_labels) or [
serie.title['title']
if isinstance(serie.title, dict)
else serie.title for serie in self.series],
else serie.title or '' for serie in self.series],
map(self._center, self._x_pos)))
def _plot(self):

95
pygal/graph/graph.py

@ -346,20 +346,8 @@ class Graph(PublicApi):
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
# gen structure - (i, (j, (l, tf)))
# i - global serie number - used for coloring and identification
# j - position within current legend box
# l - label
# tf - whether it is secondary label
gen = enumerate(enumerate(chain(
zip(self._legends, repeat(False)),
zip(self._secondary_legends, repeat(True)))))
secondary_legends = legends # svg node is the same
else:
gen = enumerate(chain(
enumerate(zip(self._legends, repeat(False))),
enumerate(zip(self._secondary_legends, repeat(True)))))
# draw secondary axis on right
x = self.margin_box.left + self.view.width + self.spacing
@ -375,44 +363,57 @@ class Graph(PublicApi):
self.nodes['graph'], class_='legends',
transform='translate(%d, %d)' % (x, y))
for (global_serie_number, (i, (title, is_secondary))) in gen:
serie_number = -1
i = 0
col = i % cols
row = i // cols
for titles, is_secondary in (
(self._legends, False),
(self._secondary_legends, True)):
if not self.legend_at_bottom and is_secondary:
i = 0
legend = self.svg.node(
secondary_legends if is_secondary else legends,
class_='legend reactive activate-serie',
id="activate-serie-%d" % global_serie_number)
self.svg.node(
legend, 'rect',
x=col * x_step,
y=1.5 * row * h + (
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,
class_="color-%d reactive" % (
global_serie_number % len(self.style.colors))
)
for title in titles:
serie_number += 1
if title is None:
continue
col = i % cols
row = i // cols
if isinstance(title, dict):
node = decorate(self.svg, legend, title)
title = title['title']
else:
node = legend
legend = self.svg.node(
secondary_legends if is_secondary else legends,
class_='legend reactive activate-serie',
id="activate-serie-%d" % serie_number)
self.svg.node(
legend, 'rect',
x=col * x_step,
y=1.5 * row * h + (
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,
class_="color-%d reactive" % (
serie_number % len(self.style.colors))
)
truncated = truncate(title, truncation)
self.svg.node(
node, 'text',
x=col * x_step + self.legend_box_size + 5,
y=1.5 * row * h + .5 * h + .3 * self.style.legend_font_size
).text = truncated
if isinstance(title, dict):
node = decorate(self.svg, legend, title)
title = title['title']
else:
node = legend
truncated = truncate(title, truncation)
self.svg.node(
node, 'text',
x=col * x_step + self.legend_box_size + 5,
y=1.5 * row * h + .5 * h + .3 * self.style.legend_font_size
).text = truncated
if truncated != title:
self.svg.node(legend, 'title').text = title
if truncated != title:
self.svg.node(legend, 'title').text = title
i += 1
def _make_title(self):
"""Make the title"""
@ -643,7 +644,7 @@ class Graph(PublicApi):
map(lambda x: truncate(x, self.truncate_legend or 15),
[serie.title['title']
if isinstance(serie.title, dict)
else serie.title for serie in series_group]),
else serie.title or '' for serie in series_group]),
self.style.legend_font_size)
if self.legend_at_bottom:
h_max = max(h, self.legend_box_size)
@ -938,7 +939,7 @@ class Graph(PublicApi):
"""Check if there is any data"""
return any([
len([
v for a in (s[1] if is_list_like(s) else [s])
v for a in (s[0] if is_list_like(s) else [s])
for v in (a if is_list_like(a) else [a])
if v is not None])
for s in self.raw_series

15
pygal/graph/public.py

@ -30,13 +30,16 @@ class PublicApi(BaseGraph):
"""Chart public functions"""
def add(self, title, values, **kwargs):
"""Add a serie to this graph"""
"""Add a serie to this graph, compat api"""
if not is_list_like(values) and not isinstance(values, dict):
values = [values]
if kwargs.get('secondary', False):
self.raw_series2.append((title, values, kwargs))
else:
self.raw_series.append((title, values, kwargs))
kwargs['title'] = title
self.raw_series.append((values, kwargs))
return self
def __call__(self, *args, **kwargs):
"""Call api: chart(1, 2, 3, title='T')"""
self.raw_series.append((args, kwargs))
return self
def add_xml_filter(self, callback):
@ -121,7 +124,7 @@ class PublicApi(BaseGraph):
bars = u('▁▂▃▄▅▆▇█')
if len(self.raw_series) == 0:
return u('')
values = list(self.raw_series[0][1])
values = list(self.raw_series[0][0])
if len(values) == 0:
return u('')

3
pygal/serie.py

@ -25,10 +25,9 @@ class Serie(object):
"""Serie class containing title, values and the graph serie index"""
def __init__(self, index, title, values, config, metadata=None):
def __init__(self, index, values, config, metadata=None):
"""Create the serie with its options"""
self.index = index
self.title = title
self.values = values
self.config = config
self.__dict__.update(config.__dict__)

3
pygal/test/test_config.py

@ -96,6 +96,9 @@ def test_config_behaviours():
l5 = line5.render()
assert l1 == l5
l6 = Line(line_config)(1, 2, 3, title='_').render()
assert l1 == l6
def test_config_alterations_class():
"""Assert a config can be changed on config class"""

Loading…
Cancel
Save