├── .DS_Store ├── .gitattributes ├── .vscode └── settings.json ├── README.md ├── autoGit.sh ├── autoPIP.sh ├── calls heat map.png ├── out.png ├── puts heat map.png └── src ├── GUI.py ├── __pycache__ ├── GUI.cpython-37.pyc ├── GUI.cpython-38.pyc └── scrolly.cpython-37.pyc ├── main.py └── scrolly.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamPom100/OptionsAnalyzer/4269476a1a86657ecb95cf7d8a56330a30504107/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/usr/bin/python3" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OptionsAnalyzer (BETA VERSION) 2 | 3 | ************************************** 4 | 5 |

6 | If you encounter a ZSH segmentation fault OR want to try the updated version
7 | click here 8 |

9 | 10 | ************************************** 11 | 12 | * Run autoPIP.sh by typing sh autoPIP.sh in terminal in your IDE to install all required modules 13 | 14 | ## How to use the text menu 15 | *Commands are: setticker, setstrike, optionchain, OIimage, CallsVolumeMap, CallsOIMap, PutsVolumeMap, PutsOIMap, CallVolume3D, CallOI3D, PutVolume3D, PutOI3D, exit* 16 | * Start with *setticker* and enter a desired stock (ex: AMD) 17 | * Next, enter *setstrike* to pick a desired date (enables further methods like OIimage and optionchain) 18 | * After these steps have been completed in order, you're free to load a heatmap or 3D bar graph of the option's open interest or volume for every date and strike, as well as a PNG of open interest / volume on a certain date 19 | 20 | ### Open Interest 3D Graph 21 | ![oi3d](https://user-images.githubusercontent.com/28206070/79798601-dc823600-831e-11ea-90da-963d36b70dbd.png) 22 | 23 | ### Open Interest Heatmap 24 | ![oiheatmap](https://user-images.githubusercontent.com/28206070/79798617-e1df8080-831e-11ea-81fa-96e3599fc783.png) 25 | 26 | 27 | ### Volume 3D Graph 28 | ![volume3d](https://user-images.githubusercontent.com/28206070/79798608-df7d2680-831e-11ea-939d-3bd367bd837a.png) 29 | 30 | 31 | ### Interest at each strike on a date 32 | ![out](https://user-images.githubusercontent.com/28206070/79798626-e3a94400-831e-11ea-9676-15ac72ab217d.png) 33 | 34 | ### Text representation of Open Interest for each date and strike 35 | oiframe 36 | 37 | 38 | ### Text option chain 39 | Screen Shot 2020-04-20 at 3 52 48 PM 40 | -------------------------------------------------------------------------------- /autoGit.sh: -------------------------------------------------------------------------------- 1 | git add . 2 | git commit -m "AutoGIT Commit" 3 | git push 4 | -------------------------------------------------------------------------------- /autoPIP.sh: -------------------------------------------------------------------------------- 1 | pip3 install seaborn 2 | pip3 install matplotlib 3 | pip3 install yfinance 4 | pip3 install numpy 5 | pip3 install pandas 6 | pip3 install Image 7 | pip3 install wx 8 | pip3 install PyQt5 9 | -------------------------------------------------------------------------------- /calls heat map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamPom100/OptionsAnalyzer/4269476a1a86657ecb95cf7d8a56330a30504107/calls heat map.png -------------------------------------------------------------------------------- /out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamPom100/OptionsAnalyzer/4269476a1a86657ecb95cf7d8a56330a30504107/out.png -------------------------------------------------------------------------------- /puts heat map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamPom100/OptionsAnalyzer/4269476a1a86657ecb95cf7d8a56330a30504107/puts heat map.png -------------------------------------------------------------------------------- /src/GUI.py: -------------------------------------------------------------------------------- 1 | import wx 2 | 3 | choice = "test" 4 | 5 | 6 | def onButton(): 7 | print("Button pressed.") 8 | 9 | app = wx.App() 10 | frame = wx.Frame(None, -1, 'win.py') 11 | frame.SetDimensions(0, 0, 200, 50) 12 | dlg = wx.TextEntryDialog(frame, 'Enter a Stock Ticker', 'Text Entry') 13 | dlg.SetValue("AMD") 14 | if dlg.ShowModal() == wx.ID_OK: 15 | # print('Ticker entered was: %s\n' % dlg.GetValue()) 16 | return dlg.GetValue() 17 | dlg.Destroy() 18 | 19 | 20 | def pickStrikePrice(choices): 21 | 22 | class MyFrame(wx.Frame): 23 | def __init__(self, parent, title): 24 | super(MyFrame, self).__init__(parent, title=title, size=(400, 200)) 25 | self.panel = MyPanel(self) 26 | 27 | class MyPanel(wx.Panel): 28 | def __init__(self, parent): 29 | super(MyPanel, self).__init__(parent) 30 | self.label = wx.StaticText( 31 | self, label="Pick a Strike Date:", pos=(50, 30)) 32 | languages = choices 33 | self.combobox = wx.ComboBox(self, choices=languages, pos=(50, 50)) 34 | self.label2 = wx.StaticText(self, label="", pos=(50, 80)) 35 | self.Bind(wx.EVT_COMBOBOX, self.OnCombo) 36 | 37 | def OnCombo(self, event): 38 | self.label2.SetLabel("You picked " + self.combobox.GetValue()) 39 | global choice 40 | choice = self.combobox.GetValue() 41 | app.ExitMainLoop() 42 | 43 | class MyApp(wx.App): 44 | def OnInit(self): 45 | self.frame = MyFrame(parent=None, title="Strike Picker") 46 | self.frame.Show() 47 | return True 48 | 49 | app = MyApp() 50 | app.MainLoop() 51 | 52 | 53 | def returnChoice(): 54 | global choice 55 | return choice 56 | -------------------------------------------------------------------------------- /src/__pycache__/GUI.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamPom100/OptionsAnalyzer/4269476a1a86657ecb95cf7d8a56330a30504107/src/__pycache__/GUI.cpython-37.pyc -------------------------------------------------------------------------------- /src/__pycache__/GUI.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamPom100/OptionsAnalyzer/4269476a1a86657ecb95cf7d8a56330a30504107/src/__pycache__/GUI.cpython-38.pyc -------------------------------------------------------------------------------- /src/__pycache__/scrolly.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamPom100/OptionsAnalyzer/4269476a1a86657ecb95cf7d8a56330a30504107/src/__pycache__/scrolly.cpython-37.pyc -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | from mpl_toolkits.mplot3d import Axes3D 2 | import yfinance as yf 3 | import pandas as pd 4 | import matplotlib.pyplot as plt 5 | from matplotlib import cm 6 | from PIL import Image 7 | from GUI import * 8 | import seaborn as sb 9 | import numpy as np 10 | import sys 11 | 12 | 13 | # pre-set values 14 | 15 | ticker = "AMD" 16 | DateArray = yf.Ticker(ticker).options 17 | strikeChoice = DateArray[2] 18 | opt = yf.Ticker(ticker).option_chain(strikeChoice) 19 | calls = opt.calls 20 | puts = opt.puts 21 | ArrayStore = None 22 | 23 | 24 | def askForTicker(): 25 | # Prompt the user for a new ticker 26 | global ticker, strikeChoice, opt 27 | ticker = onButton() 28 | print("Ticker was: " + ticker) 29 | # pickAStrike2() # <----- causes ZSH error (toDO) 30 | 31 | 32 | def getOptionsChain(inputString): 33 | # Retrieve the Options Chain Expiration dates from Yahoo Finance 34 | YFticker = yf.Ticker(inputString) 35 | global DateArray 36 | tempTuple = ("Pick a Strike Price",) 37 | DateArray = tempTuple+YFticker.options 38 | 39 | 40 | def pickAStrike(): 41 | global strikeChoice, opt 42 | pickStrikePrice(DateArray) 43 | strikeChoice = returnChoice() 44 | opt = yf.Ticker(ticker).option_chain(strikeChoice) 45 | print("Strike Choice was: " + strikeChoice) 46 | 47 | 48 | def pickAStrike2(): 49 | global strikeChoice, opt 50 | strikeChoice = DateArray[2] 51 | opt = yf.Ticker(ticker).option_chain(strikeChoice) 52 | print("Strike Choice was: " + strikeChoice) 53 | 54 | 55 | def sortCallsandPuts(): 56 | global calls, puts, opt 57 | calls = opt.calls 58 | calls = cleaner(calls) 59 | calls['Mid Price'] = calls.apply(lambda row: (row.ask + row.bid)/2, axis=1) 60 | calls = calls.drop(columns=["ask", "bid"]) 61 | calls = calls[["strike", "Mid Price", "openInterest", "volume"]] 62 | 63 | puts = opt.puts 64 | puts = cleaner(puts) 65 | puts['Mid Price'] = puts.apply(lambda row: (row.ask + row.bid)/2, axis=1) 66 | puts = puts.drop(columns=["ask", "bid"]) 67 | puts = puts[["strike", "Mid Price", "openInterest", "volume"]] 68 | 69 | 70 | def cleaner(object): 71 | return object.drop(columns=["contractSymbol", "lastTradeDate", "lastPrice", 72 | "change", "percentChange", "impliedVolatility", "inTheMoney", "contractSize", "currency"]) 73 | 74 | 75 | def heatCleaner(object): 76 | object = object.drop(columns=["contractSymbol", "lastTradeDate", "lastPrice", 77 | "change", "percentChange", "volume", "inTheMoney", "contractSize", "currency", "impliedVolatility", "ask", "bid"]) 78 | object = object[["strike", "openInterest"]] 79 | return object 80 | 81 | 82 | def heatCleanerVOLUME(object): 83 | object = object.drop(columns=["contractSymbol", "lastTradeDate", "lastPrice", 84 | "change", "percentChange", "openInterest", "inTheMoney", "contractSize", "currency", "impliedVolatility", "ask", "bid"]) 85 | object = object[["strike", "volume"]] 86 | return object 87 | 88 | 89 | def getCalls(): 90 | print("****************** Calls *********************") 91 | print(calls) 92 | 93 | 94 | def getPuts(): 95 | print("****************** Puts *********************") 96 | print(puts) 97 | 98 | 99 | def displayOptionsChain(): 100 | print("Length of the Chain: " + str(len(DateArray)) + "\n") 101 | print("\n".join(DateArray)) 102 | 103 | 104 | def displayCleanOptionChain(): 105 | print("****************** Calls ********************** ---[" + ticker.upper( 106 | )+"]--- ******************** Puts ********************* ") 107 | temp = {' ': [""]} 108 | tempPD = pd.DataFrame(data=temp) 109 | merged = pd.concat([calls, tempPD, puts], axis=1) 110 | merged = merged.fillna("") 111 | # merged.style.hide_index() 112 | print(merged.to_string(index=False)) 113 | ############ 114 | 115 | 116 | def OIChart(): 117 | sortCallsandPuts() 118 | callData = calls.drop(columns=['Mid Price', 'volume']) 119 | putData = puts.drop(columns=['Mid Price', 'volume']) 120 | finalFrame = pd.DataFrame(callData) 121 | finalFrame.rename(columns={'openInterest': 'Calls'}, inplace=True) 122 | tempFrame = pd.DataFrame(putData) 123 | 124 | tempFrame.rename(columns={'openInterest': 'Puts'}, inplace=True) 125 | # finalFrame = pd.concat([finalFrame, tempFrame]) 126 | finalFrame = pd.merge(finalFrame, tempFrame, on='strike') 127 | finalFrame.plot.bar(figsize=(20, 8), x="strike", y=["Calls", "Puts"], 128 | title="Open Interest for "+ticker.upper()+" all options at every strike on "+strikeChoice) 129 | 130 | #################### 131 | # fig = plt.figure() 132 | # a = ScrollableWindow(fig) 133 | plt.savefig("out.png") 134 | plt.clf() 135 | img = Image.open('out.png') 136 | img.show() 137 | # plt.show(block=True) 138 | print("***********************") 139 | 140 | # dataframe place NaN with 0 141 | 142 | 143 | def CallsOIMap(): # plt.style.use("dark_background") 144 | callsArray = heatCleaner(opt.calls) 145 | callsArray.rename(columns={'openInterest': DateArray[1]}, inplace=True) 146 | for x in range(2, len(DateArray)-1): 147 | opt2 = yf.Ticker(ticker).option_chain(DateArray[x]) 148 | callsArray2 = heatCleaner(opt2.calls) 149 | callsArray2.rename( 150 | columns={'openInterest': DateArray[x]}, inplace=True) 151 | callsArray = pd.merge(callsArray, callsArray2, on='strike') 152 | callsArray.set_index('strike', inplace=True) 153 | callsArray = callsArray.fillna(0) 154 | print(callsArray) 155 | heat_map = sb.heatmap(callsArray, cmap="Reds", linewidths=0) 156 | global ArrayStore 157 | ArrayStore = callsArray 158 | plt.yticks(rotation=0) 159 | plt.xticks(rotation=50) 160 | plt.gca().invert_yaxis() 161 | plt.show() 162 | plt.clf() 163 | 164 | 165 | def PutsOIMap(): # plt.style.use("dark_background") 166 | callsArray = heatCleaner(opt.puts) 167 | callsArray.rename(columns={'openInterest': DateArray[1]}, inplace=True) 168 | for x in range(2, len(DateArray)-1): 169 | opt2 = yf.Ticker(ticker).option_chain(DateArray[x]) 170 | callsArray2 = heatCleaner(opt2.puts) 171 | callsArray2.rename( 172 | columns={'openInterest': DateArray[x]}, inplace=True) 173 | callsArray = pd.merge(callsArray, callsArray2, on='strike') 174 | callsArray.set_index('strike', inplace=True) 175 | callsArray = callsArray.fillna(0) 176 | print(callsArray) 177 | heat_map = sb.heatmap(callsArray, cmap="Blues", linewidths=0) 178 | global ArrayStore 179 | ArrayStore = callsArray 180 | plt.yticks(rotation=0) 181 | plt.xticks(rotation=50) 182 | plt.gca().invert_yaxis() 183 | plt.show() 184 | plt.clf() 185 | 186 | 187 | def CallsVolumeMap(): 188 | callsArray = heatCleanerVOLUME(opt.calls) 189 | callsArray.rename(columns={'volume': DateArray[1]}, inplace=True) 190 | for x in range(2, len(DateArray)-1): 191 | opt2 = yf.Ticker(ticker).option_chain(DateArray[x]) 192 | callsArray2 = heatCleanerVOLUME(opt2.calls) 193 | callsArray2.rename( 194 | columns={'volume': DateArray[x]}, inplace=True) 195 | callsArray = pd.merge(callsArray, callsArray2, on='strike') 196 | callsArray.set_index('strike', inplace=True) 197 | callsArray = callsArray.fillna(0) 198 | print(callsArray) 199 | heat_map = sb.heatmap(callsArray, cmap="Reds", linewidths=0) 200 | global ArrayStore 201 | ArrayStore = callsArray 202 | plt.yticks(rotation=0) 203 | plt.xticks(rotation=50) 204 | plt.gca().invert_yaxis() 205 | plt.show() 206 | plt.clf() 207 | 208 | 209 | def PutsVolumeMap(): 210 | callsArray = heatCleanerVOLUME(opt.puts) 211 | callsArray.rename(columns={'volume': DateArray[1]}, inplace=True) 212 | for x in range(2, len(DateArray)-1): 213 | opt2 = yf.Ticker(ticker).option_chain(DateArray[x]) 214 | callsArray2 = heatCleanerVOLUME(opt2.puts) 215 | callsArray2.rename( 216 | columns={'volume': DateArray[x]}, inplace=True) 217 | callsArray = pd.merge(callsArray, callsArray2, on='strike') 218 | callsArray.set_index('strike', inplace=True) 219 | callsArray = callsArray.fillna(0) 220 | print(callsArray) 221 | heat_map = sb.heatmap(callsArray, cmap="Blues", linewidths=0) 222 | global ArrayStore 223 | ArrayStore = callsArray 224 | plt.yticks(rotation=0) 225 | plt.xticks(rotation=50) 226 | plt.gca().invert_yaxis() 227 | plt.show() 228 | plt.clf() 229 | 230 | 231 | def threedeegraph(object): 232 | 233 | eg = object 234 | # thickness of the bars 235 | dx, dy = .8, .8 236 | # prepare 3d axes 237 | fig = plt.figure(figsize=(10, 6)) 238 | ax = Axes3D(fig) 239 | # set up positions for the bars 240 | xpos = np.arange(eg.shape[0]) 241 | ypos = np.arange(eg.shape[1]) 242 | # set the ticks in the middle of the bars 243 | ax.set_xticks(xpos + dx/2) 244 | ax.set_yticks(ypos + dy/2) 245 | # create meshgrid 246 | # print xpos before and after this block if not clear 247 | xpos, ypos = np.meshgrid(xpos, ypos) 248 | xpos = xpos.flatten() 249 | ypos = ypos.flatten() 250 | # the bars starts from 0 attitude 251 | zpos = np.zeros(eg.shape).flatten() 252 | # the bars' heights 253 | dz = eg.values.ravel(order='F') 254 | # plot and color 255 | values = np.linspace(0.2, 1., xpos.ravel().shape[0]) 256 | colors = cm.rainbow(values) 257 | ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors) 258 | # put the column / index labels 259 | ax.w_yaxis.set_ticklabels(eg.columns) 260 | ax.w_xaxis.set_ticklabels(eg.index) 261 | # name the axes 262 | ax.set_xlabel('Strike') 263 | # ax.set_ylabel('Date') 264 | ax.set_zlabel('Open Interest / Volume') 265 | # fig.colorbar(surf1, ax=ax1, shrink=0.5, aspect=5) 266 | plt.show() 267 | plt.clf() 268 | 269 | 270 | def askForStrikePrice(): 271 | pickStrikePrice(calls['strike'].astype(str).tolist()) 272 | 273 | 274 | def setticker(): 275 | askForTicker() 276 | getOptionsChain(ticker) 277 | 278 | 279 | def optionchainMENU(): 280 | sortCallsandPuts() 281 | displayOptionsChain() 282 | 283 | 284 | def callOI3dmenu(): 285 | CallsOIMap() 286 | threedeegraph(ArrayStore) 287 | 288 | 289 | def putOI3dmenu(): 290 | PutsOIMap() 291 | threedeegraph(ArrayStore) 292 | 293 | 294 | def callVolumemenu(): 295 | CallsVolumeMap() 296 | threedeegraph(ArrayStore) 297 | 298 | 299 | def putVolumemenu(): 300 | PutsVolumeMap() 301 | threedeegraph(ArrayStore) 302 | 303 | 304 | def askForTickerMENU(): 305 | askForTicker() 306 | getOptionsChain(ticker) 307 | pickAStrike2() 308 | 309 | 310 | def mainMENUswitch(): 311 | def switchBoard(arguement): 312 | switcher = { 313 | "setTicker": lambda: askForTickerMENU(), 314 | "setStrike": lambda: pickAStrike(), 315 | "optionChain": lambda: optionchainMENU(), 316 | "OImage": lambda: OIChart(), 317 | "CallVolumeMap": lambda: CallsVolumeMap(), 318 | "CallOIMap": lambda: CallsOIMap(), 319 | "PutVolumeMap": lambda: PutsVolumeMap(), 320 | "PutsOIMap": lambda: PutsOIMap(), 321 | "CallVolume3D": lambda: callVolumemenu(), 322 | "CallOI3D": lambda: callOI3dmenu(), 323 | "PutVolume3D": lambda: putVolumemenu(), 324 | "PutOI3D": lambda: putOI3dmenu(), 325 | "exit": lambda: sys.exit, 326 | } 327 | return switcher.get(arguement, lambda: "error")() 328 | 329 | print("******* \n Welcome to Sam's Option Scanner \n *******") 330 | while(True): 331 | print("PICK ONE: setticker, setstrike, optionchain, OIimage, CallVolumeMap, CallOIMap, PutVolumeMap, PutOIMap, CallVolume3D, CallOI3D, PutVolume3D, PutOI3D, exit") 332 | choice = input() 333 | switchBoard(choice)() 334 | 335 | 336 | def mainMENUnested(): 337 | def repeat(): 338 | print("PICK ONE: setticker, setstrike, optionchain, OIimage, CallsVolumeMap, CallsOIMap, PutsVolumeMap, PutsOIMap, CallVolume3D, CallOI3D, PutVolume3D, PutOI3D, exit") 339 | choice = input() 340 | 341 | if choice == "setticker": 342 | # askForTicker() # get ticker of choice from user 343 | # getOptionsChain(ticker) # get entire option chain from yFinance 344 | askForTickerMENU() 345 | repeat() 346 | elif choice == "setstrike": 347 | pickAStrike() # asks user for specific date 348 | # askForStrikePrice() # prompts user to choose a strike from the table 349 | # displayOptionsChain() #show entire option chain 350 | repeat() 351 | elif choice == "optionchain": 352 | sortCallsandPuts() # breaks options chain into essential data and sorts by calls / puts 353 | displayCleanOptionChain() # displays calls and puts as a clean table 354 | repeat() 355 | elif choice == "OIimage": 356 | OIChart() 357 | repeat() 358 | elif choice == "CallsVolumeMap": 359 | CallsVolumeMap() 360 | repeat() 361 | elif choice == "CallsOIMap": 362 | CallsOIMap() 363 | repeat() 364 | elif choice == "PutsVolumeMap": 365 | PutsVolumeMap() 366 | repeat() 367 | elif choice == "PutsOIMap": 368 | PutsOIMap() 369 | repeat() 370 | elif choice == "CallVolume3D": 371 | CallsVolumeMap() 372 | threedeegraph(ArrayStore) 373 | repeat() 374 | elif choice == "CallOI3D": 375 | CallsOIMap() 376 | threedeegraph(ArrayStore) 377 | repeat() 378 | elif choice == "PutVolume3D": 379 | PutsVolumeMap() 380 | threedeegraph(ArrayStore) 381 | repeat() 382 | elif choice == "PutOI3D": 383 | PutsOIMap() 384 | threedeegraph(ArrayStore) 385 | repeat() 386 | elif choice == "exit": 387 | sys.exit() 388 | else: 389 | print("unexpected choice") 390 | repeat() 391 | 392 | print("******* \n Welcome to Sam's Option Scanner \n *******") 393 | repeat() 394 | 395 | 396 | # mainMENUswitch() 397 | mainMENUnested() 398 | print("All done") 399 | -------------------------------------------------------------------------------- /src/scrolly.py: -------------------------------------------------------------------------------- 1 | from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar 2 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 3 | from PyQt5 import QtWidgets 4 | import matplotlib.pyplot as plt 5 | import matplotlib 6 | # Make sure that we are using QT5 7 | matplotlib.use('Qt5Agg') 8 | 9 | 10 | class ScrollableWindow(QtWidgets.QMainWindow): 11 | def __init__(self, fig): 12 | self.qapp = QtWidgets.QApplication([]) 13 | 14 | QtWidgets.QMainWindow.__init__(self) 15 | self.widget = QtWidgets.QWidget() 16 | self.setCentralWidget(self.widget) 17 | self.widget.setLayout(QtWidgets.QVBoxLayout()) 18 | self.widget.layout().setContentsMargins(0, 0, 0, 0) 19 | self.widget.layout().setSpacing(0) 20 | 21 | self.fig = fig 22 | self.canvas = FigureCanvas(self.fig) 23 | self.canvas.draw() 24 | self.scroll = QtWidgets.QScrollArea(self.widget) 25 | self.scroll.setWidget(self.canvas) 26 | 27 | self.nav = NavigationToolbar(self.canvas, self.widget) 28 | self.widget.layout().addWidget(self.nav) 29 | self.widget.layout().addWidget(self.scroll) 30 | 31 | self.show() 32 | exit(self.qapp.exec_()) 33 | 34 | 35 | def main(): 36 | # create a figure and some subplots 37 | fig, axes = plt.subplots(ncols=4, nrows=5, figsize=(16, 16)) 38 | for ax in axes.flatten(): 39 | ax.plot([2, 3, 5, 1]) 40 | 41 | # pass the figure to the custom window 42 | a = ScrollableWindow(fig) 43 | --------------------------------------------------------------------------------