|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# This file is part of pygal
|
|
|
|
#
|
|
|
|
# A python svg graph plotting library
|
|
|
|
# Copyright © 2012 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/>.
|
|
|
|
import os
|
|
|
|
from lxml import etree
|
|
|
|
from pygal.util import template
|
|
|
|
from pygal import __version__
|
|
|
|
|
|
|
|
|
|
|
|
class Svg(object):
|
|
|
|
"""Svg object"""
|
|
|
|
ns = 'http://www.w3.org/2000/svg'
|
|
|
|
|
|
|
|
def __init__(self, graph):
|
|
|
|
self.graph = graph
|
|
|
|
|
|
|
|
def _init(self):
|
|
|
|
self.root = etree.Element(
|
|
|
|
"{%s}svg" % self.ns,
|
|
|
|
nsmap={
|
|
|
|
None: self.ns,
|
|
|
|
'xlink': 'http://www.w3.org/1999/xlink',
|
|
|
|
},
|
|
|
|
onload="svg_load();")
|
|
|
|
self.root.append(etree.Comment(
|
|
|
|
u'Generated with pygal %s ©Kozea 2012' % __version__))
|
|
|
|
self.root.append(etree.Comment(u'http://github.com/Kozea/pygal'))
|
|
|
|
self.defs = self.node(tag='defs')
|
|
|
|
|
|
|
|
def add_style(self, css):
|
|
|
|
style = self.node(self.defs, 'style', type='text/css')
|
|
|
|
with open(css) as f:
|
|
|
|
templ = template(
|
|
|
|
f.read(),
|
|
|
|
style=self.graph.style,
|
|
|
|
font_sizes=self.graph.font_sizes(),
|
|
|
|
hidden='y' if self.graph._horizontal else 'x')
|
|
|
|
style.text = templ.decode('utf-8')
|
|
|
|
|
|
|
|
def add_script(self, js):
|
|
|
|
script = self.node(self.defs, 'script', type='text/javascript')
|
|
|
|
with open(js) as f:
|
|
|
|
templ = template(
|
|
|
|
f.read(),
|
|
|
|
font_sizes=self.graph.font_sizes(False),
|
|
|
|
animation_steps=self.graph.animation_steps
|
|
|
|
)
|
|
|
|
script.text = templ.decode('utf-8')
|
|
|
|
|
|
|
|
def node(self, parent=None, tag='g', attrib=None, **extras):
|
|
|
|
if parent is None:
|
|
|
|
parent = self.root
|
|
|
|
attrib = attrib or {}
|
|
|
|
attrib.update(extras)
|
|
|
|
for key, value in attrib.items():
|
|
|
|
if value is None:
|
|
|
|
del attrib[key]
|
|
|
|
elif not isinstance(value, basestring):
|
|
|
|
attrib[key] = str(value)
|
|
|
|
elif key.endswith('_'):
|
|
|
|
attrib[key.rstrip('_')] = attrib[key]
|
|
|
|
del attrib[key]
|
|
|
|
return etree.SubElement(parent, tag, attrib)
|
|
|
|
|
|
|
|
def transposable_node(self, parent=None, tag='g', attrib=None, **extras):
|
|
|
|
if self.graph._horizontal:
|
|
|
|
for key1, key2 in (('x', 'y'), ('width', 'height')):
|
|
|
|
attr1 = extras.get(key1, None)
|
|
|
|
attr2 = extras.get(key2, None)
|
|
|
|
extras[key1], extras[key2] = attr2, attr1
|
|
|
|
return self.node(parent, tag, attrib, **extras)
|
|
|
|
|
|
|
|
def format(self, xy):
|
|
|
|
return '%f %f' % xy
|
|
|
|
|
|
|
|
def line(self, node, coords, close=False, **kwargs):
|
|
|
|
if len(coords) < 2:
|
|
|
|
return
|
|
|
|
root = 'M%s L%s Z' if close else 'M%s L%s'
|
|
|
|
origin_index = 0
|
|
|
|
while None in coords[origin_index]:
|
|
|
|
origin_index += 1
|
|
|
|
origin = self.format(coords[origin_index])
|
|
|
|
line = ' '.join([self.format(c)
|
|
|
|
for c in coords[origin_index + 1:]
|
|
|
|
if None not in c])
|
|
|
|
self.node(node, 'path',
|
|
|
|
d=root % (origin, line), **kwargs)
|
|
|
|
|
|
|
|
def _pre_render(self, no_data=False):
|
|
|
|
self.add_style(self.graph.base_css or os.path.join(
|
|
|
|
os.path.dirname(__file__), 'css', 'graph.css'))
|
|
|
|
self.add_script(self.graph.base_js or os.path.join(
|
|
|
|
os.path.dirname(__file__), 'js', 'graph.js'))
|
|
|
|
self.root.set(
|
|
|
|
'viewBox', '0 0 %d %d' % (self.graph.width, self.graph.height))
|
|
|
|
if self.graph.explicit_size:
|
|
|
|
self.root.set('width', str(self.graph.width))
|
|
|
|
self.root.set('height', str(self.graph.height))
|
|
|
|
if no_data:
|
|
|
|
no_data = self.node(self.root, 'text',
|
|
|
|
x=self.graph.width / 2,
|
|
|
|
y=self.graph.height / 2,
|
|
|
|
class_='no_data')
|
|
|
|
no_data.text = self.graph.no_data_text
|
|
|
|
|
|
|
|
def render(self):
|
|
|
|
svg = etree.tostring(
|
|
|
|
self.root, pretty_print=True,
|
|
|
|
xml_declaration=not self.graph.disable_xml_declaration,
|
|
|
|
encoding='utf-8')
|
|
|
|
if self.graph.disable_xml_declaration:
|
|
|
|
svg = svg.decode('utf-8')
|
|
|
|
return svg
|