debian-python-pygal/pygal/graph/map.py

133 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
# This file is part of pygal
#
# A python svg graph plotting library
# Copyright © 2012-2016 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
pygal contains no map but a base class to create extension
see the pygal_maps_world package to get an exemple.
https://github.com/Kozea/pygal_maps_world
"""
from __future__ import division
from pygal.etree import etree
from pygal.graph.graph import Graph
from pygal.util import alter, cached_property, cut, decorate
class BaseMap(Graph):
"""Base class for maps"""
_dual = True
@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]
def enumerate_values(self, serie):
"""Hook to replace default enumeration on values"""
return enumerate(serie.values)
def adapt_code(self, area_code):
"""Hook to change the area code"""
return area_code
def _value_format(self, value):
"""
Format value for map value display.
"""
return '%s: %s' % (
self.area_names.get(self.adapt_code(value[0]), '?'),
self._y_format(value[1]))
def _plot(self):
"""Insert a map in the chart and apply data on it"""
map = etree.fromstring(self.svg_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)
for j, (area_code, value) in self.enumerate_values(serie):
area_code = self.adapt_code(area_code)
if value is None:
continue
if max_ == min_:
ratio = 1
else:
ratio = .3 + .7 * (value - min_) / (max_ - min_)
areae = map.findall(
".//*[@class='%s%s %s map-element']" % (
self.area_prefix, area_code,
self.kind))
if not areae:
continue
for area in areae:
cls = area.get('class', '').split(' ')
cls.append('color-%d' % i)
cls.append('serie-%d' % i)
cls.append('series')
area.set('class', ' '.join(cls))
area.set('style', 'fill-opacity: %f' % ratio)
metadata = serie.metadata.get(j)
if metadata:
node = decorate(self.svg, area, metadata)
if node != area:
area.remove(node)
for g in map:
if area not in g:
continue
index = list(g).index(area)
g.remove(area)
node.append(area)
g.insert(index, node)
for node in area:
cls = node.get('class', '').split(' ')
cls.append('reactive')
cls.append('tooltip-trigger')
cls.append('map-area')
node.set('class', ' '.join(cls))
alter(node, metadata)
val = self._format(serie, j)
self._tooltip_data(area, val, 0, 0, 'auto')
self.nodes['plot'].append(map)
def _compute_x_labels(self):
pass
def _compute_y_labels(self):
pass