Friday 27 December 2019

matplotlib mouse scroll event zoom

original graph year 2014 - 2019

scroll mouse to zoom in year 2016 - 2019
xaxis date updates with zoom

scroll to zoom out, xaxis is collapsed

zoom in again, xaxis expands again

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

class ZoomClass(object):
    def __init__(self, base_scale, tick_num, x, y):
        self.base_scale = base_scale
        self.tick_num = tick_num
        self.x = x
        self.y = y

    def zoom_event(self, event):
        if not event.inaxes:
            return
        # get the current x and y limits
        cur_xlim = ax.get_xlim()
        cur_ylim = ax.get_ylim()

        xdata = event.xdata  # get event x location
        ydata = event.ydata  # get event y location

        if event.button == 'up':
            # zoom in
            scale_factor =  self.base_scale
        elif event.button == 'down':
            # zoom out
            scale_factor = 1 / self.base_scale
        else:
            scale_factor = 1
        print(event.button)

        # set new boundary, center is mouse position 
        ax.set_xlim([xdata - (xdata-cur_xlim[0]) / scale_factor, xdata + (cur_xlim[1]-xdata) / scale_factor])
        ax.set_ylim([ydata - (ydata-cur_ylim[0]) / scale_factor, ydata + (cur_ylim[1]-ydata) / scale_factor])
        # get the current x limits again
        cur_xlim = ax.get_xlim()
        #update xaxis date range, numbers on axis updates automatically, dates need update manually
        xticks = []
        for i in range(0, self.tick_num):
            value = int(cur_xlim[0]) + int(i * (cur_xlim[1] - cur_xlim[0]) / self.tick_num)
            # take care of values beyond x limits
            if value < 0:
                value = 0
            if value > np.max(self.x):
                value = np.max(self.x)
            xticks.append(value)
        print(xticks)

        xlabels = [x_format[xticks[i]] for i in range(0, self.tick_num)]
        ax.set_xticks(xticks)
        ax.set_xticklabels(xlabels)
        # refresh plot
        fig.canvas.draw()

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()
plot1 = ax.plot(x_num_index, y)

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

zoom_view = ZoomClass(1.2, n, x_num_index, y)
fig.canvas.mpl_connect('scroll_event', zoom_view.zoom_event)

x_format = x.strftime('%b %d %Y')
xticks = [int(i*len(x)/n) for i in range(0, n)]
xlabels = [x_format[int(i*len(x)/n)] for i in range(0, n)]
ax.set_xticks(xticks)
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.grid(True)
plt.show()

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

up
[14, 148, 283, 417, 552, 686, 821, 955, 1090, 1225]
down
[0, 88, 250, 411, 573, 734, 896, 1057, 1219, 1380]
down
[0, 56, 250, 444, 638, 832, 1025, 1219, 1413, 1468]
up
[0, 154, 316, 477, 639, 800, 962, 1123, 1285, 1446]
up
[157, 291, 426, 560, 695, 830, 964, 1099, 1233, 1368]
up
[264, 376, 488, 600, 712, 824, 937, 1049, 1161, 1273]
up
[353, 446, 540, 633, 727, 820, 914, 1007, 1101, 1194]
up
[427, 505, 583, 661, 739, 817, 895, 973, 1051, 1129]
down
[324, 417, 511, 605, 698, 792, 886, 980, 1073, 1167]
down
[201, 313, 426, 538, 651, 763, 876, 989, 1101, 1214]
down
[53, 188, 323, 458, 593, 728, 863, 998, 1134, 1269]
up
[200, 312, 425, 538, 650, 763, 876, 988, 1101, 1214]
down
[26, 161, 296, 431, 567, 702, 837, 972, 1108, 1243]
...
up
[545, 639, 734, 828, 923, 1018, 1112, 1207, 1301, 1396]
up
[613, 691, 770, 849, 928, 1007, 1086, 1165, 1244, 1323]
up
[670, 735, 801, 867, 933, 999, 1065, 1130, 1196, 1262]
down
[612, 691, 770, 849, 928, 1007, 1086, 1165, 1244, 1323]
down
[542, 636, 731, 826, 921, 1016, 1111, 1206, 1301, 1396]
down
[500, 614, 728, 842, 956, 1070, 1184, 1298, 1412, 1468]
up
[573, 668, 763, 858, 953, 1048, 1143, 1238, 1333, 1428]
up
[637, 716, 795, 874, 953, 1033, 1112, 1191, 1270, 1349]
down
[572, 667, 762, 857, 952, 1047, 1142, 1238, 1333, 1428]
up
[666, 745, 824, 903, 983, 1062, 1141, 1221, 1300, 1379]
down
[566, 661, 756, 851, 947, 1042, 1137, 1232, 1328, 1423]
down
[446, 560, 674, 789, 903, 1018, 1132, 1246, 1361, 1468]
down
[286, 423, 560, 698, 835, 973, 1110, 1247, 1385, 1468]
down
[94, 258, 423, 588, 753, 918, 1083, 1248, 1413, 1468]
down
[0, 61, 259, 457, 655, 853, 1051, 1249, 1447, 1468]
down
[0, 0, 62, 300, 537, 775, 1012, 1250, 1468, 1468]
down
[0, 0, 0, 192, 477, 762, 1047, 1332, 1468, 1468]
down
[0, 0, 0, 60, 402, 744, 1086, 1428, 1468, 1468]
down
[0, 0, 0, 0, 312, 722, 1133, 1468, 1468, 1468]
down
[0, 0, 0, 0, 181, 674, 1166, 1468, 1468, 1468]
up
[0, 0, 0, 0, 289, 699, 1110, 1468, 1468, 1468]
up
[0, 0, 0, 35, 377, 719, 1061, 1403, 1468, 1468]
up
[0, 0, 0, 166, 451, 736, 1021, 1306, 1468, 1468]
up
[0, 0, 37, 275, 512, 750, 987, 1225, 1462, 1468]
up
[0, 0, 168, 366, 564, 762, 960, 1158, 1356, 1468]
up
[0, 97, 262, 427, 592, 757, 922, 1087, 1252, 1417]
down
[0, 0, 154, 352, 550, 748, 946, 1144, 1342, 1468]
up
[0, 84, 249, 414, 579, 744, 909, 1074, 1239, 1404]

reference:
https://stackoverflow.com/questions/11551049/matplotlib-plot-zooming-with-scroll-wheel
https://stackoverflow.com/questions/26131607/matplotlib-get-ylim-values
https://matplotlib.org/3.1.1/gallery/showcase/integral.html#sphx-glr-gallery-showcase-integral-py

No comments:

Post a Comment