# -*- 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/>.
"""Gauge chart representing values as needles on a polar scale"""
from __future__ import division
from pygal._compat import is_str
from pygal.graph.graph import Graph
from pygal.util import alter, compute_scale, cut, decorate
from pygal.view import PolarThetaLogView, PolarThetaView
[docs]class Gauge(Graph):
"""Gauge graph class"""
needle_width = 1 / 20
def _set_view(self):
"""Assign a view to current graph"""
if self.logarithmic:
view_class = PolarThetaLogView
else:
view_class = PolarThetaView
self.view = view_class(
self.width - self.margin_box.x,
self.height - self.margin_box.y,
self._box)
[docs] def needle(self, serie):
"""Draw a needle for each value"""
serie_node = self.svg.serie(serie)
for i, theta in enumerate(serie.values):
if theta is None:
continue
def point(x, y):
return '%f %f' % self.view((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)
tolerance = 1.15
if theta < self._min:
theta = self._min * tolerance
if theta > self._max:
theta = self._max * tolerance
w = (self._box._tmax - self._box._tmin + self.view.aperture) / 4
if self.logarithmic:
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' % (
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)
x, y = self.view((.75, theta))
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):
"""Override y axis to plot a polar axis"""
axis = self.svg.node(self.nodes['plot'], class_="axis x gauge")
for i, (label, theta) in enumerate(self._y_labels):
guides = self.svg.node(axis, class_='guides')
self.svg.line(
guides, [self.view((.95, theta)), self.view((1, theta))],
close=True,
class_='line')
self.svg.line(
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 ''))
x, y = self.view((.9, theta))
self.svg.node(
guides, 'text',
x=x,
y=y
).text = label
self.svg.node(
guides, 'title',
).text = self._y_format(theta)
def _x_axis(self, draw_axes=True):
"""Override x axis to put a center circle in center"""
axis = self.svg.node(self.nodes['plot'], class_="axis y gauge")
x, y = self.view((0, 0))
self.svg.node(axis, 'circle', cx=x, cy=y, r=4)
def _compute(self):
"""Compute y min and max and y scale and set labels"""
self.min_ = self._min or 0
self.max_ = self._max or 0
if self.max_ - self.min_ == 0:
self.min_ -= 1
self.max_ += 1
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
)
if self.y_labels:
self._y_labels = []
for i, y_label in enumerate(self.y_labels):
if isinstance(y_label, dict):
pos = self._adapt(y_label.get('value'))
title = y_label.get('label', self._y_format(pos))
elif is_str(y_label):
pos = self._adapt(y_pos[i])
title = y_label
else:
pos = self._adapt(y_label)
title = self._y_format(pos)
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_)
else:
self._y_labels = list(zip(map(self._y_format, y_pos), y_pos))
def _plot(self):
"""Plot all needles"""
for serie in self.series:
self.needle(serie)