Skip to content

Commit

Permalink
Make lxml an optionnal dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed Jun 4, 2014
1 parent 44cb4b8 commit ad3464a
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 51 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ V 1.5.0 UNRELEASED
Add per serie configuration
Add half pie (thanks philt2001)
Add render_table (WIP)
Make lxml an optionnal dependency

V 1.4.6
Add support for \n separated multiline titles (thanks sirlark)
Expand Down
4 changes: 2 additions & 2 deletions perf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>.


from pygal import CHARTS_NAMES, CHARTS_BY_NAME
from pygal import CHARTS, CHARTS_BY_NAME
from pygal.test import adapt
from random import sample

Expand Down Expand Up @@ -82,7 +82,7 @@ def print_mem():

sys.exit(0)

charts = CHARTS_NAMES if '--all' in sys.argv else 'Line',
charts = CHARTS if '--all' in sys.argv else 'Line',

for chart in charts:
prt('%s\n' % chart)
Expand Down
17 changes: 17 additions & 0 deletions pygal/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,20 @@
#
# 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
import sys
from collections import Iterable

try:
if os.getenv('NO_LXML', None):
raise ImportError('Explicit lxml bypass')
from lxml import etree
etree.lxml = True
except ImportError:
from xml.etree import ElementTree as etree
etree.lxml = False


if sys.version_info[0] == 3:
base = (str, bytes)
coerce = str
Expand All @@ -41,6 +52,12 @@ def to_str(string):
return string


def to_unicode(string):
if not isinstance(string, coerce):
return string.decode('utf-8')
return string


def u(s):
if sys.version_info[0] == 2:
return s.decode('utf-8')
Expand Down
2 changes: 1 addition & 1 deletion pygal/ghost.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def render_table(self, **kwargs):
def render_pyquery(self):
"""Render the graph, and return a pyquery wrapped tree"""
from pyquery import PyQuery as pq
return pq(self.render_tree())
return pq(self.render(), parser='html')

def render_in_browser(self):
"""Render the graph, open it in your browser with black magic"""
Expand Down
2 changes: 1 addition & 1 deletion pygal/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def render(self, is_unicode=False):
is_unicode=is_unicode, pretty_print=self.pretty_print)

def render_tree(self):
"""Render the graph, and return lxml tree"""
"""Render the graph, and return (l)xml etree"""
svg = self.svg.root
for f in self.xml_filters:
svg = f(svg)
Expand Down
24 changes: 14 additions & 10 deletions pygal/graph/frenchmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@
from pygal.ghost import ChartCollection
from pygal.util import cut, cached_property, decorate
from pygal.graph.graph import Graph
from pygal._compat import u
from pygal._compat import u, etree
from numbers import Number
from lxml import etree
import os


Expand Down Expand Up @@ -190,6 +189,7 @@ class FrenchMapDepartments(Graph):
x_labels = list(DEPARTMENTS.keys())
area_names = DEPARTMENTS
area_prefix = 'z'
kind = 'departement'
svg_map = DPT_MAP


Expand Down Expand Up @@ -222,9 +222,10 @@ def _plot(self):
ratio = 1
else:
ratio = .3 + .7 * (value - min_) / (max_ - min_)
areae = map.xpath(
"//*[contains(concat(' ', normalize-space(@class), ' '),"
" ' %s%s ')]" % (self.area_prefix, area_code))
areae = map.findall(
".//*[@class='%s%s %s map-element']" % (
self.area_prefix, area_code,
self.kind))

if not areae:
continue
Expand All @@ -236,14 +237,16 @@ def _plot(self):

metadata = serie.metadata.get(j)
if metadata:
parent = area.getparent()
node = decorate(self.svg, area, metadata)
if node != area:
area.remove(node)
index = parent.index(area)
parent.remove(area)
node.append(area)
parent.insert(index, 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)

last_node = len(area) > 0 and area[-1]
if last_node is not None and last_node.tag == 'title':
Expand All @@ -265,6 +268,7 @@ class FrenchMapRegions(FrenchMapDepartments):
area_names = REGIONS
area_prefix = 'a'
svg_map = REG_MAP
kind = 'region'


