diff --git a/pygal/config.py b/pygal/config.py
index a89239cb..2ee5d0b8 100644
--- a/pygal/config.py
+++ b/pygal/config.py
@@ -24,7 +24,6 @@
from pygal.style import Style, DefaultStyle
from pygal.interpolate import INTERPOLATIONS
-
CONFIG_ITEMS = []
@@ -170,7 +169,7 @@ class Config(CommonConfig):
DefaultStyle, Style, "Style", "Style holding values injected in css")
css = Key(
- ('style.css', 'graph.css'), list, "Style",
+ ("!pygal.css.style_css", "!pygal.css.graph_css"), list, "Style",
"List of css file",
"It can be an absolute file path or an external link",
str)
@@ -428,6 +427,10 @@ class Config(CommonConfig):
"Don't prefix css")
inverse_y_axis = Key(False, bool, "Misc", "Inverse Y axis direction")
+
+ background_image = Key(None, str, "Misc", "Provide an optional background image to the plot")
+
+ preserve_aspect = Key(False, bool, "Misc", "Preserve aspect ratio")
class SerieConfig(CommonConfig):
diff --git a/pygal/css/__init__.py b/pygal/css/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/pygal/css/base.css b/pygal/css/base_css.py
similarity index 99%
rename from pygal/css/base.css
rename to pygal/css/base_css.py
index 8837ca46..f1339907 100644
--- a/pygal/css/base.css
+++ b/pygal/css/base_css.py
@@ -1,3 +1,4 @@
+data = """\
/*
* This file is part of pygal
*
@@ -55,3 +56,4 @@
{{ id }}text.no_data {
font-size: {{ font_sizes.no_data }};
}
+"""
\ No newline at end of file
diff --git a/pygal/css/graph.css b/pygal/css/graph_css.py
similarity index 99%
rename from pygal/css/graph.css
rename to pygal/css/graph_css.py
index 095e7bc8..a8dc0be9 100644
--- a/pygal/css/graph.css
+++ b/pygal/css/graph_css.py
@@ -1,3 +1,4 @@
+data="""\
/*
* This file is part of pygal
*
@@ -126,3 +127,4 @@
{{ id }}.tooltip text tspan.label {
fill-opacity: .8;
}
+"""
\ No newline at end of file
diff --git a/pygal/css/style.css b/pygal/css/style_css.py
similarity index 99%
rename from pygal/css/style.css
rename to pygal/css/style_css.py
index 2c3f7086..8c152b37 100644
--- a/pygal/css/style.css
+++ b/pygal/css/style_css.py
@@ -1,3 +1,4 @@
+data="""\
/*
* This file is part of pygal
*
@@ -139,3 +140,4 @@
{{ colors }}
+"""
\ No newline at end of file
diff --git a/pygal/graph/base.py b/pygal/graph/base.py
index 9d814391..97098cb0 100644
--- a/pygal/graph/base.py
+++ b/pygal/graph/base.py
@@ -1,4 +1,4 @@
- # -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# This file is part of pygal
#
# A python svg graph plotting library
diff --git a/pygal/graph/frenchmap.py b/pygal/graph/frenchmap.py
index 78125bb3..e4479f40 100644
--- a/pygal/graph/frenchmap.py
+++ b/pygal/graph/frenchmap.py
@@ -169,12 +169,7 @@
'06': u("Mayotte")
}
-
-with open(os.path.join(
- os.path.dirname(__file__), 'maps',
- 'fr.departments.svg')) as file:
- DPT_MAP = file.read()
-
+from .maps.fr_departments_svg import data as DPT_MAP
class IntCodeMixin(object):
def adapt_code(self, area_code):
@@ -194,12 +189,7 @@ class FrenchMapDepartments(IntCodeMixin, BaseMap):
kind = 'departement'
svg_map = DPT_MAP
-
-with open(os.path.join(
- os.path.dirname(__file__), 'maps',
- 'fr.regions.svg')) as file:
- REG_MAP = file.read()
-
+from .maps.fr_regions_svg import data as REG_MAP
class FrenchMapRegions(IntCodeMixin, BaseMap):
"""French regions map"""
diff --git a/pygal/graph/graph.py b/pygal/graph/graph.py
index 19673759..0db7dd2a 100644
--- a/pygal/graph/graph.py
+++ b/pygal/graph/graph.py
@@ -65,7 +65,8 @@ def _set_view(self):
self.view = view_class(
self.width - self.margin_box.x,
self.height - self.margin_box.y,
- self._box)
+ self._box,
+ self.preserve_aspect)
def _make_graph(self):
"""Init common graph svg structure"""
@@ -87,6 +88,28 @@ def _make_graph(self):
x=0, y=0,
width=self.view.width,
height=self.view.height)
+ if not self.background_image is None:
+ corners = list(map(self.view, ( (self._box.xmin,self._box.ymin), (self._box.xmax,self._box.ymax) ) ))
+
+ x0 = min(corners[0][0], corners[1][0])
+ y0 = min(corners[0][1], corners[1][1])
+ x1 = max(corners[0][0], corners[1][0])
+ y1 = max(corners[0][1], corners[1][1])
+ w = x1-x0
+ h = y1-y0
+ x0 += self._box.margin*w
+ y0 += self._box.margin*h
+ w -= self._box.margin*2*w
+ h -= self._box.margin*2*h
+ self.nodes['bgimage'] = self.svg.node(
+ self.nodes['plot'],
+ tag='image',
+ attrib={'xlink:href' : self.background_image},
+ x=x0,
+ y=y0,
+ width=w,
+ height=h,
+ preserveAspectRatio="none")
self.nodes['title'] = self.svg.node(
self.nodes['graph'],
class_="titles")
diff --git a/pygal/graph/maps/__init__.py b/pygal/graph/maps/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/pygal/graph/maps/ch.cantons.svg b/pygal/graph/maps/ch_cantons_svg.py
similarity index 99%
rename from pygal/graph/maps/ch.cantons.svg
rename to pygal/graph/maps/ch_cantons_svg.py
index 7f016ade..ee0836e2 100644
--- a/pygal/graph/maps/ch.cantons.svg
+++ b/pygal/graph/maps/ch_cantons_svg.py
@@ -1,3 +1,4 @@
+data = """\
-
+"""
diff --git a/pygal/graph/maps/fr.departments.svg b/pygal/graph/maps/fr_departments_svg.py
similarity index 99%
rename from pygal/graph/maps/fr.departments.svg
rename to pygal/graph/maps/fr_departments_svg.py
index 0d02fbdc..c2b33849 100644
--- a/pygal/graph/maps/fr.departments.svg
+++ b/pygal/graph/maps/fr_departments_svg.py
@@ -1,3 +1,4 @@
+data="""\
+"""
\ No newline at end of file
diff --git a/pygal/graph/maps/fr.regions.svg b/pygal/graph/maps/fr_regions_svg.py
similarity index 99%
rename from pygal/graph/maps/fr.regions.svg
rename to pygal/graph/maps/fr_regions_svg.py
index 046c62d7..4121d274 100644
--- a/pygal/graph/maps/fr.regions.svg
+++ b/pygal/graph/maps/fr_regions_svg.py
@@ -1,3 +1,4 @@
+data="""\
+"""
\ No newline at end of file
diff --git a/pygal/graph/maps/worldmap.svg b/pygal/graph/maps/worldmap_svg.py
similarity index 99%
rename from pygal/graph/maps/worldmap.svg
rename to pygal/graph/maps/worldmap_svg.py
index b024ce1c..4546b284 100644
--- a/pygal/graph/maps/worldmap.svg
+++ b/pygal/graph/maps/worldmap_svg.py
@@ -1,3 +1,4 @@
+data="""\
+"""
\ No newline at end of file
diff --git a/pygal/graph/swissmap.py b/pygal/graph/swissmap.py
index 384cf054..19cc4576 100644
--- a/pygal/graph/swissmap.py
+++ b/pygal/graph/swissmap.py
@@ -56,12 +56,7 @@
'kt-ge': u("Genf"),
}
-
-with open(os.path.join(
- os.path.dirname(__file__), 'maps',
- 'ch.cantons.svg')) as file:
- CNT_MAP = file.read()
-
+from .maps.ch_cantons_svg import data as CNT_MAP
class SwissMapCantons(BaseMap):
"""Swiss Cantons map"""
diff --git a/pygal/graph/worldmap.py b/pygal/graph/worldmap.py
index a98a2f73..9141bb95 100644
--- a/pygal/graph/worldmap.py
+++ b/pygal/graph/worldmap.py
@@ -27,12 +27,7 @@
from pygal.i18n import COUNTRIES, SUPRANATIONAL
import os
-
-with open(os.path.join(
- os.path.dirname(__file__), 'maps',
- 'worldmap.svg')) as file:
- WORLD_MAP = file.read()
-
+from .maps.worldmap_svg import data as WORLD_MAP
class Worldmap(BaseMap):
"""Worldmap graph"""
diff --git a/pygal/svg.py b/pygal/svg.py
index 11d8a2b1..0a219001 100644
--- a/pygal/svg.py
+++ b/pygal/svg.py
@@ -27,11 +27,13 @@
import io
import os
import json
+import importlib
from datetime import date, datetime
from numbers import Number
from math import cos, sin, pi
from pygal.util import template, coord_format, minify_css
from pygal import __version__
+from pygal.css import base_css
class Svg(object):
@@ -83,18 +85,25 @@ def add_styles(self):
"""Add the css to the svg"""
colors = self.graph.style.get_colors(self.id)
all_css = []
- for css in ['base.css'] + list(self.graph.css):
- if '://' in css:
+ for css in [base_css] + list(self.graph.css):
+ if type(css) == str and '://' in css:
self.processing_instructions.append(
etree.PI(
u('xml-stylesheet'), u('href="%s"' % css)))
else:
- if css.startswith('inline:'):
+ if type(css) == str and css.startswith('inline:'):
css_text = css[len('inline:'):]
else:
- if not os.path.exists(css):
- css = os.path.join(
- os.path.dirname(__file__), 'css', css)
+ if type(css) == str and css.startswith("!"):
+ css_raw = importlib.import_module(css[1:]).data
+ elif type(css) == str:
+ if not os.path.exists(css):
+ css = os.path.join(
+ os.path.dirname(__file__), 'css', css)
+ with io.open(css, encoding='utf-8') as f:
+ css_raw = f.read()
+ else:
+ css_raw = css.data
class FontSizes(object):
"""Container for font sizes"""
@@ -106,13 +115,12 @@ class FontSizes(object):
name.replace('_font_size', ''),
('%dpx' % getattr(self.graph, name)))
- with io.open(css, encoding='utf-8') as f:
- css_text = template(
- f.read(),
- style=self.graph.style,
- colors=colors,
- font_sizes=fs,
- id=self.id)
+ css_text = template(
+ css_raw,
+ style=self.graph.style,
+ colors=colors,
+ font_sizes=fs,
+ id=self.id)
if not self.graph.pretty_print:
css_text = minify_css(css_text)
all_css.append(css_text)
diff --git a/pygal/view.py b/pygal/view.py
index e0a6ff58..f2aa3dbe 100644
--- a/pygal/view.py
+++ b/pygal/view.py
@@ -130,24 +130,31 @@ def fix(self, with_margin=True):
class View(object):
"""Projection base class"""
- def __init__(self, width, height, box):
+ def __init__(self, width, height, box, preserve_aspect = False):
self.width = width
self.height = height
self.box = box
self.box.fix()
+ self.preserve_aspect = preserve_aspect
+ self.scalex = self.width / self.box.width
+ self.scaley = self.height / self.box.height
+ if preserve_aspect:
+ print("preserve aspect", self.scalex, self.scaley)
+ self.scalex = min(self.scalex, self.scaley)
+ self.scaley = self.scalex
+ print(" -> ", self.scalex, self.scaley)
def x(self, x):
"""Project x"""
if x is None:
return None
- return self.width * (x - self.box.xmin) / self.box.width
-
+ return self.scalex * (x - self.box.xmin)
+
def y(self, y):
"""Project y"""
if y is None:
return None
- return (self.height - self.height *
- (y - self.box.ymin) / self.box.height)
+ return self.height - self.scaley * (y - self.box.ymin)
def __call__(self, xy):
"""Project x and y"""
@@ -159,18 +166,14 @@ class ReverseView(View):
def y(self, y):
if y is None:
return None
- return (self.height * (y - self.box.ymin) / self.box.height)
+ return self.scaley * (y - self.box.ymin)
class HorizontalView(View):
- def __init__(self, width, height, box):
+ def __init__(self, width, height, box, preserve_aspect = False):
self._force_vertical = None
- self.width = width
- self.height = height
-
- self.box = box
- self.box.fix()
- self.box.swap()
+ box.swap()
+ super(HorizontalView, self).__init__(width, height, box, preserve_aspect)
def x(self, x):
"""Project x"""
@@ -203,8 +206,8 @@ def __call__(self, rhotheta):
class PolarLogView(View):
"""Logarithmic polar projection"""
- def __init__(self, width, height, box):
- super(PolarLogView, self).__init__(width, height, box)
+ def __init__(self, width, height, box, preserve_aspect = False):
+ super(PolarLogView, self).__init__(width, height, box, preserve_aspect)
if not hasattr(box, '_rmin') or not hasattr(box, '_rmax'):
raise Exception(
'Box must be set with set_polar_box for polar charts')
@@ -232,8 +235,8 @@ def __call__(self, rhotheta):
class PolarThetaView(View):
"""Logarithmic polar projection"""
- def __init__(self, width, height, box):
- super(PolarThetaView, self).__init__(width, height, box)
+ def __init__(self, width, height, box, preserve_aspect = False):
+ super(PolarThetaView, self).__init__(width, height, box, preserve_aspect)
if not hasattr(box, '_tmin') or not hasattr(box, '_tmax'):
raise Exception(
'Box must be set with set_polar_box for polar charts')
@@ -260,8 +263,8 @@ def __call__(self, rhotheta):
class PolarThetaLogView(View):
"""Logarithmic polar projection"""
- def __init__(self, width, height, box):
- super(PolarThetaLogView, self).__init__(width, height, box)
+ def __init__(self, width, height, box, preserve_aspect = False):
+ super(PolarThetaLogView, self).__init__(width, height, box, preserve_aspect)
if not hasattr(box, '_tmin') or not hasattr(box, '_tmax'):
raise Exception(
'Box must be set with set_polar_box for polar charts')
@@ -299,7 +302,7 @@ def __call__(self, rhotheta):
class LogView(View):
"""Logarithmic projection """
# Do not want to call the parent here
- def __init__(self, width, height, box):
+ def __init__(self, width, height, box, preserve_aspect = False):
self.width = width
self.height = height
self.box = box
@@ -321,7 +324,7 @@ def y(self, y):
class XLogView(View):
"""Logarithmic projection """
# Do not want to call the parent here
- def __init__(self, width, height, box):
+ def __init__(self, width, height, box, preserve_aspect = False):
self.width = width
self.height = height
self.box = box
@@ -339,7 +342,7 @@ def x(self, x):
class XYLogView(XLogView, LogView):
- def __init__(self, width, height, box):
+ def __init__(self, width, height, box, preserve_aspect = False):
self.width = width
self.height = height
self.box = box
@@ -353,7 +356,7 @@ def __init__(self, width, height, box):
class HorizontalLogView(XLogView):
"""Logarithmic projection """
# Do not want to call the parent here
- def __init__(self, width, height, box):
+ def __init__(self, width, height, box, preserve_aspect = False):
self._force_vertical = None
self.width = width
self.height = height
diff --git a/setup.py b/setup.py
index 6aab2bd6..5b0564c3 100644
--- a/setup.py
+++ b/setup.py
@@ -64,7 +64,7 @@ def run_tests(self):
"svg", "chart", "graph", "diagram", "plot", "histogram", "kiviat"],
tests_require=["pytest", "pyquery", "flask", "cairosvg"],
cmdclass={'test': PyTest},
- package_data={'pygal': ['css/*', 'graph/maps/*.svg']},
+ #package_data={'pygal': ['css/*', 'graph/maps/*.svg']},
extras_require={
'lxml': ['lxml'],
'png': ['cairosvg']