From 0d4a2e16a6543523590652762f56bcdfce78090a Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 23 Aug 2011 11:20:20 -0400 Subject: [PATCH 1/3] Put all clippaths in their own def at the end of the file so that they are accessible even when their referents are made invisible. Problem pointed out by David Huard on the mailing list in thread "Adding interactivity to an histogram in SVG". --- lib/matplotlib/backends/backend_svg.py | 61 +++++++++++++++++--------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 53e7346e2d70..661a7bcb221f 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -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) @@ -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), @@ -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): """ @@ -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 From 90f7d507ebadd2700e93974806b894a3fa7bf2e1 Mon Sep 17 00:00:00 2001 From: David Huard Date: Tue, 23 Aug 2011 14:33:59 -0400 Subject: [PATCH 2/3] add interactive svg histogram example. --- examples/user_interfaces/svg_histogram.py | 131 ++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 examples/user_interfaces/svg_histogram.py diff --git a/examples/user_interfaces/svg_histogram.py b/examples/user_interfaces/svg_histogram.py new file mode 100644 index 000000000000..e3d8067e3713 --- /dev/null +++ b/examples/user_interfaces/svg_histogram.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +#-*-encoding:utf8-*- + +""" +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 = """ + +"""%python2js(hist_patches) + +# Insert the script and save to file. +tree.insert(0, ET.XML(script)) + +ET.ElementTree(tree).write("svg_histogram.svg") + + + From 62d3de5c4bceb92cb05273a3fe6ebfc8a045f736 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 23 Aug 2011 16:27:41 -0400 Subject: [PATCH 3/3] Fix encoding line --- examples/user_interfaces/svg_histogram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 examples/user_interfaces/svg_histogram.py diff --git a/examples/user_interfaces/svg_histogram.py b/examples/user_interfaces/svg_histogram.py old mode 100644 new mode 100755 index e3d8067e3713..8763bbec2f17 --- a/examples/user_interfaces/svg_histogram.py +++ b/examples/user_interfaces/svg_histogram.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -#-*-encoding:utf8-*- +#-*- encoding:utf-8 -*- """ Demonstrate how to create an interactive histogram, in which bars