From b78662c33e64dc86b723ef96bf6b5765f4c596bf Mon Sep 17 00:00:00 2001 From: Luke Campagnola <> Date: Fri, 23 Mar 2012 02:41:10 -0400 Subject: [PATCH] Minor updates for exporting - curves enable antialiasing when exporting to image - plotitems hide button during export --- GraphicsScene/GraphicsScene.py | 4 +- GraphicsScene/exportDialogTemplate.py | 4 +- GraphicsScene/exportDialogTemplate.ui | 2 +- exporters/Exporter.py | 68 ++++++++++++++++++++++- exporters/ImageExporter.py | 10 +++- exporters/PrintExporter.py | 59 ++++++++++++++++++++ exporters/SVGExporter.py | 6 +- exporters/__init__.py | 24 +++++++- graphicsItems/AxisItem.py | 3 + graphicsItems/PlotCurveItem.py | 18 +++++- graphicsItems/PlotItem/PlotItem.py | 56 ++++++++++++++----- graphicsItems/ViewBox/axisCtrlTemplate.py | 26 +++++---- graphicsItems/ViewBox/axisCtrlTemplate.ui | 6 +- 13 files changed, 245 insertions(+), 41 deletions(-) create mode 100644 exporters/PrintExporter.py diff --git a/GraphicsScene/GraphicsScene.py b/GraphicsScene/GraphicsScene.py index e6679c2d..9cc2491a 100644 --- a/GraphicsScene/GraphicsScene.py +++ b/GraphicsScene/GraphicsScene.py @@ -59,12 +59,12 @@ class GraphicsScene(QtGui.QGraphicsScene): move in a drag. """ - - _addressCache = weakref.WeakValueDictionary() sigMouseHover = QtCore.Signal(object) ## emits a list of objects hovered over sigMouseMoved = QtCore.Signal(object) ## emits position of mouse on every move sigMouseClicked = QtCore.Signal(object) ## emitted when MouseClickEvent is not accepted by any items under the click. + _addressCache = weakref.WeakValueDictionary() + ExportDirectory = None @classmethod diff --git a/GraphicsScene/exportDialogTemplate.py b/GraphicsScene/exportDialogTemplate.py index ae90100d..60f18d0d 100644 --- a/GraphicsScene/exportDialogTemplate.py +++ b/GraphicsScene/exportDialogTemplate.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'exportDialogTemplate.ui' # -# Created: Sat Mar 10 17:54:53 2012 +# Created: Thu Mar 22 13:13:06 2012 # by: PyQt4 UI code generator 4.8.5 # # WARNING! All changes made in this file will be lost! @@ -18,7 +18,7 @@ class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(241, 367) - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) self.gridLayout = QtGui.QGridLayout(Form) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) diff --git a/GraphicsScene/exportDialogTemplate.ui b/GraphicsScene/exportDialogTemplate.ui index 0d840253..c81c8831 100644 --- a/GraphicsScene/exportDialogTemplate.ui +++ b/GraphicsScene/exportDialogTemplate.ui @@ -11,7 +11,7 @@ - Form + Export diff --git a/exporters/Exporter.py b/exporters/Exporter.py index 25ae367c..abcddd28 100644 --- a/exporters/Exporter.py +++ b/exporters/Exporter.py @@ -75,8 +75,74 @@ class Exporter(object): else: return self.item.mapRectToDevice(self.item.boundingRect()) + def setExportMode(self, export, opts=None): + """ + Call setExportMode(export, opts) on all items that will + be painted during the export. This informs the item + that it is about to be painted for export, allowing it to + alter its appearance temporarily - + + *export* - bool; must be True before exporting and False afterward + *opts* - dict; common parameters are 'antialias' and 'background' + """ + if opts is None: + opts = {} + for item in self.getPaintItems(): + if hasattr(item, 'setExportMode'): + item.setExportMode(export, opts) + + def getPaintItems(self, root=None): + """Return a list of all items that should be painted in the correct order.""" + if root is None: + root = self.item + preItems = [] + postItems = [] + if isinstance(root, QtGui.QGraphicsScene): + childs = [i for i in root.items() if i.parentItem() is None] + rootItem = [] + else: + childs = root.childItems() + rootItem = [root] + childs.sort(lambda a,b: cmp(a.zValue(), b.zValue())) + while len(childs) > 0: + ch = childs.pop(0) + tree = self.getPaintItems(ch) + if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): + preItems.extend(tree) + else: + postItems.extend(tree) + + return preItems + rootItem + postItems + + def render(self, painter, sourcRect, targetRect, item=None) + + #if item is None: + #item = self.item + #preItems = [] + #postItems = [] + #if isinstance(item, QtGui.QGraphicsScene): + #childs = [i for i in item.items() if i.parentItem() is None] + #rootItem = [] + #else: + #childs = item.childItems() + #rootItem = [item] + #childs.sort(lambda a,b: cmp(a.zValue(), b.zValue())) + #while len(childs) > 0: + #ch = childs.pop(0) + #if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): + #preItems.extend(tree) + #else: + #postItems.extend(tree) + + #for ch in preItems: + #self.render(painter, sourceRect, targetRect, item=ch) + ### paint root here + #for ch in postItems: + #self.render(painter, sourceRect, targetRect, item=ch) + + + self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) #def writePs(self, fileName=None, item=None): #if fileName is None: diff --git a/exporters/ImageExporter.py b/exporters/ImageExporter.py index 138ddabb..fa9b6f6d 100644 --- a/exporters/ImageExporter.py +++ b/exporters/ImageExporter.py @@ -57,6 +57,12 @@ class ImageExporter(Exporter): bg[:,:,3] = color.alpha() self.png = pg.makeQImage(bg, alpha=True) painter = QtGui.QPainter(self.png) - self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect) + try: + self.setExportMode(True, {'antialias': self.params['antialias'], 'background': self.params['background']}) + self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect) + finally: + self.setExportMode(False) self.png.save(fileName) - painter.end() \ No newline at end of file + painter.end() + + \ No newline at end of file diff --git a/exporters/PrintExporter.py b/exporters/PrintExporter.py new file mode 100644 index 00000000..12468e9c --- /dev/null +++ b/exporters/PrintExporter.py @@ -0,0 +1,59 @@ +from Exporter import Exporter +from pyqtgraph.parametertree import Parameter +from pyqtgraph.Qt import QtGui, QtCore, QtSvg +import re + +#__all__ = ['PrintExporter'] +__all__ = [] ## Printer is disabled for now--does not work very well. + +class PrintExporter(Exporter): + Name = "Printer" + def __init__(self, item): + Exporter.__init__(self, item) + tr = self.getTargetRect() + self.params = Parameter(name='params', type='group', children=[ + {'name': 'width', 'type': 'float', 'value': 0.1, 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, + {'name': 'height', 'type': 'float', 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, + ]) + self.params.param('width').sigValueChanged.connect(self.widthChanged) + self.params.param('height').sigValueChanged.connect(self.heightChanged) + + def widthChanged(self): + sr = self.getSourceRect() + ar = sr.height() / sr.width() + self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) + + def heightChanged(self): + sr = self.getSourceRect() + ar = sr.width() / sr.height() + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) + + def parameters(self): + return self.params + + def export(self, fileName=None): + printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) + dialog = QtGui.QPrintDialog(printer) + dialog.setWindowTitle("Print Document") + if dialog.exec_() != QtGui.QDialog.Accepted: + return; + + #self.svg.setSize(QtCore.QSize(100,100)) + #self.svg.setResolution(600) + res = printer.resolution() + rect = printer.pageRect() + center = rect.center() + h = self.params['height'] * res * 100. / 2.54 + w = self.params['width'] * res * 100. / 2.54 + x = center.x() - w/2. + y = center.y() - h/2. + + targetRect = QtCore.QRect(x, y, w, h) + sourceRect = self.getSourceRect() + painter = QtGui.QPainter(printer) + try: + self.setExportMode(True) + self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect) + finally: + self.setExportMode(False) + painter.end() diff --git a/exporters/SVGExporter.py b/exporters/SVGExporter.py index 40489628..9158d00c 100644 --- a/exporters/SVGExporter.py +++ b/exporters/SVGExporter.py @@ -42,7 +42,11 @@ class SVGExporter(Exporter): targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) sourceRect = self.getSourceRect() painter = QtGui.QPainter(self.svg) - self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect) + try: + self.setExportMode(True) + self.render(painter, QtCore.QRectF(targetRect), sourceRect) + finally: + self.setExportMode(False) painter.end() ## Workaround to set pen widths correctly diff --git a/exporters/__init__.py b/exporters/__init__.py index a73ef2c2..e6ac379c 100644 --- a/exporters/__init__.py +++ b/exporters/__init__.py @@ -1,6 +1,24 @@ -from SVGExporter import * -from ImageExporter import * -Exporters = [SVGExporter, ImageExporter] +Exporters = [] + +import os, sys +d = os.path.split(__file__)[0] +files = [] +for f in os.listdir(d): + if os.path.isdir(os.path.join(d, f)): + files.append(f) + elif f[-3:] == '.py' and f not in ['__init__.py', 'Exporter.py']: + files.append(f[:-3]) + +for modName in files: + mod = __import__(modName, globals(), locals(), fromlist=['*']) + if hasattr(mod, '__all__'): + names = mod.__all__ + else: + names = [n for n in dir(mod) if n[0] != '_'] + for k in names: + if hasattr(mod, k): + Exporters.append(getattr(mod, k)) + def listExporters(): return Exporters[:] diff --git a/graphicsItems/AxisItem.py b/graphicsItems/AxisItem.py index 31e43b75..4015f8fe 100644 --- a/graphicsItems/AxisItem.py +++ b/graphicsItems/AxisItem.py @@ -261,6 +261,9 @@ class AxisItem(GraphicsWidget): def drawPicture(self, p): + p.setRenderHint(p.Antialiasing, False) + p.setRenderHint(p.TextAntialiasing, True) + prof = debug.Profiler("AxisItem.paint", disabled=True) p.setPen(self.pen) diff --git a/graphicsItems/PlotCurveItem.py b/graphicsItems/PlotCurveItem.py index fcd03448..12ac044c 100644 --- a/graphicsItems/PlotCurveItem.py +++ b/graphicsItems/PlotCurveItem.py @@ -27,6 +27,8 @@ class PlotCurveItem(GraphicsObject): self.clear() self.path = None self.fillPath = None + self.exportOpts = False + self.antialias = False if y is not None: self.updateData(y, x) @@ -364,6 +366,14 @@ class PlotCurveItem(GraphicsObject): #pen.setColor(c) ##pen.setCosmetic(True) + if self.exportOpts is not False: + aa = self.exportOpts['antialias'] + else: + aa = self.antialias + + p.setRenderHint(p.Antialiasing, aa) + + if sp is not None: p.setPen(sp) p.drawPath(path) @@ -410,7 +420,13 @@ class PlotCurveItem(GraphicsObject): ev.accept() self.sigClicked.emit(self) - + def setExportMode(self, export, opts): + if export: + self.exportOpts = opts + if 'antialias' not in opts: + self.exportOpts['antialias'] = True + else: + self.exportOpts = False class ROIPlotItem(PlotCurveItem): """Plot curve that monitors an ROI and image for changes to automatically replot.""" diff --git a/graphicsItems/PlotItem/PlotItem.py b/graphicsItems/PlotItem/PlotItem.py index e6e76cb4..78e213d7 100644 --- a/graphicsItems/PlotItem/PlotItem.py +++ b/graphicsItems/PlotItem/PlotItem.py @@ -64,6 +64,20 @@ class PlotItem(GraphicsWidget): managers = {} def __init__(self, parent=None, name=None, labels=None, title=None, **kargs): + """ + Create a new PlotItem. All arguments are optional. + Any extra keyword arguments are passed to PlotItem.plot(). + + Arguments: + *title* - Title to display at the top of the item. Html is allowed. + *labels* - A dictionary specifying the axis labels to display. + {'left': (args), 'bottom': (args), ...} + The name of each axis and the corresponding arguments are passed to PlotItem.setLabel() + Optionally, PlotItem my also be initialized with the keyword arguments left, + right, top, or bottom to achieve the same effect. + *name* - Registers a name for this view so that others may link to it + """ + GraphicsWidget.__init__(self, parent) self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) @@ -250,12 +264,16 @@ class PlotItem(GraphicsWidget): #if name is not None: #self.registerPlot(name) - - if labels is not None: - for k in labels: - if isinstance(labels[k], basestring): - labels[k] = (labels[k],) - self.setLabel(k, *labels[k]) + if labels is None: + labels = {} + for label in self.scales.keys(): + if label in kargs: + labels[label] = kargs[label] + del kargs[label] + for k in labels: + if isinstance(labels[k], basestring): + labels[k] = (labels[k],) + self.setLabel(k, *labels[k]) if title is not None: self.setTitle(title) @@ -263,7 +281,7 @@ class PlotItem(GraphicsWidget): if len(kargs) > 0: self.plot(**kargs) - self.enableAutoRange() + #self.enableAutoRange() def implements(self, interface=None): return interface in ['ViewBoxWrapper'] @@ -365,6 +383,8 @@ class PlotItem(GraphicsWidget): #print " Referrers are:", refs #raise + + def updateGrid(self, *args): g = self.ctrl.gridGroup.isChecked() if g: @@ -1313,18 +1333,24 @@ class PlotItem(GraphicsWidget): return c - def saveSvgClicked(self): - self.writeSvg() + #def saveSvgClicked(self): + #self.writeSvg() - def saveSvgCurvesClicked(self): - self.writeSvgCurves() + #def saveSvgCurvesClicked(self): + #self.writeSvgCurves() - def saveImgClicked(self): - self.writeImage() + #def saveImgClicked(self): + #self.writeImage() - def saveCsvClicked(self): - self.writeCsv() + #def saveCsvClicked(self): + #self.writeCsv() + def setExportMode(self, export, opts): + if export: + self.autoBtn.hide() + else: + self.autoBtn.show() + #class PlotWidgetManager(QtCore.QObject): diff --git a/graphicsItems/ViewBox/axisCtrlTemplate.py b/graphicsItems/ViewBox/axisCtrlTemplate.py index b229bf3d..20e2a8c9 100644 --- a/graphicsItems/ViewBox/axisCtrlTemplate.py +++ b/graphicsItems/ViewBox/axisCtrlTemplate.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'axisCtrlTemplate.ui' # -# Created: Fri Jan 20 12:41:24 2012 -# by: PyQt4 UI code generator 4.8.3 +# Created: Thu Mar 22 13:13:14 2012 +# by: PyQt4 UI code generator 4.8.5 # # WARNING! All changes made in this file will be lost! @@ -19,41 +19,51 @@ class Ui_Form(object): Form.setObjectName(_fromUtf8("Form")) Form.resize(182, 120) Form.setMaximumSize(QtCore.QSize(200, 16777215)) + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) self.gridLayout = QtGui.QGridLayout(Form) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.mouseCheck = QtGui.QCheckBox(Form) + self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8)) self.mouseCheck.setChecked(True) self.mouseCheck.setObjectName(_fromUtf8("mouseCheck")) self.gridLayout.addWidget(self.mouseCheck, 0, 1, 1, 2) self.manualRadio = QtGui.QRadioButton(Form) + self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) self.manualRadio.setObjectName(_fromUtf8("manualRadio")) self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 1) self.minText = QtGui.QLineEdit(Form) + self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) self.minText.setObjectName(_fromUtf8("minText")) self.gridLayout.addWidget(self.minText, 1, 1, 1, 1) self.maxText = QtGui.QLineEdit(Form) + self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) self.maxText.setObjectName(_fromUtf8("maxText")) self.gridLayout.addWidget(self.maxText, 1, 2, 1, 1) self.autoRadio = QtGui.QRadioButton(Form) + self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) self.autoRadio.setChecked(True) self.autoRadio.setObjectName(_fromUtf8("autoRadio")) self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 1) self.autoPercentSpin = QtGui.QSpinBox(Form) self.autoPercentSpin.setEnabled(True) + self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) self.autoPercentSpin.setMinimum(1) self.autoPercentSpin.setMaximum(100) self.autoPercentSpin.setSingleStep(1) - self.autoPercentSpin.setProperty(_fromUtf8("value"), 100) + self.autoPercentSpin.setProperty("value", 100) self.autoPercentSpin.setObjectName(_fromUtf8("autoPercentSpin")) self.gridLayout.addWidget(self.autoPercentSpin, 2, 1, 1, 2) self.autoPanCheck = QtGui.QCheckBox(Form) + self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8)) self.autoPanCheck.setObjectName(_fromUtf8("autoPanCheck")) self.gridLayout.addWidget(self.autoPanCheck, 3, 1, 1, 2) self.linkCombo = QtGui.QComboBox(Form) + self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.linkCombo.setObjectName(_fromUtf8("linkCombo")) self.gridLayout.addWidget(self.linkCombo, 4, 1, 1, 2) self.label = QtGui.QLabel(Form) + self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) self.label.setObjectName(_fromUtf8("label")) self.gridLayout.addWidget(self.label, 4, 0, 1, 1) @@ -61,13 +71,5 @@ class Ui_Form(object): QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8)) - self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) - self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) + pass diff --git a/graphicsItems/ViewBox/axisCtrlTemplate.ui b/graphicsItems/ViewBox/axisCtrlTemplate.ui index f01a3f80..b463923a 100644 --- a/graphicsItems/ViewBox/axisCtrlTemplate.ui +++ b/graphicsItems/ViewBox/axisCtrlTemplate.ui @@ -94,7 +94,11 @@ - + + + QComboBox::AdjustToContents + +