1# -*- coding: utf-8 -*- 2# This file is part of pygal 3# 4# A python svg graph plotting library 5# Copyright © 2012-2016 Kozea 6# 7# This library is free software: you can redistribute it and/or modify it under 8# the terms of the GNU Lesser General Public License as published by the Free 9# Software Foundation, either version 3 of the License, or (at your option) any 10# later version. 11# 12# This library is distributed in the hope that it will be useful, but WITHOUT 13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15# details. 16# 17# You should have received a copy of the GNU Lesser General Public License 18# along with pygal. If not, see <http://www.gnu.org/licenses/>. 19 20"""Treemap chart: Visualize data using nested recangles""" 21 22from __future__ import division 23 24from pygal.adapters import none_to_zero, positive 25from pygal.graph.graph import Graph 26from pygal.util import alter, cut, decorate 27 28 29class Treemap(Graph): 30 31 """Treemap graph class""" 32 33 _adapters = [positive, none_to_zero] 34 35 def _rect(self, serie, serie_node, rects, val, x, y, w, h, i): 36 rx, ry = self.view((x, y)) 37 rw, rh = self.view((x + w, y + h)) 38 rw -= rx 39 rh -= ry 40 41 metadata = serie.metadata.get(i) 42 43 val = self._format(serie, i) 44 45 rect = decorate( 46 self.svg, 47 self.svg.node(rects, class_="rect"), 48 metadata) 49 50 alter( 51 self.svg.node( 52 rect, 'rect', 53 x=rx, 54 y=ry, 55 width=rw, 56 height=rh, 57 class_='rect reactive tooltip-trigger'), 58 metadata) 59 60 self._tooltip_data( 61 rect, val, 62 rx + rw / 2, 63 ry + rh / 2, 64 'centered', 65 self._get_x_label(i)) 66 self._static_value( 67 serie_node, val, 68 rx + rw / 2, 69 ry + rh / 2, 70 metadata) 71 72 def _binary_tree(self, data, total, x, y, w, h, parent=None): 73 if total == 0: 74 return 75 if len(data) == 1: 76 if parent: 77 i, datum = data[0] 78 serie, serie_node, rects = parent 79 self._rect(serie, serie_node, rects, datum, x, y, w, h, i) 80 else: 81 datum = data[0] 82 serie_node = self.svg.serie(datum) 83 self._binary_tree( 84 list(enumerate(datum.values)), 85 total, x, y, w, h, 86 (datum, serie_node, 87 self.svg.node(serie_node['plot'], class_="rects"))) 88 return 89 90 midpoint = total / 2 91 pivot_index = 1 92 running_sum = 0 93 for i, elt in enumerate(data): 94 if running_sum >= midpoint: 95 pivot_index = i 96 break 97 98 running_sum += elt[1] if parent else sum(elt.values) 99 100 half1 = data[:pivot_index] 101 half2 = data[pivot_index:] 102 103 if parent: 104 half1_sum = sum(cut(half1, 1)) 105 half2_sum = sum(cut(half2, 1)) 106 else: 107 half1_sum = sum(map(sum, map(lambda x: x.values, half1))) 108 half2_sum = sum(map(sum, map(lambda x: x.values, half2))) 109 pivot_pct = half1_sum / total 110 111 if h > w: 112 y_pivot = pivot_pct * h 113 self._binary_tree( 114 half1, half1_sum, x, y, w, y_pivot, parent) 115 self._binary_tree( 116 half2, half2_sum, x, y + y_pivot, w, h - y_pivot, parent) 117 else: 118 x_pivot = pivot_pct * w 119 self._binary_tree( 120 half1, half1_sum, x, y, x_pivot, h, parent) 121 self._binary_tree( 122 half2, half2_sum, x + x_pivot, y, w - x_pivot, h, parent) 123 124 def _compute_x_labels(self): 125 pass 126 127 def _compute_y_labels(self): 128 pass 129 130 def _plot(self): 131 total = sum(map(sum, map(lambda x: x.values, self.series))) 132 if total == 0: 133 return 134 135 gw = self.width - self.margin_box.x 136 gh = self.height - self.margin_box.y 137 138 self.view.box.xmin = self.view.box.ymin = x = y = 0 139 self.view.box.xmax = w = (total * gw / gh) ** .5 140 self.view.box.ymax = h = total / w 141 self.view.box.fix() 142 143 self._binary_tree(self.series, total, x, y, w, h) 144