From ef6ba78525ce74ed35bb8a6d7acfe659306a4efc Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Sat, 13 Apr 2019 01:01:59 -0300 Subject: [PATCH 01/10] Logarithmic axes X and Y in different states --- pygal/config.py | 8 ++++++-- pygal/graph/graph.py | 15 ++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pygal/config.py b/pygal/config.py index 69f12b82..7188824c 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -405,8 +405,12 @@ class Config(CommonConfig): "A function to convert ordinate numeric value to strings" ) - logarithmic = Key( - False, bool, "Value", "Display values in logarithmic scale" + x_logarithmic = Key( + False, bool, "Value", "Display x values in logarithmic scale" + ) + + y_logarithmic = Key( + False, bool, "Value", "Display y values in logarithmic scale" ) interpolate = Key( diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index 0cfedbcf..d8aab961 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -30,7 +30,7 @@ cached_property, compute_scale, cut, decorate, filter_kwargs, get_text_box, get_texts_box, majorize, rad, reverse_text_len, split_title, truncate ) -from pygal.view import LogView, ReverseView, View, XYLogView +from pygal.view import LogView, ReverseView, View, XYLogView, XLogView class Graph(PublicApi): @@ -55,9 +55,14 @@ def _axes(self): def _set_view(self): """Assign a view to current graph""" - if self.logarithmic: + if self.x_logarithmic or self.y_logarithmic: if self._dual: - view_class = XYLogView + if self.x_logarithmic and self.y_logarithmic: + view_class = XYLogView + if self.x_logarithmic: + view_class = XLogView + elif self.y_logarithmic: + view_class = LogView else: view_class = LogView else: @@ -365,8 +370,8 @@ def _legend(self): if self.legend_at_bottom: x = self.margin_box.left + self.spacing y = ( - self.margin_box.top + self.view.height + self._x_title_height + - self._x_labels_height + self.spacing + self.margin_box.top + self.view.height + self._x_title_height + + self._x_labels_height + self.spacing ) cols = self.legend_at_bottom_columns or ceil(sqrt(self._order) ) or 1 From 7350ac7bb27bb31869a2f194a2c07109d93f9378 Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Sat, 13 Apr 2019 01:12:41 -0300 Subject: [PATCH 02/10] Logarithmic axes X and Y in different states --- pygal/config.py | 4 ++++ pygal/graph/graph.py | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pygal/config.py b/pygal/config.py index 7188824c..9cbcf02a 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -405,6 +405,10 @@ class Config(CommonConfig): "A function to convert ordinate numeric value to strings" ) + logarithmic = Key( + False, bool, "Value", "Display values in logarithmic scale" + ) + x_logarithmic = Key( False, bool, "Value", "Display x values in logarithmic scale" ) diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index d8aab961..143d0974 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -55,11 +55,11 @@ def _axes(self): def _set_view(self): """Assign a view to current graph""" - if self.x_logarithmic or self.y_logarithmic: + if self.x_logarithmic or self.y_logarithmic or self.logarithmic: if self._dual: - if self.x_logarithmic and self.y_logarithmic: + if self.logarithmic: view_class = XYLogView - if self.x_logarithmic: + elif self.x_logarithmic: view_class = XLogView elif self.y_logarithmic: view_class = LogView From 7d358bad9c466bf3cc25546038d8bd60e7c36182 Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Sun, 14 Apr 2019 20:23:52 -0300 Subject: [PATCH 03/10] flake8 Fails done --- demo/moulinrouge.py | 2 +- demo/moulinrouge/__init__.py | 33 +++++++++++++++-------------- demo/moulinrouge/tests.py | 21 +++++++++--------- docs/ext/pygal_sphinx_directives.py | 2 +- pygal/formatters.py | 4 ++-- pygal/graph/dot.py | 8 +++---- pygal/graph/line.py | 4 ++-- pygal/graph/solidgauge.py | 16 +++++++------- pygal/graph/time.py | 4 ++-- pygal/interpolate.py | 16 +++++++------- pygal/test/test_config.py | 4 ++-- pygal/test/test_graph.py | 6 +++--- pygal/util.py | 6 +++--- pygal/view.py | 8 +++---- 14 files changed, 67 insertions(+), 67 deletions(-) diff --git a/demo/moulinrouge.py b/demo/moulinrouge.py index 3ab1fe96..6e464684 100755 --- a/demo/moulinrouge.py +++ b/demo/moulinrouge.py @@ -36,7 +36,7 @@ handler.setLevel(logging.DEBUG) app.logger.setLevel(logging.DEBUG) logging.getLogger('werkzeug').setLevel(logging.DEBUG) -except: +except Exception as e: pass try: diff --git a/demo/moulinrouge/__init__.py b/demo/moulinrouge/__init__.py index a42ccf1d..df8e39ac 100644 --- a/demo/moulinrouge/__init__.py +++ b/demo/moulinrouge/__init__.py @@ -288,22 +288,23 @@ def interpolation(): 'config': b64encode(pickle.dumps(config)) }) - for params in [{'type': 'catmull_rom'}, {'type': 'finite_difference'}, - {'type': 'cardinal', - 'c': .25}, {'type': 'cardinal', - 'c': .5}, {'type': 'cardinal', 'c': .75}, - {'type': 'cardinal', - 'c': 1.5}, {'type': 'cardinal', - 'c': 2}, {'type': 'cardinal', 'c': 5}, - {'type': 'kochanek_bartels', 'b': 1, 'c': 1, - 't': 1}, {'type': 'kochanek_bartels', 'b': -1, 'c': 1, - 't': 1}, {'type': 'kochanek_bartels', 'b': 1, - 'c': -1, 't': 1}, - {'type': 'kochanek_bartels', 'b': 1, 'c': 1, 't': -1}, { - 'type': 'kochanek_bartels', 'b': -1, 'c': 1, 't': -1 - }, {'type': 'kochanek_bartels', 'b': -1, 'c': -1, - 't': 1}, {'type': 'kochanek_bartels', 'b': -1, - 'c': -1, 't': -1}]: + for params in [ + {'type': 'catmull_rom'}, {'type': 'finite_difference'}, + {'type': 'cardinal', 'c': .25}, + {'type': 'cardinal', 'c': .5}, + {'type': 'cardinal', 'c': .75}, + {'type': 'cardinal', 'c': 1.5}, + {'type': 'cardinal', 'c': 2}, + {'type': 'cardinal', 'c': 5}, + {'type': 'kochanek_bartels', 'b': 1, 'c': 1, 't': 1}, + {'type': 'kochanek_bartels', 'b': -1, 'c': 1, 't': 1}, + {'type': 'kochanek_bartels', 'b': 1, 'c': -1, 't': 1}, + {'type': 'kochanek_bartels', 'b': 1, 'c': 1, 't': -1}, + {'type': 'kochanek_bartels', 'b': -1, 'c': 1, 't': -1}, + {'type': 'kochanek_bartels', 'b': -1, 'c': -1, 't': 1}, + {'type': 'kochanek_bartels', 'b': -1, 'c': -1, 't': -1} + ]: + config.title = "Hermite interpolation with params %r" % params config.interpolate = 'hermite' config.interpolation_parameters = params diff --git a/demo/moulinrouge/tests.py b/demo/moulinrouge/tests.py index 38172666..075aec4b 100644 --- a/demo/moulinrouge/tests.py +++ b/demo/moulinrouge/tests.py @@ -6,7 +6,7 @@ from flask import abort from pygal import ( CHARTS_BY_NAME, XY, Bar, Box, Config, DateLine, DateTimeLine, Dot, Funnel, - Gauge, Histogram, HorizontalBar, Line, Pie, Pyramid, Radar, SolidGauge, + Gauge, Histogram, Line, Pie, Pyramid, Radar, SolidGauge, StackedBar, StackedLine, TimeLine, Treemap, formatters, stats ) from pygal.colors import rotate @@ -706,7 +706,7 @@ def test_swissmap(): smap.add( 'links', [{ 'value': ('kt-vs', 10), - 'label': '\o/', + 'label': r'\\o/', 'xlink': 'http://google.com?q=69' }, { 'value': ('bt', 20), @@ -731,7 +731,7 @@ def test_frenchmapregions(): fmap.add( 'links', [{ 'value': ('02', 10), - 'label': '\o/', + 'label': r'\\o/', 'xlink': 'http://google.com?q=69' }, { 'value': ('72', 20), @@ -1069,7 +1069,6 @@ def test_order_min(): @app.route('/test/custom_css_file') def test_custom_css_file(): - from tempfile import NamedTemporaryFile custom_css = ''' {{ id }}text { fill: green; @@ -1141,13 +1140,13 @@ def test_legend_link_for(chart): 'href': 'http://en.wikipedia.org/wiki/Red' } }], - title={ - 'title': 'Red', - 'tooltip': 'Cramoisi', - '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', diff --git a/docs/ext/pygal_sphinx_directives.py b/docs/ext/pygal_sphinx_directives.py index 8dbd3597..35e45a05 100644 --- a/docs/ext/pygal_sphinx_directives.py +++ b/docs/ext/pygal_sphinx_directives.py @@ -76,7 +76,7 @@ def run(self): ) ] if self.render_fix: - rv = scope['rv'] + pass else: chart = None for key, value in scope.items(): diff --git a/pygal/formatters.py b/pygal/formatters.py index ed4e5eb1..6e8ec395 100644 --- a/pygal/formatters.py +++ b/pygal/formatters.py @@ -48,8 +48,8 @@ def __call__(self, val): if order == 0 or order > len(orders): return float_format(val / (1000**int(order))) return ( - float_format(val / (1000**int(order))) + - orders[int(order) - int(order > 0)] + float_format(val / (1000**int(order))) + + orders[int(order) - int(order > 0)] ) diff --git a/pygal/graph/dot.py b/pygal/graph/dot.py index a0f430dc..dcc9a0a2 100644 --- a/pygal/graph/dot.py +++ b/pygal/graph/dot.py @@ -46,8 +46,8 @@ def dot(self, serie, r_max): log10max = log10(self._max or 1) if value != 0: - size = r_max * ((log10(abs(value)) - log10min) / - (log10max - log10min)) + size = r_max * ((log10(abs(value)) - log10min) + / (log10max - log10min)) else: size = 0 else: @@ -65,8 +65,8 @@ def dot(self, serie, r_max): cx=x, cy=y, r=size, - class_='dot reactive tooltip-trigger' + - (' negative' if value < 0 else '') + class_='dot reactive tooltip-trigger' + + (' negative' if value < 0 else '') ), metadata ) diff --git a/pygal/graph/line.py b/pygal/graph/line.py index 4d3e2591..cd0c457c 100644 --- a/pygal/graph/line.py +++ b/pygal/graph/line.py @@ -182,8 +182,8 @@ def line(self, serie, rescale=False): serie_node['plot'], seq, close=self._self_close, - class_='line reactive' + - (' nofill' if not serie.fill else '') + class_='line reactive' + + (' nofill' if not serie.fill else '') ) def _compute(self): diff --git a/pygal/graph/solidgauge.py b/pygal/graph/solidgauge.py index 07932e5a..4163c724 100644 --- a/pygal/graph/solidgauge.py +++ b/pygal/graph/solidgauge.py @@ -33,17 +33,17 @@ def gaugify(self, serie, squares, sq_dimensions, current_square): serie_node = self.svg.serie(serie) if self.half_pie: start_angle = 3 * pi / 2 - center = ((current_square[1] * sq_dimensions[0]) - - (sq_dimensions[0] / 2.), - (current_square[0] * sq_dimensions[1]) - - (sq_dimensions[1] / 4)) + center = ((current_square[1] * sq_dimensions[0]) + - (sq_dimensions[0] / 2.), + (current_square[0] * sq_dimensions[1]) + - (sq_dimensions[1] / 4)) end_angle = pi / 2 else: start_angle = 0 - center = ((current_square[1] * sq_dimensions[0]) - - (sq_dimensions[0] / 2.), - (current_square[0] * sq_dimensions[1]) - - (sq_dimensions[1] / 2.)) + center = ((current_square[1] * sq_dimensions[0]) + - (sq_dimensions[0] / 2.), + (current_square[0] * sq_dimensions[1]) + - (sq_dimensions[1] / 2.)) end_angle = 2 * pi max_value = serie.metadata.get(0, {}).get('max_value', 100) diff --git a/pygal/graph/time.py b/pygal/graph/time.py index 5e9961f8..4d40a833 100644 --- a/pygal/graph/time.py +++ b/pygal/graph/time.py @@ -66,8 +66,8 @@ def timedelta_to_seconds(x): def time_to_seconds(x): """Convert a time in a seconds sum""" if isinstance(x, time): - return ((((x.hour * 60) + x.minute) * 60 + x.second) * 10**6 + - x.microsecond) / 10**6 + return ((((x.hour * 60) + x.minute) * 60 + x.second) * 10**6 + + x.microsecond) / 10**6 if is_str(x): return x diff --git a/pygal/interpolate.py b/pygal/interpolate.py index 58bc39a5..fdba8b3b 100644 --- a/pygal/interpolate.py +++ b/pygal/interpolate.py @@ -133,8 +133,8 @@ def hermite_interpolate( c = 0 if type == 'finite_difference': for i in range(1, n): - m[i] = w[i] = .5 * ((y[i + 1] - y[i]) / (x[i + 1] - x[i]) + - (y[i] - y[i - 1]) / (x[i] - x[i - 1]) + m[i] = w[i] = .5 * ((y[i + 1] - y[i]) / (x[i + 1] - x[i]) + + (y[i] - y[i - 1]) / (x[i] - x[i - 1]) ) if x[i + 1] - x[i] and x[i] - x[i - 1] else 0 elif type == 'kochanek_bartels': @@ -142,10 +142,10 @@ def hermite_interpolate( b = b or 0 t = t or 0 for i in range(1, n): - m[i] = .5 * ((1 - t) * (1 + b) * (1 + c) * (y[i] - y[i - 1]) + - (1 - t) * (1 - b) * (1 - c) * (y[i + 1] - y[i])) - w[i] = .5 * ((1 - t) * (1 + b) * (1 - c) * (y[i] - y[i - 1]) + - (1 - t) * (1 - b) * (1 + c) * (y[i + 1] - y[i])) + m[i] = .5 * ((1 - t) * (1 + b) * (1 + c) * (y[i] - y[i - 1]) + + (1 - t) * (1 - b) * (1 - c) * (y[i + 1] - y[i])) + w[i] = .5 * ((1 - t) * (1 + b) * (1 - c) * (y[i] - y[i - 1]) + + (1 - t) * (1 - b) * (1 + c) * (y[i + 1] - y[i])) if type == 'cardinal': c = c or 0 @@ -165,8 +165,8 @@ def p(i, x_): h11 = t3 - t2 return ( - h00 * y[i] + h10 * m[i] * delta_x[i] + h01 * y[i + 1] + - h11 * w[i + 1] * delta_x[i] + h00 * y[i] + h10 * m[i] * delta_x[i] + h01 * y[i + 1] + + h11 * w[i + 1] * delta_x[i] ) for i in range(n + 1): diff --git a/pygal/test/test_config.py b/pygal/test/test_config.py index 758a2eca..52507919 100644 --- a/pygal/test/test_config.py +++ b/pygal/test/test_config.py @@ -550,8 +550,8 @@ def test_formatters(Chart): 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 ())) + ) == set((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): diff --git a/pygal/test/test_graph.py b/pygal/test/test_graph.py index 6127dc4c..d454b255 100644 --- a/pygal/test/test_graph.py +++ b/pygal/test/test_graph.py @@ -309,7 +309,7 @@ def test_unicode_labels_decode(Chart): u('Série1'), [{ 'value': 1, 'xlink': 'http://1/', - 'label': u('{\}°ijæð©&×&<—×€¿_…\{_…') + 'label': u(r'{\\}°ijæð©&×&<—×€¿_…\\{_…') }, { 'value': 2, 'xlink': { @@ -335,7 +335,7 @@ def test_unicode_labels_python2(Chart): u('Série1'), [{ 'value': 1, 'xlink': 'http://1/', - 'label': eval("u'{\}°ijæð©&×&<—×€¿_…\{_…'") + 'label': eval(r"u'{\\}°ijæð©&×&<—×€¿_…\\{_…'") }, { 'value': 2, 'xlink': { @@ -361,7 +361,7 @@ def test_unicode_labels_python3(Chart): u('Série1'), [{ 'value': 1, 'xlink': 'http://1/', - 'label': eval("'{\}°ijæð©&×&<—×€¿_…\{_…'") + 'label': eval(r"'{\\}°ijæð©&×&<—×€¿_…\\{_…'") }, { 'value': 2, 'xlink': { diff --git a/pygal/util.py b/pygal/util.py index 65236173..ca2e5c5f 100644 --- a/pygal/util.py +++ b/pygal/util.py @@ -36,8 +36,8 @@ def majorize(values): """Filter sequence to return only major considered numbers""" sorted_values = sorted(values) if len(values) <= 3 or ( - abs(2 * sorted_values[1] - sorted_values[0] - sorted_values[2]) > - abs(1.5 * (sorted_values[1] - sorted_values[0]))): + abs(2 * sorted_values[1] - sorted_values[0] - sorted_values[2]) + > abs(1.5 * (sorted_values[1] - sorted_values[0]))): return [] values_step = sorted_values[1] - sorted_values[0] full_range = sorted_values[-1] - sorted_values[0] @@ -275,7 +275,7 @@ 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:] == '\*/': + if css_comment[-3:] == r'\\*/': remove_next_comment = 0 continue if remove_next_comment: diff --git a/pygal/view.py b/pygal/view.py index 68d0a2c2..4d6deb92 100644 --- a/pygal/view.py +++ b/pygal/view.py @@ -338,8 +338,8 @@ def y(self, y): if y is None or y <= 0 or self.log10_ymax - self.log10_ymin == 0: return 0 return ( - self.height - self.height * (log10(y) - self.log10_ymin) / - (self.log10_ymax - self.log10_ymin) + self.height - self.height * (log10(y) - self.log10_ymin) + / (self.log10_ymax - self.log10_ymin) ) @@ -361,8 +361,8 @@ def x(self, x): if x is None or x <= 0 or self.log10_xmax - self.log10_xmin == 0: return None return ( - self.width * (log10(x) - self.log10_xmin) / - (self.log10_xmax - self.log10_xmin) + self.width * (log10(x) - self.log10_xmin) + / (self.log10_xmax - self.log10_xmin) ) From f0313a29e0c0c148e3e628447f2524a44732a332 Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Tue, 21 May 2019 21:57:05 -0300 Subject: [PATCH 04/10] Update line graph right legend --- pygal/colors.py | 18 +- pygal/config.py | 372 ++++++++++++------------- pygal/graph/graph.py | 626 +++++++++++++++++++------------------------ pygal/graph/line.py | 79 +++--- pygal/style.py | 173 ++++++------ 5 files changed, 604 insertions(+), 664 deletions(-) diff --git a/pygal/colors.py b/pygal/colors.py index 5027fa8c..385863e4 100644 --- a/pygal/colors.py +++ b/pygal/colors.py @@ -81,10 +81,8 @@ def h_to_rgb(h): if 3 * h < 2: return m1 + 6 * (2 / 3 - h) * (m2 - m1) return m1 - - r, g, b = map( - lambda x: round(x * 255), map(h_to_rgb, (h + 1 / 3, h, h - 1 / 3)) - ) + r, g, b = map(lambda x: round(x * 255), + map(h_to_rgb, (h + 1 / 3, h, h - 1 / 3))) return r, g, b @@ -109,8 +107,7 @@ def parse_color(color): assert len(color) == 8 type = type or '#rrggbbaa' r, g, b, a = [ - int(''.join(c), 16) for c in zip(color[::2], color[1::2]) - ] + int(''.join(c), 16) for c in zip(color[::2], color[1::2])] a /= 255 elif color.startswith('rgb('): type = 'rgb' @@ -119,8 +116,8 @@ def parse_color(color): elif color.startswith('rgba('): type = 'rgba' color = color[5:-1] - r, g, b, a = [int(c) for c in color.split(',')[:-1] - ] + [float(color.split(',')[-1])] + r, g, b, a = [int(c) for c in color.split(',')[:-1]] + [ + float(color.split(',')[-1])] return r, g, b, a, type @@ -137,9 +134,8 @@ def unparse_color(r, g, b, a, type): if type == '#rgba': if r % 17 == 0 and g % 17 == 0 and b % 17 == 0: - return '#%x%x%x%x' % ( - int(r / 17), int(g / 17), int(b / 17), int(a * 15) - ) + return '#%x%x%x%x' % (int(r / 17), int(g / 17), int(b / 17), + int(a * 15)) type = '#rrggbbaa' if type == '#rrggbb': diff --git a/pygal/config.py b/pygal/config.py index 9cbcf02a..eebc2556 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -29,6 +29,7 @@ class Key(object): + """ Represents a config parameter. @@ -43,8 +44,8 @@ class Key(object): _categories = [] def __init__( - self, default_value, type_, category, doc, subdoc="", subtype=None - ): + self, default_value, type_, category, doc, + subdoc="", subtype=None): """Create a configuration key""" self.value = default_value self.type = type_ @@ -68,8 +69,10 @@ def __repr__(self): Default: %r      %s%s """ % ( - self.type.__name__, (' of %s' % self.subtype.__name__) - if self.subtype else '', self.value, self.doc, + self.type.__name__, + (' of %s' % self.subtype.__name__) if self.subtype else '', + self.value, + self.doc, (' %s' % self.subdoc) if self.subdoc else '' ) @@ -104,8 +107,9 @@ def coerce(self, value): return value elif self.type == list: return self.type( - map(self.subtype, map(lambda x: x.strip(), value.split(','))) - ) + map( + self.subtype, map( + lambda x: x.strip(), value.split(',')))) elif self.type == dict: rv = {} for pair in value.split(','): @@ -114,13 +118,14 @@ def coerce(self, value): val = val.strip() try: rv[key] = self.subtype(val) - except Exception: + except: rv[key] = val return rv return self.type(value) class MetaConfig(type): + """Config metaclass. Used to get the key name and set it on the value.""" def __new__(mcs, classname, bases, classdict): @@ -132,7 +137,8 @@ def __new__(mcs, classname, bases, classdict): return type.__new__(mcs, classname, bases, classdict) -class BaseConfig(MetaConfig('ConfigBase', (object, ), {})): +class BaseConfig(MetaConfig('ConfigBase', (object,), {})): + """ This class holds the common method for configs. @@ -144,8 +150,9 @@ def __init__(self, **kwargs): """Can be instanciated with config kwargs""" for k in dir(self): v = getattr(self, k) - if (k not in self.__dict__ and not k.startswith('_') - and not hasattr(v, '__call__')): + if (k not in self.__dict__ and not + k.startswith('_') and not + hasattr(v, '__call__')): if isinstance(v, Key): if v.is_list and v.value is not None: v = list(v.value) @@ -163,10 +170,9 @@ def _update(self, kwargs): from pygal.util import merge dir_self_set = set(dir(self)) merge( - self.__dict__, - dict([(k, v) for (k, v) in kwargs.items() - if not k.startswith('_') and k in dir_self_set]) - ) + self.__dict__, dict([ + (k, v) for (k, v) in kwargs.items() + if not k.startswith('_') and k in dir_self_set])) def to_dict(self): """Export a JSON serializable dictionary of the config""" @@ -186,224 +192,224 @@ def copy(self): class CommonConfig(BaseConfig): + """Class holding options used in both chart and serie configuration""" stroke = Key( - True, bool, "Look", "Line dots (set it to false to get a scatter plot)" - ) + True, bool, "Look", + "Line dots (set it to false to get a scatter plot)") show_dots = Key(True, bool, "Look", "Set to false to remove dots") show_only_major_dots = Key( False, bool, "Look", - "Set to true to show only major dots according to their majored label" - ) + "Set to true to show only major dots according to their majored label") dots_size = Key(2.5, float, "Look", "Radius of the dots") - fill = Key(False, bool, "Look", "Fill areas under lines") + fill = Key( + False, bool, "Look", "Fill areas under lines") - stroke_style = Key( - None, dict, "Look", "Stroke style of serie element.", - "This is a dict which can contain a " - "'width', 'linejoin', 'linecap', 'dasharray' " - "and 'dashoffset'" - ) + stroke_style = Key(None, dict, "Look", "Stroke style of serie element.", + "This is a dict which can contain a " + "'width', 'linejoin', 'linecap', 'dasharray' " + "and 'dashoffset'") rounded_bars = Key( None, int, "Look", - "Set this to the desired radius in px (for Bar-like charts)" - ) + "Set this to the desired radius in px (for Bar-like charts)") inner_radius = Key( - 0, float, "Look", "Piechart inner radius (donut), must be <.9" - ) + 0, float, "Look", "Piechart inner radius (donut), must be <.9") allow_interruptions = Key( - False, bool, "Look", "Break lines on None values" - ) + False, bool, "Look", "Break lines on None values") formatter = Key( None, callable, "Value", "A function to convert raw value to strings for this chart or serie", "Default to value_formatter in most charts, it depends on dual charts." - "(Can be overriden by value with the formatter metadata.)" - ) + "(Can be overriden by value with the formatter metadata.)") class Config(CommonConfig): + """Class holding config values""" style = Key( - DefaultStyle, Style, "Style", "Style holding values injected in css" - ) + DefaultStyle, Style, "Style", "Style holding values injected in css") css = Key( ('file://style.css', 'file://graph.css'), list, "Style", "List of css file", "It can be any uri from file:///tmp/style.css to //domain/style.css", - str - ) + str) - classes = Key(('pygal-chart', ), list, "Style", - "Classes of the root svg node", str) + classes = Key( + ('pygal-chart',), + list, "Style", "Classes of the root svg node", + str) - defs = Key([], list, "Misc", "Extraneous defs to be inserted in svg", - "Useful for adding gradients / patterns…", str) + defs = Key( + [], + list, "Misc", "Extraneous defs to be inserted in svg", + "Useful for adding gradients / patterns…", + str) # Look # title = Key( - None, str, "Look", "Graph title.", "Leave it to None to disable title." - ) + None, str, "Look", + "Graph title.", "Leave it to None to disable title.") x_title = Key( - None, str, "Look", "Graph X-Axis title.", - "Leave it to None to disable X-Axis title." - ) + None, str, "Look", + "Graph X-Axis title.", "Leave it to None to disable X-Axis title.") y_title = Key( - None, str, "Look", "Graph Y-Axis title.", - "Leave it to None to disable Y-Axis title." - ) + None, str, "Look", + "Graph Y-Axis title.", "Leave it to None to disable Y-Axis title.") - width = Key(800, int, "Look", "Graph width") + y2_title = Key( + None, str, "Look", + "Graph Secondary-Axis title.", "Leave it to None to disable Secondary-Axis title.") - height = Key(600, int, "Look", "Graph height") + width = Key( + 800, int, "Look", "Graph width") - show_x_guides = Key( - False, bool, "Look", "Set to true to always show x guide lines" - ) + height = Key( + 600, int, "Look", "Graph height") - show_y_guides = Key( - True, bool, "Look", "Set to false to hide y guide lines" - ) + show_x_guides = Key(False, bool, "Look", + "Set to true to always show x guide lines") + + show_y_guides = Key(True, bool, "Look", + "Set to false to hide y guide lines") - show_legend = Key(True, bool, "Look", "Set to false to remove legend") + show_legend = Key( + True, bool, "Look", "Set to false to remove legend") legend_at_bottom = Key( - False, bool, "Look", "Set to true to position legend at bottom" - ) + False, bool, "Look", "Set to true to position legend at bottom") legend_at_bottom_columns = Key( - None, int, "Look", "Set to true to position legend at bottom" - ) + None, int, "Look", "Set to true to position legend at bottom") - legend_box_size = Key(12, int, "Look", "Size of legend boxes") + legend_box_size = Key( + 12, int, "Look", "Size of legend boxes") rounded_bars = Key( - None, int, "Look", "Set this to the desired radius in px" - ) + None, int, "Look", "Set this to the desired radius in px") stack_from_top = Key( False, bool, "Look", "Stack from top to zero, this makes the stacked " - "data match the legend order" - ) + "data match the legend order") - spacing = Key(10, int, "Look", "Space between titles/legend/axes") + spacing = Key( + 10, int, "Look", + "Space between titles/legend/axes") - margin = Key(20, int, "Look", "Margin around chart") + margin = Key( + 20, int, "Look", + "Margin around chart") - margin_top = Key(None, int, "Look", "Margin around top of chart") + margin_top = Key( + None, int, "Look", + "Margin around top of chart") - margin_right = Key(None, int, "Look", "Margin around right of chart") + margin_right = Key( + None, int, "Look", + "Margin around right of chart") - margin_bottom = Key(None, int, "Look", "Margin around bottom of chart") + margin_bottom = Key( + None, int, "Look", + "Margin around bottom of chart") - margin_left = Key(None, int, "Look", "Margin around left of chart") + margin_left = Key( + None, int, "Look", + "Margin around left of chart") 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." - ) + "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" - ) + 0, float, "Look", "Piechart inner radius (donut), must be <.9") - half_pie = Key(False, bool, "Look", "Create a half-pie chart") + half_pie = Key( + False, bool, "Look", "Create a half-pie chart") x_labels = Key( - None, list, "Label", "X labels, must have same len than data.", - "Leave it to None to disable x labels display.", str - ) + None, list, "Label", + "X labels, must have same len than data.", + "Leave it to None to disable x labels display.", + str) x_labels_major = Key( - None, - list, - "Label", + None, list, "Label", "X labels that will be marked major.", - subtype=str - ) + subtype=str) x_labels_major_every = Key( - None, int, "Label", "Mark every n-th x label as major." - ) + None, int, "Label", + "Mark every n-th x label as major.") x_labels_major_count = Key( - None, int, "Label", "Mark n evenly distributed labels as major." - ) + None, int, "Label", + "Mark n evenly distributed labels as major.") - show_x_labels = Key(True, bool, "Label", "Set to false to hide x-labels") + show_x_labels = Key( + True, bool, "Label", "Set to false to hide x-labels") show_minor_x_labels = Key( - True, bool, "Label", "Set to false to hide x-labels not marked major" - ) + True, bool, "Label", "Set to false to hide x-labels not marked major") y_labels = Key( - None, list, "Label", "You can specify explicit y labels", - "Must be a list of numbers", float - ) + None, list, "Label", + "You can specify explicit y labels", + "Must be a list of numbers", float) y_labels_major = Key( - None, - list, - "Label", + None, list, "Label", "Y labels that will be marked major. Default: auto", - subtype=str - ) + subtype=str) y_labels_major_every = Key( - None, int, "Label", "Mark every n-th y label as major." - ) + None, int, "Label", + "Mark every n-th y label as major.") y_labels_major_count = Key( - None, int, "Label", "Mark n evenly distributed y labels as major." - ) + None, int, "Label", + "Mark n evenly distributed y labels as major.") show_minor_y_labels = Key( - True, bool, "Label", "Set to false to hide y-labels not marked major" - ) + True, bool, "Label", "Set to false to hide y-labels not marked major") - show_y_labels = Key(True, bool, "Label", "Set to false to hide y-labels") + show_y_labels = Key( + True, bool, "Label", "Set to false to hide y-labels") x_label_rotation = Key( - 0, int, "Label", "Specify x labels rotation angles", "in degrees" - ) + 0, int, "Label", "Specify x labels rotation angles", "in degrees") y_label_rotation = Key( - 0, int, "Label", "Specify y labels rotation angles", "in degrees" - ) + 0, int, "Label", "Specify y labels rotation angles", "in degrees") missing_value_fill_truncation = Key( "x", str, "Look", "Filled series with missing x and/or y values at the end of a series " "are closed at the first value with a missing " - "'x' (default), 'y' or 'either'" - ) + "'x' (default), 'y' or 'either'") # Value # x_value_formatter = Key( formatters.default, callable, "Value", "A function to convert abscissa numeric value to strings " - "(used in XY and Date charts)" - ) + "(used in XY and Date charts)") value_formatter = Key( formatters.default, callable, "Value", - "A function to convert ordinate numeric value to strings" - ) + "A function to convert ordinate numeric value to strings") logarithmic = Key( False, bool, "Value", "Display values in logarithmic scale" @@ -419,130 +425,138 @@ class Config(CommonConfig): interpolate = Key( None, str, "Value", "Interpolation", - "May be %s" % ' or '.join(INTERPOLATIONS) - ) + "May be %s" % ' or '.join(INTERPOLATIONS)) interpolation_precision = Key( - 250, int, "Value", "Number of interpolated points between two values" - ) + 250, int, "Value", "Number of interpolated points between two values") interpolation_parameters = Key( {}, dict, "Value", "Various parameters for parametric interpolations", "ie: For hermite interpolation, you can set the cardinal tension with" - "{'type': 'cardinal', 'c': .5}", int - ) + "{'type': 'cardinal', 'c': .5}", int) box_mode = Key( 'extremes', str, "Value", "Sets the mode to be used. " - "(Currently only supported on box plot)", "May be %s" % - ' or '.join(["1.5IQR", "extremes", "tukey", "stdev", "pstdev"]) - ) + "(Currently only supported on box plot)", + "May be %s" % ' or '.join([ + "1.5IQR", "extremes", "tukey", "stdev", "pstdev"])) order_min = Key( - None, int, "Value", "Minimum order of scale, defaults to None" - ) + None, int, "Value", + "Minimum order of scale, defaults to None") min_scale = Key( - 4, int, "Value", "Minimum number of scale graduation for auto scaling" - ) + 4, int, "Value", + "Minimum number of scale graduation for auto scaling") max_scale = Key( - 16, int, "Value", "Maximum number of scale graduation for auto scaling" - ) + 16, int, "Value", + "Maximum number of scale graduation for auto scaling") range = Key( None, list, "Value", "Explicitly specify min and max of values", - "(ie: (0, 100))", int - ) + "(ie: (0, 100))", int) secondary_range = Key( None, list, "Value", - "Explicitly specify min and max of secondary values", "(ie: (0, 100))", - int - ) + "Explicitly specify min and max of secondary values", + "(ie: (0, 100))", int) + + secondary_axis_round = Key( + None, int, "Value", + "Round the secondary axis values to decimal order") xrange = Key( None, list, "Value", "Explicitly specify min and max of x values " - "(used in XY and Date charts)", "(ie: (0, 100))", int - ) + "(used in XY and Date charts)", + "(ie: (0, 100))", int) - include_x_axis = Key(False, bool, "Value", "Always include x axis") + include_x_axis = Key( + False, bool, "Value", "Always include x axis") zero = Key( - 0, int, "Value", "Set the ordinate zero value", - "Useful for filling to another base than abscissa" - ) + 0, int, "Value", + "Set the ordinate zero value", + "Useful for filling to another base than abscissa") # Text # no_data_text = Key( - "No data", str, "Text", "Text to display when no data is given" - ) + "No data", str, "Text", "Text to display when no data is given") - print_values = Key(False, bool, "Text", "Display values as text over plot") + print_values = Key( + False, bool, + "Text", "Display values as text over plot") dynamic_print_values = Key( - False, bool, "Text", "Show values only on hover" - ) + False, bool, + "Text", "Show values only on hover") print_values_position = Key( - 'center', str, "Text", "Customize position of `print_values`. " - "(For bars: `top`, `center` or `bottom`)" - ) + 'center', str, + "Text", "Customize position of `print_values`. " + "(For bars: `top`, `center` or `bottom`)") - print_zeroes = Key(True, bool, "Text", "Display zero values as well") + print_zeroes = Key( + True, bool, + "Text", "Display zero values as well") - print_labels = Key(False, bool, "Text", "Display value labels") + print_labels = Key( + False, bool, + "Text", "Display value labels") truncate_legend = Key( - None, int, "Text", "Legend string length truncation threshold", - "None = auto, Negative for none" - ) + None, int, "Text", + "Legend string length truncation threshold", + "None = auto, Negative for none") truncate_label = Key( - None, int, "Text", "Label string length truncation threshold", - "None = auto, Negative for none" - ) + None, int, "Text", + "Label string length truncation threshold", + "None = auto, Negative for none") # Misc # - js = Key(('//kozea.github.io/pygal.js/2.0.x/pygal-tooltips.min.js', ), - list, "Misc", "List of js file", - "It can be any uri from file:///tmp/ext.js to //domain/ext.js", - str) + js = Key( + ('//kozea.github.io/pygal.js/2.0.x/pygal-tooltips.min.js',), + list, "Misc", "List of js file", + "It can be any uri from file:///tmp/ext.js to //domain/ext.js", + str) disable_xml_declaration = Key( False, bool, "Misc", "Don't write xml declaration and return str instead of string", - "useful for writing output directly in html" - ) + "useful for writing output directly in html") force_uri_protocol = Key( - 'https', str, "Misc", "Default uri protocol", + 'https', str, "Misc", + "Default uri protocol", "Default protocol for external files. " - "Can be set to None to use a // uri" - ) + "Can be set to None to use a // uri") explicit_size = Key( - False, bool, "Misc", "Write width and height attributes" - ) + False, bool, "Misc", "Write width and height attributes") - pretty_print = Key(False, bool, "Misc", "Pretty print the svg") + pretty_print = Key( + False, bool, "Misc", "Pretty print the svg") strict = Key( - False, bool, "Misc", "If True don't try to adapt / filter wrong values" - ) + False, bool, "Misc", + "If True don't try to adapt / filter wrong values") - no_prefix = Key(False, bool, "Misc", "Don't prefix css") + no_prefix = Key( + False, bool, "Misc", + "Don't prefix css") inverse_y_axis = Key(False, bool, "Misc", "Inverse Y axis direction") class SerieConfig(CommonConfig): + """Class holding serie config values""" title = Key( - None, str, "Look", "Serie title.", "Leave it to None to disable title." - ) + 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" - ) + False, bool, "Misc", + "Set it to put the serie in a second axis") diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index 143d0974..ac5cd9e1 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -28,12 +28,12 @@ from pygal.interpolate import INTERPOLATIONS from pygal.util import ( cached_property, compute_scale, cut, decorate, filter_kwargs, get_text_box, - get_texts_box, majorize, rad, reverse_text_len, split_title, truncate -) -from pygal.view import LogView, ReverseView, View, XYLogView, XLogView + get_texts_box, majorize, rad, reverse_text_len, split_title, truncate) +from pygal.view import LogView, ReverseView, View, XYLogView, XLogView, LogView class Graph(PublicApi): + """Graph super class containing generic common functions""" _dual = False @@ -47,6 +47,7 @@ def _decorate(self): self._make_title() self._make_x_title() self._make_y_title() + self._make_y2_title() def _axes(self): """Draw axes""" @@ -69,89 +70,65 @@ def _set_view(self): view_class = ReverseView if self.inverse_y_axis else View self.view = view_class( - self.width - self.margin_box.x, self.height - self.margin_box.y, - self._box - ) + self.width - self.margin_box.x, + self.height - self.margin_box.y, + self._box) def _make_graph(self): """Init common graph svg structure""" self.nodes['graph'] = self.svg.node( class_='graph %s-graph %s' % ( self.__class__.__name__.lower(), - 'horizontal' if self.horizontal else 'vertical' - ) - ) - self.svg.node( - self.nodes['graph'], - 'rect', - class_='background', - x=0, - y=0, - width=self.width, - height=self.height - ) + 'horizontal' if self.horizontal else 'vertical')) + self.svg.node(self.nodes['graph'], 'rect', + class_='background', + x=0, y=0, + width=self.width, + height=self.height) self.nodes['plot'] = self.svg.node( - self.nodes['graph'], - class_="plot", - transform="translate(%d, %d)" % - (self.margin_box.left, self.margin_box.top) - ) - self.svg.node( - self.nodes['plot'], - 'rect', - class_='background', - x=0, - y=0, - width=self.view.width, - height=self.view.height - ) + self.nodes['graph'], class_="plot", + transform="translate(%d, %d)" % ( + self.margin_box.left, self.margin_box.top)) + self.svg.node(self.nodes['plot'], 'rect', + class_='background', + x=0, y=0, + width=self.view.width, + height=self.view.height) self.nodes['title'] = self.svg.node( - self.nodes['graph'], class_="titles" - ) - self.nodes['overlay'] = self.svg.node( self.nodes['graph'], - class_="plot overlay", - transform="translate(%d, %d)" % - (self.margin_box.left, self.margin_box.top) - ) + class_="titles") + self.nodes['overlay'] = self.svg.node( + self.nodes['graph'], class_="plot overlay", + transform="translate(%d, %d)" % ( + self.margin_box.left, self.margin_box.top)) self.nodes['text_overlay'] = self.svg.node( - self.nodes['graph'], - class_="plot text-overlay", - transform="translate(%d, %d)" % - (self.margin_box.left, self.margin_box.top) - ) + self.nodes['graph'], class_="plot text-overlay", + transform="translate(%d, %d)" % ( + self.margin_box.left, self.margin_box.top)) self.nodes['tooltip_overlay'] = self.svg.node( - self.nodes['graph'], - class_="plot tooltip-overlay", - transform="translate(%d, %d)" % - (self.margin_box.left, self.margin_box.top) - ) + self.nodes['graph'], class_="plot tooltip-overlay", + transform="translate(%d, %d)" % ( + self.margin_box.left, self.margin_box.top)) self.nodes['tooltip'] = self.svg.node( self.nodes['tooltip_overlay'], transform='translate(0 0)', style="opacity: 0", - **{'class': 'tooltip'} - ) + **{'class': 'tooltip'}) - self.svg.node( - self.nodes['tooltip'], - 'rect', - rx=self.tooltip_border_radius, - ry=self.tooltip_border_radius, - width=0, - height=0, - **{'class': 'tooltip-box'} - ) + self.svg.node(self.nodes['tooltip'], 'rect', + rx=self.tooltip_border_radius, + ry=self.tooltip_border_radius, + width=0, height=0, + **{'class': 'tooltip-box'}) self.svg.node(self.nodes['tooltip'], 'g', class_='text') def _x_axis(self): """Make the x axis: labels and guides""" if not self._x_labels or not self.show_x_labels: return - axis = self.svg.node( - self.nodes['plot'], - class_="axis x%s" % (' always_show' if self.show_x_guides else '') - ) + axis = self.svg.node(self.nodes['plot'], class_="axis x%s" % ( + ' always_show' if self.show_x_guides else '' + )) truncation = self.truncate_label if not truncation: if self.x_label_rotation or len(self._x_labels) <= 1: @@ -159,22 +136,18 @@ def _x_axis(self): else: first_label_position = self.view.x(self._x_labels[0][1]) or 0 last_label_position = self.view.x(self._x_labels[-1][1]) or 0 - available_space = (last_label_position - first_label_position - ) / len(self._x_labels) - 1 - + available_space = ( + last_label_position - first_label_position) / ( + len(self._x_labels) - 1) truncation = reverse_text_len( - available_space, self.style.label_font_size - ) + available_space, self.style.label_font_size) truncation = max(truncation, 1) lastlabel = self._x_labels[-1][0] if 0 not in [label[1] for label in self._x_labels]: - self.svg.node( - axis, - 'path', - d='M%f %f v%f' % (0, 0, self.view.height), - class_='line' - ) + self.svg.node(axis, 'path', + d='M%f %f v%f' % (0, 0, self.view.height), + class_='line') lastlabel = None for label, position in self._x_labels: @@ -191,18 +164,18 @@ def _x_axis(self): y = self.view.height + 5 last_guide = (self._y_2nd_labels and label == lastlabel) self.svg.node( - guides, - 'path', + guides, 'path', d='M%f %f v%f' % (x or 0, 0, self.view.height), class_='%s%s%sline' % ( - 'axis ' if label == "0" else '', 'major ' - if major else '', 'guide ' - if position != 0 and not last_guide else '' - ) - ) + 'axis ' if label == "0" else '', + 'major ' if major else '', + 'guide ' if position != 0 and not last_guide else '')) y += .5 * self.style.label_font_size + 5 text = self.svg.node( - guides, 'text', x=x, y=y, class_='major' if major else '' + guides, 'text', + x=x, + y=y, + class_='major' if major else '' ) text.text = truncate(label, truncation) @@ -210,35 +183,29 @@ def _x_axis(self): self.svg.node(guides, 'title').text = label elif self._dual: self.svg.node( - guides, - 'title', + guides, 'title', ).text = self._x_format(position) if self.x_label_rotation: text.attrib['transform'] = "rotate(%d %f %f)" % ( - self.x_label_rotation, x, y - ) + self.x_label_rotation, x, y) if self.x_label_rotation >= 180: - text.attrib['class'] = ' '.join(( - text.attrib['class'] - and text.attrib['class'].split(' ') or [] - ) + ['backwards']) + text.attrib['class'] = ' '.join( + (text.attrib['class'] and text.attrib['class'].split( + ' ') or []) + ['backwards']) - if self._y_2nd_labels and 0 not in [label[1] - for label in self._x_labels]: - self.svg.node( - axis, - 'path', - d='M%f %f v%f' % (self.view.width, 0, self.view.height), - class_='line' - ) + if self._y_2nd_labels and 0 not in [ + label[1] for label in self._x_labels]: + self.svg.node(axis, 'path', + d='M%f %f v%f' % ( + self.view.width, 0, self.view.height), + class_='line') if self._x_2nd_labels: secondary_ax = self.svg.node( - self.nodes['plot'], - class_="axis x x2%s" % - (' always_show' if self.show_x_guides else '') - ) + self.nodes['plot'], class_="axis x x2%s" % ( + ' always_show' if self.show_x_guides else '' + )) for label, position in self._x_2nd_labels: major = label in self._x_labels_major if not (self.show_minor_x_labels or major): @@ -248,38 +215,37 @@ def _x_axis(self): x = self.view.x(position) y = -5 text = self.svg.node( - guides, 'text', x=x, y=y, class_='major' if major else '' + guides, 'text', + x=x, + y=y, + class_='major' if major else '' ) text.text = label if self.x_label_rotation: text.attrib['transform'] = "rotate(%d %f %f)" % ( - -self.x_label_rotation, x, y - ) + -self.x_label_rotation, x, y) if self.x_label_rotation >= 180: text.attrib['class'] = ' '.join(( - text.attrib['class'] - and text.attrib['class'].split(' ') or [] - ) + ['backwards']) + text.attrib['class'] and + text.attrib['class'].split( + ' ') or []) + ['backwards']) def _y_axis(self): """Make the y axis: labels and guides""" if not self._y_labels or not self.show_y_labels: return - axis = self.svg.node( - self.nodes['plot'], - class_="axis y%s" % (' always_show' if self.show_y_guides else '') - ) + axis = self.svg.node(self.nodes['plot'], class_="axis y%s" % ( + ' always_show' if self.show_y_guides else '' + )) - if (0 not in [label[1] for label in self._y_labels] - and self.show_y_guides): + if (0 not in [label[1] for label in self._y_labels] and + self.show_y_guides): self.svg.node( - axis, - 'path', + axis, 'path', d='M%f %f h%f' % ( 0, 0 if self.inverse_y_axis else self.view.height, - self.view.width - ), + self.view.width), class_='line' ) @@ -291,28 +257,23 @@ def _y_axis(self): if not (self.show_minor_y_labels or major): continue - guides = self.svg.node( - axis, - class_='%sguides' % - ('logarithmic ' if self.logarithmic else '') - ) + guides = self.svg.node(axis, class_='%sguides' % ( + 'logarithmic ' if self.logarithmic else '' + )) x = -5 y = self.view.y(position) if not y: continue if self.show_y_guides: self.svg.node( - guides, - 'path', + guides, 'path', d='M%f %f h%f' % (0, y, self.view.width), class_='%s%s%sline' % ( - 'axis ' if label == "0" else '', 'major ' - if major else '', 'guide ' if position != 0 else '' - ) - ) + 'axis ' if label == "0" else '', + 'major ' if major else '', + 'guide ' if position != 0 else '')) text = self.svg.node( - guides, - 'text', + guides, 'text', x=x, y=y + .35 * self.style.label_font_size, class_='major' if major else '' @@ -322,20 +283,18 @@ def _y_axis(self): if self.y_label_rotation: text.attrib['transform'] = "rotate(%d %f %f)" % ( - self.y_label_rotation, x, y - ) + self.y_label_rotation, x, y) if 90 < self.y_label_rotation < 270: - text.attrib['class'] = ' '.join(( - text.attrib['class'] - and text.attrib['class'].split(' ') or [] - ) + ['backwards']) + text.attrib['class'] = ' '.join( + (text.attrib['class'] and text.attrib['class'].split( + ' ') or []) + ['backwards']) self.svg.node( - guides, - 'title', + guides, 'title', ).text = self._y_format(position) if self._y_2nd_labels: - secondary_ax = self.svg.node(self.nodes['plot'], class_="axis y2") + secondary_ax = self.svg.node( + self.nodes['plot'], class_="axis y2") for label, position in self._y_2nd_labels: major = position in self._y_labels_major if not (self.show_minor_y_labels or major): @@ -345,8 +304,7 @@ def _y_axis(self): x = self.view.width + 5 y = self.view.y(position) text = self.svg.node( - guides, - 'text', + guides, 'text', x=x, y=y + .35 * self.style.label_font_size, class_='major' if major else '' @@ -354,13 +312,12 @@ def _y_axis(self): text.text = label if self.y_label_rotation: text.attrib['transform'] = "rotate(%d %f %f)" % ( - self.y_label_rotation, x, y - ) + self.y_label_rotation, x, y) if 90 < self.y_label_rotation < 270: - text.attrib['class'] = ' '.join(( - text.attrib['class'] - and text.attrib['class'].split(' ') or [] - ) + ['backwards']) + text.attrib['class'] = ' '.join( + (text.attrib['class'] and + text.attrib['class'].split( + ' ') or []) + ['backwards']) def _legend(self): """Make the legend box""" @@ -369,20 +326,17 @@ def _legend(self): truncation = self.truncate_legend if self.legend_at_bottom: x = self.margin_box.left + self.spacing - y = ( - self.margin_box.top + self.view.height + self._x_title_height - + self._x_labels_height + self.spacing - ) - cols = self.legend_at_bottom_columns or ceil(sqrt(self._order) - ) or 1 + y = (self.margin_box.top + self.view.height + + self._x_title_height + + self._x_labels_height + self.spacing) + cols = self.legend_at_bottom_columns or ceil( + sqrt(self._order)) or 1 if not truncation: available_space = self.view.width / cols - ( - self.legend_box_size + 5 - ) + self.legend_box_size + 5) truncation = reverse_text_len( - available_space, self.style.legend_font_size - ) + available_space, self.style.legend_font_size) else: x = self.spacing y = self.margin_box.top + self.spacing @@ -391,10 +345,8 @@ def _legend(self): truncation = 15 legends = self.svg.node( - self.nodes['graph'], - class_='legends', - transform='translate(%d, %d)' % (x, y) - ) + self.nodes['graph'], class_='legends', + transform='translate(%d, %d)' % (x, y)) h = max(self.legend_box_size, self.style.legend_font_size) x_step = self.view.width / cols @@ -406,25 +358,22 @@ def _legend(self): 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.style.label_font_size - ) - x += self.spacing + max( - w * abs(cos(rad(self.y_label_rotation))), h - ) + cut(self._y_2nd_labels), self.style.label_font_size) + x += self.spacing + max(w * abs(cos(rad( + self.y_label_rotation))), h) y = self.margin_box.top + self.spacing secondary_legends = self.svg.node( - self.nodes['graph'], - class_='legends', - transform='translate(%d, %d)' % (x, y) - ) + self.nodes['graph'], class_='legends', + transform='translate(%d, %d)' % (x, y)) serie_number = -1 i = 0 - for titles, is_secondary in ((self._legends, False), - (self._secondary_legends, True)): + for titles, is_secondary in ( + (self._legends, False), + (self._secondary_legends, True)): if not self.legend_at_bottom and is_secondary: i = 0 @@ -438,11 +387,9 @@ def _legend(self): legend = self.svg.node( secondary_legends if is_secondary else legends, class_='legend reactive activate-serie', - id="activate-serie-%d" % serie_number - ) + id="activate-serie-%d" % serie_number) self.svg.node( - legend, - 'rect', + legend, 'rect', x=col * x_step, y=1.5 * row * h + ( self.style.legend_font_size - self.legend_box_size @@ -462,8 +409,7 @@ def _legend(self): truncated = truncate(title, truncation) self.svg.node( - node, - 'text', + 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 @@ -478,22 +424,19 @@ def _make_title(self): if self._title: for i, title_line in enumerate(self._title, 1): self.svg.node( - self.nodes['title'], - 'text', - class_='title plot_title', + self.nodes['title'], 'text', class_='title plot_title', x=self.width / 2, y=i * (self.style.title_font_size + self.spacing) ).text = title_line def _make_x_title(self): """Make the X-Axis title""" - y = (self.height - self.margin_box.bottom + self._x_labels_height) + y = (self.height - self.margin_box.bottom + + self._x_labels_height) if self._x_title: for i, title_line in enumerate(self._x_title, 1): text = self.svg.node( - self.nodes['title'], - 'text', - class_='title', + self.nodes['title'], 'text', class_='title', x=self.margin_box.left + self.view.width / 2, y=y + i * (self.style.title_font_size + self.spacing) ) @@ -505,15 +448,28 @@ def _make_y_title(self): yc = self.margin_box.top + self.view.height / 2 for i, title_line in enumerate(self._y_title, 1): text = self.svg.node( - self.nodes['title'], - 'text', - class_='title', + self.nodes['title'], 'text', class_='title', x=self._legend_at_left_width, y=i * (self.style.title_font_size + self.spacing) + yc ) text.attrib['transform'] = "rotate(%d %f %f)" % ( - -90, self._legend_at_left_width, yc + -90, self._legend_at_left_width, yc) + text.text = title_line + print('legend l',self._legend_at_left_width) + print('legend r',self._legend_at_right_width) + + def _make_y2_title(self): + """Make the Y-Axis title""" + if self._y2_title: + yc = self.margin_box.top + self.view.height / 2 + for i, title_line in enumerate(self._y2_title, 1): + text = self.svg.node( + self.nodes['title'], 'text', class_='title', + x=self._legend_at_right_width, + y=i * (self.style.title_font_size + self.spacing) + yc ) + text.attrib['transform'] = "rotate(%d %f %f)" % ( + 90, self._legend_at_right_width, yc) text.text = title_line def _interpolate(self, xs, ys): @@ -527,19 +483,16 @@ def _interpolate(self, xs, ys): interpolate = INTERPOLATIONS[self.interpolate] - return list( - interpolate( - x, y, self.interpolation_precision, - **self.interpolation_parameters - ) - ) + return list(interpolate( + x, y, self.interpolation_precision, + **self.interpolation_parameters)) def _rescale(self, points): """Scale for secondary""" - return [( - x, self._scale_diff + (y - self._scale_min_2nd) * self._scale - if y is not None else None - ) for x, y in points] + return [ + (x, self._scale_diff + (y - self._scale_min_2nd) * self._scale + if y is not None else None) + for x, y in points] def _tooltip_data(self, node, value, x, y, classes=None, xlabel=None): """Insert in desc tags informations for the javascript tooltip""" @@ -552,21 +505,16 @@ def _tooltip_data(self, node, value, x, y, classes=None, xlabel=None): classes.append('top') classes = ' '.join(classes) - self.svg.node(node, 'desc', class_="x " + classes).text = to_str(x) - self.svg.node(node, 'desc', class_="y " + classes).text = to_str(y) + self.svg.node(node, 'desc', + class_="x " + classes).text = to_str(x) + self.svg.node(node, 'desc', + class_="y " + classes).text = to_str(y) if xlabel: - self.svg.node(node, 'desc', class_="x_label").text = to_str(xlabel) - - def _static_value( - self, - serie_node, - value, - x, - y, - metadata, - align_text='left', - classes=None - ): + self.svg.node(node, 'desc', + class_="x_label").text = to_str(xlabel) + + def _static_value(self, serie_node, value, x, y, metadata, + align_text='left', classes=None): """Write the print value""" label = metadata and metadata.get('label') classes = classes and [classes] or [] @@ -576,8 +524,7 @@ def _static_value( if self.print_values: y -= self.style.value_font_size / 2 self.svg.node( - serie_node['text_overlay'], - 'text', + serie_node['text_overlay'], 'text', class_=' '.join(label_cls), x=x, y=y + self.style.value_font_size / 3 @@ -590,14 +537,11 @@ def _static_value( val_cls.append('showable') self.svg.node( - serie_node['text_overlay'], - 'text', + serie_node['text_overlay'], 'text', class_=' '.join(val_cls), x=x, y=y + self.style.value_font_size / 3, - attrib={ - 'text-anchor': align_text - } + attrib={'text-anchor': align_text} ).text = value if self.print_zeroes or value != '0' else '' def _points(self, x_pos): @@ -606,7 +550,9 @@ def _points(self, x_pos): and interpolated points if interpolate option is specified """ for serie in self.all_series: - serie.points = [(x_pos[i], v) for i, v in enumerate(serie.values)] + serie.points = [ + (x_pos[i], v) + for i, v in enumerate(serie.values)] if serie.points and self.interpolate: serie.interpolated = self._interpolate(x_pos, serie.values) else: @@ -627,8 +573,12 @@ def _compute_secondary(self): left_range = abs(y_pos[-1] - y_pos[0]) right_range = abs(ymax - ymin) or 1 scale = right_range / ((steps - 1) or 1) - self._y_2nd_labels = [(self._y_format(ymin + i * scale), pos) - for i, pos in enumerate(y_pos)] + if not self.secondary_axis_round: + self._y_2nd_labels = [(self._y_format(ymin + i * scale), pos) + for i, pos in enumerate(y_pos)] + else: + self._y_2nd_labels = [(self._y_format(round(ymin + i * scale, self.secondary_axis_round)), pos) + for i, pos in enumerate(y_pos)] self._scale = left_range / right_range self._scale_diff = y_pos[0] @@ -676,18 +626,33 @@ def _format(self, serie, i): value = serie.values[i] metadata = serie.metadata.get(i) - kwargs = {'chart': self, 'serie': serie, 'index': i} - formatter = ((metadata and metadata.get('formatter')) - or serie.formatter or self.formatter - or self._value_format) + kwargs = { + 'chart': self, + 'serie': serie, + 'index': i + } + formatter = ( + (metadata and metadata.get('formatter')) or + serie.formatter or + self.formatter or + self._value_format + ) kwargs = filter_kwargs(formatter, kwargs) return formatter(value, **kwargs) def _serie_format(self, serie, value): """Format an independent value for the serie""" - kwargs = {'chart': self, 'serie': serie, 'index': None} - formatter = (serie.formatter or self.formatter or self._value_format) + kwargs = { + 'chart': self, + 'serie': serie, + 'index': None + } + formatter = ( + serie.formatter or + self.formatter or + self._value_format + ) kwargs = filter_kwargs(formatter, kwargs) return formatter(value, **kwargs) @@ -697,27 +662,22 @@ def _compute(self): def _compute_margin(self): """Compute graph margins from set texts""" self._legend_at_left_width = 0 + self._legend_at_right_width = self.width for series_group in (self.series, self.secondary_series): if self.show_legend and series_group: h, w = get_texts_box( - map( - lambda x: truncate(x, self.truncate_legend or 15), [ - serie.title['title'] - if isinstance(serie.title, dict) else serie.title - or '' for serie in series_group - ] - ), self.style.legend_font_size - ) + map(lambda x: truncate(x, self.truncate_legend or 15), + [serie.title['title'] + if isinstance(serie.title, dict) + 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) - cols = ( - self._order // self.legend_at_bottom_columns - if self.legend_at_bottom_columns else - ceil(sqrt(self._order)) or 1 - ) + cols = (self._order // self.legend_at_bottom_columns + if self.legend_at_bottom_columns + else ceil(sqrt(self._order)) or 1) self.margin_box.bottom += self.spacing + h_max * round( - cols - 1 - ) * 1.5 + h_max + cols - 1) * 1.5 + h_max else: if series_group is self.series: legend_width = self.spacing + w + self.legend_box_size @@ -725,22 +685,18 @@ def _compute_margin(self): self._legend_at_left_width += legend_width else: self.margin_box.right += ( - self.spacing + w + self.legend_box_size - ) + self.spacing + w + self.legend_box_size) self._x_labels_height = 0 if (self._x_labels or self._x_2nd_labels) and self.show_x_labels: for xlabels in (self._x_labels, self._x_2nd_labels): if xlabels: h, w = get_texts_box( - map( - lambda x: truncate(x, self.truncate_label or 25), - cut(xlabels) - ), self.style.label_font_size - ) + map(lambda x: truncate(x, self.truncate_label or 25), + cut(xlabels)), + self.style.label_font_size) self._x_labels_height = self.spacing + max( - w * abs(sin(rad(self.x_label_rotation))), h - ) + w * abs(sin(rad(self.x_label_rotation))), h) if xlabels is self._x_labels: self.margin_box.bottom += self._x_labels_height else: @@ -749,32 +705,26 @@ def _compute_margin(self): if self.x_label_rotation % 180 < 90: self.margin_box.right = max( w * abs(cos(rad(self.x_label_rotation))), - self.margin_box.right - ) + self.margin_box.right) else: self.margin_box.left = max( w * abs(cos(rad(self.x_label_rotation))), - self.margin_box.left - ) + self.margin_box.left) if self.show_y_labels: for ylabels in (self._y_labels, self._y_2nd_labels): if ylabels: h, w = get_texts_box( - cut(ylabels), self.style.label_font_size - ) + cut(ylabels), self.style.label_font_size) if ylabels is self._y_labels: self.margin_box.left += self.spacing + max( - w * abs(cos(rad(self.y_label_rotation))), h - ) + w * abs(cos(rad(self.y_label_rotation))), h) else: self.margin_box.right += self.spacing + max( - w * abs(cos(rad(self.y_label_rotation))), h - ) + w * abs(cos(rad(self.y_label_rotation))), h) self._title = split_title( - self.title, self.width, self.style.title_font_size - ) + self.title, self.width, self.style.title_font_size) if self.title: h, _ = get_text_box(self._title[0], self.style.title_font_size) @@ -782,8 +732,7 @@ def _compute_margin(self): self._x_title = split_title( self.x_title, self.width - self.margin_box.x, - self.style.title_font_size - ) + self.style.title_font_size) self._x_title_height = 0 if self._x_title: @@ -794,8 +743,7 @@ def _compute_margin(self): self._y_title = split_title( self.y_title, self.height - self.margin_box.y, - self.style.title_font_size - ) + self.style.title_font_size) self._y_title_height = 0 if self._y_title: @@ -804,6 +752,17 @@ def _compute_margin(self): self.margin_box.left += height self._y_title_height = height + self.spacing + self._y2_title = split_title( + self.y2_title, self.height - self.margin_box.y, + self.style.title_font_size) + + self._y2_title_height = 0 + if self._y2_title: + h, _ = get_text_box(self._y2_title[0], self.style.title_font_size) + height = len(self._y2_title) * (self.spacing + h) + self.margin_box.right += height + self._y2_title_height = height + self.spacing + # Inner margin if self.print_values_position == 'top': gh = self.height - self.margin_box.y @@ -820,16 +779,15 @@ def _confidence_interval(self, node, x, y, value, metadata): ci['point_estimate'] = value low, high = getattr( - stats, 'confidence_interval_%s' % ci.get('type', 'manual') + stats, + 'confidence_interval_%s' % ci.get('type', 'manual') )(**ci) self.svg.confidence_interval( - node, - x, + node, x, # Respect some charts y modifications (pyramid, stackbar) y + (self.view.y(low) - self.view.y(value)), - y + (self.view.y(high) - self.view.y(value)) - ) + y + (self.view.y(high) - self.view.y(value))) @cached_property def _legends(self): @@ -844,59 +802,54 @@ def _secondary_legends(self): @cached_property def _values(self): """Getter for series values (flattened)""" - return [ - val for serie in self.series for val in serie.values - if val is not None - ] + return [val + for serie in self.series + for val in serie.values + if val is not None] @cached_property def _secondary_values(self): """Getter for secondary series values (flattened)""" - return [ - val for serie in self.secondary_series for val in serie.values - if val is not None - ] + return [val + for serie in self.secondary_series + for val in serie.values + if val is not None] @cached_property def _len(self): """Getter for the maximum series size""" - return max([len(serie.values) for serie in self.all_series] or [0]) + return max([ + len(serie.values) + for serie in self.all_series] or [0]) @cached_property def _secondary_min(self): """Getter for the minimum series value""" - return ( - self.secondary_range[0] - if (self.secondary_range - and self.secondary_range[0] is not None) else - (min(self._secondary_values) if self._secondary_values else None) - ) + return (self.secondary_range[0] if ( + self.secondary_range and self.secondary_range[0] is not None) + else (min(self._secondary_values) + if self._secondary_values else None)) @cached_property def _min(self): """Getter for the minimum series value""" - return ( - self.range[0] if (self.range and self.range[0] is not None) else - (min(self._values) if self._values else None) - ) + return (self.range[0] if (self.range and self.range[0] is not None) + else (min(self._values) + if self._values else None)) @cached_property def _max(self): """Getter for the maximum series value""" - return ( - self.range[1] if (self.range and self.range[1] is not None) else - (max(self._values) if self._values else None) - ) + return (self.range[1] if (self.range and self.range[1] is not None) + else (max(self._values) if self._values else None)) @cached_property def _secondary_max(self): """Getter for the maximum series value""" - return ( - self.secondary_range[1] - if (self.secondary_range - and self.secondary_range[1] is not None) else - (max(self._secondary_values) if self._secondary_values else None) - ) + return (self.secondary_range[1] if ( + self.secondary_range and self.secondary_range[1] is not None) + else (max(self._secondary_values) + if self._secondary_values else None)) @cached_property def _order(self): @@ -910,17 +863,13 @@ def _x_label_format_if_value(self, label): def _compute_x_labels(self): self._x_labels = self.x_labels and list( - zip( - map(self._x_label_format_if_value, self.x_labels), self._x_pos - ) - ) + zip(map(self._x_label_format_if_value, self.x_labels), + self._x_pos)) def _compute_x_labels_major(self): if self.x_labels_major_every: - self._x_labels_major = [ - self._x_labels[i][0] for i in - range(0, len(self._x_labels), self.x_labels_major_every) - ] + self._x_labels_major = [self._x_labels[i][0] for i in range( + 0, len(self._x_labels), self.x_labels_major_every)] elif self.x_labels_major_count: label_count = len(self._x_labels) @@ -929,20 +878,17 @@ def _compute_x_labels_major(self): self._x_labels_major = [label[0] for label in self._x_labels] else: - self._x_labels_major = [ - self._x_labels[int( - i * (label_count - 1) / (major_count - 1) - )][0] for i in range(major_count) - ] + self._x_labels_major = [self._x_labels[ + int(i * (label_count - 1) / (major_count - 1))][0] + for i in range(major_count)] else: self._x_labels_major = self.x_labels_major and list( - map(self._x_label_format_if_value, self.x_labels_major) - ) or [] + map(self._x_label_format_if_value, self.x_labels_major)) or [] def _compute_y_labels(self): y_pos = compute_scale( - self._box.ymin, self._box.ymax, self.logarithmic, self.order_min, - self.min_scale, self.max_scale + self._box.ymin, self._box.ymax, self.logarithmic, + self.order_min, self.min_scale, self.max_scale ) if self.y_labels: self._y_labels = [] @@ -964,10 +910,8 @@ def _compute_y_labels(self): def _compute_y_labels_major(self): if self.y_labels_major_every: - self._y_labels_major = [ - self._y_labels[i][1] for i in - range(0, len(self._y_labels), self.y_labels_major_every) - ] + self._y_labels_major = [self._y_labels[i][1] for i in range( + 0, len(self._y_labels), self.y_labels_major_every)] elif self.y_labels_major_count: label_count = len(self._y_labels) @@ -975,11 +919,9 @@ def _compute_y_labels_major(self): if (major_count >= label_count): self._y_labels_major = [label[1] for label in self._y_labels] else: - self._y_labels_major = [ - self._y_labels[int( - i * (label_count - 1) / (major_count - 1) - )][1] for i in range(major_count) - ] + self._y_labels_major = [self._y_labels[ + int(i * (label_count - 1) / (major_count - 1))][1] + for i in range(major_count)] elif self.y_labels_major: self._y_labels_major = list(map(self._adapt, self.y_labels_major)) @@ -998,22 +940,19 @@ def add_squares(self, squares): for line in range(x_lines): _current_x += (self.width - self.margin_box.x) / squares[0] self.svg.node( - self.nodes['plot'], - 'path', + self.nodes['plot'], 'path', class_='bg-lines', - d='M%s %s L%s %s' % - (_current_x, 0, _current_x, self.height - self.margin_box.y) - ) + d='M%s %s L%s %s' % ( + _current_x, 0, _current_x, + self.height - self.margin_box.y)) for line in range(y_lines): _current_y += (self.height - self.margin_box.y) / squares[1] self.svg.node( - self.nodes['plot'], - 'path', + self.nodes['plot'], 'path', class_='bg-lines', - d='M%s %s L%s %s' % - (0, _current_y, self.width - self.margin_box.x, _current_y) - ) + d='M%s %s L%s %s' % ( + 0, _current_y, self.width - self.margin_box.x, _current_y)) return ((self.width - self.margin_box.x) / squares[0], (self.height - self.margin_box.y) / squares[1]) @@ -1038,6 +977,7 @@ def _has_data(self): return any([ len([ 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 + 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/line.py b/pygal/graph/line.py index cd0c457c..b2dc3370 100644 --- a/pygal/graph/line.py +++ b/pygal/graph/line.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ Line chart: Display series of data as markers (dots) connected by straight segments @@ -28,6 +29,7 @@ class Line(Graph): + """Line graph class""" def __init__(self, *args, **kwargs): @@ -39,19 +41,21 @@ def __init__(self, *args, **kwargs): def _values(self): """Getter for series values (flattened)""" return [ - val[1] for serie in self.series for val in - (serie.interpolated if self.interpolate else serie.points) - if val[1] is not None and (not self.logarithmic or val[1] > 0) - ] + val[1] + for serie in self.series + for val in (serie.interpolated + if self.interpolate else serie.points) + if val[1] is not None and (not self.logarithmic or val[1] > 0)] @cached_property def _secondary_values(self): """Getter for secondary series values (flattened)""" return [ - val[1] for serie in self.secondary_series for val in - (serie.interpolated if self.interpolate else serie.points) - if val[1] is not None and (not self.logarithmic or val[1] > 0) - ] + val[1] + for serie in self.secondary_series + for val in (serie.interpolated + if self.interpolate else serie.points) + if val[1] is not None and (not self.logarithmic or val[1] > 0)] def _fill(self, values): """Add extra values to fill the line""" @@ -76,12 +80,12 @@ def _fill(self, values): "Invalid value ({}) for config key " "'missing_value_fill_truncation';" " Use 'x', 'y' or 'either'".format( - self.missing_value_fill_truncation - ) - ) + self.missing_value_fill_truncation)) end -= 1 - return ([(values[0][0], zero)] + values + [(values[end][0], zero)]) + return ([(values[0][0], zero)] + + values + + [(values[end][0], zero)]) def line(self, serie, rescale=False): """Draw the line serie""" @@ -98,9 +102,9 @@ def line(self, serie, rescale=False): if self.logarithmic: if points[i][1] is None or points[i][1] <= 0: continue - if (serie.show_only_major_dots and self.x_labels - and i < len(self.x_labels) - and self.x_labels[i] not in self._x_labels_major): + if (serie.show_only_major_dots and + self.x_labels and i < len(self.x_labels) and + self.x_labels[i] not in self._x_labels_major): continue metadata = serie.metadata.get(i) @@ -112,33 +116,25 @@ def line(self, serie, rescale=False): classes = ' '.join(classes) self._confidence_interval( - serie_node['overlay'], x, y, serie.values[i], metadata - ) + serie_node['overlay'], x, y, serie.values[i], metadata) dots = decorate( self.svg, self.svg.node(serie_node['overlay'], class_="dots"), - metadata - ) + metadata) val = self._format(serie, i) - alter( - self.svg.transposable_node( - dots, - 'circle', - cx=x, - cy=y, - r=serie.dots_size, - class_='dot reactive tooltip-trigger' - ), metadata - ) + alter(self.svg.transposable_node( + dots, 'circle', cx=x, cy=y, r=serie.dots_size, + class_='dot reactive tooltip-trigger'), metadata) self._tooltip_data( - dots, val, x, y, xlabel=self._get_x_label(i) - ) + dots, val, x, y, + xlabel=self._get_x_label(i)) self._static_value( - serie_node, val, x + self.style.value_font_size, - y + self.style.value_font_size, metadata - ) + serie_node, val, + x + self.style.value_font_size, + y + self.style.value_font_size, + metadata) if serie.stroke: if self.interpolate: @@ -161,12 +157,12 @@ def line(self, serie, rescale=False): # emit current subsequence sequences.append(cur_sequence) cur_sequence = [] - elif y is None: # just discard + elif y is None: # just discard continue else: - cur_sequence.append((x, y)) # append the element + cur_sequence.append((x, y)) # append the element - if len(cur_sequence) > 0: # emit last possible sequence + if len(cur_sequence) > 0: # emit last possible sequence sequences.append(cur_sequence) else: # plain vanilla rendering @@ -179,12 +175,9 @@ def line(self, serie, rescale=False): del seq[seq.index(ele)] for seq in sequences: self.svg.line( - serie_node['plot'], - seq, - close=self._self_close, - class_='line reactive' - + (' nofill' if not serie.fill else '') - ) + serie_node['plot'], seq, close=self._self_close, + class_='line reactive' + + (' nofill' if not serie.fill else '')) def _compute(self): """Compute y min and max and y scale and set labels""" diff --git a/pygal/style.py b/pygal/style.py index 9b4b3914..80aeb98e 100644 --- a/pygal/style.py +++ b/pygal/style.py @@ -27,6 +27,7 @@ class Style(object): + """Styling class containing colors for the css generation""" plot_background = 'rgba(255, 255, 255, 1)' @@ -35,9 +36,11 @@ class Style(object): foreground = 'rgba(0, 0, 0, .87)' foreground_strong = 'rgba(0, 0, 0, 1)' foreground_subtle = 'rgba(0, 0, 0, .54)' + guide_color = 'rgba(158, 158, 158, 1)' # Monospaced font is highly encouraged - font_family = ('Consolas, "Liberation Mono", Menlo, Courier, monospace') + font_family = ( + 'Consolas, "Liberation Mono", Menlo, Courier, monospace') label_font_family = None major_label_font_family = None @@ -60,18 +63,12 @@ class Style(object): # Guide line dash array style guide_stroke_dasharray = '4,4' major_guide_stroke_dasharray = '6,6' - guide_stroke_color = 'black' - major_guide_stroke_color = 'black' opacity = '.7' opacity_hover = '.8' stroke_opacity = '.8' - stroke_width = '1' stroke_opacity_hover = '.9' - stroke_width_hover = '4' - - dot_opacity = '1' transition = '150ms' colors = ( @@ -114,36 +111,33 @@ def __init__(self, **kwargs): elif fn.startswith('googlefont:'): setattr(self, name, fn.replace('googlefont:', '')) self._google_fonts.add( - getattr(self, name).split(',')[0].strip() - ) + getattr(self, name).split(',')[0].strip()) def get_colors(self, prefix, len_): """Get the css color list""" - def color(tupl): """Make a color css""" return (( '%s.color-{0}, %s.color-{0} a:visited {{\n' ' stroke: {1};\n' ' fill: {1};\n' - '}}\n' - ) % (prefix, prefix)).format(*tupl) + '}}\n') % (prefix, prefix)).format(*tupl) def value_color(tupl): """Make a value color css""" return (( '%s .text-overlay .color-{0} text {{\n' ' fill: {1};\n' - '}}\n' - ) % (prefix, )).format(*tupl) + '}}\n') % (prefix,)).format(*tupl) def ci_color(tupl): """Make a value color css""" if not tupl[1]: return '' - return (('%s .color-{0} .ci {{\n' - ' stroke: {1};\n' - '}}\n') % (prefix, )).format(*tupl) + return (( + '%s .color-{0} .ci {{\n' + ' stroke: {1};\n' + '}}\n') % (prefix,)).format(*tupl) if len(self.colors) < len_: missing = len_ - len(self.colors) @@ -166,17 +160,13 @@ def ci_color(tupl): if i < len(self.value_colors) and self.value_colors[i] is not None: value_colors.append(self.value_colors[i]) else: - value_colors.append( - 'white' if is_foreground_light(colors[i]) else 'black' - ) - - return '\n'.join( - chain( - map(color, enumerate(colors)), - map(value_color, enumerate(value_colors)), - map(ci_color, enumerate(self.ci_colors)) - ) - ) + value_colors.append('white' if is_foreground_light( + colors[i]) else 'black') + + return '\n'.join(chain( + map(color, enumerate(colors)), + map(value_color, enumerate(value_colors)), + map(ci_color, enumerate(self.ci_colors)))) def to_dict(self): """Convert instance to a serializable mapping.""" @@ -193,6 +183,7 @@ def to_dict(self): class DarkStyle(Style): + """A dark style (old default)""" background = 'black' @@ -204,13 +195,14 @@ class DarkStyle(Style): opacity_hover = '.4' transition = '250ms' colors = ( - '#ff5995', '#b6e354', '#feed6c', '#8cedff', '#9e6ffe', '#899ca1', - '#f8f8f2', '#bf4646', '#516083', '#f92672', '#82b414', '#fd971f', - '#56c2d6', '#808384', '#8c54fe', '#465457' - ) + '#ff5995', '#b6e354', '#feed6c', '#8cedff', '#9e6ffe', + '#899ca1', '#f8f8f2', '#bf4646', '#516083', '#f92672', + '#82b414', '#fd971f', '#56c2d6', '#808384', '#8c54fe', + '#465457') class LightStyle(Style): + """A light style""" background = 'white' @@ -218,13 +210,13 @@ class LightStyle(Style): foreground = 'rgba(0, 0, 0, 0.7)' foreground_strong = 'rgba(0, 0, 0, 0.9)' foreground_subtle = 'rgba(0, 0, 0, 0.5)' - colors = ( - '#242424', '#9f6767', '#92ac68', '#d0d293', '#9aacc3', '#bb77a4', - '#77bbb5', '#777777' - ) + colors = ('#242424', '#9f6767', '#92ac68', + '#d0d293', '#9aacc3', '#bb77a4', + '#77bbb5', '#777777') class NeonStyle(DarkStyle): + """Similar to DarkStyle but with more opacity and effects""" opacity = '.1' @@ -233,6 +225,7 @@ class NeonStyle(DarkStyle): class CleanStyle(Style): + """A rather clean style""" background = 'transparent' @@ -242,11 +235,11 @@ class CleanStyle(Style): foreground_subtle = 'rgba(0, 0, 0, 0.5)' colors = ( 'rgb(12,55,149)', 'rgb(117,38,65)', 'rgb(228,127,0)', 'rgb(159,170,0)', - 'rgb(149,12,12)' - ) + 'rgb(149,12,12)') class DarkSolarizedStyle(Style): + """Dark solarized popular theme""" background = '#073642' @@ -258,12 +251,12 @@ class DarkSolarizedStyle(Style): opacity_hover = '.9' transition = '500ms ease-in' colors = ( - '#b58900', '#cb4b16', '#dc322f', '#d33682', '#6c71c4', '#268bd2', - '#2aa198', '#859900' - ) + '#b58900', '#cb4b16', '#dc322f', '#d33682', + '#6c71c4', '#268bd2', '#2aa198', '#859900') class LightSolarizedStyle(DarkSolarizedStyle): + """Light solarized popular theme""" background = '#fdf6e3' @@ -274,6 +267,7 @@ class LightSolarizedStyle(DarkSolarizedStyle): class RedBlueStyle(Style): + """A red and blue theme""" background = lighten('#e6e7e9', 7) @@ -284,13 +278,13 @@ class RedBlueStyle(Style): opacity = '.6' opacity_hover = '.9' colors = ( - '#d94e4c', '#e5884f', '#39929a', lighten('#d94e4c', 10), - darken('#39929a', 15), lighten('#e5884f', 17), darken('#d94e4c', 10), - '#234547' - ) + '#d94e4c', '#e5884f', '#39929a', + lighten('#d94e4c', 10), darken('#39929a', 15), lighten('#e5884f', 17), + darken('#d94e4c', 10), '#234547') class LightColorizedStyle(Style): + """A light colorized style""" background = '#f8f8f8' @@ -302,13 +296,13 @@ class LightColorizedStyle(Style): opacity_hover = '.9' transition = '250ms ease-in' colors = ( - '#fe9592', '#534f4c', '#3ac2c0', '#a2a7a1', darken('#fe9592', 15), - lighten('#534f4c', 15), lighten('#3ac2c0', 15), lighten('#a2a7a1', 15), - lighten('#fe9592', 15), darken('#3ac2c0', 10) - ) + '#fe9592', '#534f4c', '#3ac2c0', '#a2a7a1', + darken('#fe9592', 15), lighten('#534f4c', 15), lighten('#3ac2c0', 15), + lighten('#a2a7a1', 15), lighten('#fe9592', 15), darken('#3ac2c0', 10)) class DarkColorizedStyle(Style): + """A dark colorized style""" background = darken('#3a2d3f', 5) @@ -322,11 +316,11 @@ class DarkColorizedStyle(Style): colors = ( '#c900fe', '#01b8fe', '#59f500', '#ff00e4', '#f9fa00', darken('#c900fe', 20), darken('#01b8fe', 15), darken('#59f500', 20), - darken('#ff00e4', 15), lighten('#f9fa00', 20) - ) + darken('#ff00e4', 15), lighten('#f9fa00', 20)) class TurquoiseStyle(Style): + """A turquoise style""" background = darken('#1b8088', 15) @@ -338,12 +332,13 @@ class TurquoiseStyle(Style): opacity_hover = '.9' transition = '250ms ease-in' colors = ( - '#93d2d9', '#ef940f', '#8C6243', '#fff', darken('#93d2d9', 20), - lighten('#ef940f', 15), lighten('#8c6243', 15), '#1b8088' - ) + '#93d2d9', '#ef940f', '#8C6243', '#fff', + darken('#93d2d9', 20), lighten('#ef940f', 15), + lighten('#8c6243', 15), '#1b8088') class LightGreenStyle(Style): + """A light green style""" background = lighten('#f3f3f3', 3) @@ -357,11 +352,11 @@ class LightGreenStyle(Style): colors = ( '#7dcf30', '#247fab', lighten('#7dcf30', 10), '#ccc', darken('#7dcf30', 15), '#ddd', lighten('#247fab', 10), - darken('#247fab', 15) - ) + darken('#247fab', 15)) class DarkGreenStyle(Style): + """A dark green style""" background = darken('#251e01', 3) @@ -374,11 +369,11 @@ class DarkGreenStyle(Style): transition = '250ms ease-in' colors = ( '#adde09', '#6e8c06', '#4a5e04', '#fcd202', '#C1E34D', - lighten('#fcd202', 25) - ) + lighten('#fcd202', 25)) class DarkGreenBlueStyle(Style): + """A dark green and blue style""" background = '#000' @@ -389,14 +384,13 @@ class DarkGreenBlueStyle(Style): opacity = '.55' opacity_hover = '.9' transition = '250ms ease-in' - colors = ( - lighten('#34B8F7', 15), '#7dcf30', '#247fab', darken('#7dcf30', 10), - lighten('#247fab', 10), lighten('#7dcf30', 10), darken('#247fab', 10), - '#fff' - ) + colors = (lighten('#34B8F7', 15), '#7dcf30', '#247fab', + darken('#7dcf30', 10), lighten('#247fab', 10), + lighten('#7dcf30', 10), darken('#247fab', 10), '#fff') class BlueStyle(Style): + """A blue style""" background = darken('#f8f8f8', 3) @@ -410,11 +404,11 @@ class BlueStyle(Style): colors = ( '#00b2f0', '#43d9be', '#0662ab', darken('#00b2f0', 20), lighten('#43d9be', 20), lighten('#7dcf30', 10), darken('#0662ab', 15), - '#ffd541', '#7dcf30', lighten('#00b2f0', 15), darken('#ffd541', 20) - ) + '#ffd541', '#7dcf30', lighten('#00b2f0', 15), darken('#ffd541', 20)) class SolidColorStyle(Style): + """A light style with strong colors""" background = '#FFFFFF' @@ -426,32 +420,30 @@ class SolidColorStyle(Style): opacity_hover = '.9' transition = '400ms ease-in' colors = ( - '#FF9900', '#DC3912', '#4674D1', '#109618', '#990099', '#0099C6', - '#DD4477', '#74B217', '#B82E2E', '#316395', '#994499' - ) - - -styles = { - 'default': DefaultStyle, - 'dark': DarkStyle, - 'light': LightStyle, - 'neon': NeonStyle, - 'clean': CleanStyle, - 'light_red_blue': RedBlueStyle, - 'dark_solarized': DarkSolarizedStyle, - 'light_solarized': LightSolarizedStyle, - 'dark_colorized': DarkColorizedStyle, - 'light_colorized': LightColorizedStyle, - 'turquoise': TurquoiseStyle, - 'green': LightGreenStyle, - 'dark_green': DarkGreenStyle, - 'dark_green_blue': DarkGreenBlueStyle, - 'blue': BlueStyle, - 'solid_color': SolidColorStyle -} + '#FF9900', '#DC3912', '#4674D1', '#109618', '#990099', + '#0099C6', '#DD4477', '#74B217', '#B82E2E', '#316395', '#994499') + + +styles = {'default': DefaultStyle, + 'dark': DarkStyle, + 'light': LightStyle, + 'neon': NeonStyle, + 'clean': CleanStyle, + 'light_red_blue': RedBlueStyle, + 'dark_solarized': DarkSolarizedStyle, + 'light_solarized': LightSolarizedStyle, + 'dark_colorized': DarkColorizedStyle, + 'light_colorized': LightColorizedStyle, + 'turquoise': TurquoiseStyle, + 'green': LightGreenStyle, + 'dark_green': DarkGreenStyle, + 'dark_green_blue': DarkGreenBlueStyle, + 'blue': BlueStyle, + 'solid_color': SolidColorStyle} class ParametricStyleBase(Style): + """Parametric Style base class for all the parametric operations""" _op = None @@ -498,30 +490,35 @@ def modifier(index): class LightenStyle(ParametricStyleBase): + """Create a style by lightening the given color""" _op = 'lighten' class DarkenStyle(ParametricStyleBase): + """Create a style by darkening the given color""" _op = 'darken' class SaturateStyle(ParametricStyleBase): + """Create a style by saturating the given color""" _op = 'saturate' class DesaturateStyle(ParametricStyleBase): + """Create a style by desaturating the given color""" _op = 'desaturate' class RotateStyle(ParametricStyleBase): + """Create a style by rotating the given color""" _op = 'rotate' From 0d8be7b71dbb35348e5d37679e211f0611708fec Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Tue, 21 May 2019 23:03:34 -0300 Subject: [PATCH 05/10] Stroke width --- pygal/css/style.css | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pygal/css/style.css b/pygal/css/style.css index 74cb59e2..171c3be7 100644 --- a/pygal/css/style.css +++ b/pygal/css/style.css @@ -68,11 +68,11 @@ } {{ id }}.axis .guide.line { - stroke: {{ style.foreground_subtle }}; + stroke: {{ style.guide_color }}; } {{ id }}.axis .major.line { - stroke: {{ style.foreground }}; + stroke: {{ style.guide_color }}; } {{ id }}.axis text.major { @@ -94,7 +94,6 @@ {{ id }}.reactive { fill-opacity: {{ style.opacity }}; stroke-opacity: {{ style.stroke_opacity }}; - stroke-width: {{ style.stroke_width }}; } {{ id }}.ci { @@ -105,7 +104,7 @@ {{ id }}.active .reactive { fill-opacity: {{ style.opacity_hover }}; stroke-opacity: {{ style.stroke_opacity_hover }}; - stroke-width: {{ style.stroke_width_hover }}; + stroke-width: 4; } {{ id }}.ci .reactive.active { From 3725c48d6b50b3addd9a5f0e208a2f5766ab4134 Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Tue, 21 May 2019 23:09:20 -0300 Subject: [PATCH 06/10] Guide stroke color --- pygal/css/graph.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pygal/css/graph.css b/pygal/css/graph.css index 98f853de..a638fb73 100644 --- a/pygal/css/graph.css +++ b/pygal/css/graph.css @@ -68,12 +68,10 @@ {{ id }}.axis .guide.line { stroke-dasharray: {{ style.guide_stroke_dasharray }}; - stroke: {{ style.guide_stroke_color }}; } {{ id }}.axis .major.guide.line { stroke-dasharray: {{ style.major_guide_stroke_dasharray }}; - stroke: {{ style.major_guide_stroke_color }}; } {{ id }}.horizontal .axis.y .guide.line, @@ -107,8 +105,7 @@ {{ id }}.dot { stroke-width: 1px; - fill-opacity: {{ style.dot_opacity }}; - stroke-opacity: {{ style.dot_opacity }}; + fill-opacity: 1; } {{ id }}.dot.active { From 89fe3e2784793310f2dd6e842603a5c1036386df Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Tue, 21 May 2019 23:42:00 -0300 Subject: [PATCH 07/10] ... --- pygal/__init__.py | 16 +- pygal/_compat.py | 2 - pygal/etree.py | 5 +- pygal/formatters.py | 8 +- pygal/graph/__init__.py | 1 + pygal/graph/bar.py | 43 ++- pygal/graph/base.py | 75 +++-- pygal/graph/box.py | 128 ++++----- pygal/graph/dot.py | 70 +++-- pygal/graph/dual.py | 12 +- pygal/graph/funnel.py | 42 ++- pygal/graph/gauge.py | 67 +++-- pygal/graph/histogram.py | 54 ++-- pygal/graph/horizontal.py | 16 +- pygal/graph/horizontalbar.py | 2 + pygal/graph/horizontalline.py | 2 + pygal/graph/horizontalstackedbar.py | 2 + pygal/graph/horizontalstackedline.py | 2 + pygal/graph/map.py | 24 +- pygal/graph/pie.py | 24 +- pygal/graph/public.py | 20 +- pygal/graph/pyramid.py | 40 ++- pygal/graph/radar.py | 92 +++--- pygal/graph/solidgauge.py | 53 ++-- pygal/graph/stackedbar.py | 71 ++--- pygal/graph/stackedline.py | 19 +- pygal/graph/time.py | 25 +- pygal/graph/treemap.py | 52 ++-- pygal/graph/xy.py | 60 ++-- pygal/interpolate.py | 78 +++--- pygal/maps/__init__.py | 1 + pygal/serie.py | 1 + pygal/state.py | 2 + pygal/stats.py | 16 +- pygal/svg.py | 295 +++++++++----------- pygal/table.py | 27 +- pygal/test/__init__.py | 14 +- pygal/test/conftest.py | 12 +- pygal/test/test_bar.py | 1 + pygal/test/test_box.py | 49 ++-- pygal/test/test_colors.py | 4 +- pygal/test/test_config.py | 95 +++---- pygal/test/test_date.py | 167 +++++++---- pygal/test/test_formatters.py | 1 + pygal/test/test_graph.py | 205 ++++++-------- pygal/test/test_histogram.py | 9 +- pygal/test/test_interpolate.py | 58 ++-- pygal/test/test_line.py | 23 +- pygal/test/test_line_log_none_max_solved.py | 1 - pygal/test/test_maps.py | 1 + pygal/test/test_pie.py | 1 + pygal/test/test_serie_config.py | 1 + pygal/test/test_sparktext.py | 1 + pygal/test/test_stacked.py | 13 +- pygal/test/test_style.py | 9 +- pygal/test/test_table.py | 4 +- pygal/test/test_util.py | 45 +-- pygal/test/test_view.py | 2 + pygal/test/test_xml_filters.py | 16 +- pygal/test/utils.py | 1 + pygal/util.py | 70 +++-- pygal/view.py | 78 +++--- 62 files changed, 1114 insertions(+), 1214 deletions(-) diff --git a/pygal/__init__.py b/pygal/__init__.py index 70eb71f8..ca017097 100644 --- a/pygal/__init__.py +++ b/pygal/__init__.py @@ -54,20 +54,19 @@ from pygal.config import Config from pygal import maps -CHARTS_BY_NAME = dict([ - (k, v) for k, v in locals().items() - if isinstance(v, type) and issubclass(v, Graph) and v != Graph -]) + +CHARTS_BY_NAME = dict( + [(k, v) for k, v in locals().items() + if isinstance(v, type) and issubclass(v, Graph) and v != Graph]) + from pygal.graph.map import BaseMap for entry in pkg_resources.iter_entry_points('pygal.maps'): try: module = entry.load() except Exception: - warnings.warn( - 'Unable to load %s pygal plugin \n\n%s' % - (entry, traceback.format_exc()), Warning - ) + warnings.warn('Unable to load %s pygal plugin \n\n%s' % ( + entry, traceback.format_exc()), Warning) continue setattr(maps, entry.name, module) for k, v in module.__dict__.items(): @@ -79,6 +78,7 @@ class PluginImportFixer(object): + """ Allow external map plugins to be imported from pygal.maps package. diff --git a/pygal/_compat.py b/pygal/_compat.py index 07ab8846..5debb4b6 100644 --- a/pygal/_compat.py +++ b/pygal/_compat.py @@ -73,7 +73,6 @@ def u(s): from datetime import timezone utc = timezone.utc except ImportError: - class UTC(tzinfo): def tzname(self, dt): return 'UTC' @@ -83,7 +82,6 @@ def utcoffset(self, dt): def dst(self, dt): return None - utc = UTC() diff --git a/pygal/etree.py b/pygal/etree.py index 5f561b42..6ed513fa 100644 --- a/pygal/etree.py +++ b/pygal/etree.py @@ -25,6 +25,7 @@ class Etree(object): + """Etree wrapper using lxml.etree or standard xml.etree""" def __init__(self): @@ -45,8 +46,8 @@ def __init__(self): def __getattribute__(self, attr): """Retrieve attr from current active etree implementation""" - if (attr not in object.__getattribute__(self, '__dict__') - and attr not in Etree.__dict__): + if (attr not in object.__getattribute__(self, '__dict__') and + attr not in Etree.__dict__): return object.__getattribute__(self._etree, attr) return object.__getattribute__(self, attr) diff --git a/pygal/formatters.py b/pygal/formatters.py index 6e8ec395..fe901b52 100644 --- a/pygal/formatters.py +++ b/pygal/formatters.py @@ -46,16 +46,14 @@ def __call__(self, val): order = val and int(floor(log(abs(val)) / log(1000))) orders = self.ORDERS.split(" ")[int(order > 0)] if order == 0 or order > len(orders): - return float_format(val / (1000**int(order))) + return float_format(val / (1000 ** int(order))) return ( - float_format(val / (1000**int(order))) - + orders[int(order) - int(order > 0)] - ) + float_format(val / (1000 ** int(order))) + + orders[int(order) - int(order > 0)]) class Significant(Formatter): """Show precision significant digit of float""" - def __init__(self, precision=10): self.format = '%%.%dg' % precision diff --git a/pygal/graph/__init__.py b/pygal/graph/__init__.py index dcd25ba1..98c527c0 100644 --- a/pygal/graph/__init__.py +++ b/pygal/graph/__init__.py @@ -16,4 +16,5 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Graph package containing all builtin charts""" diff --git a/pygal/graph/bar.py b/pygal/graph/bar.py index 33679602..488b759a 100644 --- a/pygal/graph/bar.py +++ b/pygal/graph/bar.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ Bar chart that presents grouped data with rectangular bars with lengths proportional to the values that they represent. @@ -28,6 +29,7 @@ class Bar(Graph): + """Bar graph class""" _series_margin = .06 @@ -52,25 +54,15 @@ def _bar(self, serie, parent, x, y, i, zero, secondary=False): width -= 2 * serie_margin height = self.view.y(zero) - y r = serie.rounded_bars * 1 if serie.rounded_bars else 0 - alter( - self.svg.transposable_node( - parent, - 'rect', - x=x, - y=y, - rx=r, - ry=r, - width=width, - height=height, - class_='rect reactive tooltip-trigger' - ), serie.metadata.get(i) - ) + alter(self.svg.transposable_node( + parent, 'rect', + x=x, y=y, rx=r, ry=r, width=width, height=height, + class_='rect reactive tooltip-trigger'), serie.metadata.get(i)) return x, y, width, height def _tooltip_and_print_values( - self, serie_node, serie, parent, i, val, metadata, x, y, width, - height - ): + self, serie_node, serie, parent, i, val, metadata, + x, y, width, height): 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)) @@ -81,8 +73,8 @@ def _tooltip_and_print_values( v = serie.values[i] sign = -1 if v < self.zero else 1 self._tooltip_data( - parent, val, x_center, y_center, "centered", self._get_x_label(i) - ) + parent, val, x_center, y_center, "centered", + self._get_x_label(i)) if self.print_values_position == 'top': if self.horizontal: @@ -119,21 +111,20 @@ def bar(self, serie, rescale=False): val = self._format(serie, i) bar = decorate( - self.svg, self.svg.node(bars, class_='bar'), metadata - ) + self.svg, + self.svg.node(bars, class_='bar'), + metadata) x_, y_, width, height = self._bar( - serie, bar, x, y, i, self.zero, secondary=rescale - ) + serie, bar, x, y, i, self.zero, secondary=rescale) self._confidence_interval( serie_node['overlay'], x_ + width / 2, y_, serie.values[i], - metadata - ) + metadata) self._tooltip_and_print_values( - serie_node, serie, bar, i, val, metadata, x_, y_, width, height - ) + serie_node, serie, bar, i, val, metadata, + x_, y_, width, height) def _compute(self): """Compute y min and max and y scale and set labels""" diff --git a/pygal/graph/base.py b/pygal/graph/base.py index 87c73e48..cd33a74f 100644 --- a/pygal/graph/base.py +++ b/pygal/graph/base.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Base for pygal charts""" from __future__ import division @@ -35,6 +36,7 @@ class BaseGraph(object): + """Chart internal behaviour related functions""" _adapters = [] @@ -66,8 +68,8 @@ def __setattr__(self, name, value): def __getattribute__(self, name): """Get an attribute from the class or from the state if there is one""" if name.startswith('__') or name == 'state' or getattr( - self, 'state', - None) is None or name not in self.state.__dict__: + self, 'state', None + ) is None or name not in self.state.__dict__: return super(BaseGraph, self).__getattribute__(name) return getattr(self.state, name) @@ -91,7 +93,7 @@ def prepare_values(self, raw, offset=0): if not raw: return - adapters = list(self._adapters) or [lambda x: x] + adapters = list(self._adapters) or [lambda x:x] if self.logarithmic: for fun in not_zero, positive: if fun in adapters: @@ -101,18 +103,19 @@ def prepare_values(self, raw, offset=0): self._adapt = reduce(compose, adapters) if not self.strict else ident self._x_adapt = reduce( - compose, self._x_adapters - ) if not self.strict and getattr(self, '_x_adapters', None) else ident + compose, self._x_adapters) if not self.strict and getattr( + self, '_x_adapters', None) else ident series = [] raw = [( - list(raw_values) if not isinstance(raw_values, dict) else - raw_values, serie_config_kwargs + list(raw_values) if not isinstance( + raw_values, dict) else raw_values, + serie_config_kwargs ) for raw_values, serie_config_kwargs in raw] - width = max([len(values) - for values, _ in raw] + [len(self.x_labels or [])]) + width = max([len(values) for values, _ in raw] + + [len(self.x_labels or [])]) for raw_values, serie_config_kwargs in raw: metadata = {} @@ -127,9 +130,10 @@ def prepare_values(self, raw, offset=0): value_list[self.x_labels.index(k)] = v raw_values = value_list - for index, raw_value in enumerate(raw_values + ( - (width - len(raw_values)) * [None] # aligning values - if len(raw_values) < width else [])): + for index, raw_value in enumerate( + raw_values + ( + (width - len(raw_values)) * [None] # aligning values + if len(raw_values) < width else [])): if isinstance(raw_value, dict): raw_value = dict(raw_value) value = raw_value.pop('value', None) @@ -153,8 +157,8 @@ def prepare_values(self, raw, offset=0): value = (value, self.zero) if self._x_adapt: value = ( - self._x_adapt(value[0]), self._adapt(value[1]) - ) + self._x_adapt(value[0]), + self._adapt(value[1])) if isinstance(self, BaseMap): value = (self._adapt(value[0]), value[1]) else: @@ -164,14 +168,11 @@ def prepare_values(self, raw, offset=0): values.append(value) serie_config = SerieConfig() - serie_config( - **dict((k, v) for k, v in self.state.__dict__.items() - if k in dir(serie_config)) - ) + serie_config(**dict((k, v) for k, v in self.state.__dict__.items() + if k in dir(serie_config))) serie_config(**serie_config_kwargs) series.append( - Serie(offset + len(series), values, serie_config, metadata) - ) + Serie(offset + len(series), values, serie_config, metadata)) return series def setup(self, **kwargs): @@ -184,12 +185,11 @@ def setup(self, **kwargs): self.state = State(self, **kwargs) if isinstance(self.style, type): self.style = self.style() - self.series = self.prepare_values([ - rs for rs in self.raw_series if not rs[1].get('secondary') - ]) or [] - self.secondary_series = self.prepare_values([ - rs for rs in self.raw_series if rs[1].get('secondary') - ], len(self.series)) or [] + self.series = self.prepare_values( + [rs for rs in self.raw_series if not rs[1].get('secondary')]) or [] + self.secondary_series = self.prepare_values( + [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 @@ -198,23 +198,20 @@ def setup(self, **kwargs): self._y_2nd_labels = None self.nodes = {} self.margin_box = Margin( - self.margin_top or self.margin, self.margin_right or self.margin, - self.margin_bottom or self.margin, self.margin_left or self.margin - ) + self.margin_top or self.margin, + self.margin_right or self.margin, + self.margin_bottom or self.margin, + self.margin_left or self.margin) self._box = Box() self.view = None if self.logarithmic and self.zero == 0: # Explicit min to avoid interpolation dependency - positive_values = list( - filter( - lambda x: x > 0, [ - val[1] or 1 if self._dual else val - for serie in self.series for val in serie.safe_values - ] - ) - ) - - self.zero = min(positive_values or (1, )) or 1 + positive_values = list(filter( + lambda x: x > 0, + [val[1] or 1 if self._dual else val + for serie in self.series for val in serie.safe_values])) + + self.zero = min(positive_values or (1,)) or 1 if self._len < 3: self.interpolate = None self._draw() diff --git a/pygal/graph/box.py b/pygal/graph/box.py index 66975e1a..5a77afcc 100644 --- a/pygal/graph/box.py +++ b/pygal/graph/box.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ Box plot: a convenient way to display series as box with whiskers and outliers Different types are available throught the box_mode option @@ -30,6 +31,7 @@ class Box(Graph): + """ Box plot For each series, shows the median value, the 25th and 75th percentiles, @@ -47,20 +49,17 @@ def _value_format(self, value, serie): """ if self.box_mode == "extremes": return ( - 'Min: %s\nQ1 : %s\nQ2 : %s\nQ3 : %s\nMax: %s' % - tuple(map(self._y_format, serie.points[1:6])) - ) + 'Min: %s\nQ1 : %s\nQ2 : %s\nQ3 : %s\nMax: %s' % tuple( + map(self._y_format, serie.points[1:6]))) elif self.box_mode in ["tukey", "stdev", "pstdev"]: return ( 'Min: %s\nLower Whisker: %s\nQ1: %s\nQ2: %s\nQ3: %s\n' - 'Upper Whisker: %s\nMax: %s' % - tuple(map(self._y_format, serie.points)) - ) + 'Upper Whisker: %s\nMax: %s' % tuple(map( + self._y_format, serie.points))) elif self.box_mode == '1.5IQR': # 1.5IQR mode - return 'Q1: %s\nQ2: %s\nQ3: %s' % tuple( - map(self._y_format, serie.points[2:5]) - ) + return 'Q1: %s\nQ2: %s\nQ3: %s' % tuple(map( + self._y_format, serie.points[2:5])) else: return self._y_format(serie.points) @@ -73,7 +72,8 @@ def _compute(self): serie.points, serie.outliers = \ self._box_points(serie.values, self.box_mode) - self._x_pos = [(i + .5) / self._order for i in range(self._order)] + self._x_pos = [ + (i + .5) / self._order for i in range(self._order)] if self._min: self._box.ymin = min(self._min, self.zero) @@ -100,17 +100,17 @@ def _boxf(self, serie): metadata = serie.metadata.get(0) - box = decorate(self.svg, self.svg.node(boxes, class_='box'), metadata) + box = decorate( + self.svg, + self.svg.node(boxes, class_='box'), + metadata) val = self._format(serie, 0) x_center, y_center = self._draw_box( - box, serie.points[1:6], serie.outliers, serie.index, metadata - ) - self._tooltip_data( - box, val, x_center, y_center, "centered", - self._get_x_label(serie.index) - ) + box, serie.points[1:6], serie.outliers, serie.index, metadata) + self._tooltip_data(box, val, x_center, y_center, "centered", + self._get_x_label(serie.index)) self._static_value(serie_node, val, x_center, y_center, metadata) def _draw_box(self, parent_node, quartiles, outliers, box_index, metadata): @@ -124,72 +124,55 @@ def _draw_box(self, parent_node, quartiles, outliers, box_index, metadata): width -= 2 * series_margin # draw lines for whiskers - bottom, median, and top - for i, whisker in enumerate((quartiles[0], quartiles[2], - quartiles[4])): + for i, whisker in enumerate( + (quartiles[0], quartiles[2], quartiles[4])): whisker_width = width if i == 1 else width / 2 shift = (width - whisker_width) / 2 xs = left_edge + shift xe = left_edge + width - shift - alter( - self.svg.line( - parent_node, - coords=[(xs, self.view.y(whisker)), - (xe, self.view.y(whisker))], - class_='reactive tooltip-trigger', - attrib={'stroke-width': 3} - ), metadata - ) - - # draw lines connecting whiskers to box (Q1 and Q3) - alter( - self.svg.line( - parent_node, - coords=[(left_edge + width / 2, self.view.y(quartiles[0])), - (left_edge + width / 2, self.view.y(quartiles[1]))], - class_='reactive tooltip-trigger', - attrib={'stroke-width': 2} - ), metadata - ) - alter( - self.svg.line( + alter(self.svg.line( parent_node, - coords=[(left_edge + width / 2, self.view.y(quartiles[4])), - (left_edge + width / 2, self.view.y(quartiles[3]))], + coords=[(xs, self.view.y(whisker)), + (xe, self.view.y(whisker))], class_='reactive tooltip-trigger', - attrib={'stroke-width': 2} - ), metadata - ) + attrib={'stroke-width': 3}), metadata) + + # draw lines connecting whiskers to box (Q1 and Q3) + alter(self.svg.line( + parent_node, + coords=[(left_edge + width / 2, self.view.y(quartiles[0])), + (left_edge + width / 2, self.view.y(quartiles[1]))], + class_='reactive tooltip-trigger', + attrib={'stroke-width': 2}), metadata) + alter(self.svg.line( + parent_node, + coords=[(left_edge + width / 2, self.view.y(quartiles[4])), + (left_edge + width / 2, self.view.y(quartiles[3]))], + class_='reactive tooltip-trigger', + attrib={'stroke-width': 2}), metadata) # box, bounded by Q1 and Q3 - alter( - self.svg.node( - parent_node, - tag='rect', - x=left_edge, - y=self.view.y(quartiles[1]), - height=self.view.y(quartiles[3]) - self.view.y(quartiles[1]), - width=width, - class_='subtle-fill reactive tooltip-trigger' - ), metadata - ) + alter(self.svg.node( + parent_node, + tag='rect', + x=left_edge, + y=self.view.y(quartiles[1]), + height=self.view.y(quartiles[3]) - self.view.y(quartiles[1]), + width=width, + class_='subtle-fill reactive tooltip-trigger'), metadata) # draw outliers for o in outliers: - alter( - self.svg.node( - parent_node, - tag='circle', - cx=left_edge + width / 2, - cy=self.view.y(o), - r=3, - class_='subtle-fill reactive tooltip-trigger' - ), metadata - ) - - return ( - left_edge + width / 2, - self.view.y(sum(quartiles) / len(quartiles)) - ) + alter(self.svg.node( + parent_node, + tag='circle', + cx=left_edge + width / 2, + cy=self.view.y(o), + r=3, + class_='subtle-fill reactive tooltip-trigger'), metadata) + + return (left_edge + width / 2, self.view.y( + sum(quartiles) / len(quartiles))) @staticmethod def _box_points(values, mode='extremes'): @@ -216,7 +199,6 @@ def _box_points(values, mode='extremes'): Sincich, T. L. Statistics for Engineering and the Sciences, 4th ed. Prentice-Hall, 1995. """ - def median(seq): n = len(seq) if n % 2 == 0: # seq has an even length diff --git a/pygal/graph/dot.py b/pygal/graph/dot.py index dcc9a0a2..89aadcb7 100644 --- a/pygal/graph/dot.py +++ b/pygal/graph/dot.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ Dot chart displaying values as a grid of dots, the bigger the value the bigger the dot @@ -32,6 +33,7 @@ class Dot(Graph): + """Dot graph class""" def dot(self, serie, r_max): @@ -46,8 +48,10 @@ def dot(self, serie, r_max): log10max = log10(self._max or 1) if value != 0: - size = r_max * ((log10(abs(value)) - log10min) - / (log10max - log10min)) + size = r_max * ( + (log10(abs(value)) - log10min) / + (log10max - log10min) + ) else: size = 0 else: @@ -55,25 +59,19 @@ def dot(self, serie, r_max): metadata = serie.metadata.get(i) dots = decorate( - self.svg, self.svg.node(serie_node['plot'], class_="dots"), - metadata - ) - alter( - self.svg.node( - dots, - 'circle', - cx=x, - cy=y, - r=size, - class_='dot reactive tooltip-trigger' - + (' negative' if value < 0 else '') - ), metadata - ) + self.svg, + self.svg.node(serie_node['plot'], class_="dots"), + metadata) + alter(self.svg.node( + dots, 'circle', + cx=x, cy=y, r=size, + class_='dot reactive tooltip-trigger' + ( + ' negative' if value < 0 else '')), metadata) val = self._format(serie, i) self._tooltip_data( - dots, val, x, y, 'centered', self._get_x_label(i) - ) + dots, val, x, y, 'centered', + self._get_x_label(i)) self._static_value(serie_node, val, x, y, metadata) def _compute(self): @@ -87,28 +85,26 @@ def _compute(self): self._y_pos = [n / 2 for n in reversed(range(1, 2 * y_len, 2))] for j, serie in enumerate(self.series): - serie.points = [(self._x_pos[i], self._y_pos[j]) - for i in range(x_len)] + serie.points = [ + (self._x_pos[i], self._y_pos[j]) + for i in range(x_len)] def _compute_y_labels(self): - self._y_labels = list( - zip( - self.y_labels and map(to_str, self.y_labels) or [ - serie.title['title'] - if isinstance(serie.title, dict) else serie.title or '' - for serie in self.series - ], self._y_pos - ) - ) + self._y_labels = list(zip( + self.y_labels and map(to_str, self.y_labels) or [ + serie.title['title'] + if isinstance(serie.title, dict) + else serie.title or '' for serie in self.series], + self._y_pos)) def _set_view(self): """Assign a view to current graph""" view_class = ReverseView if self.inverse_y_axis else View self.view = view_class( - self.width - self.margin_box.x, self.height - self.margin_box.y, - self._box - ) + self.width - self.margin_box.x, + self.height - self.margin_box.y, + self._box) @cached_property def _values(self): @@ -118,16 +114,14 @@ def _values(self): @cached_property def _max(self): """Getter for the maximum series value""" - return ( - self.range[1] if (self.range and self.range[1] is not None) else - (max(map(abs, self._values)) if self._values else None) - ) + return (self.range[1] if (self.range and self.range[1] is not None) + else (max(map(abs, self._values)) if self._values else None)) def _plot(self): """Plot all dots for series""" r_max = min( self.view.x(1) - self.view.x(0), - (self.view.y(0) or 0) - self.view.y(1) - ) / (2 * 1.05) + (self.view.y(0) or 0) - self.view.y(1)) / ( + 2 * 1.05) for serie in self.series: self.dot(serie, r_max) diff --git a/pygal/graph/dual.py b/pygal/graph/dual.py index 2e0fe6b1..ac8d4a83 100644 --- a/pygal/graph/dual.py +++ b/pygal/graph/dual.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Dual chart base. Dual means a chart with 2 scaled axis like xy""" from pygal._compat import is_str @@ -30,12 +31,14 @@ def _value_format(self, value): """ Format value for dual value display. """ - return '%s: %s' % (self._x_format(value[0]), self._y_format(value[1])) + return '%s: %s' % ( + self._x_format(value[0]), + self._y_format(value[1])) def _compute_x_labels(self): x_pos = compute_scale( - self._box.xmin, self._box.xmax, self.logarithmic, self.order_min, - self.min_scale, self.max_scale + self._box.xmin, self._box.xmax, self.logarithmic, + self.order_min, self.min_scale, self.max_scale ) if self.x_labels: self._x_labels = [] @@ -60,8 +63,7 @@ def _compute_x_labels(self): def _compute_x_labels_major(self): # In case of dual, x labels must adapters and so majors too self.x_labels_major = self.x_labels_major and list( - map(self._x_adapt, self.x_labels_major) - ) + map(self._x_adapt, self.x_labels_major)) super(Dual, self)._compute_x_labels_major() def _get_x_label(self, i): diff --git a/pygal/graph/funnel.py b/pygal/graph/funnel.py index 7c4a3040..00e3dc76 100644 --- a/pygal/graph/funnel.py +++ b/pygal/graph/funnel.py @@ -26,6 +26,7 @@ class Funnel(Graph): + """Funnel graph class""" _adapters = [positive, none_to_zero] @@ -43,27 +44,22 @@ def funnel(self, serie): val = self._format(serie, i) funnels = decorate( - self.svg, self.svg.node(serie_node['plot'], class_="funnels"), - metadata - ) - - alter( - self.svg.node( - funnels, - 'polygon', - points=' '.join(map(fmt, map(self.view, poly))), - class_='funnel reactive tooltip-trigger' - ), metadata - ) + self.svg, + self.svg.node(serie_node['plot'], class_="funnels"), + metadata) + + alter(self.svg.node( + funnels, 'polygon', + points=' '.join(map(fmt, map(self.view, poly))), + class_='funnel reactive tooltip-trigger'), metadata) # Poly center from label x, y = self.view(( self._center(self._x_pos[serie.index]), - sum([point[1] for point in poly]) / len(poly) - )) + sum([point[1] for point in poly]) / len(poly))) self._tooltip_data( - funnels, val, x, y, 'centered', self._get_x_label(serie.index) - ) + funnels, val, x, y, 'centered', + self._get_x_label(serie.index)) self._static_value(serie_node, val, x, y, metadata) def _center(self, x): @@ -77,7 +73,7 @@ def _compute(self): previous = [[self.zero, self.zero] for i in range(self._len)] for i, serie in enumerate(self.series): - y_height = -sum(serie.safe_values) / 2 + y_height = - sum(serie.safe_values) / 2 all_x_pos = [0] + self._x_pos serie.points = [] for j, value in enumerate(serie.values): @@ -102,14 +98,12 @@ def _compute(self): def _compute_x_labels(self): self._x_labels = list( - zip( - self.x_labels and map(self._x_format, self.x_labels) or [ + zip(self.x_labels and + map(self._x_format, self.x_labels) or [ serie.title['title'] - if isinstance(serie.title, dict) else serie.title or '' - for serie in self.series - ], map(self._center, self._x_pos) - ) - ) + if isinstance(serie.title, dict) + else serie.title or '' for serie in self.series], + map(self._center, self._x_pos))) def _plot(self): """Plot the funnel""" diff --git a/pygal/graph/gauge.py b/pygal/graph/gauge.py index 83ea6de6..974fafd1 100644 --- a/pygal/graph/gauge.py +++ b/pygal/graph/gauge.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Gauge chart representing values as needles on a polar scale""" from __future__ import division @@ -27,6 +28,7 @@ class Gauge(Graph): + """Gauge graph class""" needle_width = 1 / 20 @@ -39,9 +41,9 @@ def _set_view(self): view_class = PolarThetaView self.view = view_class( - self.width - self.margin_box.x, self.height - self.margin_box.y, - self._box - ) + self.width - self.margin_box.x, + self.height - self.margin_box.y, + self._box) def needle(self, serie): """Draw a needle for each value""" @@ -56,9 +58,9 @@ def point(x, y): val = self._format(serie, i) metadata = serie.metadata.get(i) gauges = decorate( - self.svg, self.svg.node(serie_node['plot'], class_="dots"), - metadata - ) + self.svg, + self.svg.node(serie_node['plot'], class_="dots"), + metadata) tolerance = 1.15 @@ -71,24 +73,23 @@ def point(x, y): w = (self._box._tmax - self._box._tmin + self.view.aperture) / 4 if self.logarithmic: - w = min(w, self._min - self._min * 10**-10) + w = min(w, self._min - self._min * 10 ** -10) alter( self.svg.node( - gauges, - 'path', - d='M %s L %s A %s 1 0 1 %s Z' % ( + gauges, 'path', d='M %s L %s A %s 1 0 1 %s Z' % ( point(.85, theta), point(self.needle_width, theta - w), '%f %f' % (self.needle_width, self.needle_width), point(self.needle_width, theta + w), ), - class_='line reactive tooltip-trigger' - ), metadata - ) + class_='line reactive tooltip-trigger'), + metadata) x, y = self.view((.75, theta)) - self._tooltip_data(gauges, val, x, y, xlabel=self._get_x_label(i)) + self._tooltip_data( + gauges, val, x, y, + xlabel=self._get_x_label(i)) self._static_value(serie_node, val, x, y, metadata) def _y_axis(self, draw_axes=True): @@ -99,26 +100,26 @@ def _y_axis(self, draw_axes=True): guides = self.svg.node(axis, class_='guides') self.svg.line( - guides, [self.view((.95, theta)), - self.view((1, theta))], + guides, [self.view((.95, theta)), self.view((1, theta))], close=True, - class_='line' - ) + class_='line') self.svg.line( - guides, [self.view((0, theta)), - self.view((.95, theta))], + guides, [self.view((0, theta)), self.view((.95, theta))], close=True, - class_='guide line %s' % - ('major' if i in (0, len(self._y_labels) - 1) else '') - ) + class_='guide line %s' % ( + 'major' if i in (0, len(self._y_labels) - 1) + else '')) x, y = self.view((.9, theta)) - self.svg.node(guides, 'text', x=x, y=y).text = label + self.svg.node( + guides, 'text', + x=x, + y=y + ).text = label self.svg.node( - guides, - 'title', + guides, 'title', ).text = self._y_format(theta) def _x_axis(self, draw_axes=True): @@ -135,15 +136,18 @@ def _compute(self): self.min_ -= 1 self.max_ += 1 - self._box.set_polar_box(0, 1, self.min_, self.max_) + self._box.set_polar_box( + 0, 1, + self.min_, + self.max_) def _compute_x_labels(self): pass def _compute_y_labels(self): y_pos = compute_scale( - self.min_, self.max_, self.logarithmic, self.order_min, - self.min_scale, self.max_scale + self.min_, self.max_, self.logarithmic, + self.order_min, self.min_scale, self.max_scale ) if self.y_labels: self._y_labels = [] @@ -160,7 +164,10 @@ def _compute_y_labels(self): self._y_labels.append((title, pos)) self.min_ = min(self.min_, min(cut(self._y_labels, 1))) self.max_ = max(self.max_, max(cut(self._y_labels, 1))) - self._box.set_polar_box(0, 1, self.min_, self.max_) + self._box.set_polar_box( + 0, 1, + self.min_, + self.max_) else: self._y_labels = list(zip(map(self._y_format, y_pos), y_pos)) diff --git a/pygal/graph/histogram.py b/pygal/graph/histogram.py index 268b7238..979eff42 100644 --- a/pygal/graph/histogram.py +++ b/pygal/graph/histogram.py @@ -29,6 +29,7 @@ class Histogram(Dual, Bar): + """Histogram chart class""" _series_margin = 0 @@ -40,26 +41,27 @@ def _values(self): @cached_property def _secondary_values(self): """Getter for secondary series values (flattened)""" - return [ - val[0] for serie in self.secondary_series for val in serie.values - if val[0] is not None - ] + return [val[0] + for serie in self.secondary_series + for val in serie.values + if val[0] is not None] @cached_property def xvals(self): """All x values""" - return [ - val for serie in self.all_series for dval in serie.values - for val in dval[1:3] if val is not None - ] + return [val + for serie in self.all_series + for dval in serie.values + for val in dval[1:3] + if val is not None] @cached_property def yvals(self): """All y values""" - return [ - val[0] for serie in self.series for val in serie.values - if val[0] is not None - ] + return [val[0] + for serie in self.series + for val in serie.values + if val[0] is not None] def _bar(self, serie, parent, x0, x1, y, i, zero, secondary=False): """Internal bar drawing function""" @@ -72,19 +74,10 @@ def _bar(self, serie, parent, x0, x1, y, i, zero, secondary=False): width -= 2 * series_margin r = serie.rounded_bars * 1 if serie.rounded_bars else 0 - alter( - self.svg.transposable_node( - parent, - 'rect', - x=x, - y=y, - rx=r, - ry=r, - width=width, - height=height, - class_='rect reactive tooltip-trigger' - ), serie.metadata.get(i) - ) + alter(self.svg.transposable_node( + parent, 'rect', + x=x, y=y, rx=r, ry=r, width=width, height=height, + class_='rect reactive tooltip-trigger'), serie.metadata.get(i)) return x, y, width, height def bar(self, serie, rescale=False): @@ -99,16 +92,15 @@ def bar(self, serie, rescale=False): metadata = serie.metadata.get(i) bar = decorate( - self.svg, self.svg.node(bars, class_='histbar'), metadata - ) + self.svg, + self.svg.node(bars, class_='histbar'), + metadata) val = self._format(serie, i) bounds = self._bar( - serie, bar, x0, x1, y, i, self.zero, secondary=rescale - ) + serie, bar, x0, x1, y, i, self.zero, secondary=rescale) self._tooltip_and_print_values( - serie_node, serie, bar, i, val, metadata, *bounds - ) + serie_node, serie, bar, i, val, metadata, *bounds) def _compute(self): """Compute x/y min and max and x/y scale and set labels""" diff --git a/pygal/graph/horizontal.py b/pygal/graph/horizontal.py index ed66cc3b..9939b21a 100644 --- a/pygal/graph/horizontal.py +++ b/pygal/graph/horizontal.py @@ -23,6 +23,7 @@ class HorizontalGraph(Graph): + """Horizontal graph mixin""" def __init__(self, *args, **kwargs): @@ -34,14 +35,11 @@ def _post_compute(self): """After computations transpose labels""" self._x_labels, self._y_labels = self._y_labels, self._x_labels self._x_labels_major, self._y_labels_major = ( - self._y_labels_major, self._x_labels_major - ) + self._y_labels_major, self._x_labels_major) self._x_2nd_labels, self._y_2nd_labels = ( - self._y_2nd_labels, self._x_2nd_labels - ) + self._y_2nd_labels, self._x_2nd_labels) self.show_y_guides, self.show_x_guides = ( - self.show_x_guides, self.show_y_guides - ) + self.show_x_guides, self.show_y_guides) def _axes(self): """Set the _force_vertical flag when rendering axes""" @@ -57,9 +55,9 @@ def _set_view(self): view_class = HorizontalView self.view = view_class( - self.width - self.margin_box.x, self.height - self.margin_box.y, - self._box - ) + self.width - self.margin_box.x, + self.height - self.margin_box.y, + self._box) def _get_x_label(self, i): """Convenience function to get the x_label of a value index""" diff --git a/pygal/graph/horizontalbar.py b/pygal/graph/horizontalbar.py index ad9c2ffa..5e801cef 100644 --- a/pygal/graph/horizontalbar.py +++ b/pygal/graph/horizontalbar.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Horizontal bar graph""" from pygal.graph.bar import Bar @@ -23,6 +24,7 @@ class HorizontalBar(HorizontalGraph, Bar): + """Horizontal Bar graph""" def _plot(self): diff --git a/pygal/graph/horizontalline.py b/pygal/graph/horizontalline.py index 801dada4..e13039d7 100644 --- a/pygal/graph/horizontalline.py +++ b/pygal/graph/horizontalline.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Horizontal line graph""" from pygal.graph.horizontal import HorizontalGraph @@ -23,6 +24,7 @@ class HorizontalLine(HorizontalGraph, Line): + """Horizontal Line graph""" def _plot(self): diff --git a/pygal/graph/horizontalstackedbar.py b/pygal/graph/horizontalstackedbar.py index 29f4c64c..990361f1 100644 --- a/pygal/graph/horizontalstackedbar.py +++ b/pygal/graph/horizontalstackedbar.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Horizontal stacked graph""" from pygal.graph.horizontal import HorizontalGraph @@ -23,4 +24,5 @@ class HorizontalStackedBar(HorizontalGraph, StackedBar): + """Horizontal Stacked Bar graph""" diff --git a/pygal/graph/horizontalstackedline.py b/pygal/graph/horizontalstackedline.py index 88155230..9748d819 100644 --- a/pygal/graph/horizontalstackedline.py +++ b/pygal/graph/horizontalstackedline.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Horizontal Stacked Line graph""" from pygal.graph.horizontal import HorizontalGraph @@ -23,6 +24,7 @@ class HorizontalStackedLine(HorizontalGraph, StackedLine): + """Horizontal Stacked Line graph""" def _plot(self): diff --git a/pygal/graph/map.py b/pygal/graph/map.py index 57ff4e1c..1a1405a8 100644 --- a/pygal/graph/map.py +++ b/pygal/graph/map.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ pygal contains no map but a base class to create extension see the pygal_maps_world package to get an exemple. @@ -30,6 +31,7 @@ class BaseMap(Graph): + """Base class for maps""" _dual = True @@ -37,10 +39,10 @@ class BaseMap(Graph): @cached_property def _values(self): """Getter for series values (flattened)""" - return [ - val[1] for serie in self.series for val in serie.values - if val[1] is not None - ] + return [val[1] + for serie in self.series + for val in serie.values + if val[1] is not None] def enumerate_values(self, serie): """Hook to replace default enumeration on values""" @@ -56,8 +58,7 @@ def _value_format(self, value): """ return '%s: %s' % ( self.area_names.get(self.adapt_code(value[0]), '?'), - self._y_format(value[1]) - ) + self._y_format(value[1])) def _plot(self): """Insert a map in the chart and apply data on it""" @@ -66,9 +67,8 @@ def _plot(self): map.set('height', str(self.view.height)) for i, serie in enumerate(self.series): - safe_vals = list( - filter(lambda x: x is not None, cut(serie.values, 1)) - ) + safe_vals = list(filter( + lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) @@ -83,9 +83,9 @@ def _plot(self): ratio = .3 + .7 * (value - min_) / (max_ - min_) areae = map.findall( - ".//*[@class='%s%s %s map-element']" % - (self.area_prefix, area_code, self.kind) - ) + ".//*[@class='%s%s %s map-element']" % ( + self.area_prefix, area_code, + self.kind)) if not areae: continue diff --git a/pygal/graph/pie.py b/pygal/graph/pie.py index d6fcf3cb..be2d4255 100644 --- a/pygal/graph/pie.py +++ b/pygal/graph/pie.py @@ -31,6 +31,7 @@ class Pie(Graph): + """Pie graph class""" _adapters = [positive, none_to_zero] @@ -61,8 +62,9 @@ def slice(self, serie, start_angle, total): val = self._format(serie, i) metadata = serie.metadata.get(i) slice_ = decorate( - self.svg, self.svg.node(slices, class_="slice"), metadata - ) + self.svg, + self.svg.node(slices, class_="slice"), + metadata) if dual: small_radius = radius * .9 big_radius = radius @@ -70,21 +72,17 @@ def slice(self, serie, start_angle, total): big_radius = radius * .9 small_radius = radius * serie.inner_radius - alter( - self.svg.slice( - serie_node, slice_, big_radius, small_radius, angle, - start_angle, center, val, i, metadata - ), metadata - ) + alter(self.svg.slice( + serie_node, slice_, big_radius, small_radius, + angle, start_angle, center, val, i, metadata), metadata) start_angle += angle if dual: val = self._serie_format(serie, sum(serie.values)) - self.svg.slice( - serie_node, self.svg.node(slices, - class_="big_slice"), radius * .9, 0, - serie_angle, original_start_angle, center, val, i, metadata - ) + self.svg.slice(serie_node, + self.svg.node(slices, class_="big_slice"), + radius * .9, 0, serie_angle, + original_start_angle, center, val, i, metadata) return serie_angle def _compute_x_labels(self): diff --git a/pygal/graph/public.py b/pygal/graph/public.py index a01a0579..efb78721 100644 --- a/pygal/graph/public.py +++ b/pygal/graph/public.py @@ -26,6 +26,7 @@ class PublicApi(BaseGraph): + """Chart public functions""" def add(self, title, values, **kwargs): @@ -50,8 +51,7 @@ def render(self, is_unicode=False, **kwargs): """Render the graph, and return the svg string""" self.setup(**kwargs) svg = self.svg.render( - is_unicode=is_unicode, pretty_print=self.pretty_print - ) + is_unicode=is_unicode, pretty_print=self.pretty_print) self.teardown() return svg @@ -96,16 +96,16 @@ def render_django_response(self, **kwargs): """Render the graph, and return a Django response""" from django.http import HttpResponse return HttpResponse( - self.render(**kwargs), content_type='image/svg+xml' - ) + self.render(**kwargs), content_type='image/svg+xml') def render_data_uri(self, **kwargs): """Output a base 64 encoded data uri""" # Force protocol as data uri have none kwargs.setdefault('force_uri_protocol', 'https') return "data:image/svg+xml;charset=utf-8;base64,%s" % ( - base64.b64encode(self.render(**kwargs) - ).decode('utf-8').replace('\n', '') + base64.b64encode( + self.render(**kwargs) + ).decode('utf-8').replace('\n', '') ) def render_to_file(self, filename, **kwargs): @@ -117,8 +117,7 @@ def render_to_png(self, filename=None, dpi=72, **kwargs): """Render the graph, convert it to png and write it to filename""" import cairosvg return cairosvg.svg2png( - bytestring=self.render(**kwargs), write_to=filename, dpi=dpi - ) + bytestring=self.render(**kwargs), write_to=filename, dpi=dpi) def render_sparktext(self, relative_to=None): """Make a mini text sparkline from chart""" @@ -142,9 +141,8 @@ def render_sparktext(self, relative_to=None): divisions = len(bars) - 1 for value in values: - chart += bars[int( - divisions * (value - relative_to) / (vmax - relative_to) - )] + chart += bars[int(divisions * + (value - relative_to) / (vmax - relative_to))] return chart def render_sparkline(self, **kwargs): diff --git a/pygal/graph/pyramid.py b/pygal/graph/pyramid.py index d74e21e5..97555ac9 100644 --- a/pygal/graph/pyramid.py +++ b/pygal/graph/pyramid.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ Pyramid chart: Stacked bar chart containing only positive values divided by two axes, generally gender for age pyramid. @@ -29,6 +30,7 @@ class VerticalPyramid(StackedBar): + """Vertical Pyramid graph class""" _adapters = [positive] @@ -40,37 +42,26 @@ def _value_format(self, value): def _get_separated_values(self, secondary=False): """Separate values between odd and even series stacked""" series = self.secondary_series if secondary else self.series - positive_vals = map( - sum, - zip( - *[ - serie.safe_values for index, serie in enumerate(series) - if index % 2 - ] - ) - ) - negative_vals = map( - sum, - zip( - *[ - serie.safe_values for index, serie in enumerate(series) - if not index % 2 - ] - ) - ) + positive_vals = map(sum, zip( + *[serie.safe_values + for index, serie in enumerate(series) + if index % 2])) + negative_vals = map(sum, zip( + *[serie.safe_values + for index, serie in enumerate(series) + if not index % 2])) return list(positive_vals), list(negative_vals) def _compute_box(self, positive_vals, negative_vals): """Compute Y min and max""" max_ = max( max(positive_vals or [self.zero]), - max(negative_vals or [self.zero]) - ) + max(negative_vals or [self.zero])) if self.range and self.range[0] is not None: self._box.ymin = self.range[0] else: - self._box.ymin = -max_ + self._box.ymin = - max_ if self.range and self.range[1] is not None: self._box.ymax = self.range[1] @@ -80,15 +71,16 @@ def _compute_box(self, positive_vals, negative_vals): def _pre_compute_secondary(self, positive_vals, negative_vals): """Compute secondary y min and max""" self._secondary_max = max(max(positive_vals), max(negative_vals)) - self._secondary_min = -self._secondary_max + self._secondary_min = - self._secondary_max def _bar(self, serie, parent, x, y, i, zero, secondary=False): """Internal stacking bar drawing function""" if serie.index % 2: y = -y - return super(VerticalPyramid, - self)._bar(serie, parent, x, y, i, zero, secondary) + return super(VerticalPyramid, self)._bar( + serie, parent, x, y, i, zero, secondary) class Pyramid(HorizontalGraph, VerticalPyramid): + """Horizontal Pyramid graph class like the one used by age pyramid""" diff --git a/pygal/graph/radar.py b/pygal/graph/radar.py index c9f7c47e..588cf10f 100644 --- a/pygal/graph/radar.py +++ b/pygal/graph/radar.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ Radar chart: As known as kiviat chart or spider chart is a polar line chart useful for multivariate observation. @@ -33,6 +34,7 @@ class Radar(Line): + """Rada graph class""" _adapters = [positive, none_to_zero] @@ -50,9 +52,8 @@ def _fill(self, values): def _values(self): """Getter for series values (flattened)""" if self.interpolate: - return [ - val[0] for serie in self.series for val in serie.interpolated - ] + return [val[0] for serie in self.series + for val in serie.interpolated] else: return super(Line, self)._values @@ -64,20 +65,18 @@ def _set_view(self): view_class = PolarView self.view = view_class( - self.width - self.margin_box.x, self.height - self.margin_box.y, - self._box - ) + self.width - self.margin_box.x, + self.height - self.margin_box.y, + self._box) def _x_axis(self, draw_axes=True): """Override x axis to make it polar""" if not self._x_labels or not self.show_x_labels: return - axis = self.svg.node( - self.nodes['plot'], - class_="axis x web%s" % - (' always_show' if self.show_x_guides else '') - ) + axis = self.svg.node(self.nodes['plot'], class_="axis x web%s" % ( + ' always_show' if self.show_x_guides else '' + )) format_ = lambda x: '%f %f' % x center = self.view((0, 0)) r = self._rmax @@ -93,37 +92,32 @@ def _x_axis(self, draw_axes=True): end = self.view((r, theta)) self.svg.node( - guides, - 'path', + guides, 'path', d='M%s L%s' % (format_(center), format_(end)), - class_='%s%sline' % - ('axis ' if label == "0" else '', 'major ' if major else '') - ) + class_='%s%sline' % ( + 'axis ' if label == "0" else '', + 'major ' if major else '')) r_txt = (1 - self._box.__class__.margin) * self._box.ymax pos_text = self.view((r_txt, theta)) text = self.svg.node( - guides, - 'text', + guides, 'text', x=pos_text[0], y=pos_text[1], - class_='major' if major else '' - ) + class_='major' if major else '') text.text = truncate(label, truncation) if text.text != label: self.svg.node(guides, 'title').text = label else: self.svg.node( - guides, - 'title', + guides, 'title', ).text = self._x_format(theta) - angle = -theta + pi / 2 + angle = - theta + pi / 2 if cos(angle) < 0: angle -= pi text.attrib['transform'] = 'rotate(%f %s)' % ( - self.x_label_rotation or deg(angle), format_(pos_text) - ) + self.x_label_rotation or deg(angle), format_(pos_text)) def _y_axis(self, draw_axes=True): """Override y axis to make it polar""" @@ -136,32 +130,31 @@ def _y_axis(self, draw_axes=True): major = r in self._y_labels_major if not (self.show_minor_y_labels or major): continue - guides = self.svg.node( - axis, - class_='%sguides' % - ('logarithmic ' if self.logarithmic else '') - ) + guides = self.svg.node(axis, class_='%sguides' % ( + 'logarithmic ' if self.logarithmic else '' + )) if self.show_y_guides: self.svg.line( guides, [self.view((r, theta)) for theta in self._x_pos], close=True, - class_='%sguide line' % ('major ' if major else '') - ) + class_='%sguide line' % ( + 'major ' if major else '')) x, y = self.view((r, self._x_pos[0])) x -= 5 text = self.svg.node( - guides, 'text', x=x, y=y, class_='major' if major else '' + guides, 'text', + x=x, + y=y, + class_='major' if major else '' ) text.text = label if self.y_label_rotation: - text.attrib[ - 'transform' - ] = "rotate(%d %f %f)" % (self.y_label_rotation, x, y) + text.attrib['transform'] = "rotate(%d %f %f)" % ( + self.y_label_rotation, x, y) self.svg.node( - guides, - 'title', + guides, 'title', ).text = self._y_format(r) def _compute(self): @@ -169,20 +162,19 @@ def _compute(self): delta = 2 * pi / self._len if self._len else 0 self._x_pos = [.5 * pi + i * delta for i in range(self._len + 1)] for serie in self.all_series: - serie.points = [(v, self._x_pos[i]) - for i, v in enumerate(serie.values)] + serie.points = [ + (v, self._x_pos[i]) + for i, v in enumerate(serie.values)] if self.interpolate: - extended_x_pos = ([.5 * pi - delta] + self._x_pos) - extended_vals = (serie.values[-1:] + serie.values) + extended_x_pos = ( + [.5 * pi - delta] + self._x_pos) + extended_vals = (serie.values[-1:] + + serie.values) serie.interpolated = list( - map( - tuple, - map( - reversed, - self._interpolate(extended_x_pos, extended_vals) - ) - ) - ) + map(tuple, + map(reversed, + self._interpolate( + extended_x_pos, extended_vals)))) # x labels space self._box.margin *= 2 diff --git a/pygal/graph/solidgauge.py b/pygal/graph/solidgauge.py index 4163c724..fc32b87a 100644 --- a/pygal/graph/solidgauge.py +++ b/pygal/graph/solidgauge.py @@ -16,6 +16,8 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + + """ Solid Guage For each series a solid guage is shown on the plot area. @@ -29,21 +31,24 @@ class SolidGauge(Graph): + def gaugify(self, serie, squares, sq_dimensions, current_square): serie_node = self.svg.serie(serie) if self.half_pie: start_angle = 3 * pi / 2 - center = ((current_square[1] * sq_dimensions[0]) - - (sq_dimensions[0] / 2.), - (current_square[0] * sq_dimensions[1]) - - (sq_dimensions[1] / 4)) + center = ( + (current_square[1] * sq_dimensions[0]) - ( + sq_dimensions[0] / 2.), + (current_square[0] * sq_dimensions[1]) - ( + sq_dimensions[1] / 4)) end_angle = pi / 2 else: start_angle = 0 - center = ((current_square[1] * sq_dimensions[0]) - - (sq_dimensions[0] / 2.), - (current_square[0] * sq_dimensions[1]) - - (sq_dimensions[1] / 2.)) + center = ( + (current_square[1] * sq_dimensions[0]) - ( + sq_dimensions[0] / 2.), + (current_square[0] * sq_dimensions[1]) - ( + sq_dimensions[1] / 2.)) end_angle = 2 * pi max_value = serie.metadata.get(0, {}).get('max_value', 100) @@ -52,8 +57,7 @@ def gaugify(self, serie, squares, sq_dimensions, current_square): self.svg.gauge_background( serie_node, start_angle, center, radius, small_radius, end_angle, - self.half_pie, self._serie_format(serie, max_value) - ) + self.half_pie, self._serie_format(serie, max_value)) sum_ = 0 for i, value in enumerate(serie.values): @@ -69,30 +73,27 @@ def gaugify(self, serie, squares, sq_dimensions, current_square): metadata = serie.metadata.get(i) gauge_ = decorate( - self.svg, self.svg.node(serie_node['plot'], class_="gauge"), - metadata - ) + self.svg, + self.svg.node(serie_node['plot'], class_="gauge"), + metadata) alter( self.svg.solid_gauge( - serie_node, gauge_, radius, small_radius, angle, - start_angle, center, val, i, metadata, self.half_pie, - end_angle, self._serie_format(serie, max_value) - ), metadata - ) + serie_node, gauge_, radius, small_radius, + angle, start_angle, center, val, i, metadata, + self.half_pie, end_angle, + self._serie_format(serie, max_value)), + metadata) start_angle += angle sum_ += value x, y = center self.svg.node( - serie_node['text_overlay'], - 'text', + serie_node['text_overlay'], 'text', class_='value gauge-sum', x=x, y=y + self.style.value_font_size / 3, - attrib={ - 'text-anchor': 'middle' - } + attrib={'text-anchor': 'middle'} ).text = self._serie_format(serie, sum_) def _compute_x_labels(self): @@ -108,7 +109,8 @@ def _plot(self): for index, serie in enumerate(self.series): current_square = self._current_square(squares, index) - self.gaugify(serie, squares, sq_dimensions, current_square) + self.gaugify( + serie, squares, sq_dimensions, current_square) def _squares(self): @@ -148,5 +150,4 @@ def _current_square(self, squares, index): else: return tuple(current_square) raise Exception( - 'Something went wrong with the current square assignment.' - ) + 'Something went wrong with the current square assignment.') diff --git a/pygal/graph/stackedbar.py b/pygal/graph/stackedbar.py index abc08121..a75a9238 100644 --- a/pygal/graph/stackedbar.py +++ b/pygal/graph/stackedbar.py @@ -28,6 +28,7 @@ class StackedBar(Bar): + """Stacked Bar graph class""" _adapters = [none_to_zero] @@ -36,14 +37,15 @@ def _get_separated_values(self, secondary=False): """Separate values between positives and negatives stacked""" series = self.secondary_series if secondary else self.series transposed = list(zip(*[serie.values for serie in series])) - positive_vals = [ - sum([val for val in vals if val is not None and val >= self.zero]) - for vals in transposed - ] - negative_vals = [ - sum([val for val in vals if val is not None and val < self.zero]) - for vals in transposed - ] + positive_vals = [sum([ + val for val in vals + if val is not None and val >= self.zero]) + for vals in transposed] + negative_vals = [sum([ + val + for val in vals + if val is not None and val < self.zero]) + for vals in transposed] return positive_vals, negative_vals def _compute_box(self, positive_vals, negative_vals): @@ -52,26 +54,22 @@ def _compute_box(self, positive_vals, negative_vals): self._box.ymin = self.range[0] else: self._box.ymin = negative_vals and min( - min(negative_vals), self.zero - ) or self.zero + min(negative_vals), self.zero) or self.zero if self.range and self.range[1] is not None: self._box.ymax = self.range[1] else: self._box.ymax = positive_vals and max( - max(positive_vals), self.zero - ) or self.zero + max(positive_vals), self.zero) or self.zero def _compute(self): """Compute y min and max and y scale and set labels""" positive_vals, negative_vals = self._get_separated_values() if self.logarithmic: - positive_vals = list( - filter(lambda x: x > self.zero, positive_vals) - ) - negative_vals = list( - filter(lambda x: x > self.zero, negative_vals) - ) + positive_vals = list(filter( + lambda x: x > self.zero, positive_vals)) + negative_vals = list(filter( + lambda x: x > self.zero, negative_vals)) self._compute_box(positive_vals, negative_vals) positive_vals = positive_vals or [self.zero] @@ -98,25 +96,21 @@ def _compute(self): def _pre_compute_secondary(self, positive_vals, negative_vals): """Compute secondary y min and max""" - self._secondary_min = ( - negative_vals and min(min(negative_vals), self.zero) - ) or self.zero - self._secondary_max = ( - positive_vals and max(max(positive_vals), self.zero) - ) or self.zero + self._secondary_min = (negative_vals and min( + min(negative_vals), self.zero)) or self.zero + self._secondary_max = (positive_vals and max( + max(positive_vals), self.zero)) or self.zero def _bar(self, serie, parent, x, y, i, zero, secondary=False): """Internal stacking bar drawing function""" if secondary: - cumulation = ( - self.secondary_negative_cumulation - if y < self.zero else self.secondary_positive_cumulation - ) + cumulation = (self.secondary_negative_cumulation + if y < self.zero else + self.secondary_positive_cumulation) else: - cumulation = ( - self.negative_cumulation - if y < self.zero else self.positive_cumulation - ) + cumulation = (self.negative_cumulation + if y < self.zero else + self.positive_cumulation) zero = cumulation[i] cumulation[i] = zero + y if zero == 0: @@ -139,16 +133,9 @@ def _bar(self, serie, parent, x, y, i, zero, secondary=False): height = self.view.y(zero) - y r = serie.rounded_bars * 1 if serie.rounded_bars else 0 self.svg.transposable_node( - parent, - 'rect', - x=x, - y=y, - rx=r, - ry=r, - width=width, - height=height, - class_='rect reactive tooltip-trigger' - ) + parent, 'rect', + x=x, y=y, rx=r, ry=r, width=width, height=height, + class_='rect reactive tooltip-trigger') return x, y, width, height def _plot(self): diff --git a/pygal/graph/stackedline.py b/pygal/graph/stackedline.py index fae7486a..455e9cb4 100644 --- a/pygal/graph/stackedline.py +++ b/pygal/graph/stackedline.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ Stacked Line chart: Like a line chart but with all lines stacking on top of the others. Used along fill=True option. @@ -28,6 +29,7 @@ class StackedLine(Line): + """Stacked Line graph class""" _adapters = [none_to_zero] @@ -43,11 +45,15 @@ def _value_format(self, value, serie, index): """ sum_ = serie.points[index][1] if serie in self.series and ( - self.stack_from_top - and self.series.index(serie) == self._order - 1 - or not self.stack_from_top and self.series.index(serie) == 0): + self.stack_from_top and + self.series.index(serie) == self._order - 1 or + not self.stack_from_top and + self.series.index(serie) == 0): return super(StackedLine, self)._value_format(value) - return '%s (+%s)' % (self._y_format(sum_), self._y_format(value)) + return '%s (+%s)' % ( + self._y_format(sum_), + self._y_format(value) + ) def _fill(self, values): """Add extra values to fill the line""" @@ -67,8 +73,9 @@ def _points(self, x_pos): accumulation = [0] * self._len for serie in series_group[::-1 if self.stack_from_top else 1]: accumulation = list(map(sum, zip(accumulation, serie.values))) - serie.points = [(x_pos[i], v) - for i, v in enumerate(accumulation)] + serie.points = [ + (x_pos[i], v) + for i, v in enumerate(accumulation)] if serie.points and self.interpolate: serie.interpolated = self._interpolate(x_pos, accumulation) else: diff --git a/pygal/graph/time.py b/pygal/graph/time.py index 4d40a833..abd8f251 100644 --- a/pygal/graph/time.py +++ b/pygal/graph/time.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ XY time extensions: handle convertion of date, time, datetime, timedelta into float for xy plot and back to their type for display @@ -66,20 +67,21 @@ def timedelta_to_seconds(x): def time_to_seconds(x): """Convert a time in a seconds sum""" if isinstance(x, time): - return ((((x.hour * 60) + x.minute) * 60 + x.second) * 10**6 - + x.microsecond) / 10**6 + return (( + ((x.hour * 60) + x.minute) * 60 + x.second + ) * 10 ** 6 + x.microsecond) / 10 ** 6 if is_str(x): return x # Clamp to valid time - return x and max(0, min(x, 24 * 3600 - 10**-6)) + return x and max(0, min(x, 24 * 3600 - 10 ** -6)) def seconds_to_time(x): """Convert a number of second into a time""" - t = int(x * 10**6) - ms = t % 10**6 - t = t // 10**6 + t = int(x * 10 ** 6) + ms = t % 10 ** 6 + t = t // 10 ** 6 s = t % 60 t = t // 60 m = t % 60 @@ -89,6 +91,7 @@ def seconds_to_time(x): class DateTimeLine(XY): + """DateTime abscissa xy graph class""" _x_adapters = [datetime_to_timestamp, date_to_datetime] @@ -96,29 +99,27 @@ class DateTimeLine(XY): @property def _x_format(self): """Return the value formatter for this graph""" - def datetime_to_str(x): dt = datetime.utcfromtimestamp(x) return self.x_value_formatter(dt) - return datetime_to_str class DateLine(DateTimeLine): + """Date abscissa xy graph class""" @property def _x_format(self): """Return the value formatter for this graph""" - def date_to_str(x): d = datetime.utcfromtimestamp(x).date() return self.x_value_formatter(d) - return date_to_str class TimeLine(DateTimeLine): + """Time abscissa xy graph class""" _x_adapters = [positive, time_to_seconds, datetime_to_time] @@ -126,15 +127,14 @@ class TimeLine(DateTimeLine): @property def _x_format(self): """Return the value formatter for this graph""" - def date_to_str(x): t = seconds_to_time(x) return self.x_value_formatter(t) - return date_to_str class TimeDeltaLine(XY): + """TimeDelta abscissa xy graph class""" _x_adapters = [timedelta_to_seconds] @@ -142,7 +142,6 @@ class TimeDeltaLine(XY): @property def _x_format(self): """Return the value formatter for this graph""" - def timedelta_to_str(x): td = timedelta(seconds=x) return self.x_value_formatter(td) diff --git a/pygal/graph/treemap.py b/pygal/graph/treemap.py index 41d1b277..8b954bd3 100644 --- a/pygal/graph/treemap.py +++ b/pygal/graph/treemap.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Treemap chart: Visualize data using nested recangles""" from __future__ import division @@ -26,6 +27,7 @@ class Treemap(Graph): + """Treemap graph class""" _adapters = [positive, none_to_zero] @@ -41,26 +43,31 @@ def _rect(self, serie, serie_node, rects, val, x, y, w, h, i): val = self._format(serie, i) rect = decorate( - self.svg, self.svg.node(rects, class_="rect"), metadata - ) + self.svg, + self.svg.node(rects, class_="rect"), + metadata) alter( self.svg.node( - rect, - 'rect', + rect, 'rect', x=rx, y=ry, width=rw, height=rh, - class_='rect reactive tooltip-trigger' - ), metadata - ) + class_='rect reactive tooltip-trigger'), + metadata) self._tooltip_data( - rect, val, rx + rw / 2, ry + rh / 2, 'centered', - self._get_x_label(i) - ) - self._static_value(serie_node, val, rx + rw / 2, ry + rh / 2, metadata) + rect, val, + rx + rw / 2, + ry + rh / 2, + 'centered', + self._get_x_label(i)) + self._static_value( + serie_node, val, + rx + rw / 2, + ry + rh / 2, + metadata) def _binary_tree(self, data, total, x, y, w, h, parent=None): if total == 0: @@ -74,11 +81,10 @@ def _binary_tree(self, data, total, x, y, w, h, parent=None): datum = data[0] serie_node = self.svg.serie(datum) self._binary_tree( - list(enumerate(datum.values)), total, x, y, w, h, ( - datum, serie_node, - self.svg.node(serie_node['plot'], class_="rects") - ) - ) + list(enumerate(datum.values)), + total, x, y, w, h, + (datum, serie_node, + self.svg.node(serie_node['plot'], class_="rects"))) return midpoint = total / 2 @@ -104,16 +110,16 @@ def _binary_tree(self, data, total, x, y, w, h, parent=None): if h > w: y_pivot = pivot_pct * h - self._binary_tree(half1, half1_sum, x, y, w, y_pivot, parent) self._binary_tree( - half2, half2_sum, x, y + y_pivot, w, h - y_pivot, parent - ) + half1, half1_sum, x, y, w, y_pivot, parent) + self._binary_tree( + half2, half2_sum, x, y + y_pivot, w, h - y_pivot, parent) else: x_pivot = pivot_pct * w - self._binary_tree(half1, half1_sum, x, y, x_pivot, h, parent) self._binary_tree( - half2, half2_sum, x + x_pivot, y, w - x_pivot, h, parent - ) + half1, half1_sum, x, y, x_pivot, h, parent) + self._binary_tree( + half2, half2_sum, x + x_pivot, y, w - x_pivot, h, parent) def _compute_x_labels(self): pass @@ -130,7 +136,7 @@ def _plot(self): gh = self.height - self.margin_box.y self.view.box.xmin = self.view.box.ymin = x = y = 0 - self.view.box.xmax = w = (total * gw / gh)**.5 + self.view.box.xmax = w = (total * gw / gh) ** .5 self.view.box.ymax = h = total / w self.view.box.fix() diff --git a/pygal/graph/xy.py b/pygal/graph/xy.py index cb99115b..e24caa13 100644 --- a/pygal/graph/xy.py +++ b/pygal/graph/xy.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """ XY Line graph: Plot a set of couple data points (x, y) connected by straight segments. @@ -31,6 +32,7 @@ class XY(Line, Dual): + """XY Line graph class""" _x_adapters = [] @@ -38,42 +40,38 @@ class XY(Line, Dual): @cached_property def xvals(self): """All x values""" - return [ - val[0] for serie in self.all_series for val in serie.values - if val[0] is not None - ] + return [val[0] + for serie in self.all_series + for val in serie.values + if val[0] is not None] @cached_property def yvals(self): """All y values""" - return [ - val[1] for serie in self.series for val in serie.values - if val[1] is not None - ] + return [val[1] + for serie in self.series + for val in serie.values + if val[1] is not None] @cached_property def _min(self): """Getter for the minimum series value""" - return ( - self.range[0] if (self.range and self.range[0] is not None) else - (min(self.yvals) if self.yvals else None) - ) + return (self.range[0] if (self.range and self.range[0] is not None) + else (min(self.yvals) if self.yvals else None)) @cached_property def _max(self): """Getter for the maximum series value""" - return ( - self.range[1] if (self.range and self.range[1] is not None) else - (max(self.yvals) if self.yvals else None) - ) + return (self.range[1] if (self.range and self.range[1] is not None) + else (max(self.yvals) if self.yvals else None)) def _compute(self): """Compute x/y min and max and x/y scale and set labels""" if self.xvals: if self.xrange: - x_adapter = reduce(compose, self._x_adapters) if getattr( - self, '_x_adapters', None - ) else ident + x_adapter = reduce( + compose, self._x_adapters) if getattr( + self, '_x_adapters', None) else ident xmin = x_adapter(self.xrange[0]) xmax = x_adapter(self.xrange[1]) @@ -100,24 +98,18 @@ def _compute(self): for serie in self.all_series: serie.points = serie.values if self.interpolate: - vals = list( - zip( - *sorted( - filter(lambda t: None not in t, serie.points), - key=lambda x: x[0] - ) - ) - ) + vals = list(zip(*sorted( + filter(lambda t: None not in t, + serie.points), key=lambda x: x[0]))) serie.interpolated = self._interpolate(vals[0], vals[1]) if self.interpolate: - self.xvals = [ - val[0] for serie in self.all_series - for val in serie.interpolated - ] - self.yvals = [ - val[1] for serie in self.series for val in serie.interpolated - ] + self.xvals = [val[0] + for serie in self.all_series + for val in serie.interpolated] + self.yvals = [val[1] + for serie in self.series + for val in serie.interpolated] if self.xvals: xmin = min(self.xvals) xmax = max(self.xvals) diff --git a/pygal/interpolate.py b/pygal/interpolate.py index fdba8b3b..6a10dda0 100644 --- a/pygal/interpolate.py +++ b/pygal/interpolate.py @@ -102,9 +102,8 @@ def cubic_interpolate(x, y, precision=250, **kwargs): yield x[i] + X, a[i] + b[i] * X + c[i] * X2 + d[i] * X3 -def hermite_interpolate( - x, y, precision=250, type='cardinal', c=None, b=None, t=None -): +def hermite_interpolate(x, y, precision=250, + type='cardinal', c=None, b=None, t=None): """ Interpolate x, y using the hermite method. See https://en.wikipedia.org/wiki/Cubic_Hermite_spline @@ -133,26 +132,28 @@ def hermite_interpolate( c = 0 if type == 'finite_difference': for i in range(1, n): - m[i] = w[i] = .5 * ((y[i + 1] - y[i]) / (x[i + 1] - x[i]) - + (y[i] - y[i - 1]) / (x[i] - x[i - 1]) - ) if x[i + 1] - x[i] and x[i] - x[i - 1] else 0 + m[i] = w[i] = .5 * ( + (y[i + 1] - y[i]) / (x[i + 1] - x[i]) + + (y[i] - y[i - 1]) / ( + x[i] - x[i - 1]) + ) if x[i + 1] - x[i] and x[i] - x[i - 1] else 0 elif type == 'kochanek_bartels': c = c or 0 b = b or 0 t = t or 0 for i in range(1, n): - m[i] = .5 * ((1 - t) * (1 + b) * (1 + c) * (y[i] - y[i - 1]) - + (1 - t) * (1 - b) * (1 - c) * (y[i + 1] - y[i])) - w[i] = .5 * ((1 - t) * (1 + b) * (1 - c) * (y[i] - y[i - 1]) - + (1 - t) * (1 - b) * (1 + c) * (y[i + 1] - y[i])) + m[i] = .5 * ((1 - t) * (1 + b) * (1 + c) * (y[i] - y[i - 1]) + + (1 - t) * (1 - b) * (1 - c) * (y[i + 1] - y[i])) + w[i] = .5 * ((1 - t) * (1 + b) * (1 - c) * (y[i] - y[i - 1]) + + (1 - t) * (1 - b) * (1 + c) * (y[i + 1] - y[i])) if type == 'cardinal': c = c or 0 for i in range(1, n): - m[i] = w[i] = (1 - c) * (y[i + 1] - y[i - 1]) / ( - x[i + 1] - x[i - 1] - ) if x[i + 1] - x[i - 1] else 0 + m[i] = w[i] = (1 - c) * ( + y[i + 1] - y[i - 1]) / ( + x[i + 1] - x[i - 1]) if x[i + 1] - x[i - 1] else 0 def p(i, x_): t = (x_ - x[i]) / delta_x[i] @@ -161,13 +162,13 @@ def p(i, x_): h00 = 2 * t3 - 3 * t2 + 1 h10 = t3 - 2 * t2 + t - h01 = -2 * t3 + 3 * t2 + h01 = - 2 * t3 + 3 * t2 h11 = t3 - t2 - return ( - h00 * y[i] + h10 * m[i] * delta_x[i] + h01 * y[i + 1] - + h11 * w[i + 1] * delta_x[i] - ) + return (h00 * y[i] + + h10 * m[i] * delta_x[i] + + h01 * y[i + 1] + + h11 * w[i + 1] * delta_x[i]) for i in range(n + 1): yield x[i], y[i] @@ -238,6 +239,7 @@ def trigonometric_interpolate(x, y, precision=250, **kwargs): 'trigonometric': trigonometric_interpolate } + if __name__ == '__main__': from pygal import XY points = [(.1, 7), (.3, -4), (.6, 10), (.9, 8), (1.4, 3), (1.7, 1)] @@ -247,32 +249,16 @@ def trigonometric_interpolate(x, y, precision=250, **kwargs): xy.add('cubic', cubic_interpolate(*zip(*points))) xy.add('lagrange', lagrange_interpolate(*zip(*points))) xy.add('trigonometric', trigonometric_interpolate(*zip(*points))) - xy.add( - 'hermite catmul_rom', - hermite_interpolate(*zip(*points), type='catmul_rom') - ) - xy.add( - 'hermite finite_difference', - hermite_interpolate(*zip(*points), type='finite_difference') - ) - xy.add( - 'hermite cardinal -.5', - hermite_interpolate(*zip(*points), type='cardinal', c=-.5) - ) - xy.add( - 'hermite cardinal .5', - hermite_interpolate(*zip(*points), type='cardinal', c=.5) - ) - xy.add( - 'hermite kochanek_bartels .5 .75 -.25', - hermite_interpolate( - *zip(*points), type='kochanek_bartels', c=.5, b=.75, t=-.25 - ) - ) - xy.add( - 'hermite kochanek_bartels .25 -.75 .5', - hermite_interpolate( - *zip(*points), type='kochanek_bartels', c=.25, b=-.75, t=.5 - ) - ) + xy.add('hermite catmul_rom', hermite_interpolate( + *zip(*points), type='catmul_rom')) + xy.add('hermite finite_difference', hermite_interpolate( + *zip(*points), type='finite_difference')) + xy.add('hermite cardinal -.5', hermite_interpolate( + *zip(*points), type='cardinal', c=-.5)) + xy.add('hermite cardinal .5', hermite_interpolate( + *zip(*points), type='cardinal', c=.5)) + xy.add('hermite kochanek_bartels .5 .75 -.25', hermite_interpolate( + *zip(*points), type='kochanek_bartels', c=.5, b=.75, t=-.25)) + xy.add('hermite kochanek_bartels .25 -.75 .5', hermite_interpolate( + *zip(*points), type='kochanek_bartels', c=.25, b=-.75, t=.5)) xy.render_in_browser() diff --git a/pygal/maps/__init__.py b/pygal/maps/__init__.py index 4a1f243c..28ea40f9 100644 --- a/pygal/maps/__init__.py +++ b/pygal/maps/__init__.py @@ -16,4 +16,5 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Maps extensions namespace module""" diff --git a/pygal/serie.py b/pygal/serie.py index 46ea65ea..a5b70c42 100644 --- a/pygal/serie.py +++ b/pygal/serie.py @@ -22,6 +22,7 @@ class Serie(object): + """Serie class containing title, values and the graph serie index""" def __init__(self, index, values, config, metadata=None): diff --git a/pygal/state.py b/pygal/state.py index e85a7a55..7570a845 100644 --- a/pygal/state.py +++ b/pygal/state.py @@ -16,12 +16,14 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Class holding state during render""" from pygal.util import merge class State(object): + """ Class containing config values overriden by chart values diff --git a/pygal/stats.py b/pygal/stats.py index b49809af..55d03b7a 100644 --- a/pygal/stats.py +++ b/pygal/stats.py @@ -35,18 +35,15 @@ def ppf(x, n): # http://eprints.maths.ox.ac.uk/184/1/tdist.pdf raise ImportError( 'You must have scipy installed to use t-student ' - 'when sample_size is below 30' - ) + 'when sample_size is below 30') return norm_ppf(x) - # According to http://sphweb.bumc.bu.edu/otlt/MPH-Modules/BS/ # BS704_Confidence_Intervals/BS704_Confidence_Intervals_print.html def confidence_interval_continuous( - point_estimate, stddev, sample_size, confidence=.95, **kwargs -): + point_estimate, stddev, sample_size, confidence=.95, **kwargs): """Continuous confidence interval from sample size and standard error""" alpha = ppf((confidence + 1) / 2, sample_size - 1) @@ -55,13 +52,8 @@ def confidence_interval_continuous( def confidence_interval_dichotomous( - point_estimate, - sample_size, - confidence=.95, - bias=False, - percentage=True, - **kwargs -): + point_estimate, sample_size, confidence=.95, bias=False, + percentage=True, **kwargs): """Dichotomous confidence interval from sample size and maybe a bias""" alpha = ppf((confidence + 1) / 2, sample_size - 1) p = point_estimate diff --git a/pygal/svg.py b/pygal/svg.py index 5afdef6d..d72c1e68 100644 --- a/pygal/svg.py +++ b/pygal/svg.py @@ -32,13 +32,13 @@ from pygal.etree import etree from pygal.util import ( coord_abs_project, coord_diff, coord_dual, coord_format, coord_project, - minify_css, template -) + minify_css, template) nearly_2pi = 2 * pi - .00001 class Svg(object): + """Svg related methods""" ns = 'http://www.w3.org/2000/svg' @@ -53,9 +53,16 @@ def __init__(self, graph): self.id = '' self.processing_instructions = [] if etree.lxml: - attrs = {'nsmap': {None: self.ns, 'xlink': self.xlink_ns}} + attrs = { + 'nsmap': { + None: self.ns, + 'xlink': self.xlink_ns + } + } else: - attrs = {'xmlns': self.ns} + attrs = { + 'xmlns': self.ns + } if hasattr(etree, 'register_namespace'): etree.register_namespace('xlink', self.xlink_ns) else: @@ -66,15 +73,11 @@ def __init__(self, graph): if graph.classes: self.root.attrib['class'] = ' '.join(graph.classes) self.root.append( - etree.Comment( - u( - 'Generated with pygal %s (%s) ©Kozea 2012-2016 on %s' % ( - __version__, 'lxml' if etree.lxml else 'etree', - date.today().isoformat() - ) - ) - ) - ) + etree.Comment(u( + 'Generated with pygal %s (%s) ©Kozea 2012-2016 on %s' % ( + __version__, + 'lxml' if etree.lxml else 'etree', + date.today().isoformat())))) self.root.append(etree.Comment(u('http://pygal.org'))) self.root.append(etree.Comment(u('http://github.com/Kozea/pygal'))) self.defs = self.node(tag='defs') @@ -93,8 +96,8 @@ def add_styles(self): if self.graph.style._google_fonts: auto_css.append( - '//fonts.googleapis.com/css?family=%s' % - quote_plus('|'.join(self.graph.style._google_fonts)) + '//fonts.googleapis.com/css?family=%s' % quote_plus( + '|'.join(self.graph.style._google_fonts)) ) for css in auto_css + list(self.graph.css): @@ -105,7 +108,8 @@ def add_styles(self): css = css[len('file://'):] if not os.path.exists(css): - css = os.path.join(os.path.dirname(__file__), 'css', css) + css = os.path.join( + os.path.dirname(__file__), 'css', css) with io.open(css, encoding='utf-8') as f: css_text = template( @@ -113,8 +117,7 @@ def add_styles(self): style=self.graph.style, colors=colors, strokes=strokes, - id=self.id - ) + id=self.id) if css_text is not None: if not self.graph.pretty_print: @@ -124,11 +127,10 @@ def add_styles(self): if css.startswith('//') and self.graph.force_uri_protocol: css = '%s:%s' % (self.graph.force_uri_protocol, css) self.processing_instructions.append( - etree.PI(u('xml-stylesheet'), u('href="%s"' % css)) - ) + etree.PI( + u('xml-stylesheet'), u('href="%s"' % css))) self.node( - self.defs, 'style', type='text/css' - ).text = '\n'.join(all_css) + self.defs, 'style', type='text/css').text = '\n'.join(all_css) def add_scripts(self): """Add the js to the svg""" @@ -138,9 +140,8 @@ def get_js_dict(): return dict( (k, getattr(self.graph.state, k)) for k in dir(self.graph.config) - if not k.startswith('_') and hasattr(self.graph.state, k) - and not hasattr(getattr(self.graph.state, k), '__call__') - ) + if not k.startswith('_') and hasattr(self.graph.state, k) and + not hasattr(getattr(self.graph.state, k), '__call__')) def json_default(o): if isinstance(o, (datetime, date)): @@ -153,8 +154,7 @@ def json_default(o): # Config adds dct['legends'] = [ l.get('title') if isinstance(l, dict) else l - for l in self.graph._legends + self.graph._secondary_legends - ] + for l in self.graph._legends + self.graph._secondary_legends] common_js = 'window.pygal = window.pygal || {};' common_js += 'window.pygal.config = window.pygal.config || {};' @@ -187,7 +187,7 @@ def in_attrib_and_number(key): for pos, dim in (('x', 'width'), ('y', 'height')): if in_attrib_and_number(dim) and attrib[dim] < 0: - attrib[dim] = -attrib[dim] + attrib[dim] = - attrib[dim] if in_attrib_and_number(pos): attrib[pos] = attrib[pos] - attrib[dim] @@ -200,8 +200,8 @@ def in_attrib_and_number(key): attrib[key.rstrip('_')] = attrib[key] del attrib[key] elif key == 'href': - attrib[etree.QName('http://www.w3.org/1999/xlink', - key)] = attrib[key] + attrib[etree.QName( + 'http://www.w3.org/1999/xlink', key)] = attrib[key] del attrib[key] return etree.SubElement(parent, tag, attrib) @@ -226,17 +226,16 @@ def serie(self, serie): return dict( plot=self.node( self.graph.nodes['plot'], - class_='series serie-%d color-%d' % (serie.index, serie.index) - ), + class_='series serie-%d color-%d' % ( + serie.index, serie.index)), overlay=self.node( self.graph.nodes['overlay'], - class_='series serie-%d color-%d' % (serie.index, serie.index) - ), + class_='series serie-%d color-%d' % ( + serie.index, serie.index)), text_overlay=self.node( self.graph.nodes['text_overlay'], - class_='series serie-%d color-%d' % (serie.index, serie.index) - ) - ) + class_='series serie-%d color-%d' % ( + serie.index, serie.index))) def line(self, node, coords, close=False, **kwargs): """Draw a svg line""" @@ -255,54 +254,47 @@ def line(self, node, coords, close=False, **kwargs): coord_format = lambda xy: '%f %f' % xy origin = coord_format(coords[origin_index]) - line = ' '.join([ - coord_format(c) for c in coords[origin_index + 1:] if None not in c - ]) - return self.node(node, 'path', d=root % (origin, line), **kwargs) + line = ' '.join([coord_format(c) + for c in coords[origin_index + 1:] + if None not in c]) + return self.node( + node, 'path', d=root % (origin, line), **kwargs) def slice( - self, serie_node, node, radius, small_radius, angle, start_angle, - center, val, i, metadata - ): + self, serie_node, node, radius, small_radius, + angle, start_angle, center, val, i, metadata): """Draw a pie slice""" if angle == 2 * pi: angle = nearly_2pi if angle > 0: - to = [ - coord_abs_project(center, radius, start_angle), - coord_abs_project(center, radius, start_angle + angle), - coord_abs_project(center, small_radius, start_angle + angle), - coord_abs_project(center, small_radius, start_angle) - ] + to = [coord_abs_project(center, radius, start_angle), + coord_abs_project(center, radius, start_angle + angle), + coord_abs_project(center, small_radius, start_angle + angle), + coord_abs_project(center, small_radius, start_angle)] rv = self.node( - node, - 'path', + node, 'path', d='M%s A%s 0 %d 1 %s L%s A%s 0 %d 0 %s z' % ( - to[0], coord_dual(radius), int(angle > pi), to[1], to[2], - coord_dual(small_radius), int(angle > pi), to[3] - ), - class_='slice reactive tooltip-trigger' - ) + to[0], + coord_dual(radius), int(angle > pi), to[1], + to[2], + coord_dual(small_radius), int(angle > pi), to[3]), + class_='slice reactive tooltip-trigger') else: rv = None - x, y = coord_diff( - center, - coord_project((radius + small_radius) / 2, start_angle + angle / 2) - ) + x, y = coord_diff(center, coord_project( + (radius + small_radius) / 2, start_angle + angle / 2)) self.graph._tooltip_data( - node, val, x, y, "centered", self.graph._x_labels - and self.graph._x_labels[i][0] - ) + node, val, x, y, "centered", + self.graph._x_labels and self.graph._x_labels[i][0]) if angle >= 0.3: # 0.3 radians is about 17 degrees self.graph._static_value(serie_node, val, x, y, metadata) return rv def gauge_background( self, serie_node, start_angle, center, radius, small_radius, - end_angle, half_pie, max_value - ): + end_angle, half_pie, max_value): if end_angle == 2 * pi: end_angle = nearly_2pi @@ -311,45 +303,37 @@ def gauge_background( coord_abs_project(center, radius, start_angle), coord_abs_project(center, radius, end_angle), coord_abs_project(center, small_radius, end_angle), - coord_abs_project(center, small_radius, start_angle) - ] + coord_abs_project(center, small_radius, start_angle)] self.node( - serie_node['plot'], - 'path', + serie_node['plot'], 'path', d='M%s A%s 0 1 1 %s L%s A%s 0 1 0 %s z' % ( - to_shade[0], coord_dual(radius), to_shade[1], to_shade[2], - coord_dual(small_radius), to_shade[3] - ), - class_='gauge-background reactive' - ) + to_shade[0], + coord_dual(radius), + to_shade[1], + to_shade[2], + coord_dual(small_radius), + to_shade[3]), + class_='gauge-background reactive') if half_pie: begin_end = [ coord_diff( center, coord_project( - radius - (radius - small_radius) / 2, start_angle - ) - ), + radius - (radius - small_radius) / 2, start_angle)), coord_diff( center, coord_project( - radius - (radius - small_radius) / 2, end_angle - ) - ) - ] + radius - (radius - small_radius) / 2, end_angle))] pos = 0 for i in begin_end: self.node( - serie_node['plot'], - 'text', + serie_node['plot'], 'text', class_='y-{} bound reactive'.format(pos), x=i[0], y=i[1] + 10, - attrib={ - 'text-anchor': 'middle' - } + attrib={'text-anchor': 'middle'} ).text = '{}'.format(0 if pos == 0 else max_value) pos += 1 else: @@ -357,21 +341,22 @@ def gauge_background( # Correct text vertical alignment middle_radius -= .1 * (radius - small_radius) to_labels = [ - coord_abs_project(center, middle_radius, 0), - coord_abs_project(center, middle_radius, nearly_2pi) + coord_abs_project( + center, middle_radius, 0), + coord_abs_project( + center, middle_radius, nearly_2pi) ] self.node( - self.defs, - 'path', - id='valuePath-%s%s' % center, - d='M%s A%s 0 1 1 %s' % - (to_labels[0], coord_dual(middle_radius), to_labels[1]) - ) - text_ = self.node(serie_node['text_overlay'], 'text') + self.defs, 'path', id='valuePath-%s%s' % center, + d='M%s A%s 0 1 1 %s' % ( + to_labels[0], + coord_dual(middle_radius), + to_labels[1] + )) + text_ = self.node( + serie_node['text_overlay'], 'text') self.node( - text_, - 'textPath', - class_='max-value reactive', + text_, 'textPath', class_='max-value reactive', attrib={ 'href': '#valuePath-%s%s' % center, 'startOffset': '99%', @@ -380,42 +365,40 @@ def gauge_background( ).text = max_value def solid_gauge( - self, serie_node, node, radius, small_radius, angle, start_angle, - center, val, i, metadata, half_pie, end_angle, max_value - ): + self, serie_node, node, radius, small_radius, + angle, start_angle, center, val, i, metadata, half_pie, end_angle, + max_value): """Draw a solid gauge slice and background slice""" if angle == 2 * pi: angle = nearly_2pi if angle > 0: - to = [ - coord_abs_project(center, radius, start_angle), - coord_abs_project(center, radius, start_angle + angle), - coord_abs_project(center, small_radius, start_angle + angle), - coord_abs_project(center, small_radius, start_angle) - ] + to = [coord_abs_project(center, radius, start_angle), + coord_abs_project(center, radius, start_angle + angle), + coord_abs_project(center, small_radius, start_angle + angle), + coord_abs_project(center, small_radius, start_angle)] self.node( - node, - 'path', + node, 'path', d='M%s A%s 0 %d 1 %s L%s A%s 0 %d 0 %s z' % ( - to[0], coord_dual(radius), int(angle > pi), to[1], to[2], - coord_dual(small_radius), int(angle > pi), to[3] - ), - class_='slice reactive tooltip-trigger' - ) + to[0], + coord_dual(radius), + int(angle > pi), + to[1], + to[2], + coord_dual(small_radius), + int(angle > pi), + to[3]), + class_='slice reactive tooltip-trigger') else: return - x, y = coord_diff( - center, - coord_project((radius + small_radius) / 2, start_angle + angle / 2) - ) + x, y = coord_diff(center, coord_project( + (radius + small_radius) / 2, start_angle + angle / 2)) self.graph._static_value(serie_node, val, x, y, metadata, 'middle') self.graph._tooltip_data( - node, val, x, y, "centered", self.graph._x_labels - and self.graph._x_labels[i][0] - ) + node, val, x, y, "centered", + self.graph._x_labels and self.graph._x_labels[i][0]) def confidence_interval(self, node, x, low, high, width=7): if self.graph.horizontal: @@ -432,17 +415,12 @@ def confidence_interval(self, node, x, low, high, width=7): ci = self.node(node, class_="ci") self.node( - ci, - 'path', - d="M%s L%s M%s L%s M%s L%s L%s M%s L%s" % tuple( - map( - fmt, ( - top, shr(top), top, shl(top), top, bottom, shr(bottom), - bottom, shl(bottom) - ) - ) - ), - class_='nofill reactive' + ci, 'path', d="M%s L%s M%s L%s M%s L%s L%s M%s L%s" % tuple( + map(fmt, ( + top, shr(top), top, shl(top), top, + bottom, shr(bottom), bottom, shl(bottom) + )) + ), class_='nofill reactive' ) def pre_render(self): @@ -450,28 +428,26 @@ def pre_render(self): self.add_styles() self.add_scripts() self.root.set( - 'viewBox', '0 0 %d %d' % (self.graph.width, self.graph.height) - ) + 'viewBox', '0 0 %d %d' % (self.graph.width, self.graph.height)) if self.graph.explicit_size: self.root.set('width', str(self.graph.width)) self.root.set('height', str(self.graph.height)) def draw_no_data(self): """Write the no data text to the svg""" - no_data = self.node( - self.graph.nodes['text_overlay'], - 'text', - x=self.graph.view.width / 2, - y=self.graph.view.height / 2, - class_='no_data' - ) + no_data = self.node(self.graph.nodes['text_overlay'], 'text', + x=self.graph.view.width / 2, + y=self.graph.view.height / 2, + class_='no_data') no_data.text = self.graph.no_data_text def render(self, is_unicode=False, pretty_print=False): """Last thing to do before rendering""" for f in self.graph.xml_filters: self.root = f(self.root) - args = {'encoding': 'utf-8'} + args = { + 'encoding': 'utf-8' + } svg = b'' if etree.lxml: @@ -481,12 +457,14 @@ def render(self, is_unicode=False, pretty_print=False): svg = b"\n" if not self.graph.disable_xml_declaration: - svg += b'\n'.join([ - etree.tostring(pi, **args) - for pi in self.processing_instructions - ]) + svg += b'\n'.join( + [etree.tostring( + pi, **args) + for pi in self.processing_instructions] + ) - svg += etree.tostring(self.root, **args) + svg += etree.tostring( + self.root, **args) if self.graph.disable_xml_declaration or is_unicode: svg = svg.decode('utf-8') @@ -494,17 +472,16 @@ def render(self, is_unicode=False, pretty_print=False): def get_strokes(self): """Return a css snippet containing all stroke style options""" - def stroke_dict_to_css(stroke, i=None): """Return a css style for the given option""" - css = [ - '%s.series%s {\n' % - (self.id, '.serie-%d' % i if i is not None else '') - ] - for key in ('width', 'linejoin', 'linecap', 'dasharray', - 'dashoffset'): + css = ['%s.series%s {\n' % ( + self.id, '.serie-%d' % i if i is not None else '')] + for key in ( + 'width', 'linejoin', 'linecap', + 'dasharray', 'dashoffset'): if stroke.get(key): - css.append(' stroke-%s: %s;\n' % (key, stroke[key])) + css.append(' stroke-%s: %s;\n' % ( + key, stroke[key])) css.append('}') return '\n'.join(css) @@ -517,9 +494,5 @@ def stroke_dict_to_css(stroke, i=None): for secondary_serie in self.graph.secondary_series: if secondary_serie.stroke_style is not None: - css.append( - stroke_dict_to_css( - secondary_serie.stroke_style, secondary_serie.index - ) - ) + css.append(stroke_dict_to_css(secondary_serie.stroke_style, secondary_serie.index)) return '\n'.join(css) diff --git a/pygal/table.py b/pygal/table.py index a7c03dc5..5c51a13f 100644 --- a/pygal/table.py +++ b/pygal/table.py @@ -25,10 +25,12 @@ import uuid from lxml.html import builder, tostring + from pygal.util import template class HTML(object): + """Lower case adapter of lxml builder""" def __getattribute__(self, attr): @@ -37,6 +39,7 @@ def __getattribute__(self, attr): class Table(object): + """Table generator class""" _dual = None @@ -132,23 +135,33 @@ def render(self, total=False, transpose=False, style=False): if thead: parts.append( html.thead( - *[html.tr(*[html.th(_(col)) for col in r]) for r in thead] + *[html.tr( + *[html.th(_(col)) for col in r] + ) for r in thead] ) ) if tbody: parts.append( html.tbody( - *[html.tr(*[html.td(_(col)) for col in r]) for r in tbody] + *[html.tr( + *[html.td(_(col)) for col in r] + ) for r in tbody] ) ) if tfoot: parts.append( html.tfoot( - *[html.tr(*[html.th(_(col)) for col in r]) for r in tfoot] + *[html.tr( + *[html.th(_(col)) for col in r] + ) for r in tfoot] ) ) - table = tostring(html.table(*parts, **attrs)) + table = tostring( + html.table( + *parts, **attrs + ) + ) if style: if style is True: css = ''' @@ -184,9 +197,9 @@ def render(self, total=False, transpose=False, style=False): ''' else: css = style - table = tostring( - html.style(template(css, **attrs), scoped='scoped') - ) + table + table = tostring(html.style( + template(css, **attrs), + scoped='scoped')) + table table = table.decode('utf-8') self.chart.teardown() return table diff --git a/pygal/test/__init__.py b/pygal/test/__init__.py index 15725c07..1edf0357 100644 --- a/pygal/test/__init__.py +++ b/pygal/test/__init__.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Pygal test package""" import pygal @@ -26,9 +27,12 @@ def get_data(i): """Return sample test data for an index""" - return [[(-1, 1), (2, 0), (0, 4)], [(0, 1), (None, 2), (3, 2)], - [(-3, 3), (1, 3), (1, 1)], [(1, 1), (Decimal('1.'), 1), - (1, 1)], [(3, 2), (2, 1), (1., 1)]][i] + return [ + [(-1, 1), (2, 0), (0, 4)], + [(0, 1), (None, 2), (3, 2)], + [(-3, 3), (1, 3), (1, 1)], + [(1, 1), (Decimal('1.'), 1), (1, 1)], + [(3, 2), (2, 1), (1., 1)]][i] def adapt(chart, data): @@ -48,5 +52,7 @@ def adapt(chart, data): def make_data(chart, datas): """Add sample data to the test chart""" for i, data in enumerate(datas): - chart.add(data[0], adapt(chart, data[1]), secondary=bool(i % 2)) + chart.add(data[0], + adapt(chart, data[1]), + secondary=bool(i % 2)) return chart diff --git a/pygal/test/conftest.py b/pygal/test/conftest.py index ea360107..b8c8a593 100644 --- a/pygal/test/conftest.py +++ b/pygal/test/conftest.py @@ -16,12 +16,14 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """pytest fixtures""" import sys -import pygal import pytest + +import pygal from pygal.etree import etree from . import get_data @@ -52,6 +54,8 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("Chart", pygal.CHARTS) if "datas" in metafunc.funcargnames: metafunc.parametrize( - "datas", [[("Serie %d" % i, get_data(i)) for i in range(s)] - for s in (5, 1, 0)] - ) + "datas", + [ + [("Serie %d" % i, get_data(i)) for i in range(s)] + for s in (5, 1, 0) + ]) diff --git a/pygal/test/test_bar.py b/pygal/test/test_bar.py index ca74663c..631990b0 100644 --- a/pygal/test/test_bar.py +++ b/pygal/test/test_bar.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Bar chart related tests""" from pygal import Bar diff --git a/pygal/test/test_box.py b/pygal/test/test_box.py index e4d8c96c..48b42f58 100644 --- a/pygal/test/test_box.py +++ b/pygal/test/test_box.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Box chart related tests""" from pygal.graph.box import Box @@ -25,8 +26,7 @@ def test_quartiles(): """Test box points for the 1.5IQR computation method""" a = [-2.0, 3.0, 4.0, 5.0, 8.0] # odd test data (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - a, mode='1.5IQR' - ) + a, mode='1.5IQR') assert q1 == 7.0 / 4.0 assert q2 == 4.0 @@ -36,22 +36,19 @@ def test_quartiles(): b = [1.0, 4.0, 6.0, 8.0] # even test data (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - b, mode='1.5IQR' - ) + b, mode='1.5IQR') assert q2 == 5.0 c = [2.0, None, 4.0, 6.0, None] # odd with None elements (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - c, mode='1.5IQR' - ) + c, mode='1.5IQR') assert q2 == 4.0 d = [4] (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - d, mode='1.5IQR' - ) + d, mode='1.5IQR') assert q0 == 4 assert q1 == 4 @@ -64,8 +61,7 @@ def test_quartiles_min_extremes(): """Test box points for the extremes computation method""" a = [-2.0, 3.0, 4.0, 5.0, 8.0] # odd test data (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - a, mode='extremes' - ) + a, mode='extremes') assert q1 == 7.0 / 4.0 assert q2 == 4.0 @@ -75,22 +71,19 @@ def test_quartiles_min_extremes(): b = [1.0, 4.0, 6.0, 8.0] # even test data (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - b, mode='extremes' - ) + b, mode='extremes') assert q2 == 5.0 c = [2.0, None, 4.0, 6.0, None] # odd with None elements (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - c, mode='extremes' - ) + c, mode='extremes') assert q2 == 4.0 d = [4] (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - d, mode='extremes' - ) + d, mode='extremes') assert q0 == 4 assert q1 == 4 @@ -103,16 +96,14 @@ def test_quartiles_tukey(): """Test box points for the tukey computation method""" a = [] # empty data (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - a, mode='tukey' - ) + a, mode='tukey') assert min_s == q0 == q1 == q2 == q3 == q4 == 0 assert outliers == [] # https://en.wikipedia.org/wiki/Quartile example 1 b = [6, 7, 15, 36, 39, 40, 41, 42, 43, 47, 49] (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - b, mode='tukey' - ) + b, mode='tukey') assert min_s == q0 == 6 assert q1 == 20.25 assert q2 == 40 @@ -123,8 +114,7 @@ def test_quartiles_tukey(): # previous test with added outlier 75 c = [6, 7, 15, 36, 39, 40, 41, 42, 43, 47, 49, 75] (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - c, mode='tukey' - ) + c, mode='tukey') assert min_s == q0 == 6 assert q1 == 25.5 assert q2 == (40 + 41) / 2.0 @@ -135,8 +125,7 @@ def test_quartiles_tukey(): # one more outlier, 77 c = [6, 7, 15, 36, 39, 40, 41, 42, 43, 47, 49, 75, 77] (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - c, mode='tukey' - ) + c, mode='tukey') assert min_s == q0 == 6 assert q1 == 30.75 assert q2 == 41 @@ -148,14 +137,11 @@ def test_quartiles_tukey(): def test_quartiles_stdev(): """Test box points for the stdev computation method""" - a = [ - 35, 42, 35, 41, 36, 6, 12, 51, 33, 27, 46, 36, 44, 53, 75, 46, 16, 51, - 45, 29, 25, 26, 54, 61, 27, 40, 23, 34, 51, 37 - ] + a = [35, 42, 35, 41, 36, 6, 12, 51, 33, 27, 46, 36, 44, 53, 75, 46, 16, + 51, 45, 29, 25, 26, 54, 61, 27, 40, 23, 34, 51, 37] SD = 14.67 (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - a, mode='stdev' - ) + a, mode='stdev') assert min_s == min(a) assert max_s == max(a) assert q2 == 36.5 @@ -165,8 +151,7 @@ def test_quartiles_stdev(): b = [5] # test for posible zero division (min_s, q0, q1, q2, q3, q4, max_s), outliers = Box._box_points( - b, mode='stdev' - ) + b, mode='stdev') assert min_s == q0 == q1 == q2 == q3 == q4 == max_s == b[0] assert outliers == [] diff --git a/pygal/test/test_colors.py b/pygal/test/test_colors.py index 5913efe6..46af59ce 100644 --- a/pygal/test/test_colors.py +++ b/pygal/test/test_colors.py @@ -16,14 +16,14 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Color utility functions tests""" from __future__ import division from pygal.colors import ( darken, desaturate, hsl_to_rgb, lighten, parse_color, rgb_to_hsl, rotate, - saturate, unparse_color -) + saturate, unparse_color) def test_parse_color(): diff --git a/pygal/test/test_config.py b/pygal/test/test_config.py index 52507919..ec0e587f 100644 --- a/pygal/test/test_config.py +++ b/pygal/test/test_config.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Various config options tested on one chart type or more""" from tempfile import NamedTemporaryFile @@ -24,8 +25,7 @@ XY, Bar, Box, Config, DateLine, DateTimeLine, Dot, Funnel, Gauge, Histogram, HorizontalBar, HorizontalLine, HorizontalStackedBar, HorizontalStackedLine, Line, Pie, Pyramid, Radar, SolidGauge, - TimeDeltaLine, TimeLine, Treemap, formatters -) + TimeDeltaLine, TimeLine, Treemap, formatters) from pygal._compat import _ellipsis, u from pygal.graph.dual import Dual from pygal.graph.horizontal import HorizontalGraph @@ -59,8 +59,7 @@ def test_config_behaviours(): fill=True, pretty_print=True, no_prefix=True, - x_labels=['a', 'b', 'c'] - ) + x_labels=['a', 'b', 'c']) line2.add('_', [1, 2, 3]) l2 = line2.render() assert l1 == l2 @@ -100,7 +99,6 @@ 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 @@ -124,7 +122,6 @@ 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,7 +146,6 @@ 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 @@ -185,7 +181,7 @@ class LineConfig(Config): def test_logarithmic(): """Test logarithmic option""" line = Line(logarithmic=True) - line.add('_', [1, 10**10, 1]) + line.add('_', [1, 10 ** 10, 1]) q = line.render_pyquery() assert len(q(".axis.x")) == 0 assert len(q(".axis.y")) == 1 @@ -231,7 +227,7 @@ def test_logarithmic_bad_interpolation(): def test_logarithmic_big_scale(): """Test logarithmic option with a large range of value""" line = Line(logarithmic=True) - line.add('_', [10**-10, 10**10, 1]) + line.add('_', [10 ** -10, 10 ** 10, 1]) q = line.render_pyquery() assert len(q(".y.axis .guides")) == 21 @@ -239,20 +235,17 @@ def test_logarithmic_big_scale(): def test_value_formatter(): """Test value formatter option""" line = Line(value_formatter=lambda x: str(x) + u('‰')) - line.add('_', [10**4, 10**5, 23 * 10**4]) + line.add('_', [10 ** 4, 10 ** 5, 23 * 10 ** 4]) q = line.render_pyquery() assert len(q(".y.axis .guides")) == 11 - assert q(".axis.y text").map(texts) == list( - map( - lambda x: str(x) + u('‰'), map(float, range(20000, 240000, 20000)) - ) - ) + assert q(".axis.y text").map(texts) == list(map( + lambda x: str(x) + u('‰'), map(float, range(20000, 240000, 20000)))) def test_logarithmic_small_scale(): """Test logarithmic with a small range of values""" line = Line(logarithmic=True) - line.add('_', [1 + 10**10, 3 + 10**10, 2 + 10**10]) + line.add('_', [1 + 10 ** 10, 3 + 10 ** 10, 2 + 10 ** 10]) q = line.render_pyquery() assert len(q(".y.axis .guides")) == 11 @@ -260,18 +253,16 @@ def test_logarithmic_small_scale(): def test_human_readable(): """Test human readable option""" line = Line() - line.add('_', [10**4, 10**5, 23 * 10**4]) + line.add('_', [10 ** 4, 10 ** 5, 23 * 10 ** 4]) q = line.render_pyquery() - assert q(".axis.y text").map(texts) == list( - map(str, range(20000, 240000, 20000)) - ) + assert q(".axis.y text").map(texts) == list(map( + str, range(20000, 240000, 20000))) line.value_formatter = formatters.human_readable q = line.render_pyquery() - assert q(".axis.y text").map(texts) == list( - map(lambda x: '%dk' % x, range(20, 240, 20)) - ) + assert q(".axis.y text").map(texts) == list(map( + lambda x: '%dk' % x, range(20, 240, 20))) def test_show_legend(): @@ -309,8 +300,9 @@ def test_no_data(): def test_include_x_axis(Chart): """Test x axis inclusion option""" chart = Chart() - if Chart in (Pie, Treemap, Radar, Funnel, Dot, Gauge, Histogram, Box, - SolidGauge) or issubclass(Chart, BaseMap): + if Chart in ( + Pie, Treemap, Radar, Funnel, Dot, Gauge, Histogram, Box, SolidGauge + ) or issubclass(Chart, BaseMap): return if not chart._dual: data = 100, 200, 150 @@ -320,8 +312,7 @@ def test_include_x_axis(Chart): q = chart.render_pyquery() # Ghost thing yaxis = ".axis.%s .guides text" % ( - 'y' if not getattr(chart, 'horizontal', False) else 'x' - ) + 'y' if not getattr(chart, 'horizontal', False) else 'x') if not isinstance(chart, Bar): assert '0' not in q(yaxis).map(texts) else: @@ -395,11 +386,9 @@ def test_legend_at_bottom(Chart): def test_x_y_title(Chart): """Test x title and y title options""" - chart = Chart( - title='I Am A Title', - x_title="I am a x title", - y_title="I am a y title" - ) + chart = Chart(title='I Am A Title', + x_title="I am a x title", + y_title="I am a y title") chart.add('1', [4, -5, 123, 59, 38]) chart.add('2', [89, 0, 8, .12, 8]) q = chart.render_pyquery() @@ -408,7 +397,9 @@ def test_x_y_title(Chart): def test_range(Chart): """Test y label major option""" - if Chart in (Pie, Treemap, Dot, SolidGauge) or issubclass(Chart, BaseMap): + if Chart in ( + Pie, Treemap, Dot, SolidGauge + ) or issubclass(Chart, BaseMap): return chart = Chart() chart.range = (0, 100) @@ -423,10 +414,11 @@ def test_range(Chart): def test_x_label_major(Chart): """Test x label major option""" - if Chart in (Pie, Treemap, Funnel, Dot, Gauge, Histogram, Box, SolidGauge, - Pyramid, DateTimeLine, TimeLine, DateLine, - TimeDeltaLine) or issubclass( - Chart, (BaseMap, Dual, HorizontalGraph)): + if Chart in ( + Pie, Treemap, Funnel, Dot, Gauge, Histogram, Box, SolidGauge, + Pyramid, DateTimeLine, TimeLine, DateLine, + TimeDeltaLine + ) or issubclass(Chart, (BaseMap, Dual, HorizontalGraph)): return chart = Chart() chart.add('test', range(12)) @@ -467,10 +459,13 @@ def test_x_label_major(Chart): def test_y_label_major(Chart): """Test y label major option""" - if Chart in (Pie, Treemap, Funnel, Dot, Gauge, Histogram, Box, SolidGauge, - HorizontalBar, HorizontalStackedBar, HorizontalStackedLine, - HorizontalLine, Pyramid, DateTimeLine, TimeLine, DateLine, - TimeDeltaLine) or issubclass(Chart, BaseMap): + if Chart in ( + Pie, Treemap, Funnel, Dot, Gauge, Histogram, Box, SolidGauge, + HorizontalBar, HorizontalStackedBar, + HorizontalStackedLine, HorizontalLine, + Pyramid, DateTimeLine, TimeLine, DateLine, + TimeDeltaLine + ) or issubclass(Chart, BaseMap): return chart = Chart() data = range(12) @@ -534,24 +529,24 @@ def test_render_data_uri(Chart): chart = Chart(fill=True) chart.add(u('ééé'), [1, 2, 3]) chart.add(u('èèè'), [10, 21, 5]) - assert chart.render_data_uri( - ).startswith('data:image/svg+xml;charset=utf-8;base64,') + assert chart.render_data_uri().startswith( + 'data:image/svg+xml;charset=utf-8;base64,') def test_formatters(Chart): """Test custom formatters""" if Chart._dual or Chart == Box: return - chart = Chart(formatter=lambda x, chart, serie: '%s%s$' % (x, serie.title)) + chart = Chart( + formatter=lambda x, chart, serie: '%s%s$' % (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 ())) + 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 ())) def test_classes(Chart): @@ -562,10 +557,10 @@ def test_classes(Chart): chart = Chart(classes=()) assert not chart.render_pyquery().attr('class') - chart = Chart(classes=(_ellipsis, )) + chart = Chart(classes=(_ellipsis,)) assert chart.render_pyquery().attr('class') == 'pygal-chart' - chart = Chart(classes=('graph', )) + chart = Chart(classes=('graph',)) assert chart.render_pyquery().attr('class') == 'graph' chart = Chart(classes=('pygal-chart', 'graph')) diff --git a/pygal/test/test_date.py b/pygal/test/test_date.py index 4f8da1ad..36882b91 100644 --- a/pygal/test/test_date.py +++ b/pygal/test/test_date.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Date related charts tests""" from datetime import date, datetime, time, timedelta @@ -28,107 +29,149 @@ def test_date(): """Test a simple dateline""" date_chart = DateLine(truncate_label=1000) - date_chart.add( - 'dates', [(date(2013, 1, 2), 300), (date(2013, 1, 12), 412), - (date(2013, 2, 2), 823), (date(2013, 2, 22), 672)] - ) + date_chart.add('dates', [ + (date(2013, 1, 2), 300), + (date(2013, 1, 12), 412), + (date(2013, 2, 2), 823), + (date(2013, 2, 22), 672) + ]) q = date_chart.render_pyquery() - dates = list(map(lambda t: t.split(' ')[0], q(".axis.x text").map(texts))) - assert dates == ['2013-01-12', '2013-01-24', '2013-02-04', '2013-02-16'] + + assert list( + map(lambda t: t.split(' ')[0], + q(".axis.x text").map(texts))) == [ + '2013-01-12', + '2013-01-24', + '2013-02-04', + '2013-02-16'] def test_time(): """Test a simple timeline""" time_chart = TimeLine(truncate_label=1000) - time_chart.add( - 'times', [(time(1, 12, 29), 2), (time(21, 2, 29), 10), - (time(12, 30, 59), 7)] - ) + time_chart.add('times', [ + (time(1, 12, 29), 2), + (time(21, 2, 29), 10), + (time(12, 30, 59), 7) + ]) q = time_chart.render_pyquery() - dates = list(map(lambda t: t.split(' ')[0], q(".axis.x text").map(texts))) - assert dates == [ - '02:46:40', '05:33:20', '08:20:00', '11:06:40', '13:53:20', '16:40:00', - '19:26:40' - ] + + assert list( + map(lambda t: t.split(' ')[0], + q(".axis.x text").map(texts))) == [ + '02:46:40', + '05:33:20', + '08:20:00', + '11:06:40', + '13:53:20', + '16:40:00', + '19:26:40'] def test_datetime(): """Test a simple datetimeline""" datetime_chart = DateTimeLine(truncate_label=1000) - datetime_chart.add( - 'datetimes', - [(datetime(2013, 1, 2, 1, 12, 29), 300), - (datetime(2013, 1, 12, 21, 2, 29), 412), - (datetime(2013, 2, 2, 12, 30, 59), 823), (datetime(2013, 2, 22), 672)] - ) + datetime_chart.add('datetimes', [ + (datetime(2013, 1, 2, 1, 12, 29), 300), + (datetime(2013, 1, 12, 21, 2, 29), 412), + (datetime(2013, 2, 2, 12, 30, 59), 823), + (datetime(2013, 2, 22), 672) + ]) q = datetime_chart.render_pyquery() - dates = list(map(lambda t: t.split(' ')[0], q(".axis.x text").map(texts))) - assert dates == [ - '2013-01-12T14:13:20', '2013-01-24T04:00:00', '2013-02-04T17:46:40', - '2013-02-16T07:33:20' - ] + + assert list( + map(lambda t: t.split(' ')[0], + q(".axis.x text").map(texts))) == [ + '2013-01-12T14:13:20', + '2013-01-24T04:00:00', + '2013-02-04T17:46:40', + '2013-02-16T07:33:20'] def test_timedelta(): """Test a simple timedeltaline""" timedelta_chart = TimeDeltaLine(truncate_label=1000) - timedelta_chart.add( - 'timedeltas', [ - (timedelta(seconds=1), 10), - (timedelta(weeks=1), 50), - (timedelta(hours=3, seconds=30), 3), - (timedelta(microseconds=12112), .3), - ] - ) + timedelta_chart.add('timedeltas', [ + (timedelta(seconds=1), 10), + (timedelta(weeks=1), 50), + (timedelta(hours=3, seconds=30), 3), + (timedelta(microseconds=12112), .3), + ]) q = timedelta_chart.render_pyquery() - assert list(t for t in q(".axis.x text").map(texts) if t != '0:00:00') == [ - '1 day, 3:46:40', '2 days, 7:33:20', '3 days, 11:20:00', - '4 days, 15:06:40', '5 days, 18:53:20', '6 days, 22:40:00' - ] + assert list( + t for t in q(".axis.x text").map(texts) if t != '0:00:00' + ) == [ + '1 day, 3:46:40', + '2 days, 7:33:20', + '3 days, 11:20:00', + '4 days, 15:06:40', + '5 days, 18:53:20', + '6 days, 22:40:00'] def test_date_xrange(): """Test dateline with xrange""" datey = DateLine(truncate_label=1000) - datey.add( - 'dates', [(date(2013, 1, 2), 300), (date(2013, 1, 12), 412), - (date(2013, 2, 2), 823), (date(2013, 2, 22), 672)] - ) + datey.add('dates', [ + (date(2013, 1, 2), 300), + (date(2013, 1, 12), 412), + (date(2013, 2, 2), 823), + (date(2013, 2, 22), 672) + ]) datey.xrange = (date(2013, 1, 1), date(2013, 3, 1)) q = datey.render_pyquery() - dates = list(map(lambda t: t.split(' ')[0], q(".axis.x text").map(texts))) - assert dates == [ - '2013-01-01', '2013-01-12', '2013-01-24', '2013-02-04', '2013-02-16', - '2013-02-27' - ] + assert list( + map(lambda t: t.split(' ')[0], + q(".axis.x text").map(texts))) == [ + '2013-01-01', + '2013-01-12', + '2013-01-24', + '2013-02-04', + '2013-02-16', + '2013-02-27'] def test_date_labels(): """Test dateline with xrange""" datey = DateLine(truncate_label=1000) - datey.add( - 'dates', [(date(2013, 1, 2), 300), (date(2013, 1, 12), 412), - (date(2013, 2, 2), 823), (date(2013, 2, 22), 672)] - ) - - datey.x_labels = [date(2013, 1, 1), date(2013, 2, 1), date(2013, 3, 1)] + datey.add('dates', [ + (date(2013, 1, 2), 300), + (date(2013, 1, 12), 412), + (date(2013, 2, 2), 823), + (date(2013, 2, 22), 672) + ]) + + datey.x_labels = [ + date(2013, 1, 1), + date(2013, 2, 1), + date(2013, 3, 1) + ] q = datey.render_pyquery() - dates = list(map(lambda t: t.split(' ')[0], q(".axis.x text").map(texts))) - assert dates == ['2013-01-01', '2013-02-01', '2013-03-01'] + assert list( + map(lambda t: t.split(' ')[0], + q(".axis.x text").map(texts))) == [ + '2013-01-01', + '2013-02-01', + '2013-03-01'] def test_utc_timestamping(): - assert timestamp(datetime(2017, 7, 14, 2, - 40).replace(tzinfo=utc)) == 1500000000 - - for d in (datetime.now(), datetime.utcnow(), datetime( - 1999, 12, 31, 23, 59, 59), datetime(2000, 1, 1, 0, 0, 0)): - assert datetime.utcfromtimestamp(timestamp(d) - ) - d < timedelta(microseconds=10) + assert timestamp( + datetime(2017, 7, 14, 2, 40).replace(tzinfo=utc) + ) == 1500000000 + + for d in ( + datetime.now(), + datetime.utcnow(), + datetime(1999, 12, 31, 23, 59, 59), + datetime(2000, 1, 1, 0, 0, 0) + ): + assert datetime.utcfromtimestamp( + timestamp(d)) - d < timedelta(microseconds=10) diff --git a/pygal/test/test_formatters.py b/pygal/test/test_formatters.py index 61c8d944..1e6eb736 100644 --- a/pygal/test/test_formatters.py +++ b/pygal/test/test_formatters.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Test formatters""" from pygal import formatters diff --git a/pygal/test/test_graph.py b/pygal/test/test_graph.py index d454b255..3ee99f14 100644 --- a/pygal/test/test_graph.py +++ b/pygal/test/test_graph.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Generate tests for different chart types with different data""" import io @@ -23,8 +24,9 @@ import sys import uuid -import pygal import pytest + +import pygal from pygal._compat import u from pygal.graph.map import BaseMap from pygal.test import make_data @@ -80,44 +82,26 @@ def test_metadata(Chart): """Test metadata values""" chart = Chart() v = range(7) - if Chart in (pygal.Box, ): + if Chart in (pygal.Box,): return # summary charts cannot display per-value metadata elif Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) elif issubclass(Chart, BaseMap): - v = [(k, i) for i, k in enumerate(Chart.x_labels) - if k not in ['oecd', 'nafta', 'eur']] - - chart.add( - 'Serie with metadata', [ - v[0], { - 'value': v[1] - }, { - 'value': v[2], - 'label': 'Three' - }, { - 'value': v[3], - 'xlink': 'http://4.example.com/' - }, { - 'value': v[4], - 'xlink': 'http://5.example.com/', - 'label': 'Five' - }, { - 'value': v[5], - 'xlink': { - 'href': 'http://6.example.com/' - }, - 'label': 'Six' - }, { - 'value': v[6], - 'xlink': { - 'href': 'http://7.example.com/', - 'target': '_blank' - }, - 'label': 'Seven' - } - ] - ) + v = [(k, i) for i, k in enumerate(Chart.x_labels) if k not in [ + 'oecd', 'nafta', 'eur']] + + chart.add('Serie with metadata', [ + v[0], + {'value': v[1]}, + {'value': v[2], 'label': 'Three'}, + {'value': v[3], 'xlink': 'http://4.example.com/'}, + {'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five'}, + {'value': v[5], 'xlink': { + 'href': 'http://6.example.com/'}, 'label': 'Six'}, + {'value': v[6], 'xlink': { + 'href': 'http://7.example.com/', + 'target': '_blank'}, 'label': 'Seven'} + ]) q = chart.render_pyquery() for md in ('Three', 'Five', 'Seven'): assert md in cut(q('desc'), 'text') @@ -305,22 +289,20 @@ def test_no_data_with_lists_of_nones(Chart): def test_unicode_labels_decode(Chart): """Test unicode labels""" chart = Chart() - chart.add( - u('Série1'), [{ - 'value': 1, - 'xlink': 'http://1/', - 'label': u(r'{\\}°ijæð©&×&<—×€¿_…\\{_…') - }, { - 'value': 2, - 'xlink': { - 'href': 'http://6.example.com/' - }, - 'label': u('æ°€≠|€æ°€əæ') - }, { - 'value': 3, - 'label': 'unicode <3' - }] - ) + chart.add(u('Série1'), [{ + 'value': 1, + 'xlink': 'http://1/', + 'label': u('{\}°ijæð©&×&<—×€¿_…\{_…') + }, { + 'value': 2, + 'xlink': { + 'href': 'http://6.example.com/' + }, + 'label': u('æ°€≠|€æ°€əæ') + }, { + 'value': 3, + 'label': 'unicode <3' + }]) if not chart._dual: chart.x_labels = [u('&œ'), u('¿?'), u('††††††††'), 'unicode <3'] chart.render_pyquery() @@ -331,22 +313,20 @@ def test_unicode_labels_python2(Chart): if sys.version_info[0] == 3: return chart = Chart() - chart.add( - u('Série1'), [{ - 'value': 1, - 'xlink': 'http://1/', - 'label': eval(r"u'{\\}°ijæð©&×&<—×€¿_…\\{_…'") - }, { - 'value': 2, - 'xlink': { - 'href': 'http://6.example.com/' - }, - 'label': eval("u'æ°€≠|€æ°€əæ'") - }, { - 'value': 3, - 'label': eval("'unicode <3'") - }] - ) + chart.add(u('Série1'), [{ + 'value': 1, + 'xlink': 'http://1/', + 'label': eval("u'{\}°ijæð©&×&<—×€¿_…\{_…'") + }, { + 'value': 2, + 'xlink': { + 'href': 'http://6.example.com/' + }, + 'label': eval("u'æ°€≠|€æ°€əæ'") + }, { + 'value': 3, + 'label': eval("'unicode <3'") + }]) if not chart._dual: chart.x_labels = eval("[u'&œ', u'¿?', u'††††††††', 'unicode <3']") chart.render_pyquery() @@ -357,22 +337,20 @@ def test_unicode_labels_python3(Chart): if sys.version_info[0] == 2: return chart = Chart() - chart.add( - u('Série1'), [{ - 'value': 1, - 'xlink': 'http://1/', - 'label': eval(r"'{\\}°ijæð©&×&<—×€¿_…\\{_…'") - }, { - 'value': 2, - 'xlink': { - 'href': 'http://6.example.com/' - }, - 'label': eval("'æ°€≠|€æ°€əæ'") - }, { - 'value': 3, - 'label': eval("b'unicode <3'") - }] - ) + chart.add(u('Série1'), [{ + 'value': 1, + 'xlink': 'http://1/', + 'label': eval("'{\}°ijæð©&×&<—×€¿_…\{_…'") + }, { + 'value': 2, + 'xlink': { + 'href': 'http://6.example.com/' + }, + 'label': eval("'æ°€≠|€æ°€əæ'") + }, { + 'value': 3, + 'label': eval("b'unicode <3'") + }]) if not chart._dual: chart.x_labels = eval("['&œ', '¿?', '††††††††', 'unicode <3']") chart.render_pyquery() @@ -383,49 +361,31 @@ def test_labels_with_links(Chart): chart = Chart() # link on chart and label chart.add({ - 'title': 'Red', - 'xlink': { - 'href': 'http://en.wikipedia.org/wiki/Red' - } + 'title': 'Red', 'xlink': {'href': 'http://en.wikipedia.org/wiki/Red'} }, [{ 'value': 2, 'label': 'This is red', - 'xlink': { - 'href': 'http://en.wikipedia.org/wiki/Red' - } - }]) + 'xlink': {'href': 'http://en.wikipedia.org/wiki/Red'}}]) # link on chart only - chart.add( - 'Green', [{ - 'value': 4, - 'label': 'This is green', - 'xlink': { - 'href': 'http://en.wikipedia.org/wiki/Green', - 'target': '_top' - } - }] - ) + chart.add('Green', [{ + 'value': 4, + 'label': 'This is green', + 'xlink': { + 'href': 'http://en.wikipedia.org/wiki/Green', + 'target': '_top'}}]) # link on label only opens in new tab - chart.add({ - 'title': 'Yellow', - 'xlink': { - 'href': 'http://en.wikipedia.org/wiki/Yellow', - 'target': '_blank' - } - }, 7) + chart.add({'title': 'Yellow', 'xlink': { + 'href': 'http://en.wikipedia.org/wiki/Yellow', + 'target': '_blank'}}, 7) # link on chart only - chart.add( - 'Blue', [{ - 'value': 5, - 'xlink': { - 'href': 'http://en.wikipedia.org/wiki/Blue', - 'target': '_blank' - } - }] - ) + chart.add('Blue', [{ + 'value': 5, + 'xlink': { + 'href': 'http://en.wikipedia.org/wiki/Blue', + 'target': '_blank'}}]) # link on label and chart with diffrent behaviours chart.add({ @@ -436,9 +396,7 @@ def test_labels_with_links(Chart): 'label': 'This is violet', 'xlink': { 'href': 'http://en.wikipedia.org/wiki/Violet_(color)', - 'target': '_self' - } - }]) + 'target': '_self'}}]) q = chart.render_pyquery() links = q('a') @@ -458,7 +416,9 @@ def test_secondary(Chart): chart = Chart() rng = [83, .12, -34, 59] chart.add('First serie', rng) - chart.add('Secondary serie', map(lambda x: x * 2, rng), secondary=True) + chart.add('Secondary serie', + map(lambda x: x * 2, rng), + secondary=True) assert chart.render_pyquery() @@ -476,8 +436,7 @@ def test_long_title(Chart, datas): "'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." - ) + "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/test/test_histogram.py b/pygal/test/test_histogram.py index d3813f91..04793a0e 100644 --- a/pygal/test/test_histogram.py +++ b/pygal/test/test_histogram.py @@ -16,15 +16,22 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Histogram chart related tests""" + from pygal import Histogram def test_histogram(): """Simple histogram test""" hist = Histogram() - hist.add('1', [(2, 0, 1), (4, 1, 3), (3, 3.5, 5), (1.5, 5, 10)]) + hist.add('1', [ + (2, 0, 1), + (4, 1, 3), + (3, 3.5, 5), + (1.5, 5, 10) + ]) hist.add('2', [(2, 2, 8)], secondary=True) q = hist.render_pyquery() assert len(q('.rect')) == 5 diff --git a/pygal/test/test_interpolate.py b/pygal/test/test_interpolate.py index 3ef3b87c..a2eda9c8 100644 --- a/pygal/test/test_interpolate.py +++ b/pygal/test/test_interpolate.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Interpolations tests""" from pygal.test import make_data @@ -69,71 +70,44 @@ def test_hermite(Chart, datas): def test_hermite_finite(Chart, datas): """Test hermite finite difference interpolation""" - chart = Chart( - interpolate='hermite', - interpolation_parameters={'type': 'finite_difference'} - ) + chart = Chart(interpolate='hermite', + interpolation_parameters={'type': 'finite_difference'}) chart = make_data(chart, datas) assert chart.render() def test_hermite_cardinal(Chart, datas): """Test hermite cardinal interpolation""" - chart = Chart( - interpolate='hermite', - interpolation_parameters={ - 'type': 'cardinal', - 'c': .75 - } - ) + chart = Chart(interpolate='hermite', + interpolation_parameters={'type': 'cardinal', 'c': .75}) chart = make_data(chart, datas) assert chart.render() def test_hermite_catmull_rom(Chart, datas): """Test hermite catmull rom interpolation""" - chart = Chart( - interpolate='hermite', - interpolation_parameters={'type': 'catmull_rom'} - ) + chart = Chart(interpolate='hermite', + interpolation_parameters={'type': 'catmull_rom'}) chart = make_data(chart, datas) assert chart.render() def test_hermite_kochanek_bartels(Chart, datas): """Test hermite kochanek bartels interpolation""" - chart = Chart( - interpolate='hermite', - interpolation_parameters={ - 'type': 'kochanek_bartels', - 'b': -1, - 'c': 1, - 't': 1 - } - ) + chart = Chart(interpolate='hermite', + interpolation_parameters={ + 'type': 'kochanek_bartels', 'b': -1, 'c': 1, 't': 1}) chart = make_data(chart, datas) assert chart.render() - chart = Chart( - interpolate='hermite', - interpolation_parameters={ - 'type': 'kochanek_bartels', - 'b': -1, - 'c': -8, - 't': 0 - } - ) + chart = Chart(interpolate='hermite', + interpolation_parameters={ + 'type': 'kochanek_bartels', 'b': -1, 'c': -8, 't': 0}) chart = make_data(chart, datas) assert chart.render() - chart = Chart( - interpolate='hermite', - interpolation_parameters={ - 'type': 'kochanek_bartels', - 'b': 0, - 'c': 10, - 't': -1 - } - ) + chart = Chart(interpolate='hermite', + interpolation_parameters={ + 'type': 'kochanek_bartels', 'b': 0, 'c': 10, 't': -1}) chart = make_data(chart, datas) assert chart.render() diff --git a/pygal/test/test_line.py b/pygal/test/test_line.py index 7a66475f..c1022320 100644 --- a/pygal/test/test_line.py +++ b/pygal/test/test_line.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Line chart related tests""" from __future__ import division @@ -44,13 +45,11 @@ def test_simple_line(): assert len(q(".y.axis .guides")) == 13 assert len(q(".dots")) == 3 * 13 assert q(".axis.x text").map(texts) == [ - '-30', '-25', '-20', '-15', '-10', '-5', '0', '5', '10', '15', '20', - '25', '30' - ] + '-30', '-25', '-20', '-15', '-10', '-5', + '0', '5', '10', '15', '20', '25', '30'] assert q(".axis.y text").map(texts) == [ - '-1.2', '-1', '-0.8', '-0.6', '-0.4', '-0.2', '0', '0.2', '0.4', '0.6', - '0.8', '1', '1.2' - ] + '-1.2', '-1', '-0.8', '-0.6', '-0.4', '-0.2', + '0', '0.2', '0.4', '0.6', '0.8', '1', '1.2'] assert q(".title").text() == 'cos sin and cos - sin' assert q(".legend text").map(texts) == ['test1', 'test2', 'test3'] @@ -104,8 +103,7 @@ def test_not_equal_x_labels(): assert len(q(".dots")) == 100 assert len(q(".axis.x")) == 1 assert q(".axis.x text").map(texts) == [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' - ] + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] def test_int_x_labels(): @@ -118,8 +116,7 @@ def test_int_x_labels(): assert len(q(".dots")) == 100 assert len(q(".axis.x")) == 1 assert q(".axis.x text").map(texts) == [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' - ] + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] def test_only_major_dots_every(): @@ -151,7 +148,7 @@ def test_only_major_dots_count(): def test_only_major_dots(): """Test major dots with specified major labels""" - line = Line(show_only_major_dots=True, ) + line = Line(show_only_major_dots=True,) line.add('test', range(12)) line.x_labels = map(str, range(12)) line.x_labels_major = ['1', '5', '11'] @@ -164,7 +161,9 @@ def test_line_secondary(): line = Line() rng = [8, 12, 23, 73, 39, 57] line.add('First serie', rng) - line.add('Secondary serie', map(lambda x: x * 2, rng), secondary=True) + line.add('Secondary serie', + map(lambda x: x * 2, rng), + secondary=True) line.title = "One serie" q = line.render_pyquery() assert len(q(".axis.x")) == 0 diff --git a/pygal/test/test_line_log_none_max_solved.py b/pygal/test/test_line_log_none_max_solved.py index 72b25777..41e72f8f 100644 --- a/pygal/test/test_line_log_none_max_solved.py +++ b/pygal/test/test_line_log_none_max_solved.py @@ -4,7 +4,6 @@ # in the Log graph will be max or not (issue #309) from __future__ import division - from pygal import Line chart = Line(title='test', logarithmic=True) diff --git a/pygal/test/test_maps.py b/pygal/test/test_maps.py index 8b6ba830..e2a3c5f7 100644 --- a/pygal/test/test_maps.py +++ b/pygal/test/test_maps.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Map plugins tests are imported here""" import pkg_resources diff --git a/pygal/test/test_pie.py b/pygal/test/test_pie.py index 3dfbef1a..2bca0191 100644 --- a/pygal/test/test_pie.py +++ b/pygal/test/test_pie.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Donut chart related tests""" from pygal import Pie diff --git a/pygal/test/test_serie_config.py b/pygal/test/test_serie_config.py index ec85990f..f8072a2b 100644 --- a/pygal/test/test_serie_config.py +++ b/pygal/test/test_serie_config.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Test per serie configuration""" from pygal import Line diff --git a/pygal/test/test_sparktext.py b/pygal/test/test_sparktext.py index d95d8b28..80661699 100644 --- a/pygal/test/test_sparktext.py +++ b/pygal/test/test_sparktext.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Test sparktext rendering""" from pygal import Bar, Line diff --git a/pygal/test/test_stacked.py b/pygal/test/test_stacked.py index d4b37324..bb2f706d 100644 --- a/pygal/test/test_stacked.py +++ b/pygal/test/test_stacked.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Stacked chart related tests""" from pygal import StackedLine @@ -28,8 +29,7 @@ def test_stacked_line(): stacked.add('ten_twelve', [10, 12]) q = stacked.render_pyquery() assert set([v.text for v in q("desc.value")]) == set( - ('1', '2', '11 (+10)', '14 (+12)') - ) + ('1', '2', '11 (+10)', '14 (+12)')) def test_stacked_line_reverse(): @@ -39,8 +39,7 @@ def test_stacked_line_reverse(): stacked.add('ten_twelve', [10, 12]) q = stacked.render_pyquery() assert set([v.text for v in q("desc.value")]) == set( - ('11 (+1)', '14 (+2)', '10', '12') - ) + ('11 (+1)', '14 (+2)', '10', '12')) def test_stacked_line_log(): @@ -50,8 +49,7 @@ def test_stacked_line_log(): stacked.add('ten_twelve', [10, 12]) q = stacked.render_pyquery() assert set([v.text for v in q("desc.value")]) == set( - ('1', '2', '11 (+10)', '14 (+12)') - ) + ('1', '2', '11 (+10)', '14 (+12)')) def test_stacked_line_interpolate(): @@ -61,5 +59,4 @@ def test_stacked_line_interpolate(): stacked.add('ten_twelve', [10, 12]) q = stacked.render_pyquery() assert set([v.text for v in q("desc.value")]) == set( - ('1', '2', '11 (+10)', '14 (+12)') - ) + ('1', '2', '11 (+10)', '14 (+12)')) diff --git a/pygal/test/test_style.py b/pygal/test/test_style.py index 91b8d388..9fe01396 100644 --- a/pygal/test/test_style.py +++ b/pygal/test/test_style.py @@ -16,13 +16,13 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Style related tests""" from pygal import Line from pygal.style import ( DarkenStyle, DesaturateStyle, LightenStyle, LightStyle, RotateStyle, - SaturateStyle -) + SaturateStyle) STYLES = LightenStyle, DarkenStyle, SaturateStyle, DesaturateStyle, RotateStyle @@ -41,9 +41,8 @@ def test_parametric_styles(): def test_parametric_styles_with_parameters(): """Test a parametric style with parameters""" - line = Line( - style=RotateStyle('#de3804', step=12, max_=180, base_style=LightStyle) - ) + line = Line(style=RotateStyle( + '#de3804', step=12, max_=180, base_style=LightStyle)) line.add('_', [1, 2, 3]) line.x_labels = 'abc' assert line.render() diff --git a/pygal/test/test_table.py b/pygal/test/test_table.py index 07eed4c6..4d7eb400 100644 --- a/pygal/test/test_table.py +++ b/pygal/test/test_table.py @@ -16,11 +16,13 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Box chart related tests""" -from pygal import Pie from pyquery import PyQuery as pq +from pygal import Pie + def test_pie_table(): """Test rendering a table for a pie""" diff --git a/pygal/test/test_util.py b/pygal/test/test_util.py index 275bab81..0925b1e2 100644 --- a/pygal/test/test_util.py +++ b/pygal/test/test_util.py @@ -16,16 +16,17 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Utility functions tests""" import sys +from pytest import raises + from pygal._compat import _ellipsis, u from pygal.util import ( _swap_curly, majorize, mergextend, minify_css, round_to_float, - round_to_int, template, truncate -) -from pytest import raises + round_to_int, template, truncate) def test_round_to_int(): @@ -53,8 +54,11 @@ def test_round_to_float(): def test_swap_curly(): """Test swap curly function""" - for str in ('foo', u('foo foo foo bar'), 'foo béè b¡ð/ijə˘©þß®~¯æ', - u('foo béè b¡ð/ijə˘©þß®~¯æ')): + for str in ( + 'foo', + u('foo foo foo bar'), + 'foo béè b¡ð/ijə˘©þß®~¯æ', + u('foo béè b¡ð/ijə˘©þß®~¯æ')): assert _swap_curly(str) == str assert _swap_curly('foo{bar}baz') == 'foo{{bar}}baz' assert _swap_curly('foo{{bar}}baz') == 'foo{bar}baz' @@ -76,12 +80,13 @@ def test_format(): class Object(object): pass - obj = Object() obj.a = 1 obj.b = True obj.c = '3' - assert template('foo {{ o.a }} {{o.b}}-{{o.c}}', o=obj) == 'foo 1 True-3' + assert template( + 'foo {{ o.a }} {{o.b}}-{{o.c}}', + o=obj) == 'foo 1 True-3' def test_truncate(): @@ -114,29 +119,28 @@ def test_minify_css(): ''' assert minify_css(css) == ( '.title{font-family:sans;font-size:12}' - '.legends .legend text{font-family:monospace;font-size:14}' - ) + '.legends .legend text{font-family:monospace;font-size:14}') def test_majorize(): """Test majorize function""" assert majorize(()) == [] - assert majorize((0, )) == [] + assert majorize((0,)) == [] assert majorize((0, 1)) == [] assert majorize((0, 1, 2)) == [] assert majorize((-1, 0, 1, 2)) == [0] assert majorize((0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1)) == [0, .5, 1] assert majorize((0, .2, .4, .6, .8, 1)) == [0, 1] assert majorize((-.4, -.2, 0, .2, .4, .6, .8, 1)) == [0, 1] - assert majorize((-1, -.8, -.6, -.4, -.2, 0, .2, .4, .6, .8, - 1)) == [-1, 0, 1] + assert majorize( + (-1, -.8, -.6, -.4, -.2, 0, .2, .4, .6, .8, 1)) == [-1, 0, 1] assert majorize((0, .2, .4, .6, .8, 1, 1.2, 1.4, 1.6)) == [0, 1] assert majorize((0, .2, .4, .6, .8, 1, 1.2, 1.4, 1.6, 1.8, 2)) == [0, 1, 2] - assert majorize((0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, - 120)) == [0, 50, 100] - assert majorize(( - 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36 - )) == [0, 10, 20, 30] + assert majorize( + (0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120)) == [0, 50, 100] + assert majorize( + (0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, + 22, 24, 26, 28, 30, 32, 34, 36)) == [0, 10, 20, 30] assert majorize((0, 1, 2, 3, 4, 5)) == [0, 5] assert majorize((-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5)) == [-5, 0, 5] assert majorize((-5, 5, -4, 4, 0, 1, -1, 3, -2, 2, -3)) == [-5, 0, 5] @@ -163,11 +167,10 @@ def test_mergextend(): assert mergextend([_ellipsis], ['c', 'd']) == ['c', 'd'] assert mergextend([_ellipsis, 'b'], ['c', 'd']) == ['c', 'd', 'b'] assert mergextend(['a', _ellipsis], ['c', 'd']) == ['a', 'c', 'd'] - assert mergextend(['a', _ellipsis, 'b'], - ['c', 'd']) == ['a', 'c', 'd', 'b'] + assert mergextend(['a', _ellipsis, 'b'], ['c', 'd']) == [ + 'a', 'c', 'd', 'b'] if sys.version_info[0] >= 3: # For @#! sake it's 2016 now assert eval("mergextend(['a', ..., 'b'], ['c', 'd'])") == [ - 'a', 'c', 'd', 'b' - ] + 'a', 'c', 'd', 'b'] diff --git a/pygal/test/test_view.py b/pygal/test/test_view.py index a6cabe88..db325ad6 100644 --- a/pygal/test/test_view.py +++ b/pygal/test/test_view.py @@ -16,6 +16,8 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + + """View related tests""" # TODO diff --git a/pygal/test/test_xml_filters.py b/pygal/test/test_xml_filters.py index 5834281a..86aff3c0 100644 --- a/pygal/test/test_xml_filters.py +++ b/pygal/test/test_xml_filters.py @@ -16,12 +16,14 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Xml filter tests""" from pygal import Bar class ChangeBarsXMLFilter(object): + """xml filter that insert a subplot""" def __init__(self, a, b): @@ -30,9 +32,8 @@ def __init__(self, a, b): def __call__(self, T): """Apply the filter on the tree""" - subplot = Bar( - legend_at_bottom=True, explicit_size=True, width=800, height=150 - ) + subplot = Bar(legend_at_bottom=True, explicit_size=True, + width=800, height=150) subplot.add("Difference", self.data) subplot = subplot.render_tree() subplot = subplot.findall("g")[0] @@ -54,9 +55,8 @@ def test_xml_filters_round_trip(): def test_xml_filters_change_bars(): """Test the use a xml filter""" - plot = Bar( - legend_at_bottom=True, explicit_size=True, width=800, height=600 - ) + plot = Bar(legend_at_bottom=True, explicit_size=True, + width=800, height=600) A = [60, 75, 80, 78, 83, 90] B = [92, 87, 81, 73, 68, 55] plot.add("A", A) @@ -64,5 +64,5 @@ def test_xml_filters_change_bars(): plot.add_xml_filter(ChangeBarsXMLFilter(A, B)) q = plot.render_tree() assert len(q.findall("g")) == 2 - assert q.findall("g")[1].attrib["transform" - ] == "translate(0,150), scale(1,0.75)" + assert q.findall("g")[1].attrib[ + "transform"] == "translate(0,150), scale(1,0.75)" diff --git a/pygal/test/utils.py b/pygal/test/utils.py index 835f073e..da6a3bf7 100644 --- a/pygal/test/utils.py +++ b/pygal/test/utils.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Tests helpers""" from pyquery import PyQuery as pq diff --git a/pygal/util.py b/pygal/util.py index ca2e5c5f..4d284a83 100644 --- a/pygal/util.py +++ b/pygal/util.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Various utility functions""" from __future__ import division @@ -36,15 +37,15 @@ def majorize(values): """Filter sequence to return only major considered numbers""" sorted_values = sorted(values) if len(values) <= 3 or ( - abs(2 * sorted_values[1] - sorted_values[0] - sorted_values[2]) - > abs(1.5 * (sorted_values[1] - sorted_values[0]))): + abs(2 * sorted_values[1] - sorted_values[0] - sorted_values[2]) > + abs(1.5 * (sorted_values[1] - sorted_values[0]))): return [] values_step = sorted_values[1] - sorted_values[0] full_range = sorted_values[-1] - sorted_values[0] - step = 10**int(log10(full_range)) + step = 10 ** int(log10(full_range)) if step == values_step: step *= 10 - step_factor = 10**(int(log10(step)) + 1) + step_factor = 10 ** (int(log10(step)) + 1) if round(step * step_factor) % (round(values_step * step_factor) or 1): # TODO: Find lower common multiple instead step *= values_step @@ -53,8 +54,7 @@ def majorize(values): elif full_range >= 5 * step: step *= 5 major_values = [ - value for value in values if value / step == round(value / step) - ] + value for value in values if value / step == round(value / step)] return [value for value in sorted_values if value in major_values] @@ -67,8 +67,9 @@ def round_to_int(number, precision): def round_to_float(number, precision): """Round a float to a precision""" - rounded = Decimal(str(floor((number + precision / 2) // precision)) - ) * Decimal(str(precision)) + rounded = Decimal( + str(floor((number + precision / 2) // precision)) + ) * Decimal(str(precision)) return float(rounded) @@ -100,11 +101,15 @@ def deg(radiants): def _swap_curly(string): """Swap single and double curly brackets""" - return ( - string.replace('{{ ', '{{').replace('{{', '\x00').replace('{', '{{') - .replace('\x00', '{').replace(' }}', '}}').replace('}}', '\x00') - .replace('}', '}}').replace('\x00', '}') - ) + return (string + .replace('{{ ', '{{') + .replace('{{', '\x00') + .replace('{', '{{') + .replace('\x00', '{') + .replace(' }}', '}}') + .replace('}}', '\x00') + .replace('}', '}}') + .replace('\x00', '}')) def template(string, **kwargs): @@ -133,21 +138,24 @@ def compute_logarithmic_scale(min_, max_, min_scale, max_scale): detail /= 2 for order in range(min_order, max_order + 1): for i in range(int(detail)): - tick = (10 * i / detail or 1) * 10**order + tick = (10 * i / detail or 1) * 10 ** order tick = round_to_scale(tick, tick) if min_ <= tick <= max_ and tick not in positions: positions.append(tick) return positions -def compute_scale(min_, max_, logarithmic, order_min, min_scale, max_scale): +def compute_scale( + min_, max_, logarithmic, order_min, + min_scale, max_scale): """Compute an optimal scale between min and max""" if min_ == 0 and max_ == 0: return [0] if max_ - min_ == 0: return [min_] if logarithmic: - log_scale = compute_logarithmic_scale(min_, max_, min_scale, max_scale) + log_scale = compute_logarithmic_scale( + min_, max_, min_scale, max_scale) if log_scale: return log_scale # else we fallback to normal scalling @@ -156,10 +164,10 @@ def compute_scale(min_, max_, logarithmic, order_min, min_scale, max_scale): if order_min is not None and order < order_min: order = order_min else: - while ((max_ - min_) / (10**order) < min_scale - and (order_min is None or order > order_min)): + while ((max_ - min_) / (10 ** order) < min_scale and + (order_min is None or order > order_min)): order -= 1 - step = float(10**order) + step = float(10 ** order) while (max_ - min_) / step > max_scale: step *= 2. positions = [] @@ -205,24 +213,24 @@ def decorate(svg, node, metadata): if not isinstance(xlink, dict): xlink = {'href': xlink, 'target': '_blank'} node = svg.node(node, 'a', **xlink) - svg.node( - node, 'desc', class_='xlink' - ).text = to_unicode(xlink.get('href')) + svg.node(node, 'desc', class_='xlink').text = to_unicode( + xlink.get('href')) if 'tooltip' in metadata: - svg.node(node, 'title').text = to_unicode(metadata['tooltip']) + svg.node(node, 'title').text = to_unicode( + metadata['tooltip']) if 'color' in metadata: color = metadata.pop('color') - node.attrib['style'] = 'fill: %s; stroke: %s' % (color, color) + node.attrib['style'] = 'fill: %s; stroke: %s' % ( + color, color) if 'style' in metadata: node.attrib['style'] = metadata.pop('style') - if 'label' in metadata and metadata['label']: - svg.node( - node, 'desc', class_='label' - ).text = to_unicode(metadata['label']) + if 'label' in metadata: + svg.node(node, 'desc', class_='label').text = to_unicode( + metadata['label']) return node @@ -230,8 +238,7 @@ def alter(node, metadata): """Override nodes attributes from metadata node mapping""" if node is not None and metadata and 'node' in metadata: node.attrib.update( - dict((k, str(v)) for k, v in metadata['node'].items()) - ) + dict((k, str(v)) for k, v in metadata['node'].items())) def truncate(string, index): @@ -243,6 +250,7 @@ def truncate(string, index): # # Stolen partly from brownie http://packages.python.org/Brownie/ class cached_property(object): + """Memoize a property""" def __init__(self, getter, doc=None): @@ -275,7 +283,7 @@ 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:] == r'\\*/': + if css_comment[-3:] == '\*/': remove_next_comment = 0 continue if remove_next_comment: diff --git a/pygal/view.py b/pygal/view.py index 4d6deb92..9360a584 100644 --- a/pygal/view.py +++ b/pygal/view.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . + """Projection and bounding helpers""" from __future__ import division @@ -24,6 +25,7 @@ class Margin(object): + """Class reprensenting a margin (top, right, left, bottom)""" def __init__(self, top, right, bottom, left): @@ -45,6 +47,7 @@ def y(self): class Box(object): + """Chart boundings""" margin = .02 @@ -144,6 +147,7 @@ def fix(self, with_margin=True): class View(object): + """Projection base class""" def __init__(self, width, height, box): @@ -163,9 +167,8 @@ def y(self, y): """Project y""" if y is None: return None - return ( - self.height - self.height * (y - self.box.ymin) / self.box.height - ) + return (self.height - self.height * + (y - self.box.ymin) / self.box.height) def __call__(self, xy): """Project x and y""" @@ -174,6 +177,7 @@ def __call__(self, xy): class ReverseView(View): + """Same as view but reversed vertically""" def y(self, y): @@ -184,6 +188,7 @@ def y(self, y): class HorizontalView(View): + """Same as view but transposed""" def __init__(self, width, height, box): @@ -214,6 +219,7 @@ def y(self, y): class PolarView(View): + """Polar projection for pie like graphs""" def __call__(self, rhotheta): @@ -221,11 +227,12 @@ def __call__(self, rhotheta): if None in rhotheta: return None, None rho, theta = rhotheta - return super(PolarView, - self).__call__((rho * cos(theta), rho * sin(theta))) + return super(PolarView, self).__call__( + (rho * cos(theta), rho * sin(theta))) class PolarLogView(View): + """Logarithmic polar projection""" def __init__(self, width, height, box): @@ -233,8 +240,7 @@ def __init__(self, width, height, box): super(PolarLogView, self).__init__(width, height, box) if not hasattr(box, '_rmin') or not hasattr(box, '_rmax'): raise Exception( - 'Box must be set with set_polar_box for polar charts' - ) + 'Box must be set with set_polar_box for polar charts') self.log10_rmax = log10(self.box._rmax) self.log10_rmin = log10(self.box._rmin) @@ -250,13 +256,14 @@ def __call__(self, rhotheta): if rho == 0: return super(PolarLogView, self).__call__((0, 0)) rho = (self.box._rmax - self.box._rmin) * ( - log10(rho) - self.log10_rmin - ) / (self.log10_rmax - self.log10_rmin) - return super(PolarLogView, - self).__call__((rho * cos(theta), rho * sin(theta))) + log10(rho) - self.log10_rmin) / ( + self.log10_rmax - self.log10_rmin) + return super(PolarLogView, self).__call__( + (rho * cos(theta), rho * sin(theta))) class PolarThetaView(View): + """Logarithmic polar projection""" def __init__(self, width, height, box, aperture=pi / 3): @@ -265,8 +272,7 @@ def __init__(self, width, height, box, aperture=pi / 3): self.aperture = aperture if not hasattr(box, '_tmin') or not hasattr(box, '_tmax'): raise Exception( - 'Box must be set with set_polar_box for polar charts' - ) + 'Box must be set with set_polar_box for polar charts') def __call__(self, rhotheta): """Project rho and theta""" @@ -274,14 +280,15 @@ def __call__(self, rhotheta): return None, None rho, theta = rhotheta start = 3 * pi / 2 + self.aperture / 2 - theta = start + (2 * pi - self.aperture) * (theta - self.box._tmin) / ( - self.box._tmax - self.box._tmin - ) - return super(PolarThetaView, - self).__call__((rho * cos(theta), rho * sin(theta))) + theta = start + (2 * pi - self.aperture) * ( + theta - self.box._tmin) / ( + self.box._tmax - self.box._tmin) + return super(PolarThetaView, self).__call__( + (rho * cos(theta), rho * sin(theta))) class PolarThetaLogView(View): + """Logarithmic polar projection""" def __init__(self, width, height, box, aperture=pi / 3): @@ -290,8 +297,7 @@ def __init__(self, width, height, box, aperture=pi / 3): self.aperture = aperture if not hasattr(box, '_tmin') or not hasattr(box, '_tmax'): raise Exception( - 'Box must be set with set_polar_box for polar charts' - ) + 'Box must be set with set_polar_box for polar charts') self.log10_tmax = log10(self.box._tmax) if self.box._tmax > 0 else 0 self.log10_tmin = log10(self.box._tmin) if self.box._tmin > 0 else 0 if self.log10_tmin == self.log10_tmax: @@ -306,19 +312,20 @@ def __call__(self, rhotheta): if theta == 0: return super(PolarThetaLogView, self).__call__((0, 0)) theta = self.box._tmin + (self.box._tmax - self.box._tmin) * ( - log10(theta) - self.log10_tmin - ) / (self.log10_tmax - self.log10_tmin) + log10(theta) - self.log10_tmin) / ( + self.log10_tmax - self.log10_tmin) start = 3 * pi / 2 + self.aperture / 2 - theta = start + (2 * pi - self.aperture) * (theta - self.box._tmin) / ( - self.box._tmax - self.box._tmin - ) + theta = start + (2 * pi - self.aperture) * ( + theta - self.box._tmin) / ( + self.box._tmax - self.box._tmin) - return super(PolarThetaLogView, - self).__call__((rho * cos(theta), rho * sin(theta))) + return super(PolarThetaLogView, self).__call__( + (rho * cos(theta), rho * sin(theta))) class LogView(View): + """Y Logarithmic projection""" # Do not want to call the parent here @@ -337,13 +344,13 @@ def y(self, y): """Project y""" if y is None or y <= 0 or self.log10_ymax - self.log10_ymin == 0: return 0 - return ( - self.height - self.height * (log10(y) - self.log10_ymin) - / (self.log10_ymax - self.log10_ymin) - ) + return (self.height - self.height * + (log10(y) - self.log10_ymin) / ( + self.log10_ymax - self.log10_ymin)) class XLogView(View): + """X logarithmic projection""" # Do not want to call the parent here @@ -360,13 +367,13 @@ def x(self, x): """Project x""" if x is None or x <= 0 or self.log10_xmax - self.log10_xmin == 0: return None - return ( - self.width * (log10(x) - self.log10_xmin) - / (self.log10_xmax - self.log10_xmin) - ) + return (self.width * + (log10(x) - self.log10_xmin) / + (self.log10_xmax - self.log10_xmin)) class XYLogView(XLogView, LogView): + """X and Y logarithmic projection""" def __init__(self, width, height, box): @@ -382,6 +389,7 @@ def __init__(self, width, height, box): class HorizontalLogView(XLogView): + """Transposed Logarithmic projection""" # Do not want to call the parent here From daada9f8fd01a9c7125f7ac3b171816986553344 Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Sat, 23 Dec 2023 16:18:45 -0300 Subject: [PATCH 08/10] bug: fix git protocol --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index a1c8f765..23c05a7d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule ".git_hooks"] path = .git_hooks - url = git://github.com/Kozea/.git_hooks.git + url = https://github.com/Kozea/.git_hooks.git From 74a881153831c95a60edf5916e48b5101ec3a3e3 Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Fri, 5 Jan 2024 12:02:54 -0300 Subject: [PATCH 09/10] bug: fix import --- pygal/graph/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index b072e374..e954a642 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -38,7 +38,7 @@ split_title, truncate, ) -from pygal.view import LogView, ReverseView, View, XYLogView +from pygal.view import LogView, ReverseView, View, XYLogView, XLogView class Graph(PublicApi): From 497388819b847a0d3b7d53f3db919120fdb70364 Mon Sep 17 00:00:00 2001 From: Douglas Cerqueira Date: Sat, 15 Jun 2024 15:10:10 -0300 Subject: [PATCH 10/10] :fire: remove prints --- pygal/graph/graph.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py index e954a642..f000a68d 100644 --- a/pygal/graph/graph.py +++ b/pygal/graph/graph.py @@ -464,8 +464,6 @@ def _make_y_title(self): text.attrib['transform'] = "rotate(%d %f %f)" % ( -90, self._legend_at_left_width, yc) text.text = title_line - print('legend l',self._legend_at_left_width) - print('legend r',self._legend_at_right_width) def _make_y2_title(self): """Make the Y-Axis title"""