Closed
Description
Bug report
Bug summary
I'm working with this Matplotlib User Interface example, and when I run the base example it works as advertised. As I start to adapt it to my needs, things start to go sideways.
- When I add a third patch, it still works as advertised.
- When I add a fourth patch, its mouseover seems to be mapped to the subplot X axis top instead of the patch.
- When I add a fifth patch, the fourth is still mapped to the subplot X axis top and I can't find the mouseover area for the fifth patch.
Further detail on the problem and the solution can be found on Stack Overflow.
Code for reproduction
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from io import BytesIO
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import xml.etree.ElementTree as ET
ET.register_namespace("", "http://www.w3.org/2000/svg")
fig = plt.figure(figsize=(25,10))
fig, ax1 = plt.subplots()
ax1.add_patch(mpatches.FancyBboxPatch((1980, 1), 1, 1, boxstyle=mpatches.BoxStyle("Round", pad=0.15)))
ax1.annotate('One', xy=(1980, 1), xytext=(0, 0), textcoords='offset points', color='w', ha='center', fontsize=8, bbox=dict(boxstyle='round, pad=.5', fc=(.1, .1, .1, .92), ec=(1., 1., 1.), lw=1, zorder=1))
ax1.add_patch(mpatches.FancyBboxPatch((1990, 2), 1, 1, boxstyle=mpatches.BoxStyle("Round", pad=0.15)))
ax1.annotate('Two', xy=(1990, 2), xytext=(0, 0), textcoords='offset points', color='w', ha='center', fontsize=8, bbox=dict(boxstyle='round, pad=.5', fc=(.1, .1, .1, .92), ec=(1., 1., 1.), lw=1, zorder=1))
ax1.add_patch(mpatches.FancyBboxPatch((2000, 3), 1, 1, boxstyle=mpatches.BoxStyle("Round", pad=0.15)))
ax1.annotate('Three', xy=(2000, 3), xytext=(0, 0), textcoords='offset points', color='w', ha='center', fontsize=8, bbox=dict(boxstyle='round, pad=.5', fc=(.1, .1, .1, .92), ec=(1., 1., 1.), lw=1, zorder=1))
ax1.add_patch(mpatches.FancyBboxPatch((2010, 4), 1, 1, boxstyle=mpatches.BoxStyle("Round", pad=0.15)))
ax1.annotate('Four', xy=(2010, 4), xytext=(0, 0), textcoords='offset points', color='w', ha='center', fontsize=8, bbox=dict(boxstyle='round, pad=.5', fc=(.1, .1, .1, .92), ec=(1., 1., 1.), lw=1, zorder=1))
# Save the figure in a fake file object
ax1.set_xlim(1970, 2017)
ax1.set_ylim(0, 8)
# Set id for the patches
for i, t in enumerate(ax1.patches):
t.set_gid('patch_%d' % i)
# Set id for the annotations
for i, t in enumerate(ax1.texts):
t.set_gid('tooltip_%d' % i)
f = BytesIO()
plt.savefig(f, format="svg")
# --- Add interactivity ---
# Create XML tree from the SVG file.
tree, xmlid = ET.XMLID(f.getvalue())
tree.set('onload', 'init(evt)')
# Hide the tooltips
for i, t in enumerate(ax1.texts):
el = xmlid['tooltip_%d' % i]
el.set('visibility', 'hidden')
# Assign onmouseover and onmouseout callbacks to patches.
for i, t in enumerate(ax1.patches):
el = xmlid['patch_%d' % i]
el.set('onmouseover', "ShowTooltip(this)")
el.set('onmouseout', "HideTooltip(this)")
# This is the script defining the ShowTooltip and HideTooltip functions.
script = """
<script type="text/ecmascript">
<![CDATA[
function init(evt) {
if ( window.svgDocument == null ) {
svgDocument = evt.target.ownerDocument;
}
}
function ShowTooltip(obj) {
var cur = obj.id.slice(-1);
var tip = svgDocument.getElementById('tooltip_' + cur);
tip.setAttribute('visibility',"visible")
}
function HideTooltip(obj) {
var cur = obj.id.slice(-1);
var tip = svgDocument.getElementById('tooltip_' + cur);
tip.setAttribute('visibility',"hidden")
}
]]>
</script>
"""
# Insert the script at the top of the file and save it.
tree.insert(0, ET.XML(script))
ET.ElementTree(tree).write('svg_tooltip_1.svg')
Actual outcome
- The fourth patch is invoked by hovering on the X Axis Top Line.
The following code is a working solution to the issue (credit to the author of the solution (who is not me) would be greatly appreciated.) Stack Overflow
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from io import BytesIO
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import xml.etree.ElementTree as ET
ET.register_namespace("", "http://www.w3.org/2000/svg")
fig = plt.figure(figsize=(25,10))
fig, ax1 = plt.subplots()
years = [1980,1990, 2000, 2010]
labels = ["One", "Two", "Three", "Four"]
for i, year in enumerate(years):
patch = mpatches.FancyBboxPatch((year, i+1), 1, 1,
boxstyle=mpatches.BoxStyle("Round", pad=0.15))
annotate = ax1.annotate(labels[i], xy=(year, i+1), xytext=(0, 0),
textcoords='offset points', color='w', ha='center',
fontsize=8, bbox=dict(boxstyle='round, pad=.5', fc=(.1, .1, .1, .92),
ec=(1., 1., 1.), lw=1, zorder=1))
ax1.add_patch(patch)
patch.set_gid('mypatch_%d' % i)
annotate.set_gid('mytooltip_%d' % i)
# Save the figure in a fake file object
ax1.set_xlim(1970, 2017)
ax1.set_ylim(0, 8)
f = BytesIO()
plt.savefig(f, format="svg")
# --- Add interactivity ---
# Create XML tree from the SVG file.
tree, xmlid = ET.XMLID(f.getvalue())
tree.set('onload', 'init(evt)')
for i, y in enumerate(years):
# Hide the tooltips
tooltip = xmlid['mytooltip_%d' % i]
tooltip.set('visibility', 'hidden')
# Assign onmouseover and onmouseout callbacks to patches.
mypatch = xmlid['mypatch_%d' % i]
mypatch.set('onmouseover', "ShowTooltip(this)")
mypatch.set('onmouseout', "HideTooltip(this)")
# This is the script defining the ShowTooltip and HideTooltip functions.
script = """
<script type="text/ecmascript">
<![CDATA[
function init(evt) {
if ( window.svgDocument == null ) {
svgDocument = evt.target.ownerDocument;
}
}
function ShowTooltip(obj) {
var cur = obj.id.slice(-1);
var tip = svgDocument.getElementById('mytooltip_' + cur);
tip.setAttribute('visibility',"visible")
}
function HideTooltip(obj) {
var cur = obj.id.slice(-1);
var tip = svgDocument.getElementById('mytooltip_' + cur);
tip.setAttribute('visibility',"hidden")
}
]]>
</script>
"""
# Insert the script at the top of the file and save it.
tree.insert(0, ET.XML(script))
ET.ElementTree(tree).write('svg_tooltip_1.svg')
Expected outcome
- The fourth patch behaves as expected.
Matplotlib version
- [Python 2.7.10, Matplotlib 1.3.1]
- Installed with Mac OS X.