55Demonstrate how to create an interactive histogram, in which bars
66are hidden or shown by cliking on legend markers.
77
8- The interactivity is encoded in ecmascript and inserted in the SVG code
9- in a post-processing step. To render the image, open it in a web
10- browser. SVG is supported in most web browsers used by Linux and OSX
11- users. Windows IE9 supports SVG, but earlier versions do not.
8+ The interactivity is encoded in ecmascript (javascript) and inserted in
9+ the SVG code in a post-processing step. To render the image, open it in
10+ a web browser. SVG is supported in most web browsers used by Linux and
11+ OSX users. Windows IE9 supports SVG, but earlier versions do not.
12+
13+ Notes
14+ -----
15+ The matplotlib backend lets us assign ids to each object. This is the
16+ mechanism used here to relate matplotlib objects created in python and
17+ the corresponding SVG constructs that are parsed in the second step.
18+ While flexible, ids are cumbersome to use for large collection of
19+ objects. Two mechanisms could be used to simplify things:
20+ * systematic grouping of objects into SVG <g> tags,
21+ * assingning classes to each SVG object according to its origin.
22+
23+ For example, instead of modifying the properties of each individual bar,
24+ the bars from the `hist` function could either be grouped in
25+ a PatchCollection, or be assigned a class="hist_##" attribute.
26+
27+ CSS could also be used more extensively to replace repetitive markup
28+ troughout the generated SVG.
1229
13301431
1532"""
1633
34+
1735import numpy as np
1836import matplotlib .pyplot as plt
1937import xml .etree .ElementTree as ET
2038from StringIO import StringIO
39+ import json
2140
2241plt .rcParams ['svg.embed_char_paths' ] = 'none'
2342
2645# space with ns0.
2746ET .register_namespace ("" ,"http://www.w3.org/2000/svg" )
2847
29-
30- def python2js (d ):
31- """Return a string representation of a python dictionary in
32- ecmascript object syntax."""
33-
34- objs = []
35- for key , value in d .items ():
36- objs .append ( key + ':' + str (value ) )
37-
38- return '{' + ', ' .join (objs ) + '}'
48+
3949
4050
4151# --- Create histogram, legend and title ---
@@ -62,7 +72,11 @@ def python2js(d):
6272# Set ids for the legend patches
6373for i , t in enumerate (leg .get_patches ()):
6474 t .set_gid ('leg_patch_%d' % i )
65-
75+
76+ # Set ids for the text patches
77+ for i , t in enumerate (leg .get_texts ()):
78+ t .set_gid ('leg_text_%d' % i )
79+
6680# Save SVG in a fake file object.
6781f = StringIO ()
6882plt .savefig (f , format = "svg" )
@@ -77,10 +91,15 @@ def python2js(d):
7791for i , t in enumerate (leg .get_patches ()):
7892 el = xmlid ['leg_patch_%d' % i ]
7993 el .set ('cursor' , 'pointer' )
80- el .set ('opacity' , '1.0' )
81- el .set ('onclick' , "toggle_element(evt, 'hist_%d')" % i )
94+ el .set ('onclick' , "toggle_hist(this)" )
8295
83- # Create script defining the function `toggle_element`.
96+ # Add attributes to the text objects.
97+ for i , t in enumerate (leg .get_texts ()):
98+ el = xmlid ['leg_text_%d' % i ]
99+ el .set ('cursor' , 'pointer' )
100+ el .set ('onclick' , "toggle_hist(this)" )
101+
102+ # Create script defining the function `toggle_hist`.
84103# We create a global variable `container` that stores the patches id
85104# belonging to each histogram. Then a function "toggle_element" sets the
86105# visibility attribute of all patches of each histogram and the opacity
@@ -91,37 +110,46 @@ def python2js(d):
91110<![CDATA[
92111var container = %s
93112
94- function toggle_element(evt, element) {
113+ function toggle(oid, attribute, values) {
114+ /* Toggle the style attribute of an object between two values.
115+
116+ Parameters
117+ ----------
118+ oid : str
119+ Object identifier.
120+ attribute : str
121+ Name of syle attribute.
122+ values : [on state, off state]
123+ The two values that are switched between.
124+ */
125+ var obj = document.getElementById(oid);
126+ var a = obj.style[attribute];
127+
128+ a = (a == values[0] || a == "") ? values[1] : values[0];
129+ obj.style[attribute] = a;
130+ }
95131
96- var names = container[element]
97- var el, state;
132+ function toggle_hist(obj) {
98133
99- state = evt.target.getAttribute("opacity") == 1.0 ||
100- evt.target.getAttribute("opacity") == null;
101-
102- if (state) {
103- evt.target.setAttribute("opacity", 0.5);
104-
105- for (var i=0; i < names.length; i++) {
106- el = document.getElementById(names[i]);
107- el.setAttribute("visibility","hidden");
108- }
109- }
110-
111- else {
112- evt.target.setAttribute("opacity", 1);
113-
114- for (var i=0; i < names.length; i++) {
115- el = document.getElementById(names[i]);
116- el.setAttribute("visibility","visible");
117- }
118-
119- };
134+ var num = obj.id.slice(-1);
135+
136+ toggle('leg_patch_' + num, 'opacity', [1, 0.3]);
137+ toggle('leg_text_' + num, 'opacity', [1, 0.5]);
138+
139+ var names = container['hist_'+num]
140+
141+ for (var i=0; i < names.length; i++) {
142+ toggle(names[i], 'opacity', [1,0])
120143 };
144+ }
121145]]>
122146</script>
123- """ % python2js (hist_patches )
147+ """ % json . dumps (hist_patches )
124148
149+ # Add a transition effect
150+ css = tree .getchildren ()[0 ][0 ]
151+ css .text = css .text + "g {-webkit-transition:opacity 0.4s ease-out;-moz-transition:opacity 0.4s ease-out;}"
152+
125153# Insert the script and save to file.
126154tree .insert (0 , ET .XML (script ))
127155
0 commit comments