diff --git a/pygal/config.py b/pygal/config.py index e1e7ce14..fa575fc3 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -256,7 +256,6 @@ class Config(CommonConfig): list, "Misc", "Extraneous defs to be inserted in svg", "Useful for adding gradients / patterns…", str) - # Look # title = Key( None, str, "Look", @@ -498,6 +497,11 @@ class Config(CommonConfig): "Label string length truncation threshold", "None = auto, Negative for none") + percent_values = Key( + False, bool, "Look", + "Bar Values", + "Set to True to display a bar's percentage contribution per series atop the bar.") + # Misc # js = Key( ('//kozea.github.io/pygal.js/2.0.x/pygal-tooltips.min.js',), diff --git a/pygal/graph/bar.py b/pygal/graph/bar.py index 488b759a..3a8cabba 100644 --- a/pygal/graph/bar.py +++ b/pygal/graph/bar.py @@ -62,7 +62,8 @@ def _bar(self, serie, parent, x, y, i, zero, secondary=False): def _tooltip_and_print_values( self, serie_node, serie, parent, i, val, metadata, - x, y, width, height): + x, y, width, height, total): + percent = ((float(val)/total)*100) transpose = swap if self.horizontal else ident x_center, y_center = transpose((x + width / 2, y + height / 2)) x_top, y_top = transpose((x + width, y + height)) @@ -75,7 +76,6 @@ def _tooltip_and_print_values( self._tooltip_data( parent, val, x_center, y_center, "centered", self._get_x_label(i)) - if self.print_values_position == 'top': if self.horizontal: x = x_bottom + sign * self.style.value_font_size / 2 @@ -93,7 +93,9 @@ def _tooltip_and_print_values( else: x = x_center y = y_center - self._static_value(serie_node, val, x, y, metadata, "middle") + + self.style.value_font_size = width / 1.41421 + self._static_value(serie_node, val, percent, x, y, metadata, "middle") def bar(self, serie, rescale=False): """Draw a bar graph for a serie""" @@ -103,7 +105,7 @@ def bar(self, serie, rescale=False): points = self._rescale(serie.points) else: points = serie.points - + total = sum(list(filter(None, serie.values))) for i, (x, y) in enumerate(points): if None in (x, y) or (self.logarithmic and y <= 0): continue @@ -124,7 +126,7 @@ def bar(self, serie, rescale=False): self._tooltip_and_print_values( serie_node, serie, bar, i, val, metadata, - x_, y_, width, height) + x_, y_, width, height, total) def _compute(self): """Compute y min and max and y scale and set labels""" diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index 23e0669f..12a66b37 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -491,12 +491,12 @@ def _tooltip_data(self, node, value, x, y, classes=None, xlabel=None): self.svg.node(node, 'desc', class_="x_label").text = to_str(xlabel) - def _static_value(self, serie_node, value, x, y, metadata, + def _static_value(self, serie_node, value, percent, x, y, metadata, align_text='left', classes=None): """Write the print value""" label = metadata and metadata.get('label') classes = classes and [classes] or [] - + per_str = ('{0:.2f}%'.format(percent)) if self.print_labels and label: label_cls = classes + ['label'] if self.print_values: @@ -509,18 +509,26 @@ def _static_value(self, serie_node, value, x, y, metadata, ).text = label y += self.style.value_font_size - if self.print_values or self.dynamic_print_values: + if self.print_values or self.dynamic_print_values or self.percent_values: val_cls = classes + ['value'] if self.dynamic_print_values: val_cls.append('showable') - - self.svg.node( - serie_node['text_overlay'], 'text', - class_=' '.join(val_cls), - x=x, - y=y + self.style.value_font_size / 3, - attrib={'text-anchor': align_text} - ).text = value if self.print_zeroes or value != '0' else '' + if self.print_values: + self.svg.node( + serie_node['text_overlay'], 'text', + class_=' '.join(val_cls), + x=x, + y=y + self.style.value_font_size / 3, + attrib={'text-anchor': align_text} + ).text = value if self.print_zeroes or value != '0' else '' + if self.percent_values: + self.svg.node( + serie_node['text_overlay'], 'text', + class_=' '.join(val_cls), + x=x, + y=y + 4/3*self.style.value_font_size, + attrib={'text-anchor': align_text} + ).text = per_str if self.print_zeroes or per_str != '0.00%' else '' def _points(self, x_pos): """ @@ -836,7 +844,7 @@ def _compute_x_labels_major(self): elif self.x_labels_major_count: label_count = len(self._x_labels) major_count = self.x_labels_major_count - if (major_count >= label_count): + if major_count >= label_count: self._x_labels_major = [label[0] for label in self._x_labels] else: diff --git a/pygal/test/conftest.py b/pygal/test/conftest.py index e6f08f48..8ff8aac1 100644 --- a/pygal/test/conftest.py +++ b/pygal/test/conftest.py @@ -23,7 +23,7 @@ import pygal from pygal.etree import etree import sys -from . import get_data +#from . import get_data @pytest.fixture diff --git a/pygal/test/test_bar.py b/pygal/test/test_bar.py index 631990b0..18f293fa 100644 --- a/pygal/test/test_bar.py +++ b/pygal/test/test_bar.py @@ -18,7 +18,6 @@ # along with pygal. If not, see . """Bar chart related tests""" - from pygal import Bar @@ -35,3 +34,48 @@ def test_simple_bar(): assert len(q(".axis.y")) == 1 assert len(q(".legend")) == 2 assert len(q(".plot .series rect")) == 2 * 3 + + +def test_difference(): + """Tests the difference between labeled graphs and unlabeled graphs""" + bar = Bar(bar_values=False) + rng = [-3, -32, -39] + bar.add('test1', rng) + bar.add('test2', map(abs, rng)) + bar.x_labels = map(str, rng) + bar_labelled = Bar(bar_values=True) + rng = [-3, -32, -39] + bar_labelled.add('test1', rng) + bar_labelled.add('test2', map(abs, rng)) + bar.labelled = map(str, rng) + + assert bar != bar_labelled + + +def test_bar_percent_difference(): + """Tests the difference between percent labeled graphs and unlabeled graphs""" + bar = Bar() + rng = [-3, -32, -39] + bar.add('test1', rng) + bar.add('test2', map(abs, rng)) + bar.x_labels = map(str, rng) + + barpercent = Bar(percent_values=True) + rng = [-3, -32, -39] + barpercent.add('test1', rng) + barpercent.add('test2', map(abs, rng)) + barpercent.x_labels = map(str, rng) + + assert (bar != barpercent) + + +def test_chart_renders(): + """Tests that print values and percent values renders""" + line_chart = Bar(print_values=True, percent_values=True, print_values_position='top') + line_chart.title = 'Browser usage evolution (in %)' + line_chart.x_labels = map(str, range(2002, 2013)) + line_chart.add('Firefox', [None, None, 0, 16.6, 25, 31, 36.4, 45.5, 46.3, 42.8, 37.1]) + line_chart.add('Chrome', [None, None, None, None, None, None, 0, 3.9, 10.8, 23.8, 35.3]) + line_chart.add('IE', [85.8, 84.6, 84.7, 74.5, 66, 58.6, 54.7, 44.8, 36.2, 26.6, 20.1]) + line_chart.add('Others', [14.2, 15.4, 15.3, 8.9, 9, 10.4, 8.9, 5.8, 6.7, 6.8, 7.5]) + assert line_chart.render() diff --git a/pygal/test/test_config.py b/pygal/test/test_config.py index 11001c92..df28354e 100644 --- a/pygal/test/test_config.py +++ b/pygal/test/test_config.py @@ -102,6 +102,7 @@ class LineConfig(Config): def test_config_alterations_class(): """Assert a config can be changed on config class""" + class LineConfig(Config): no_prefix = True show_legend = False @@ -125,6 +126,7 @@ class LineConfig(Config): def test_config_alterations_instance(): """Assert a config can be changed on instance""" + class LineConfig(Config): no_prefix = True show_legend = False @@ -149,6 +151,7 @@ class LineConfig(Config): def test_config_alterations_kwargs(): """Assert a config can be changed with keyword args""" + class LineConfig(Config): no_prefix = True show_legend = False @@ -541,15 +544,15 @@ def test_formatters(Chart): if Chart._dual or Chart == Box: return chart = Chart(formatter=lambda x, chart, serie: '%s%s$' % ( - x, serie.title)) + x, serie.title)) chart.add('_a', [1, 2, {'value': 3, 'formatter': lambda x: u('%s¥') % x}]) chart.add('_b', [4, 5, 6], formatter=lambda x: u('%s€') % x) chart.x_labels = [2, 4, 6] chart.x_labels_major = [4] q = chart.render_pyquery() assert set([v.text for v in q(".value")]) == set(( - u('4€'), u('5€'), u('6€'), '1_a$', '2_a$', u('3¥')) + ( - ('6_a$', u('15€')) if Chart in (Pie, SolidGauge) else ())) + u('4€'), u('5€'), u('6€'), '1_a$', '2_a$', u('3¥')) + ( + ('6_a$', u('15€')) if Chart in (Pie, SolidGauge) else ())) def test_classes(Chart): @@ -574,3 +577,8 @@ def test_classes(Chart): chart = Chart(classes=('graph', _ellipsis)) assert chart.render_pyquery().attr('class') == 'graph pygal-chart' + + +def test_bar_value(Chart): + chart = Chart() + assert chart.bar_values == False diff --git a/pygal/test/test_gauge.py b/pygal/test/test_gauge.py new file mode 100644 index 00000000..80a853be --- /dev/null +++ b/pygal/test/test_gauge.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# This file is part of pygal +# +# A python svg graph plotting library +# Copyright © 2012-2016 Kozea +# +# This library is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with pygal. If not, see . + +"""Gauge Chart related tests.""" + + +from pygal import Gauge + + +def test_render(): + """Tests that a gauge plots""" + chart = Gauge() + chart.range = [0,40] + chart.add('Connor', 21) + chart.add('Mike', 30) + chart.add('Jules', 10) + assert chart.render() diff --git a/pygal/test/test_graph.py b/pygal/test/test_graph.py index 21226474..52d21a9b 100644 --- a/pygal/test/test_graph.py +++ b/pygal/test/test_graph.py @@ -20,6 +20,10 @@ """Generate tests for different chart types with different data""" import os +from unittest import TestCase + +from pygal.graph.bar import Bar + import pygal import uuid import sys @@ -431,10 +435,11 @@ def test_long_title(Chart, datas): """Test chart rendering with a long title""" chart = Chart( title="A chart is a graphical representation of data, in which " - "'the data is represented by symbols, such as bars in a bar chart, " - "lines in a line chart, or slices in a pie chart'. A chart can " - "represent tabular numeric data, functions or some kinds of " - "qualitative structure and provides different info.") + "'the data is represented by symbols, such as bars in a bar chart, " + "lines in a line chart, or slices in a pie chart'. A chart can " + "represent tabular numeric data, functions or some kinds of " + "qualitative structure and provides different info.") chart = make_data(chart, datas) q = chart.render_pyquery() assert len(q('.titles text')) == 5 + diff --git a/pygal/view.py b/pygal/view.py index 9360a584..a106b7ee 100644 --- a/pygal/view.py +++ b/pygal/view.py @@ -26,7 +26,7 @@ class Margin(object): - """Class reprensenting a margin (top, right, left, bottom)""" + """Class representing a margin (top, right, left, bottom)""" def __init__(self, top, right, bottom, left): """Create the margin object from the top, right, left, bottom margin"""