class FrenchMap(ChartCollection):
Expand Down
9 changes: 4 additions & 5 deletions pygal/graph/supranationalworldmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from pygal.graph.worldmap import Worldmap
from pygal.i18n import SUPRANATIONAL
from pygal.util import cut, decorate
from lxml import etree
from pygal._compat import etree
import os

with open(os.path.join(
Expand Down Expand Up @@ -68,14 +68,13 @@ def _plot(self):

metadata = serie.metadata.get(j)
if metadata:
parent = country.getparent()
node = decorate(self.svg, country, metadata)
if node != country:
country.remove(node)
index = parent.index(country)
parent.remove(country)
index = list(map).index(country)
map.remove(country)
node.append(country)
parent.insert(index, node)
map.insert(index, node)

last_node = len(country) > 0 and country[-1]
if last_node is not None and last_node.tag == 'title':
Expand Down
9 changes: 4 additions & 5 deletions pygal/graph/worldmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from pygal.util import cut, cached_property, decorate
from pygal.graph.graph import Graph
from pygal.i18n import COUNTRIES
from lxml import etree
from pygal._compat import etree
import os

with open(os.path.join(
Expand Down Expand Up @@ -86,14 +86,13 @@ def _plot(self):

metadata = serie.metadata.get(j)
if metadata:
parent = country.getparent()
node = decorate(self.svg, country, metadata)
if node != country:
country.remove(node)
index = parent.index(country)
parent.remove(country)
index = list(map).index(country)
map.remove(country)
node.append(country)
parent.insert(index, node)
map.insert(index, node)

last_node = len(country) > 0 and country[-1]
if last_node is not None and last_node.tag == 'title':
Expand Down
44 changes: 29 additions & 15 deletions pygal/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@
"""

from __future__ import division
from pygal._compat import to_str, u
from pygal._compat import to_str, u, etree
import io
import os
import json
from datetime import date, datetime
from numbers import Number
from lxml import etree
from math import cos, sin, pi
from pygal.util import template, coord_format, minify_css
from pygal import __version__
Expand All @@ -37,6 +36,7 @@
class Svg(object):
"""Svg object"""
ns = 'http://www.w3.org/2000/svg'
xlink_ns = 'http://www.w3.org/1999/xlink'

def __init__(self, graph):
self.graph = graph
Expand All @@ -45,19 +45,30 @@ def __init__(self, graph):
else:
self.id = ''
self.processing_instructions = [
etree.PI(u('xml'), u("version='1.0' encoding='utf-8'"))]
self.root = etree.Element(
"{%s}svg" % self.ns,
nsmap={
None: self.ns,
'xlink': 'http://www.w3.org/1999/xlink',
})
etree.ProcessingInstruction(
u('xml'), u("version='1.0' encoding='utf-8'"))]
if etree.lxml:
attrs = {
'nsmap': {
None: self.ns,
'xlink': self.xlink_ns
}
}
else:
attrs = {
'xmlns': self.ns
}
etree.register_namespace('xlink', self.xlink_ns)

self.root = etree.Element('svg', **attrs)
self.root.attrib['id'] = self.id.lstrip('#').rstrip()
self.root.attrib['class'] = 'pygal-chart'
self.root.append(
etree.Comment(u(
'Generated with pygal %s ©Kozea 2011-2014 on %s' % (
__version__, date.today().isoformat()))))
'Generated with pygal %s (%s) ©Kozea 2011-2014 on %s' % (
__version__,
'lxml' if etree.lxml else 'etree',
date.today().isoformat()))))
self.root.append(etree.Comment(u('http://pygal.org')))
self.root.append(etree.Comment(u('http://github.com/Kozea/pygal')))
self.defs = self.node(tag='defs')
Expand Down Expand Up @@ -227,14 +238,17 @@ def render(self, is_unicode=False, pretty_print=False):
"""Last thing to do before rendering"""
for f in self.graph.xml_filters:
self.root = f(self.root)
args = {
'encoding': 'utf-8'
}
if etree.lxml:
args['pretty_print'] = pretty_print
svg = etree.tostring(
self.root, pretty_print=pretty_print,
xml_declaration=False,
encoding='utf-8')
self.root, **args)
if not self.graph.disable_xml_declaration:
svg = b'\n'.join(
[etree.tostring(
pi, encoding='utf-8', pretty_print=pretty_print)
pi, **args)
for pi in self.processing_instructions]
) + b'\n' + svg
if self.graph.disable_xml_declaration or is_unicode:
Expand Down
1 change: 1 addition & 0 deletions pygal/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from datetime import datetime
from pygal.i18n import COUNTRIES
from pygal.graph.frenchmap import DEPARTMENTS, REGIONS
from pygal._compat import etree


def get_data(i):
Expand Down
20 changes: 13 additions & 7 deletions pygal/test/test_xml_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
from pygal import Bar


class ChangeBarsXMLFilter(object):
def __init__(self, a, b):
self.data = [b[i] - a[i] for i in range(len(a))]

def __call__(self, T):
subplot = Bar(legend_at_bottom=True, explicit_size=True, width=800, height=150)
subplot = Bar(legend_at_bottom=True, explicit_size=True,
width=800, height=150)
subplot.add("Difference", self.data)
subplot = subplot.render_tree()
subplot = subplot.xpath("g")[0]
subplot = subplot.findall("g")[0]
T.insert(2, subplot)
T.xpath("g")[1].set('transform', 'translate(0,150), scale(1,0.75)')
T.findall("g")[1].set('transform', 'translate(0,150), scale(1,0.75)')
return T


def test_xml_filters_round_trip():
plot = Bar()
plot.add("A", [60, 75, 80, 78, 83, 90])
Expand All @@ -40,13 +43,16 @@ def test_xml_filters_round_trip():
after = plot.render()
assert before == after


def test_xml_filters_change_bars():
plot = Bar(legend_at_bottom=True, explicit_size=True, width=800, height=600)
plot = Bar(legend_at_bottom=True, explicit_size=True,
width=800, height=600)
A = [60, 75, 80, 78, 83, 90]
B = [92, 87, 81, 73, 68, 55]
plot.add("A", A)
plot.add("B", B)
plot.add_xml_filter(ChangeBarsXMLFilter(A,B))
plot.add_xml_filter(ChangeBarsXMLFilter(A, B))
q = plot.render_tree()
assert len(q.xpath("g")) == 2
assert q.xpath("g")[1].attrib["transform"] == "translate(0,150), scale(1,0.75)"
assert len(q.findall("g")) == 2
assert q.findall("g")[1].attrib[
"transform"] == "translate(0,150), scale(1,0.75)"
6 changes: 3 additions & 3 deletions pygal/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"""
from __future__ import division
from pygal._compat import to_str, u, is_list_like
from pygal._compat import u, is_list_like, to_unicode
import re
from decimal import Decimal
from math import floor, pi, log, log10, ceil
Expand Down Expand Up @@ -241,7 +241,7 @@ def decorate(svg, node, metadata):
if key == 'xlink' and isinstance(value, dict):
value = value.get('href', value)
if value:
svg.node(node, 'desc', class_=key).text = to_str(value)
svg.node(node, 'desc', class_=key).text = to_unicode(value)
return node


Expand Down Expand Up @@ -328,7 +328,7 @@ def prepare_values(raw, config, cls):
from pygal.graph.worldmap import Worldmap
from pygal.graph.frenchmap import FrenchMapDepartments
if config.x_labels is None and hasattr(cls, 'x_labels'):
config.x_labels = cls.x_labels
config.x_labels = list(map(to_unicode, cls.x_labels))
if config.zero == 0 and issubclass(cls, (Worldmap, FrenchMapDepartments)):
config.zero = 1

Expand Down
8 changes: 6 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def finalize_options(self):
self.test_suite = True

def run_tests(self):
#import here, cause outside the eggs aren't loaded
# import here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(self.test_args)
sys.exit(errno)
Expand Down Expand Up @@ -65,7 +65,11 @@ def run_tests(self):
tests_require=["pytest", "pyquery", "flask", "cairosvg"],
cmdclass={'test': PyTest},
package_data={'pygal': ['css/*', 'graph/*.svg']},
install_requires=['lxml'],
extra_requires={
'lxml': ['lxml'],
'png': ['cairosvg']

},
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
Expand Down

0 comments on commit ad3464a

Please sign in to comment.