Buy Me a Coffee? Your support is much appreciated!
PayPal Me: https://www.paypal.me/jiejenn/5
Venmo: @Jie-Jenn
Source Code:
Source: https://doc-snapshots.qt.io/qtforpython-dev/examples/example_external_matplotlib_widget3d.html
import sys
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import axes3d
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import (QApplication, QComboBox, QHBoxLayout,
QHeaderView, QLabel, QMainWindow, QSlider,
QTableWidget, QTableWidgetItem, QVBoxLayout,
QWidget, QAction)
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.window_width, self.window_height = 1700, 800
self.setMinimumSize(self.window_width, self.window_height)
self.setStyleSheet('''
QWidget {
font-size: 25px;
}
''')
# Central widget
self._main = QWidget()
self.setCentralWidget(self._main)
self.createMenuBar()
self.createWidgets()
def createMenuBar(self):
self.menu = self.menuBar()
self.menu_file = self.menu.addMenu("File")
exit = QAction("Exit", self, triggered=app.quit)
self.menu_file.addAction(exit)
self.menu_about = self.menu.addMenu("&About")
about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.StandardKey.HelpContents),
triggered=app.aboutQt)
self.menu_about.addAction(about)
def createWidgets(self):
self.fig = Figure(figsize=(10, 10))
self.canvas = FigureCanvas(self.fig)
# Sliders (Left)
self.slider_azim = QSlider(minimum=0, maximum=360, orientation=Qt.Horizontal)
self.slider_elev = QSlider(minimum=0, maximum=360, orientation=Qt.Horizontal)
self.slider_azim_layout = QHBoxLayout()
self.slider_azim_layout.addWidget(QLabel("{}".format(self.slider_azim.minimum())))
self.slider_azim_layout.addWidget(self.slider_azim)
self.slider_azim_layout.addWidget(QLabel("{}".format(self.slider_azim.maximum())))
self.slider_elev_layout = QHBoxLayout()
self.slider_elev_layout.addWidget(QLabel("{}".format(self.slider_elev.minimum())))
self.slider_elev_layout.addWidget(self.slider_elev)
self.slider_elev_layout.addWidget(QLabel("{}".format(self.slider_elev.maximum())))
# Table (Right)
self.table = QTableWidget()
header = self.table.horizontalHeader()
header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
# ComboBox (Right)
self.combo = QComboBox()
self.combo.addItems(["Wired", "Surface", "Triangular Surface", "Sphere"])
# Main layout
layout = QHBoxLayout(self._main)
# Left layout
llayout = QVBoxLayout()
layout.addLayout(llayout, 70)
# rlayout.setContentsMargins(1, 1, 1, 1)
llayout.addWidget(self.canvas, 88)
llayout.addWidget(QLabel("Azimuth:"), 1)
llayout.addLayout(self.slider_azim_layout, 5)
llayout.addWidget(QLabel("Elevation:"), 1)
llayout.addLayout(self.slider_elev_layout, 5)
# Right layout
rlayout = QVBoxLayout()
layout.addLayout(rlayout, 30)
rlayout.setContentsMargins(1, 1, 1, 1)
rlayout.addWidget(QLabel("Plot type:"))
rlayout.addWidget(self.combo)
rlayout.addWidget(self.table)
# Set up graph
self.plot_wire()
self._ax.view_init(30, 30)
self.slider_azim.setValue(30)
self.slider_elev.setValue(30)
self.fig.canvas.mpl_connect("button_release_event", self.on_click)
# Signal and Slots connections
self.combo.currentTextChanged.connect(self.combo_option)
self.slider_azim.valueChanged.connect(self.rotate_azim)
self.slider_elev.valueChanged.connect(self.rotate_elev)
def on_click(self, event):
azim, elev = int(self._ax.azim), int(self._ax.elev)
# add 180 degress
self.slider_azim.setValue(azim + 180)
self.slider_elev.setValue(elev + 180)
def rotate_azim(self, value):
# view_init: Set the elevation and azimuth of the axes in degrees (not radians).
self._ax.view_init(self._ax.elev, value)
self.fig.set_canvas(self.canvas)
self.canvas.draw()
def rotate_elev(self, value):
self._ax.view_init(value, self._ax.azim)
self.fig.set_canvas(self.canvas)
self.canvas.draw()
def plot_wire(self):
# Data
self.X, self.Y, self.Z = axes3d.get_test_data(0.03)
self.set_canvas_table_configuration(len(self.X[0]), (self.X[0], self.Y[0], self.Z[0]))
self._ax.plot_wireframe(self.X, self.Y, self.Z, rstride=10, cstride=10, cmap="viridis")
self.canvas.draw()
def set_canvas_table_configuration(self, row_count, data):
self.fig.set_canvas(self.canvas)
self._ax = self.canvas.figure.add_subplot(projection="3d")
# Set axis label title
self.column_names = ["X", "Y", "Z"]
self._ax.set_xlabel(self.column_names[0])
self._ax.set_ylabel(self.column_names[1])
self._ax.set_zlabel(self.column_names[2])
# Add data to table widget
self.table.setRowCount(row_count)
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels(self.column_names)
self.set_table_data(data[0], data[1], data[2])
def set_table_data(self, X, Y, Z):
for i in range(len(X)):
self.table.setItem(i, 0, QTableWidgetItem("{:.2f}".format(X[i])))
self.table.setItem(i, 1, QTableWidgetItem("{:.2f}".format(Y[i])))
self.table.setItem(i, 2, QTableWidgetItem("{:.2f}".format(Z[i])))
def plot_surface(self):
# Data
self.X, self.Y = np.meshgrid(np.linspace(-6, 6, 30), np.linspace(-6, 6, 30))
self.Z = np.sin(np.sqrt(self.X ** 2 + self.Y ** 2))
self.set_canvas_table_configuration(len(self.X[0]), (self.X[0], self.Y[0], self.Z[0]))
self._ax.plot_surface(self.X, self.Y, self.Z,
rstride=1, cstride=1, cmap="viridis", edgecolor="none")
self.canvas.draw()
def plot_triangular_surface(self):
# Data
radii = np.linspace(0.125, 1.0, 8)
angles = np.linspace(0, 2 * np.pi, 36, endpoint=False)[..., np.newaxis]
self.X = np.append(0, (radii * np.cos(angles)).flatten())
self.Y = np.append(0, (radii * np.sin(angles)).flatten())
self.Z = np.sin(-self.X * self.Y)
self.set_canvas_table_configuration(len(self.X), (self.X, self.Y, self.Z))
self._ax.plot_trisurf(self.X, self.Y, self.Z, linewidth=0.2, antialiased=True)
self.canvas.draw()
def plot_sphere(self):
# Data
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
self.X = 10 * np.outer(np.cos(u), np.sin(v))
self.Y = 10 * np.outer(np.sin(u), np.sin(v))
self.Z = 9 * np.outer(np.ones(np.size(u)), np.cos(v))
self.set_canvas_table_configuration(len(self.X), (self.X[0], self.Y[0], self.Z[0]))
self._ax.plot_surface(self.X, self.Y, self.Z)
self.canvas.draw()
def combo_option(self, text):
if text == "Wired":
self.plot_wire()
elif text == "Surface":
self.plot_surface()
elif text == "Triangular Surface":
self.plot_triangular_surface()
elif text == "Sphere":
self.plot_sphere()
if __name__ == '__main__':
# don't auto scale when drag app to a different monitor.
# QApplication.setAttribute(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
app = QApplication(sys.argv)
myApp = MyApp()
myApp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
