diff --git a/demo/moulinrouge/tests.py b/demo/moulinrouge/tests.py index e596ed6..f03895e 100644 --- a/demo/moulinrouge/tests.py +++ b/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/') + 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/') 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', diff --git a/docs/changelog.rst b/docs/changelog.rst index af538ea..0815aad 100644 --- a/docs/changelog.rst +++ b/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 ===== diff --git a/pygal/config.py b/pygal/config.py index 7465287..e1e7ce1 100644 --- a/pygal/config.py +++ b/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") diff --git a/pygal/graph/base.py b/pygal/graph/base.py index b0bd939..cd33a74 100644 --- a/pygal/graph/base.py +++ b/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 diff --git a/pygal/graph/dot.py b/pygal/graph/dot.py index 9446964..89aadcb 100644 --- a/pygal/graph/dot.py +++ b/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): diff --git a/pygal/graph/funnel.py b/pygal/graph/funnel.py index 93fc978..00e3dc7 100644 --- a/pygal/graph/funnel.py +++ b/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): diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index e325a27..1c349fc 100644 --- a/pygal/graph/graph.py +++ b/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 diff --git a/pygal/graph/public.py b/pygal/graph/public.py index b6eceda..eb6e43f 100644 --- a/pygal/graph/public.py +++ b/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('') diff --git a/pygal/serie.py b/pygal/serie.py index 44b8a80..a5b70c4 100644 --- a/pygal/serie.py +++ b/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__) diff --git a/pygal/test/test_config.py b/pygal/test/test_config.py index a382591..11001c9 100644 --- a/pygal/test/test_config.py +++ b/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"""