Skip to content

Matplotlib User Interface Example Breaks With More Than Three Patches #8316

Closed
@DaveL17

Description

@DaveL17

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions