#*************************************************************************** #* * #* Copyright (c) 2011, 2012 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** import FreeCAD import PySide from PySide import QtCore, QtGui from distutils.version import LooseVersion as V try: import matplotlib matplotlib.use('Qt4Agg') matplotlib.rcParams['backend.qt4']='PySide' import matplotlib.pyplot as plt from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas if V(matplotlib.__version__) < V("1.4.0"): from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar else: from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure except ImportError: msg = PySide.QtGui.QApplication.translate( "plot_console", "matplotlib not found, so Plot module can not be loaded", None) FreeCAD.Console.PrintMessage(msg + '\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. """ # Get active tab mdi = getMdiArea() if not mdi: return None sub = mdi.activeSubWindow() if not sub: return None # Explore childrens looking for Plot class for i in sub.children(): if i.metaObject().className() == "Plot": return i return None def closePlot(): """ closePlot(): Close the active plot window. """ # Get active tab mdi = getMdiArea() if not mdi: return None sub = mdi.activeSubWindow() if not sub: return None # Explore childrens looking for Plot class 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). """ # Get active plot, or create another one if don't exist plt = getPlot() if not plt: plt = figure() # Call to plot 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. """ # Get active series plt = getPlot() if not plt: return plots = plt.series if not plots: return # Remove line from plot axes = plots[index].axes axes.lines.pop(plots[index].lid) # Remove serie from list del plt.series[index] # Update GUI 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 # Hide all legends for axes in plt.axesList: axes.legend_ = None # Legend must be activated on last axes axes = plt.axesList[-1] if status: # Setup legend handles and names lines = series() handles = [] names = [] for l in lines: if l.name is not None: handles.append(l.line) names.append(l.name) # Show the legend (at selected position or at best) if pos: l = axes.legend(handles, names, bbox_to_anchor=pos) plt.legPos = pos else: l = axes.legend(handles, names, loc='best') # Update canvas in order to compute legend data plt.canvas.draw() # Get resultant position try: fax = axes.get_frame().get_extents() except: 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) # Set fontsize 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 # Backup figure options fig = plt.fig sizeBack = fig.get_size_inches() dpiBack = fig.get_dpi() # Save figure with new options if figsize: fig.set_size_inches(figsize[0], figsize[1]) if dpi: fig.set_dpi(dpi) plt.canvas.print_figure(path) # Restore figure options 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 # Check that the navigation toolbar has not been already created if plt.mpl_toolbar is not None: return # Create the navigation toolbar and add it 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 # Check that the navigation toolbar already exist if plt.mpl_toolbar is None: return # Remove the widget from the layout vbox = plt.layout() vbox.removeWidget(plt.mpl_toolbar) # Destroy the navigation 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) # Create matplotlib canvas self.fig = Figure() self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self) # Get axes 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') # Add the navigation toolbar by default self.mpl_toolbar = NavigationToolbar(self.canvas, self) # Setup layout vbox = PySide.QtGui.QVBoxLayout() vbox.addWidget(self.canvas) vbox.addWidget(self.mpl_toolbar) self.setLayout(vbox) # Active series self.series = [] # Indicators 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) # Update window 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)