Skip to content

Svg references #432

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 29, 2011
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions examples/user_interfaces/svg_histogram.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env python
#-*- encoding:utf-8 -*-

"""
Demonstrate how to create an interactive histogram, in which bars
are hidden or shown by cliking on legend markers.

The interactivity is encoded in ecmascript and inserted in the SVG code
in a post-processing step. To render the image, open it in a web
browser. SVG is supported in most web browsers used by Linux and OSX
users. Windows IE9 supports SVG, but earlier versions do not.

__author__="david.huard@gmail.com"

"""

import numpy as np
import matplotlib.pyplot as plt
import xml.etree.ElementTree as ET
from StringIO import StringIO

plt.rcParams['svg.embed_char_paths'] = 'none'

# Apparently, this `register_namespace` method works only with
# python 2.7 and up and is necessary to avoid garbling the XML name
# space with ns0.
ET.register_namespace("","http://www.w3.org/2000/svg")


def python2js(d):
"""Return a string representation of a python dictionary in
ecmascript object syntax."""

objs = []
for key, value in d.items():
objs.append( key + ':' + str(value) )

return '{' + ', '.join(objs) + '}'


# --- Create histogram, legend and title ---
plt.figure()
r = np.random.randn(100)
r1 = r + 1
labels = ['Rabbits', 'Frogs']
H = plt.hist([r,r1], label=labels)
containers = H[-1]
leg = plt.legend(frameon=False)
plt.title("""From a web browser, click on the legend
marker to toggle the corresponding histogram.""")


# --- Add ids to the svg objects we'll modify

hist_patches = {}
for ic, c in enumerate(containers):
hist_patches['hist_%d'%ic] = []
for il, element in enumerate(c):
element.set_gid('hist_%d_patch_%d'%(ic, il))
hist_patches['hist_%d'%ic].append('hist_%d_patch_%d'%(ic,il))

# Set ids for the legend patches
for i, t in enumerate(leg.get_patches()):
t.set_gid('leg_patch_%d'%i)

# Save SVG in a fake file object.
f = StringIO()
plt.savefig(f, format="svg")

# Create XML tree from the SVG file.
tree, xmlid = ET.XMLID(f.getvalue())


# --- Add interactivity ---

# Add attributes to the patch objects.
for i, t in enumerate(leg.get_patches()):
el = xmlid['leg_patch_%d'%i]
el.set('cursor', 'pointer')
el.set('opacity', '1.0')
el.set('onclick', "toggle_element(evt, 'hist_%d')"%i)

# Create script defining the function `toggle_element`.
# We create a global variable `container` that stores the patches id
# belonging to each histogram. Then a function "toggle_element" sets the
# visibility attribute of all patches of each histogram and the opacity
# of the marker itself.

script = """
<script type="text/ecmascript">
<![CDATA[
var container = %s

function toggle_element(evt, element) {

var names = container[element]
var el, state;

state = evt.target.getAttribute("opacity") == 1.0 ||
evt.target.getAttribute("opacity") == null;

if (state) {
evt.target.setAttribute("opacity", 0.5);

for (var i=0; i < names.length; i++) {
el = document.getElementById(names[i]);
el.setAttribute("visibility","hidden");
}
}

else {
evt.target.setAttribute("opacity", 1);

for (var i=0; i < names.length; i++) {
el = document.getElementById(names[i]);
el.setAttribute("visibility","visible");
}

};
};
]]>
</script>
"""%python2js(hist_patches)

# Insert the script and save to file.
tree.insert(0, ET.XML(script))

ET.ElementTree(tree).write("svg_histogram.svg")



61 changes: 41 additions & 20 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ def __init__(self, width, height, svgwriter, basename=None):
self._write_default_style()

def finalize(self):
self._write_clips()
self._write_hatches()
self._write_svgfonts()
self.writer.close(self._start_id)

Expand Down Expand Up @@ -321,26 +323,35 @@ def _get_hatch(self, gc, rgbFace):
"""
Create a new hatch pattern
"""
writer = self.writer
HATCH_SIZE = 72
dictkey = (gc.get_hatch(), rgbFace, gc.get_rgb())
oid = self._hatchd.get(dictkey)
if oid is None:
oid = self._make_id('h', dictkey)
writer.start('defs')
self._hatchd[dictkey] = ((gc.get_hatch_path(), rgbFace, gc.get_rgb()), oid)
else:
_, oid = oid
return oid

def _write_hatches(self):
if not len(self._hatchd):
return
HATCH_SIZE = 72
writer = self.writer
writer.start('defs')
for ((path, face, stroke), oid) in self._hatchd.values():
writer.start(
'pattern',
id=oid,
patternUnits="userSpaceOnUse",
x="0", y="0", width=str(HATCH_SIZE), height=str(HATCH_SIZE))
path_data = self._convert_path(
gc.get_hatch_path(),
path,
Affine2D().scale(HATCH_SIZE).scale(1.0, -1.0).translate(0, HATCH_SIZE),
simplify=False)
if rgbFace is None:
if face is None:
fill = 'none'
else:
fill = rgb2hex(rgbFace)
fill = rgb2hex(face)
writer.element(
'rect',
x="0", y="0", width=str(HATCH_SIZE+1), height=str(HATCH_SIZE+1),
Expand All @@ -349,17 +360,15 @@ def _get_hatch(self, gc, rgbFace):
'path',
d=path_data,
style=generate_css({
'fill': rgb2hex(gc.get_rgb()),
'stroke': rgb2hex(gc.get_rgb()),
'fill': rgb2hex(stroke),
'stroke': rgb2hex(stroke),
'stroke-width': str(1.0),
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter'
})
)
writer.end('pattern')
writer.end('defs')
self._hatchd[dictkey] = oid
return oid
writer.end('defs')

def _get_style(self, gc, rgbFace):
"""
Expand Down Expand Up @@ -409,22 +418,34 @@ def _get_clip(self, gc):
else:
return None

oid = self._clipd.get(dictkey)
if oid is None:
writer = self.writer
clip = self._clipd.get(dictkey)
if clip is None:
oid = self._make_id('p', dictkey)
writer.start('defs')
writer.start('clipPath', id=oid)
if clippath is not None:
self._clipd[dictkey] = ((clippath, clippath_trans), oid)
else:
self._clipd[dictkey] = (dictkey, oid)
else:
clip, oid = clip
return oid

def _write_clips(self):
if not len(self._clipd):
return
writer = self.writer
writer.start('defs')
for clip, oid in self._clipd.values():
writer.start('clipPath', id=oid)
if len(clip) == 2:
clippath, clippath_trans = clip
path_data = self._convert_path(clippath, clippath_trans, simplify=False)
writer.element('path', d=path_data)
else:
x, y, w, h = clip
writer.element('rect', x=str(x), y=str(y), width=str(w), height=str(h))
writer.end('clipPath')
writer.end('defs')
self._clipd[dictkey] = oid
return oid

writer.end('defs')

def _write_svgfonts(self):
if not rcParams['svg.fonttype'] == 'svgfont':
return
Expand Down