|
| 1 | +""" |
| 2 | +============================== |
| 3 | +3D ploting using PyQt5 for GUI |
| 4 | +============================== |
| 5 | +
|
| 6 | +Brief: |
| 7 | +------ |
| 8 | + A **matplotlib** widget displays a 3D graph inside |
| 9 | + a **PyQt5** widget window. |
| 10 | +
|
| 11 | +Important |
| 12 | +--------- |
| 13 | + This project is published under **MIT License** |
| 14 | +""" |
| 15 | + |
| 16 | +import argparse |
| 17 | +import sys |
| 18 | + |
| 19 | +import numpy as np |
| 20 | + |
| 21 | +from matplotlib import cm |
| 22 | +from matplotlib.pyplot import figure |
| 23 | +from matplotlib.ticker import LinearLocator, FormatStrFormatter |
| 24 | +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas |
| 25 | +from mpl_toolkits.mplot3d import Axes3D |
| 26 | + |
| 27 | +from PyQt5.QtCore import Qt, pyqtSlot |
| 28 | +from PyQt5.QtWidgets import QApplication as Application, QWidget as Widget, QPushButton as Button |
| 29 | +from PyQt5.QtWidgets import QLabel as Label, QGridLayout, QDesktopWidget |
| 30 | + |
| 31 | +import print_string_colors as COLOUR |
| 32 | + |
| 33 | + |
| 34 | +# |
| 35 | +# Following functions are used to generate nice looking plots |
| 36 | +# |
| 37 | +def f(x, y): # For Generating Z coordinates |
| 38 | + return np.sin(np.sqrt(x ** 2 + y ** 2)) |
| 39 | + |
| 40 | + |
| 41 | +def g(x, y): # For Generating Z coordinates (alternative) |
| 42 | + return np.sin(x) + np.cos(y) |
| 43 | + |
| 44 | + |
| 45 | +# |
| 46 | +# Following class code partly taken from 'StackOverflow' |
| 47 | +# |
| 48 | +class ThreeDSurfaceGraphWindow(FigureCanvas): # Class for 3D window |
| 49 | + def __init__(self): |
| 50 | + self.plot_colorbar = None |
| 51 | + self.plot_figure = figure(figsize=(7, 7)) |
| 52 | + FigureCanvas.__init__(self, self.plot_figure) # creating FigureCanvas |
| 53 | + self.axes = self.plot_figure.gca(projection='3d') # generates 3D Axes object |
| 54 | + self.setWindowTitle("figure") # sets Window title |
| 55 | + |
| 56 | + def draw_graph(self, x, y, z): # Function for graph plotting |
| 57 | + self.axes.clear() |
| 58 | + if self.plot_colorbar is not None: # avoids adding one more colorbar at each draw operation |
| 59 | + self.plot_colorbar.remove() |
| 60 | + # plots the 3D surface plot |
| 61 | + plot_stuff = self.axes.plot_surface(x, y, z, |
| 62 | + cmap=cm.coolwarm, linewidth=0, antialiased=False) |
| 63 | + self.axes.zaxis.set_major_locator(LinearLocator(10)) |
| 64 | + self.axes.zaxis.set_major_formatter(FormatStrFormatter('%.02f')) |
| 65 | + # Add a color bar which maps values to colors. |
| 66 | + self.plot_colorbar = self.plot_figure.colorbar(plot_stuff, shrink=0.5, aspect=5) |
| 67 | + # draw plot |
| 68 | + self.draw() |
| 69 | + |
| 70 | + |
| 71 | +class ProgramGUI(Widget): |
| 72 | + |
| 73 | + def __init__(self): |
| 74 | + super().__init__() |
| 75 | + # GUI window specific values |
| 76 | + self.title = '\'MatPlotLib\' test program' |
| 77 | + self.left = 10 |
| 78 | + self.top = 10 |
| 79 | + self.width = 640 |
| 80 | + self.height = 480 |
| 81 | + # Other object specific values |
| 82 | + self.label_str = "This program will test the \'MatPlotLib\' through a Qt widget display." |
| 83 | + self.plot_status = u'a' |
| 84 | + self.X_plot_val = None |
| 85 | + self.Y_plot_val = None |
| 86 | + self.Z_plot_val = None |
| 87 | + |
| 88 | + # Call argument parsing to enable/disable debug options |
| 89 | + dbg_parse = argparse.ArgumentParser() |
| 90 | + dbg_parse.add_argument(u'-d', |
| 91 | + u'--debug', |
| 92 | + action='store_true', |
| 93 | + help=u'Enable DEBUG specific functions') |
| 94 | + self.argh = dbg_parse.parse_args() |
| 95 | + |
| 96 | + # Debug output |
| 97 | + if self.argh.debug is True: |
| 98 | + print(COLOUR.STRONG_BLUE + |
| 99 | + "====================\n" + |
| 100 | + "==== DEBUG MODE ====\n" + |
| 101 | + "====================" + |
| 102 | + COLOUR.NORMAL) |
| 103 | + |
| 104 | + # initialize UI |
| 105 | + self.init_ui() |
| 106 | + |
| 107 | + def init_ui(self): |
| 108 | + # |
| 109 | + # Setup Window Title and geometry |
| 110 | + # |
| 111 | + self.setWindowTitle(self.title) |
| 112 | + self.setGeometry(self.left, self.top, self.width, self.height) |
| 113 | + self.center() |
| 114 | + # |
| 115 | + # Setup User message |
| 116 | + # |
| 117 | + self.label = Label(self.label_str) |
| 118 | + # |
| 119 | + # Setup "Program" button |
| 120 | + # |
| 121 | + self.test_graph_button = Button(u'Update graph', self) |
| 122 | + self.test_graph_button.setToolTip(u'Call update function to change graph') |
| 123 | + self.test_graph_button.clicked.connect(self.test_std_out) |
| 124 | + # |
| 125 | + # Setup "Plot" object |
| 126 | + # |
| 127 | + self.plot_container = ThreeDSurfaceGraphWindow() # creating 3D Window |
| 128 | + # |
| 129 | + # Setup grid layout for global window |
| 130 | + # |
| 131 | + main_layout = QGridLayout() # Layout for Main Tab Widget |
| 132 | + main_layout.setRowMinimumHeight(0, 5) # setting layout parameters |
| 133 | + main_layout.setRowMinimumHeight(2, 10) |
| 134 | + main_layout.setRowMinimumHeight(4, 5) |
| 135 | + main_layout.addWidget(self.label, 1, 1, Qt.AlignHCenter) |
| 136 | + main_layout.addWidget(self.test_graph_button, 2, 1) |
| 137 | + main_layout.addWidget(self.plot_container, 3, 1) # add 3D Window to Main layout |
| 138 | + self.setLayout(main_layout) # sets Main layout |
| 139 | + # |
| 140 | + # calculate 3D sin function |
| 141 | + # |
| 142 | + self.test_std_out() |
| 143 | + # |
| 144 | + # Special setup when '--debug' is passed as an argument |
| 145 | + # |
| 146 | + if self.argh.debug is True: |
| 147 | + # tell user that all debug Widgets are setup |
| 148 | + print(COLOUR.YELLOW + |
| 149 | + "Debug Wigets & objects setup done" + |
| 150 | + COLOUR.NORMAL) |
| 151 | + |
| 152 | + def center(self): |
| 153 | + """centers the window on the screen""" |
| 154 | + screen = QDesktopWidget().screenGeometry() |
| 155 | + size = self.geometry() |
| 156 | + self.move((screen.width() - size.width()) / 2, |
| 157 | + (screen.height() - size.height()) / 2) |
| 158 | + |
| 159 | + @pyqtSlot() |
| 160 | + def test_std_out(self): |
| 161 | + # Make plot data |
| 162 | + if self.plot_status == u'a': |
| 163 | + self.X_plot_val = np.arange(-10, 10, 0.25) # X coordinates |
| 164 | + self.Y_plot_val = np.arange(-10, 10, 0.25) # Y coordinates |
| 165 | + # Forming MeshGrid |
| 166 | + self.X_plot_val, self.Y_plot_val = np.meshgrid(self.X_plot_val, self.Y_plot_val) |
| 167 | + self.Z_plot_val = g(self.X_plot_val, self.Y_plot_val) |
| 168 | + self.plot_status = u'b' |
| 169 | + else: |
| 170 | + self.X_plot_val = np.arange(-5, 5, 0.25) # X coordinates |
| 171 | + self.Y_plot_val = np.arange(-5, 5, 0.25) # Y coordinates |
| 172 | + # Forming MeshGrid |
| 173 | + self.X_plot_val, self.Y_plot_val = np.meshgrid(self.X_plot_val, self.Y_plot_val) |
| 174 | + self.Z_plot_val = f(self.X_plot_val, self.Y_plot_val) |
| 175 | + self.plot_status = u'a' |
| 176 | + # call plot for tests |
| 177 | + self.plot_container.draw_graph(self.X_plot_val, self.Y_plot_val, self.Z_plot_val) |
| 178 | + |
| 179 | + |
| 180 | +if __name__ == '__main__': |
| 181 | + app = Application(sys.argv) |
| 182 | + gui = ProgramGUI() |
| 183 | + |
| 184 | + qr = gui.frameGeometry() |
| 185 | + cp = QDesktopWidget().availableGeometry().center() |
| 186 | + qr.moveCenter(cp) |
| 187 | + gui.move(qr.topLeft()) |
| 188 | + app.processEvents() |
| 189 | + |
| 190 | + gui.show() |
| 191 | + |
| 192 | + exit_val = app.exec_() |
| 193 | + |
| 194 | + # behaviour to trigger on exit |
| 195 | + sys.exit(exit_val) |
0 commit comments