6

I am working on plot with two axes which supports picking the lines. I am using matplotlib and the the twinx() command. Unfortunately the pick event is only called for the artists in the top-most axes (see example below).

import matplotlib.pyplot as plt
import numpy as np

def onPick(event):
    print(event.artist.get_label())
    
def pick():
    fig=plt.figure(figsize=(5, 4), dpi=100, tight_layout=True)
    axis_1=fig.add_subplot(111)
    axis_2=axis_1.twinx()

    axis_1.set_ylim(0, 10)
    axis_2.set_ylim(0, 10)

    x=np.array([1,2,3,4])
    y_1=np.array([1,1,1,1])
    y_2=y_1+4
    
    axis_1.plot(x, y_1, picker=5, label='line_1')
    axis_2.plot(x, y_2, picker=5, label='line_2')
    
    fig.canvas.mpl_connect('pick_event', onPick)
    plt.show()

if __name__=='__main__':
    pick()

Is there a way to pick the lines from the axis below?

3
  • 1
    Does this answer your question? Matplotlib picker event on secondary y-axis Commented Jul 5, 2020 at 12:40
  • I know this posting. I am searching for a solution in general. Not related to twinx(). How to realize the pick event with two axes? Commented Jul 6, 2020 at 18:16
  • Well, it is impossible Commented Jul 6, 2020 at 18:53

2 Answers 2

2

Impossible. ^^ I found a solution. I do not pick from the axes, I pick from the legend.
I think this is a good compromise.

import matplotlib.pyplot as plt
import numpy as np
from numpy.random import rand


def onpick(event):
        print(event.artist.get_label())


if __name__ == '__main__':
    t=np.linspace(1, 10, 100)
    y1, y2=1*t, 2*t
    y3, y4=3*t, 4*t

    fig, ax1=plt.subplots()
    ax2=ax1.twinx()
    ax2._get_lines.prop_cycler = ax1._get_lines.prop_cycler # Send Color cycle state to second axis.
    
    line1, = ax1.plot(t, y1, lw=2, label='1 HZ')
    line2, = ax1.plot(t, y2, lw=2, label='2 HZ')
    line3, = ax2.plot(t, y3, lw=2, label='3 HZ')
    line4, = ax2.plot(t, y4, lw=2, label='4 HZ')
    
    leg=ax1.legend(handles=[line1, line2, line3, line4], bbox_to_anchor=(0,1.02,1,0.2), loc="lower left", mode="expand", borderaxespad=0, ncol=3)
    for line in leg.get_lines(): line.set_picker(5)
    
    fig.canvas.mpl_connect('pick_event', onpick)
    plt.show()
Sign up to request clarification or add additional context in comments.

2 Comments

This does work. But for some reason if I overlay the bbox_to_anchor over the chart, instead of above it, then it does not work 😄 change to bbox_to_anchor=(0,0.95,1,0.2) and you will notice that the 2Hz one dos not trigger the event
Found a workaround. Insted of ax1.legend() I used plt.legend()
1

Not impossible! But instead of using pick_event you must handle the button_press_event and determine for yourself what was picked, by looking through each plotted line to find the MouseEvent. Each axes also needs its own annotation, or you'll have a hard time finding the correct annotation position.

Here's what I did, inspired by this answer:

from dataclasses import dataclass
from matplotlib.backend_bases import PickEvent
import matplotlib.pyplot as plt
import matplotlib

def on_button_press(event):
    if not event.inaxes:
        return # off canvas.

    all_picked = [dca for dca in dcAxes if dca.line.contains(event)[0]]
    if not all_picked:
        return # nothing was picked

    picked = all_picked[0] # take the first

    ind = picked.line.contains(event)[1]['ind']
    x_index = ind[0]

    x_val = picked.line.get_xdata()[x_index]
    y_val = picked.line.get_ydata()[x_index]

    annotation_visbility = picked.annotation.get_visible()
    if annotation_visbility and picked.annotation.xy==(x_val,y_val):
        picked.annotation.set_visible(False)
        fig.canvas.draw_idle()
    else:
        picked.annotation.xy = (x_val,y_val)

        text_label = f'{picked.line.get_label()}:({x_val},{y_val})'
        picked.annotation.set_text(text_label)

        picked.annotation.set_visible(True)
        fig.canvas.draw_idle()

 # create data to plot
x = []
y = []
y.append([])
y.append([])
for i in range(10):
    x.append(i)
    y[0].append(i)
    y[1].append(i*i)

# create plots, saving axes/line/annotation for lookup
@dataclass
class DataClassAxes:
    ax: plt.axes
    line: matplotlib.lines.Line2D
    annotation: matplotlib.text.Annotation

dcAxes: list[DataClassAxes] = []

for i in range(2):
    if i==0:
        fig, ax = plt.subplots()
        line, = ax.plot(x, y[i], 'o', picker=5, color='red', label='reds')
    else:
        ax = dcAxes[0].ax.twinx()
        line, = ax.plot(x, y[i], 'o', picker=5, color='blue', label='blues')

    annotation = ax.annotate(
        text='',
        xy=(0, 0),
        xytext=(15, 15), # distance from x, y
        textcoords='offset points',
        bbox={'boxstyle': 'round', 'fc': 'w'},
        arrowprops={'arrowstyle': '->'}
    )
    annotation.set_visible(False)

    dcAxes.append(DataClassAxes(ax, line, annotation))

fig.canvas.mpl_connect('button_press_event', on_button_press)

plt.show()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.