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

144 lines
4.3 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/>.
"""Treemap chart: Visualize data using nested recangles"""
from __future__ import division
from pygal.adapters import none_to_zero, positive
from pygal.graph.graph import Graph
from pygal.util import alter, cut, decorate
class Treemap(Graph):
"""Treemap graph class"""
_adapters = [positive, none_to_zero]
def _rect(self, serie, serie_node, rects, val, x, y, w, h, i):
rx, ry = self.view((x, y))
rw, rh = self.view((x + w, y + h))
rw -= rx
rh -= ry
metadata = serie.metadata.get(i)
val = self._format(serie, i)
rect = decorate(
self.svg,
self.svg.node(rects, class_="rect"),
metadata)
alter(
self.svg.node(
rect, 'rect',
x=rx,
y=ry,
width=rw,
height=rh,
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)
def _binary_tree(self, data, total, x, y, w, h, parent=None):
if total == 0:
return
if len(data) == 1:
if parent:
i, datum = data[0]
serie, serie_node, rects = parent
self._rect(serie, serie_node, rects, datum, x, y, w, h, i)
else:
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")))
return
midpoint = total / 2
pivot_index = 1
running_sum = 0
for i, elt in enumerate(data):
if running_sum >= midpoint:
pivot_index = i
break
running_sum += elt[1] if parent else sum(elt.values)
half1 = data[:pivot_index]
half2 = data[pivot_index:]
if parent:
half1_sum = sum(cut(half1, 1))
half2_sum = sum(cut(half2, 1))
else:
half1_sum = sum(map(sum, map(lambda x: x.values, half1)))
half2_sum = sum(map(sum, map(lambda x: x.values, half2)))
pivot_pct = half1_sum / total
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)
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)
def _compute_x_labels(self):
pass
def _compute_y_labels(self):
pass
def _plot(self):
total = sum(map(sum, map(lambda x: x.values, self.series)))
if total == 0:
return
gw = self.width - self.margin_box.x
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.ymax = h = total / w
self.view.box.fix()
self._binary_tree(self.series, total, x, y, w, h)