| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import FreeCAD |
| |
|
| | import PySide |
| | from PySide import QtCore, QtGui |
| | import sys |
| |
|
| | try: |
| | import matplotlib |
| |
|
| | matplotlib.use("QtAgg") |
| |
|
| | |
| | pyqt5_unloaded = False |
| | pyqt6_unloaded = False |
| | if "PyQt5.QtCore" in sys.modules: |
| | del sys.modules["PyQt5.QtCore"] |
| | pyqt5_unloaded = True |
| | if "PyQt6.QtCore" in sys.modules: |
| | del sys.modules["PyQt6.QtCore"] |
| | pyqt6_unloaded = True |
| |
|
| | import matplotlib.pyplot as plt |
| |
|
| | |
| | if pyqt5_unloaded: |
| | import PyQt5.QtCore |
| | if pyqt6_unloaded: |
| | import PyQt6.QtCore |
| |
|
| | from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas |
| | from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar |
| |
|
| | from matplotlib.figure import Figure |
| | except ImportError: |
| | FreeCAD.Console.PrintWarning( |
| | "The 'matplotlib' Python package was not found. Plot module cannot be loaded\n" |
| | ) |
| | raise ImportError("matplotlib not installed") |
| |
|
| |
|
| | def getMainWindow(): |
| | """Return the FreeCAD main window.""" |
| | toplevel = PySide.QtGui.QApplication.topLevelWidgets() |
| | for i in toplevel: |
| | if i.metaObject().className() == "Gui::MainWindow": |
| | return i |
| | return None |
| |
|
| |
|
| | def getMdiArea(): |
| | """Return FreeCAD MdiArea.""" |
| | mw = getMainWindow() |
| | if not mw: |
| | return None |
| | childs = mw.children() |
| | for c in childs: |
| | if isinstance(c, PySide.QtGui.QMdiArea): |
| | return c |
| | return None |
| |
|
| |
|
| | def getPlot(): |
| | """Return the selected Plot document if exist.""" |
| | |
| | mdi = getMdiArea() |
| | if not mdi: |
| | return None |
| | sub = mdi.activeSubWindow() |
| | if not sub: |
| | return None |
| | |
| | for i in sub.children(): |
| | if i.metaObject().className() == "Plot": |
| | return i |
| | return None |
| |
|
| |
|
| | def closePlot(): |
| | """closePlot(): Close the active plot window.""" |
| | |
| | mdi = getMdiArea() |
| | if not mdi: |
| | return None |
| | sub = mdi.activeSubWindow() |
| | if not sub: |
| | return None |
| | |
| | for i in sub.children(): |
| | if i.metaObject().className() == "Plot": |
| | sub.close() |
| |
|
| |
|
| | def figure(winTitle="plot"): |
| | """Create a new plot subwindow/tab. |
| | |
| | Keyword arguments: |
| | winTitle -- Plot tab title. |
| | """ |
| | mdi = getMdiArea() |
| | if not mdi: |
| | return None |
| | win = Plot(winTitle) |
| | sub = mdi.addSubWindow(win) |
| | sub.show() |
| | return win |
| |
|
| |
|
| | def plot(x, y, name=None): |
| | """Plots a new serie (as line plot) |
| | |
| | Keyword arguments: |
| | x -- X values |
| | y -- Y values |
| | name -- Data serie name (for legend). |
| | """ |
| | |
| | plt = getPlot() |
| | if not plt: |
| | plt = figure() |
| | |
| | return plt.plot(x, y, name) |
| |
|
| |
|
| | def series(): |
| | """Return all the lines from a selected plot.""" |
| | plt = getPlot() |
| | if not plt: |
| | return [] |
| | return plt.series |
| |
|
| |
|
| | def removeSerie(index): |
| | """Remove a data serie from the active plot. |
| | |
| | Keyword arguments: |
| | index -- Index of the serie to remove. |
| | """ |
| | |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | plots = plt.series |
| | if not plots: |
| | return |
| | |
| | axes = plots[index].axes |
| | axes.lines.pop(plots[index].lid) |
| | |
| | del plt.series[index] |
| | |
| | plt.update() |
| |
|
| |
|
| | def legend(status=True, pos=None, fontsize=None): |
| | """Show/Hide the legend from the active plot. |
| | |
| | Keyword arguments: |
| | status -- True if legend must be shown, False otherwise. |
| | pos -- Legend position. |
| | fontsize -- Font size |
| | """ |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | plt.legend = status |
| | if fontsize: |
| | plt.legSiz = fontsize |
| | |
| | for axes in plt.axesList: |
| | axes.legend_ = None |
| | |
| | axes = plt.axesList[-1] |
| | if status: |
| | |
| | lines = series() |
| | handles = [] |
| | names = [] |
| | for l in lines: |
| | if l.name is not None: |
| | handles.append(l.line) |
| | names.append(l.name) |
| | |
| | if pos: |
| | l = axes.legend(handles, names, bbox_to_anchor=pos) |
| | plt.legPos = pos |
| | else: |
| | l = axes.legend(handles, names, loc="best") |
| | |
| | plt.canvas.draw() |
| | |
| | try: |
| | fax = axes.get_frame().get_extents() |
| | except Exception: |
| | fax = axes.patch.get_extents() |
| | fl = l.get_frame() |
| | plt.legPos = ( |
| | (fl._x + fl._width - fax.x0) / fax.width, |
| | (fl._y + fl._height - fax.y0) / fax.height, |
| | ) |
| | |
| | for t in l.get_texts(): |
| | t.set_fontsize(plt.legSiz) |
| | plt.update() |
| |
|
| |
|
| | def grid(status=True): |
| | """Show/Hide the grid from the active plot. |
| | |
| | Keyword arguments: |
| | status -- True if grid must be shown, False otherwise. |
| | """ |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | plt.grid = status |
| | axes = plt.axes |
| | axes.grid(status) |
| | plt.update() |
| |
|
| |
|
| | def title(string): |
| | """Setup the plot title. |
| | |
| | Keyword arguments: |
| | string -- Plot title. |
| | """ |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | axes = plt.axes |
| | axes.set_title(string) |
| | plt.update() |
| |
|
| |
|
| | def xlabel(string): |
| | """Setup the x label. |
| | |
| | Keyword arguments: |
| | string -- Title to set. |
| | """ |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | axes = plt.axes |
| | axes.set_xlabel(string) |
| | plt.update() |
| |
|
| |
|
| | def ylabel(string): |
| | """Setup the y label. |
| | |
| | Keyword arguments: |
| | string -- Title to set. |
| | """ |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | axes = plt.axes |
| | axes.set_ylabel(string) |
| | plt.update() |
| |
|
| |
|
| | def axesList(): |
| | """Return the plot axes sets list.""" |
| | plt = getPlot() |
| | if not plt: |
| | return [] |
| | return plt.axesList |
| |
|
| |
|
| | def axes(): |
| | """Return the active plot axes.""" |
| | plt = getPlot() |
| | if not plt: |
| | return None |
| | return plt.axes |
| |
|
| |
|
| | def addNewAxes(rect=None, frameon=True, patchcolor="none"): |
| | """Add new axes to plot, setting it as the active one. |
| | |
| | Keyword arguments: |
| | rect -- Axes area, None to copy from the last axes data. |
| | frameon -- True to show frame, False otherwise. |
| | patchcolor -- Patch color, 'none' for transparent plot. |
| | """ |
| | plt = getPlot() |
| | if not plt: |
| | return None |
| | fig = plt.fig |
| | if rect is None: |
| | rect = plt.axes.get_position() |
| | ax = fig.add_axes(rect, frameon=frameon) |
| | ax.xaxis.set_ticks_position("bottom") |
| | ax.spines["top"].set_color("none") |
| | ax.yaxis.set_ticks_position("left") |
| | ax.spines["right"].set_color("none") |
| | ax.patch.set_facecolor(patchcolor) |
| | plt.axesList.append(ax) |
| | plt.setActiveAxes(-1) |
| | plt.update() |
| | return ax |
| |
|
| |
|
| | def save(path, figsize=None, dpi=None): |
| | """Save plot. |
| | |
| | Keyword arguments: |
| | path -- Destination file path. |
| | figsize -- w,h figure size tuple in inches. |
| | dpi -- Dots per inch. |
| | """ |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | |
| | fig = plt.fig |
| | sizeBack = fig.get_size_inches() |
| | dpiBack = fig.get_dpi() |
| | |
| | if figsize: |
| | fig.set_size_inches(figsize[0], figsize[1]) |
| | if dpi: |
| | fig.set_dpi(dpi) |
| | plt.canvas.print_figure(path) |
| | |
| | fig.set_size_inches(sizeBack[0], sizeBack[1]) |
| | fig.set_dpi(dpiBack) |
| | plt.update() |
| |
|
| |
|
| | def addNavigationToolbar(): |
| | """Add the matplotlib QT navigation toolbar to the plot.""" |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | |
| | if plt.mpl_toolbar is not None: |
| | return |
| | |
| | plt.mpl_toolbar = NavigationToolbar(plt.canvas, plt) |
| | vbox = plt.layout() |
| | vbox.addWidget(plt.mpl_toolbar) |
| |
|
| |
|
| | def delNavigationToolbar(): |
| | """Remove the matplotlib QT navigation toolbar from the plot.""" |
| | plt = getPlot() |
| | if not plt: |
| | return |
| | |
| | if plt.mpl_toolbar is None: |
| | return |
| | |
| | vbox = plt.layout() |
| | vbox.removeWidget(plt.mpl_toolbar) |
| | |
| | plt.mpl_toolbar.deleteLater() |
| | plt.mpl_toolbar = None |
| |
|
| |
|
| | class Line: |
| | def __init__(self, axes, x, y, name): |
| | """Construct a new plot serie. |
| | |
| | Keyword arguments: |
| | axes -- Active axes |
| | x -- X values |
| | y -- Y values |
| | name -- Data serie name (for legend). |
| | """ |
| | self.axes = axes |
| | self.x = x |
| | self.y = y |
| | self.name = name |
| | self.lid = len(axes.lines) |
| | (self.line,) = axes.plot(x, y) |
| |
|
| | def setp(self, prop, value): |
| | """Change a line property value. |
| | |
| | Keyword arguments: |
| | prop -- Property name. |
| | value -- New property value. |
| | """ |
| | plt.setp(self.line, prop, value) |
| |
|
| | def getp(self, prop): |
| | """Get line property value. |
| | |
| | Keyword arguments: |
| | prop -- Property name. |
| | """ |
| | return plt.getp(self.line, prop) |
| |
|
| |
|
| | class Plot(PySide.QtGui.QWidget): |
| | def __init__(self, winTitle="plot", parent=None, flags=PySide.QtCore.Qt.WindowFlags(0)): |
| | """Construct a new plot widget. |
| | |
| | Keyword arguments: |
| | winTitle -- Tab title. |
| | parent -- Widget parent. |
| | flags -- QWidget flags |
| | """ |
| | PySide.QtGui.QWidget.__init__(self, parent, flags) |
| | self.setWindowTitle(winTitle) |
| | |
| | self.fig = Figure() |
| | self.canvas = FigureCanvas(self.fig) |
| | self.canvas.setParent(self) |
| | |
| | self.axes = self.fig.add_subplot(111) |
| | self.axesList = [self.axes] |
| | self.axes.xaxis.set_ticks_position("bottom") |
| | self.axes.spines["top"].set_color("none") |
| | self.axes.yaxis.set_ticks_position("left") |
| | self.axes.spines["right"].set_color("none") |
| | |
| | self.mpl_toolbar = NavigationToolbar(self.canvas, self) |
| | |
| | vbox = PySide.QtGui.QVBoxLayout() |
| | vbox.addWidget(self.canvas) |
| | vbox.addWidget(self.mpl_toolbar) |
| | self.setLayout(vbox) |
| | |
| | self.series = [] |
| | |
| | self.skip = False |
| | self.legend = False |
| | self.legPos = (1.0, 1.0) |
| | self.legSiz = 14 |
| | self.grid = False |
| |
|
| | def plot(self, x, y, name=None): |
| | """Plot a new line and return it. |
| | |
| | Keyword arguments: |
| | x -- X values |
| | y -- Y values |
| | name -- Serie name (for legend).""" |
| | l = Line(self.axes, x, y, name) |
| | self.series.append(l) |
| | |
| | self.update() |
| | return l |
| |
|
| | def update(self): |
| | """Update the plot, redrawing the canvas.""" |
| | if not self.skip: |
| | self.skip = True |
| | if self.legend: |
| | legend(self.legend, self.legPos, self.legSiz) |
| | self.canvas.draw() |
| | self.skip = False |
| |
|
| | def isGrid(self): |
| | """Return True if Grid is active, False otherwise.""" |
| | return bool(self.grid) |
| |
|
| | def isLegend(self): |
| | """Return True if Legend is active, False otherwise.""" |
| | return bool(self.legend) |
| |
|
| | def setActiveAxes(self, index): |
| | """Change the current active axes. |
| | |
| | Keyword arguments: |
| | index -- Index of the new active axes set. |
| | """ |
| | self.axes = self.axesList[index] |
| | self.fig.sca(self.axes) |
| |
|