diff --git a/pygal/ghost.py b/pygal/ghost.py index 44f81ce..a991867 100644 --- a/pygal/ghost.py +++ b/pygal/ghost.py @@ -29,6 +29,7 @@ import sys from pygal.config import Config from pygal._compat import u, is_list_like from pygal.graph import CHARTS_NAMES +from pygal.i18n import SUPRANATIONAL from pygal.util import prepare_values from uuid import uuid4 @@ -65,11 +66,29 @@ class Ghost(object): """Add a serie to this graph""" if not is_list_like(values) and not isinstance(values, dict): values = [values] + values = self.replace_supranationals(values) if secondary: self.raw_series2.append((title, values)) else: self.raw_series.append((title, values)) + def replace_supranationals(self, values): + """Replaces the values if it contains a supranational code.""" + from pygal import Worldmap + if not isinstance(self, Worldmap): + return values + for suprakey in SUPRANATIONAL.keys(): + if suprakey in values: + if not isinstance(values, dict): + del values[values.index(suprakey)] + values.extend(SUPRANATIONAL[suprakey]) + else: + values.update( + dict([(code, values[suprakey]) + for code in SUPRANATIONAL[suprakey]])) + del values[suprakey] + return values + def make_series(self, series): return prepare_values(series, self.config, self.cls) diff --git a/pygal/graph/__init__.py b/pygal/graph/__init__.py index d464591..d6418ce 100644 --- a/pygal/graph/__init__.py +++ b/pygal/graph/__init__.py @@ -38,6 +38,5 @@ CHARTS_NAMES = [ 'Gauge', 'DateY', 'Worldmap', - 'SupranationalWorldmap', 'Histogram' ] diff --git a/pygal/graph/supranationalworldmap.py b/pygal/graph/supranationalworldmap.py new file mode 100644 index 0000000..dd24c1d --- /dev/null +++ b/pygal/graph/supranationalworldmap.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# This file is part of pygal +# +# A python svg graph plotting library +# Copyright © 2012-2013 Kozea +# +# This library is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with pygal. If not, see . +""" +Supranational Worldmap chart + +""" + +from __future__ import division +from pygal.graph.worldmap import Worldmap +from pygal.i18n import SUPRANATIONAL +from pygal.util import cut, decorate +from lxml import etree +import os + +with open(os.path.join( + os.path.dirname(__file__), + 'worldmap.svg')) as file: + MAP = file.read() + + +class SupranationalWorldmap(Worldmap): + """SupranationalWorldmap graph""" + def _plot(self): + map = etree.fromstring(MAP) + map.set('width', str(self.view.width)) + 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))) + if not safe_vals: + continue + min_ = min(safe_vals) + max_ = max(safe_vals) + serie.values = self.replace_supranationals(serie.values) + for j, (country_code, value) in enumerate(serie.values): + if value is None: + continue + if max_ == min_: + ratio = 1 + else: + ratio = .3 + .7 * (value - min_) / (max_ - min_) + country = map.find('.//*[@id="%s"]' % country_code) + if country is None: + continue + cls = country.get('class', '').split(' ') + cls.append('color-%d' % i) + country.set('class', ' '.join(cls)) + country.set( + 'style', 'fill-opacity: %f' % ( + ratio)) + + metadata = serie.metadata.get(j) + if metadata: + parent = country.getparent() + node = decorate(self.svg, country, metadata) + if node != country: + country.remove(node) + index = parent.index(country) + parent.remove(country) + node.append(country) + parent.insert(index, node) + + last_node = len(country) > 0 and country[-1] + if last_node is not None and last_node.tag == 'title': + title_node = last_node + text = title_node.text + '\n' + else: + title_node = self.svg.node(country, 'title') + text = '' + title_node.text = text + '[%s] %s: %d' % ( + serie.title, + self.country_names[country_code], value) + + self.nodes['plot'].append(map) + + def replace_supranationals(self, values): + """Replaces the values if it contains a supranational code.""" + for i, (code, value) in enumerate(values[:]): + for suprakey in SUPRANATIONAL.keys(): + if suprakey == code: + values.extend( + [(country, value) for country in SUPRANATIONAL[code]]) + values.remove((code, value)) + return values diff --git a/pygal/i18n.py b/pygal/i18n.py index 3b0b714..d97651f 100644 --- a/pygal/i18n.py +++ b/pygal/i18n.py @@ -185,12 +185,9 @@ COUNTRIES = { 'zw': 'Zimbabwe' } -EUROPE = ['at', 'be', 'bg', 'hr', 'cy', 'cz', 'dk', 'ee', 'fi', 'fr', 'de', - 'gr', 'hu', 'ie', 'it', 'lv', 'lt', 'lu', 'mt', 'nl', 'pl', 'pt', - 'ro', 'sk', 'si', 'es', 'se', 'gb'] - -EUR = ['be', 'de', 'ie', 'gr', 'es', 'fr', 'it', 'cy', 'lu', 'mt', 'nl', 'at', - 'pt', 'si', 'sk', 'fi', 'ee'] +EUROPE = ['bg', 'cs', 'da', 'de', 'et', 'el', 'en', 'es', 'fr', 'ga', 'hr', + 'it', 'lt', 'lv', 'hu', 'mt', 'nl', 'pl', 'pt', 'ro', 'sk', 'sl', + 'fi', 'sv'] OECD = ['au', 'at', 'be', 'ca', 'cl', 'cz', 'dk', 'ee', 'fi', 'fr', 'de', 'gr', 'hu', 'is', 'ie', 'il', 'it', 'jp', 'kr', 'lu', 'mx', 'nl', 'nz', 'no', @@ -199,7 +196,7 @@ OECD = ['au', 'at', 'be', 'ca', 'cl', 'cz', 'dk', 'ee', 'fi', 'fr', 'de', 'gr', NAFTA = ['ca', 'mx', 'us'] -SUPRANATIONAL = {'europe': EUROPE, 'oecd': OECD, 'nafta': NAFTA, 'eur': EUR} +SUPRANATIONAL = {'europe': EUROPE, 'oecd': OECD, 'nafta': NAFTA} def set_countries(countries): diff --git a/pygal/test/test_config.py b/pygal/test/test_config.py index 1f688bf..a8b83df 100644 --- a/pygal/test/test_config.py +++ b/pygal/test/test_config.py @@ -17,8 +17,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . from pygal import ( - Line, Dot, Pie, Radar, Config, Bar, Funnel, Worldmap, - SupranationalWorldmap, Histogram, Gauge) + Line, Dot, Pie, Radar, Config, Bar, Funnel, Worldmap, Histogram, Gauge) from pygal._compat import u from pygal.test.utils import texts from pygal.test import pytest_generate_tests, make_data @@ -260,8 +259,7 @@ def test_no_data(): def test_include_x_axis(Chart): chart = Chart() - if Chart in (Pie, Radar, Funnel, Dot, Gauge, Worldmap, - SupranationalWorldmap, Histogram): + if Chart in (Pie, Radar, Funnel, Dot, Gauge, Worldmap, Histogram): return if not chart.cls._dual: data = 100, 200, 150 diff --git a/pygal/test/test_graph.py b/pygal/test/test_graph.py index aa2931f..77df284 100644 --- a/pygal/test/test_graph.py +++ b/pygal/test/test_graph.py @@ -70,7 +70,7 @@ def test_metadata(Chart): v = range(7) if Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) - elif Chart == pygal.Worldmap or Chart == pygal.SupranationalWorldmap: + elif Chart == pygal.Worldmap: v = list(map(lambda x: x, i18n.COUNTRIES)) chart.add('Serie with metadata', [ @@ -94,7 +94,7 @@ def test_metadata(Chart): if Chart == pygal.Pie: # Slices with value 0 are not rendered assert len(v) - 1 == len(q('.tooltip-trigger').siblings('.value')) - elif Chart != pygal.Worldmap and Chart != pygal.SupranationalWorldmap: + elif Chart != pygal.Worldmap: # Tooltip are not working on worldmap assert len(v) == len(q('.tooltip-trigger').siblings('.value')) diff --git a/pygal/util.py b/pygal/util.py index 0111cd2..8ab6958 100644 --- a/pygal/util.py +++ b/pygal/util.py @@ -300,10 +300,9 @@ def prepare_values(raw, config, cls): from pygal.graph.datey import DateY from pygal.graph.histogram import Histogram from pygal.graph.worldmap import Worldmap - from pygal.graph.supranationalworldmap import SupranationalWorldmap if config.x_labels is None and hasattr(cls, 'x_labels'): config.x_labels = cls.x_labels - if config.zero == 0 and issubclass(cls, (Worldmap, SupranationalWorldmap)): + if config.zero == 0 and issubclass(cls, Worldmap): config.zero = 1 for key in ('x_labels', 'y_labels'): @@ -333,7 +332,7 @@ def prepare_values(raw, config, cls): metadata = {} values = [] if isinstance(raw_values, dict): - if issubclass(cls, (Worldmap, SupranationalWorldmap)): + if issubclass(cls, Worldmap): raw_values = list(raw_values.items()) else: value_list = [None] * width @@ -365,9 +364,8 @@ def prepare_values(raw, config, cls): value = (None, None) elif not is_list_like(value): value = (value, config.zero) - if issubclass(cls, DateY) or issubclass( - cls, (Worldmap, SupranationalWorldmap)): - value = (adapter(value[0]), value[1]) + if issubclass(cls, DateY) or issubclass(cls, Worldmap): + value = (adapter(value[0]), value[1]) else: value = list(map(adapter, value)) else: