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
Hi! Great job!
ReplyDeleteIs there any way how to record coordintates of red lines, in order to plot them later with code?
coordinates from xydata. Got it.
Deletehey man is there any way to speed this up by using blit or anything like that? This was incredibly helpful thanks a lot.
ReplyDelete