Wednesday, 25 December 2019

matplotlib mouse click event draw line

left click to set 1 point of a line, the second point follows the mouse position, 
line is floating

left click again to complete the line

left click again to start a new line, line is floating

left click again to complete second line

draw third line

right click to delete 3rd line end point, line is floating

left click to set end point of 3rd line again, line is fixed

right click twice to delete 3rd line

delete second line

keep right click, original graph is intact

from matplotlib import pyplot as plt
import numpy as np
import datetime as dt
import pandas_datareader.data as web

class LineBuilder(object):
    def __init__(self, fig, ax):
        self.xs = []
        self.ys = []
        self.ax = ax
        self.fig = fig

    def mouse_click(self, event):
        print('click', event)
        if not event.inaxes:
            return
        #left click
        if event.button == 1:
            self.xs.append(event.xdata)
            self.ys.append(event.ydata)
            #add a line to plot if it has 2 points
            if len(self.xs) % 2 == 0:
                line, = self.ax.plot([self.xs[-2], self.xs[-1]], [self.ys[-2], self.ys[-1]], 'r')
                line.figure.canvas.draw()

        #right click
        if event.button == 3:
            if len(self.xs) > 0:
                self.xs.pop()
                self.ys.pop()
            #delete last line drawn if the line is missing a point,
            #never delete the original stock plot
            if len(self.xs) % 2 == 1 and len(self.ax.lines) > 1:
                self.ax.lines.pop()
            #refresh plot
            self.fig.canvas.draw()

    def mouse_move(self, event):
        if not event.inaxes:
            return
        #dtaw a temporary line from a single point to the mouse position
        #delete the temporary line when mouse move to another position
        if len(self.xs) % 2 == 1:
            line, =self.ax.plot([self.xs[-1], event.xdata], [self.ys[-1], event.ydata], 'r')
            line.figure.canvas.draw()
            self.ax.lines.pop()

start = dt.datetime(2014, 1, 1)
end = dt.datetime(2019, 10, 30)
ticker = 'BNS'

df = web.DataReader(ticker, 'yahoo', start, end)
x = df['Adj Close'].index
y = df['Adj Close'].values
#number index is need for the cursor position, xaxis is mapped with integer first and converted to date later
x_num_index = np.arange(0, len(x), 1)

fig, ax = plt.subplots()
ax.plot(x_num_index, y)

draw_line = LineBuilder(fig, ax)
fig.canvas.mpl_connect('button_press_event', draw_line.mouse_click)
fig.canvas.mpl_connect('motion_notify_event', draw_line.mouse_move)

#get default # of lables on x axis
n = len(ax.get_xticklabels())
print(n)

x_format = x.strftime('%b %d %Y')
xlabels = [x_format[int(i*len(x)/n)] for i in range(0, n)]
ax.set_xticklabels(xlabels)

ax.set_ylim([np.min(y), np.max(y)])
ax.set_title('Closing Price of %s Click to Draw Lines' %(ticker))

plt.gcf().autofmt_xdate()
plt.show()

---------------------------------------
#logs

click button_press_event: xy=(243, 448) xydata=(129.8095001772421, 49.733963975081586) button=1 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(447, 161) xydata=(522.8799401263872, 30.376382342657486) button=1 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(700, 599) xydata=(1009.1782920476983, 59.91861493848242) button=1 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(447, 234) xydata=(529.4506930074507, 35.30008777529497) button=1 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(720, 549) xydata=(1060.785741080495, 56.54621395722387) button=1 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(924, 480) xydata=(1457.8273154647482, 51.892300603087065) button=1 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(837, 426) xydata=(1288.5007616832286, 48.25010754332782) button=3 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(856, 520) xydata=(1566.6027451122914, 54.59022138809391) button=1 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(756, 495) xydata=(1336.569036952411, 52.904020897464626) button=3 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(610, 494) xydata=(1000.7198230389853, 52.83657287783946) button=3 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(611, 249) xydata=(1003.0201601205841, 36.31180806967254) button=3 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(611, 249) xydata=(1003.0201601205841, 36.31180806967254) button=3 dblclick=True inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(670, 446) xydata=(1138.7400479349135, 49.59906793583124) button=3 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(650, 438) xydata=(1092.7333063029378, 49.05948377882987) button=3 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)
click button_press_event: xy=(651, 438) xydata=(1095.0336433845364, 49.05948377882987) button=3 dblclick=False inaxes=AxesSubplot(0.125,0.2;0.775x0.68)

reference:
https://stackoverflow.com/questions/33569626/matplotlib-responding-to-click-events
http://chuanshuoge2.blogspot.com/2019/12/matplotlib-cursor.html
https://matplotlib.org/3.1.1/users/event_handling.html
https://stackoverflow.com/questions/4981815/how-to-remove-lines-in-a-matplotlib-plot
https://stackoverflow.com/questions/4098131/how-to-update-a-plot-in-matplotlib

3 comments:

  1. Hi! Great job!

    Is there any way how to record coordintates of red lines, in order to plot them later with code?

    ReplyDelete
  2. hey man is there any way to speed this up by using blit or anything like that? This was incredibly helpful thanks a lot.

    ReplyDelete