From d4e8e2b8837b8b7232d3b37ddff82f1218050c9a Mon Sep 17 00:00:00 2001 From: Luke Campagnola <> Date: Thu, 1 Mar 2012 21:55:32 -0500 Subject: [PATCH] Imported major changes from acq4 project. --- GradientWidget.py | 452 - GraphicsScene.py | 747 ++ GraphicsScene.pyc | Bin 0 -> 27489 bytes ImageViewTemplate.ui | 283 - PlotItem.py | 1284 --- Point.py | 17 +- Point.pyc | Bin 0 -> 6601 bytes Qt.py | 5 + Qt.pyc | Bin 0 -> 367 bytes SignalProxy.py | 76 +- SignalProxy.pyc | Bin 0 -> 3604 bytes Transform.py | 6 +- Transform.pyc | Bin 0 -> 9368 bytes WidgetGroup.py | 282 + WidgetGroup.pyc | Bin 0 -> 9737 bytes __init__.py | 81 +- __init__.pyc | Bin 0 -> 3585 bytes canvas/Canvas.py | 554 ++ canvas/CanvasItem.py | 490 + canvas/CanvasManager.py | 76 + canvas/CanvasTemplate.py | 96 + canvas/CanvasTemplate.ui | 142 + canvas/TransformGuiTemplate.py | 53 + canvas/TransformGuiTemplate.ui | 64 + canvas/__init__.py | 3 + debug.py | 2 +- debug.pyc | Bin 0 -> 29902 bytes dockarea/Container.py | 267 + dockarea/Dock.py | 350 + dockarea/DockArea.py | 267 + dockarea/DockDrop.py | 129 + dockarea/__init__.py | 2 + dockarea/__main__.py | 83 + documentation/Makefile | 130 + .../build/doctrees/apireference.doctree | Bin 0 -> 3040 bytes .../build/doctrees/environment.pickle | Bin 0 -> 77056 bytes .../build/doctrees/functions.doctree | Bin 0 -> 54676 bytes .../doctrees/graphicsItems/arrowitem.doctree | Bin 0 -> 5249 bytes .../doctrees/graphicsItems/axisitem.doctree | Bin 0 -> 11126 bytes .../doctrees/graphicsItems/buttonitem.doctree | Bin 0 -> 5775 bytes .../doctrees/graphicsItems/curvearrow.doctree | Bin 0 -> 6107 bytes .../doctrees/graphicsItems/curvepoint.doctree | Bin 0 -> 6727 bytes .../graphicsItems/gradienteditoritem.doctree | Bin 0 -> 6649 bytes .../graphicsItems/gradientlegend.doctree | Bin 0 -> 7223 bytes .../graphicsItems/graphicslayout.doctree | Bin 0 -> 8137 bytes .../graphicsItems/graphicsobject.doctree | Bin 0 -> 17301 bytes .../graphicsItems/graphicswidget.doctree | Bin 0 -> 5637 bytes .../doctrees/graphicsItems/griditem.doctree | Bin 0 -> 4913 bytes .../graphicsItems/histogramlutitem.doctree | Bin 0 -> 4895 bytes .../doctrees/graphicsItems/imageitem.doctree | Bin 0 -> 16931 bytes .../doctrees/graphicsItems/index.doctree | Bin 0 -> 4809 bytes .../graphicsItems/infiniteline.doctree | Bin 0 -> 11533 bytes .../doctrees/graphicsItems/labelitem.doctree | Bin 0 -> 10554 bytes .../graphicsItems/linearregionitem.doctree | Bin 0 -> 8537 bytes .../graphicsItems/plotcurveitem.doctree | Bin 0 -> 9555 bytes .../graphicsItems/plotdataitem.doctree | Bin 0 -> 30382 bytes .../doctrees/graphicsItems/plotitem.doctree | Bin 0 -> 31151 bytes .../build/doctrees/graphicsItems/roi.doctree | Bin 0 -> 21560 bytes .../doctrees/graphicsItems/scalebar.doctree | Bin 0 -> 6287 bytes .../graphicsItems/scatterplotitem.doctree | Bin 0 -> 17644 bytes .../graphicsItems/uigraphicsitem.doctree | Bin 0 -> 15483 bytes .../doctrees/graphicsItems/viewbox.doctree | Bin 0 -> 27898 bytes .../doctrees/graphicsItems/vtickgroup.doctree | Bin 0 -> 5951 bytes .../build/doctrees/graphicswindow.doctree | Bin 0 -> 3501 bytes .../build/doctrees/how_to_use.doctree | Bin 0 -> 9236 bytes documentation/build/doctrees/images.doctree | Bin 0 -> 11808 bytes documentation/build/doctrees/index.doctree | Bin 0 -> 6042 bytes .../build/doctrees/introduction.doctree | Bin 0 -> 13863 bytes .../build/doctrees/parametertree.doctree | Bin 0 -> 3458 bytes documentation/build/doctrees/plotting.doctree | Bin 0 -> 26167 bytes .../build/doctrees/region_of_interest.doctree | Bin 0 -> 4508 bytes documentation/build/doctrees/style.doctree | Bin 0 -> 5421 bytes .../build/doctrees/widgets/checktable.doctree | Bin 0 -> 4775 bytes .../doctrees/widgets/colorbutton.doctree | Bin 0 -> 5607 bytes .../doctrees/widgets/datatreewidget.doctree | Bin 0 -> 6995 bytes .../build/doctrees/widgets/dockarea.doctree | Bin 0 -> 2862 bytes .../build/doctrees/widgets/filedialog.doctree | Bin 0 -> 4763 bytes .../doctrees/widgets/gradientwidget.doctree | Bin 0 -> 5742 bytes .../widgets/graphicslayoutwidget.doctree | Bin 0 -> 5215 bytes .../doctrees/widgets/graphicsview.doctree | Bin 0 -> 12436 bytes .../widgets/histogramlutwidget.doctree | Bin 0 -> 5455 bytes .../build/doctrees/widgets/imageview.doctree | Bin 0 -> 12149 bytes .../build/doctrees/widgets/index.doctree | Bin 0 -> 4236 bytes .../doctrees/widgets/joystickbutton.doctree | Bin 0 -> 4859 bytes .../doctrees/widgets/multiplotwidget.doctree | Bin 0 -> 5295 bytes .../doctrees/widgets/parametertree.doctree | Bin 0 -> 2912 bytes .../build/doctrees/widgets/plotwidget.doctree | Bin 0 -> 5476 bytes .../doctrees/widgets/progressdialog.doctree | Bin 0 -> 10374 bytes .../doctrees/widgets/rawimagewidget.doctree | Bin 0 -> 7770 bytes .../build/doctrees/widgets/spinbox.doctree | Bin 0 -> 12210 bytes .../doctrees/widgets/tablewidget.doctree | Bin 0 -> 11304 bytes .../build/doctrees/widgets/treewidget.doctree | Bin 0 -> 7154 bytes .../doctrees/widgets/verticallabel.doctree | Bin 0 -> 5483 bytes documentation/build/html/.buildinfo | 4 + .../build/html/_images/plottingClasses.png | Bin 0 -> 68667 bytes documentation/build/html/_modules/index.html | 89 + .../build/html/_modules/pyqtgraph.html | 192 + .../build/html/_sources/apireference.txt | 11 + .../build/html/_sources/functions.txt | 53 + .../html/_sources/graphicsItems/arrowitem.txt | 8 + .../html/_sources/graphicsItems/axisitem.txt | 8 + .../_sources/graphicsItems/buttonitem.txt | 8 + .../_sources/graphicsItems/curvearrow.txt | 8 + .../_sources/graphicsItems/curvepoint.txt | 8 + .../graphicsItems/gradienteditoritem.txt | 8 + .../_sources/graphicsItems/gradientlegend.txt | 8 + .../_sources/graphicsItems/graphicslayout.txt | 8 + .../_sources/graphicsItems/graphicsobject.txt | 8 + .../_sources/graphicsItems/graphicswidget.txt | 8 + .../html/_sources/graphicsItems/griditem.txt | 8 + .../graphicsItems/histogramlutitem.txt | 8 + .../html/_sources/graphicsItems/imageitem.txt | 8 + .../html/_sources/graphicsItems/index.txt | 37 + .../_sources/graphicsItems/infiniteline.txt | 8 + .../html/_sources/graphicsItems/labelitem.txt | 8 + .../graphicsItems/linearregionitem.txt | 8 + .../_sources/graphicsItems/plotcurveitem.txt | 8 + .../_sources/graphicsItems/plotdataitem.txt | 8 + .../html/_sources/graphicsItems/plotitem.txt | 7 + .../build/html/_sources/graphicsItems/roi.txt | 8 + .../html/_sources/graphicsItems/scalebar.txt | 8 + .../graphicsItems/scatterplotitem.txt | 8 + .../_sources/graphicsItems/uigraphicsitem.txt | 8 + .../html/_sources/graphicsItems/viewbox.txt | 8 + .../_sources/graphicsItems/vtickgroup.txt | 8 + .../build/html/_sources/graphicswindow.txt | 8 + .../build/html/_sources/how_to_use.txt | 47 + documentation/build/html/_sources/images.txt | 26 + documentation/build/html/_sources/index.txt | 32 + .../build/html/_sources/introduction.txt | 51 + .../build/html/_sources/parametertree.txt | 7 + .../build/html/_sources/plotting.txt | 73 + .../html/_sources/region_of_interest.txt | 19 + documentation/build/html/_sources/style.txt | 17 + .../html/_sources/widgets/checktable.txt | 8 + .../html/_sources/widgets/colorbutton.txt | 8 + .../html/_sources/widgets/datatreewidget.txt | 8 + .../build/html/_sources/widgets/dockarea.txt | 5 + .../html/_sources/widgets/filedialog.txt | 8 + .../html/_sources/widgets/gradientwidget.txt | 8 + .../_sources/widgets/graphicslayoutwidget.txt | 8 + .../html/_sources/widgets/graphicsview.txt | 8 + .../_sources/widgets/histogramlutwidget.txt | 8 + .../build/html/_sources/widgets/imageview.txt | 8 + .../build/html/_sources/widgets/index.txt | 31 + .../html/_sources/widgets/joystickbutton.txt | 8 + .../html/_sources/widgets/multiplotwidget.txt | 8 + .../html/_sources/widgets/parametertree.txt | 5 + .../html/_sources/widgets/plotwidget.txt | 8 + .../html/_sources/widgets/progressdialog.txt | 8 + .../html/_sources/widgets/rawimagewidget.txt | 8 + .../build/html/_sources/widgets/spinbox.txt | 8 + .../html/_sources/widgets/tablewidget.txt | 8 + .../html/_sources/widgets/treewidget.txt | 8 + .../html/_sources/widgets/verticallabel.txt | 8 + documentation/build/html/_static/basic.css | 509 + documentation/build/html/_static/default.css | 255 + documentation/build/html/_static/doctools.js | 247 + documentation/build/html/_static/file.png | Bin 0 -> 392 bytes documentation/build/html/_static/jquery.js | 8176 +++++++++++++++++ documentation/build/html/_static/minus.png | Bin 0 -> 199 bytes documentation/build/html/_static/plus.png | Bin 0 -> 199 bytes documentation/build/html/_static/pygments.css | 61 + .../build/html/_static/searchtools.js | 518 ++ documentation/build/html/_static/sidebar.js | 147 + .../build/html/_static/underscore.js | 16 + documentation/build/html/apireference.html | 178 + documentation/build/html/functions.html | 341 + documentation/build/html/genindex.html | 457 + .../build/html/graphicsItems/arrowitem.html | 132 + .../build/html/graphicsItems/axisitem.html | 154 + .../build/html/graphicsItems/buttonitem.html | 131 + .../build/html/graphicsItems/curvearrow.html | 132 + .../build/html/graphicsItems/curvepoint.html | 136 + .../graphicsItems/gradienteditoritem.html | 136 + .../html/graphicsItems/gradientlegend.html | 138 + .../html/graphicsItems/graphicslayout.html | 144 + .../html/graphicsItems/graphicsobject.html | 189 + .../html/graphicsItems/graphicswidget.html | 132 + .../build/html/graphicsItems/griditem.html | 132 + .../html/graphicsItems/histogramlutitem.html | 130 + .../build/html/graphicsItems/imageitem.html | 184 + .../build/html/graphicsItems/index.html | 149 + .../html/graphicsItems/infiniteline.html | 155 + .../build/html/graphicsItems/labelitem.html | 154 + .../html/graphicsItems/linearregionitem.html | 138 + .../html/graphicsItems/plotcurveitem.html | 141 + .../html/graphicsItems/plotdataitem.html | 289 + .../build/html/graphicsItems/plotitem.html | 245 + .../build/html/graphicsItems/roi.html | 185 + .../build/html/graphicsItems/scalebar.html | 131 + .../html/graphicsItems/scatterplotitem.html | 171 + .../html/graphicsItems/uigraphicsitem.html | 179 + .../build/html/graphicsItems/viewbox.html | 227 + .../build/html/graphicsItems/vtickgroup.html | 132 + documentation/build/html/graphicswindow.html | 123 + documentation/build/html/how_to_use.html | 161 + documentation/build/html/images.html | 135 + documentation/build/html/index.html | 160 + documentation/build/html/introduction.html | 163 + documentation/build/html/objects.inv | Bin 0 -> 2225 bytes documentation/build/html/parametertree.html | 123 + documentation/build/html/plotting.html | 217 + documentation/build/html/py-modindex.html | 118 + .../build/html/region_of_interest.html | 140 + documentation/build/html/search.html | 102 + documentation/build/html/searchindex.js | 1 + documentation/build/html/style.html | 127 + .../build/html/widgets/checktable.html | 130 + .../build/html/widgets/colorbutton.html | 130 + .../build/html/widgets/datatreewidget.html | 138 + .../build/html/widgets/dockarea.html | 120 + .../build/html/widgets/filedialog.html | 130 + .../build/html/widgets/gradientwidget.html | 130 + .../html/widgets/graphicslayoutwidget.html | 130 + .../build/html/widgets/graphicsview.html | 162 + .../html/widgets/histogramlutwidget.html | 130 + .../build/html/widgets/imageview.html | 158 + documentation/build/html/widgets/index.html | 144 + .../build/html/widgets/joystickbutton.html | 130 + .../build/html/widgets/multiplotwidget.html | 131 + .../build/html/widgets/parametertree.html | 120 + .../build/html/widgets/plotwidget.html | 131 + .../build/html/widgets/progressdialog.html | 153 + .../build/html/widgets/rawimagewidget.html | 132 + documentation/build/html/widgets/spinbox.html | 164 + .../build/html/widgets/tablewidget.html | 168 + .../build/html/widgets/treewidget.html | 140 + .../build/html/widgets/verticallabel.html | 130 + documentation/make.bat | 155 + documentation/source/apireference.rst | 11 + documentation/source/conf.py | 217 + documentation/source/functions.rst | 53 + .../source/graphicsItems/arrowitem.rst | 8 + .../source/graphicsItems/axisitem.rst | 8 + .../source/graphicsItems/buttonitem.rst | 8 + .../source/graphicsItems/curvearrow.rst | 8 + .../source/graphicsItems/curvepoint.rst | 8 + .../graphicsItems/gradienteditoritem.rst | 8 + .../source/graphicsItems/gradientlegend.rst | 8 + .../source/graphicsItems/graphicslayout.rst | 8 + .../source/graphicsItems/graphicsobject.rst | 8 + .../source/graphicsItems/graphicswidget.rst | 8 + .../source/graphicsItems/griditem.rst | 8 + .../source/graphicsItems/histogramlutitem.rst | 8 + .../source/graphicsItems/imageitem.rst | 8 + documentation/source/graphicsItems/index.rst | 37 + .../source/graphicsItems/infiniteline.rst | 8 + .../source/graphicsItems/labelitem.rst | 8 + .../source/graphicsItems/linearregionitem.rst | 8 + documentation/source/graphicsItems/make | 37 + .../source/graphicsItems/plotcurveitem.rst | 8 + .../source/graphicsItems/plotdataitem.rst | 8 + .../source/graphicsItems/plotitem.rst | 7 + documentation/source/graphicsItems/roi.rst | 8 + .../source/graphicsItems/scalebar.rst | 8 + .../source/graphicsItems/scatterplotitem.rst | 8 + .../source/graphicsItems/uigraphicsitem.rst | 8 + .../source/graphicsItems/viewbox.rst | 8 + .../source/graphicsItems/vtickgroup.rst | 8 + documentation/source/graphicswindow.rst | 8 + documentation/source/how_to_use.rst | 47 + documentation/source/images.rst | 26 + .../source/images/plottingClasses.png | Bin 0 -> 68667 bytes .../source/images/plottingClasses.svg | 580 ++ documentation/source/index.rst | 32 + documentation/source/internals.rst | 9 + documentation/source/introduction.rst | 51 + documentation/source/parametertree.rst | 7 + documentation/source/plotting.rst | 73 + documentation/source/region_of_interest.rst | 19 + documentation/source/style.rst | 17 + documentation/source/widgets/checktable.rst | 8 + documentation/source/widgets/colorbutton.rst | 8 + .../source/widgets/datatreewidget.rst | 8 + documentation/source/widgets/dockarea.rst | 5 + documentation/source/widgets/filedialog.rst | 8 + .../source/widgets/gradientwidget.rst | 8 + .../source/widgets/graphicslayoutwidget.rst | 8 + documentation/source/widgets/graphicsview.rst | 8 + .../source/widgets/histogramlutwidget.rst | 8 + documentation/source/widgets/imageview.rst | 8 + documentation/source/widgets/index.rst | 31 + .../source/widgets/joystickbutton.rst | 8 + documentation/source/widgets/make | 31 + .../source/widgets/multiplotwidget.rst | 8 + .../source/widgets/parametertree.rst | 5 + documentation/source/widgets/plotwidget.rst | 8 + .../source/widgets/progressdialog.rst | 8 + .../source/widgets/rawimagewidget.rst | 8 + documentation/source/widgets/spinbox.rst | 8 + documentation/source/widgets/tablewidget.rst | 8 + documentation/source/widgets/treewidget.rst | 8 + .../source/widgets/verticallabel.rst | 8 + examples/{test_Arrow.py => Arrow.py} | 5 +- examples/CLIexample.py | 22 + examples/DataSlicing.py | 55 + examples/{test_draw.py => Draw.py} | 11 +- examples/Flowchart.py | 61 + examples/GradientEditor.py | 27 + examples/GraphicsLayout.py | 46 + examples/GraphicsScene.py | 65 + examples/HistogramLUT.py | 49 + examples/{test_ImageItem.py => ImageItem.py} | 45 +- examples/{test_ImageView.py => ImageView.py} | 0 ..._MultiPlotWidget.py => MultiPlotWidget.py} | 2 +- examples/PlotSpeedTest.py | 46 + .../{test_PlotWidget.py => PlotWidget.py} | 20 +- examples/Plotting.py | 72 + examples/{test_ROItypes.py => ROItypes.py} | 77 +- examples/ScatterPlot.py | 87 + examples/VideoSpeedTest.py | 139 + examples/VideoTemplate.py | 149 + examples/VideoTemplate.ui | 250 + examples/{test_viewBox.py => ViewBox.py} | 27 +- examples/__init__.py | 1 + examples/__main__.py | 101 + examples/exampleLoaderTemplate.py | 55 + examples/exampleLoaderTemplate.ui | 65 + examples/test_scatterPlot.py | 82 - flowchart/Flowchart.py | 920 ++ flowchart/FlowchartCtrlTemplate.py | 71 + flowchart/FlowchartCtrlTemplate.ui | 120 + flowchart/FlowchartGraphicsView.py | 109 + flowchart/FlowchartTemplate.py | 59 + flowchart/FlowchartTemplate.ui | 98 + flowchart/Node.py | 561 ++ flowchart/Terminal.py | 555 ++ flowchart/__init__.py | 4 + flowchart/eq.py | 29 + flowchart/library/Data.py | 352 + flowchart/library/Display.py | 245 + flowchart/library/EventDetection.py | 187 + flowchart/library/Filters.py | 245 + flowchart/library/Operators.py | 64 + flowchart/library/__init__.py | 100 + flowchart/library/common.py | 148 + functions.py | 548 +- functions.pyc | Bin 0 -> 19701 bytes graphicsItems.py | 2997 ------ graphicsItems/ArrowItem.py | 60 + graphicsItems/AxisItem.py | 441 + graphicsItems/ButtonItem.py | 51 + graphicsItems/CurvePoint.py | 113 + graphicsItems/GradientEditorItem.py | 624 ++ graphicsItems/GradientLegend.py | 112 + graphicsItems/GraphicsItemMethods.py | 256 + graphicsItems/GraphicsLayout.py | 97 + graphicsItems/GraphicsObject.py | 19 + graphicsItems/GraphicsWidget.py | 44 + graphicsItems/GridItem.py | 116 + graphicsItems/HistogramLUTItem.py | 178 + graphicsItems/ImageItem.old | 398 + graphicsItems/ImageItem.py | 537 ++ graphicsItems/InfiniteLine.py | 255 + graphicsItems/ItemGroup.py | 23 + graphicsItems/LabelItem.py | 91 + graphicsItems/LinearRegionItem.py | 232 + .../MultiPlotItem.py | 30 +- graphicsItems/PlotCurveItem.py | 444 + graphicsItems/PlotDataItem.py | 534 ++ graphicsItems/PlotItem/PlotItem.py | 1389 +++ graphicsItems/PlotItem/__init__.py | 1 + graphicsItems/PlotItem/auto.png | Bin 0 -> 1022 bytes graphicsItems/PlotItem/ctrl.png | Bin 0 -> 934 bytes graphicsItems/PlotItem/icons.svg | 135 + graphicsItems/PlotItem/lock.png | Bin 0 -> 913 bytes graphicsItems/PlotItem/plotConfigTemplate.py | 130 + graphicsItems/PlotItem/plotConfigTemplate.ui | 258 + widgets.py => graphicsItems/ROI.py | 719 +- graphicsItems/ScaleBar.py | 50 + graphicsItems/ScatterPlotItem.py | 377 + graphicsItems/UIGraphicsItem.py | 127 + graphicsItems/VTickGroup.py | 154 + graphicsItems/ViewBox.pyc.renamed1 | Bin 0 -> 22064 bytes graphicsItems/ViewBox/ViewBox.py | 978 ++ graphicsItems/ViewBox/ViewBoxMenu.py | 222 + graphicsItems/ViewBox/__init__.py | 1 + graphicsItems/ViewBox/axisCtrlTemplate.py | 73 + graphicsItems/ViewBox/axisCtrlTemplate.ui | 110 + graphicsItems/__init__.py | 21 + graphicsWindows.py | 62 +- graphicsWindows.pyc | Bin 0 -> 4242 bytes ImageView.py => imageview/ImageView.py | 212 +- .../ImageViewTemplate.py | 69 +- imageview/ImageViewTemplate.ui | 252 + imageview/__init__.py | 6 + parametertree/Parameter.py | 465 + parametertree/ParameterItem.py | 148 + parametertree/ParameterTree.py | 108 + parametertree/__init__.py | 5 + parametertree/__main__.py | 140 + parametertree/default.png | Bin 0 -> 810 bytes parametertree/parameterTypes.py | 480 + plotConfigTemplate.py | 295 - plotConfigTemplate.ui | 563 -- ptime.pyc | Bin 0 -> 1273 bytes widgets/CheckTable.py | 89 + ColorButton.py => widgets/ColorButton.py | 22 +- widgets/DataTreeWidget.py | 106 + widgets/FileDialog.py | 14 + widgets/GradientWidget.py | 620 ++ widgets/GraphicsLayoutWidget.py | 12 + GraphicsView.py => widgets/GraphicsView.py | 170 +- widgets/HistogramLUTWidget.py | 33 + widgets/JoystickButton.py | 90 + .../MultiPlotWidget.py | 7 +- PlotWidget.py => widgets/PlotWidget.py | 4 +- widgets/ProgressDialog.py | 105 + widgets/RawImageWidget.py | 79 + widgets/SpinBox.py | 481 + widgets/TableWidget.py | 249 + widgets/TreeWidget.py | 194 + widgets/VerticalLabel.py | 99 + widgets/__init__.py | 21 + 415 files changed, 45249 insertions(+), 6649 deletions(-) delete mode 100644 GradientWidget.py create mode 100644 GraphicsScene.py create mode 100644 GraphicsScene.pyc delete mode 100644 ImageViewTemplate.ui delete mode 100644 PlotItem.py create mode 100644 Point.pyc create mode 100644 Qt.py create mode 100644 Qt.pyc create mode 100644 SignalProxy.pyc create mode 100644 Transform.pyc create mode 100644 WidgetGroup.py create mode 100644 WidgetGroup.pyc create mode 100644 __init__.pyc create mode 100644 canvas/Canvas.py create mode 100644 canvas/CanvasItem.py create mode 100644 canvas/CanvasManager.py create mode 100644 canvas/CanvasTemplate.py create mode 100644 canvas/CanvasTemplate.ui create mode 100644 canvas/TransformGuiTemplate.py create mode 100644 canvas/TransformGuiTemplate.ui create mode 100644 canvas/__init__.py create mode 100644 debug.pyc create mode 100644 dockarea/Container.py create mode 100644 dockarea/Dock.py create mode 100644 dockarea/DockArea.py create mode 100644 dockarea/DockDrop.py create mode 100644 dockarea/__init__.py create mode 100644 dockarea/__main__.py create mode 100644 documentation/Makefile create mode 100644 documentation/build/doctrees/apireference.doctree create mode 100644 documentation/build/doctrees/environment.pickle create mode 100644 documentation/build/doctrees/functions.doctree create mode 100644 documentation/build/doctrees/graphicsItems/arrowitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/axisitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/buttonitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/curvearrow.doctree create mode 100644 documentation/build/doctrees/graphicsItems/curvepoint.doctree create mode 100644 documentation/build/doctrees/graphicsItems/gradienteditoritem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/gradientlegend.doctree create mode 100644 documentation/build/doctrees/graphicsItems/graphicslayout.doctree create mode 100644 documentation/build/doctrees/graphicsItems/graphicsobject.doctree create mode 100644 documentation/build/doctrees/graphicsItems/graphicswidget.doctree create mode 100644 documentation/build/doctrees/graphicsItems/griditem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/histogramlutitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/imageitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/index.doctree create mode 100644 documentation/build/doctrees/graphicsItems/infiniteline.doctree create mode 100644 documentation/build/doctrees/graphicsItems/labelitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/linearregionitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/plotcurveitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/plotdataitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/plotitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/roi.doctree create mode 100644 documentation/build/doctrees/graphicsItems/scalebar.doctree create mode 100644 documentation/build/doctrees/graphicsItems/scatterplotitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/uigraphicsitem.doctree create mode 100644 documentation/build/doctrees/graphicsItems/viewbox.doctree create mode 100644 documentation/build/doctrees/graphicsItems/vtickgroup.doctree create mode 100644 documentation/build/doctrees/graphicswindow.doctree create mode 100644 documentation/build/doctrees/how_to_use.doctree create mode 100644 documentation/build/doctrees/images.doctree create mode 100644 documentation/build/doctrees/index.doctree create mode 100644 documentation/build/doctrees/introduction.doctree create mode 100644 documentation/build/doctrees/parametertree.doctree create mode 100644 documentation/build/doctrees/plotting.doctree create mode 100644 documentation/build/doctrees/region_of_interest.doctree create mode 100644 documentation/build/doctrees/style.doctree create mode 100644 documentation/build/doctrees/widgets/checktable.doctree create mode 100644 documentation/build/doctrees/widgets/colorbutton.doctree create mode 100644 documentation/build/doctrees/widgets/datatreewidget.doctree create mode 100644 documentation/build/doctrees/widgets/dockarea.doctree create mode 100644 documentation/build/doctrees/widgets/filedialog.doctree create mode 100644 documentation/build/doctrees/widgets/gradientwidget.doctree create mode 100644 documentation/build/doctrees/widgets/graphicslayoutwidget.doctree create mode 100644 documentation/build/doctrees/widgets/graphicsview.doctree create mode 100644 documentation/build/doctrees/widgets/histogramlutwidget.doctree create mode 100644 documentation/build/doctrees/widgets/imageview.doctree create mode 100644 documentation/build/doctrees/widgets/index.doctree create mode 100644 documentation/build/doctrees/widgets/joystickbutton.doctree create mode 100644 documentation/build/doctrees/widgets/multiplotwidget.doctree create mode 100644 documentation/build/doctrees/widgets/parametertree.doctree create mode 100644 documentation/build/doctrees/widgets/plotwidget.doctree create mode 100644 documentation/build/doctrees/widgets/progressdialog.doctree create mode 100644 documentation/build/doctrees/widgets/rawimagewidget.doctree create mode 100644 documentation/build/doctrees/widgets/spinbox.doctree create mode 100644 documentation/build/doctrees/widgets/tablewidget.doctree create mode 100644 documentation/build/doctrees/widgets/treewidget.doctree create mode 100644 documentation/build/doctrees/widgets/verticallabel.doctree create mode 100644 documentation/build/html/.buildinfo create mode 100644 documentation/build/html/_images/plottingClasses.png create mode 100644 documentation/build/html/_modules/index.html create mode 100644 documentation/build/html/_modules/pyqtgraph.html create mode 100644 documentation/build/html/_sources/apireference.txt create mode 100644 documentation/build/html/_sources/functions.txt create mode 100644 documentation/build/html/_sources/graphicsItems/arrowitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/axisitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/buttonitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/curvearrow.txt create mode 100644 documentation/build/html/_sources/graphicsItems/curvepoint.txt create mode 100644 documentation/build/html/_sources/graphicsItems/gradienteditoritem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/gradientlegend.txt create mode 100644 documentation/build/html/_sources/graphicsItems/graphicslayout.txt create mode 100644 documentation/build/html/_sources/graphicsItems/graphicsobject.txt create mode 100644 documentation/build/html/_sources/graphicsItems/graphicswidget.txt create mode 100644 documentation/build/html/_sources/graphicsItems/griditem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/histogramlutitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/imageitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/index.txt create mode 100644 documentation/build/html/_sources/graphicsItems/infiniteline.txt create mode 100644 documentation/build/html/_sources/graphicsItems/labelitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/linearregionitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/plotcurveitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/plotdataitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/plotitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/roi.txt create mode 100644 documentation/build/html/_sources/graphicsItems/scalebar.txt create mode 100644 documentation/build/html/_sources/graphicsItems/scatterplotitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/uigraphicsitem.txt create mode 100644 documentation/build/html/_sources/graphicsItems/viewbox.txt create mode 100644 documentation/build/html/_sources/graphicsItems/vtickgroup.txt create mode 100644 documentation/build/html/_sources/graphicswindow.txt create mode 100644 documentation/build/html/_sources/how_to_use.txt create mode 100644 documentation/build/html/_sources/images.txt create mode 100644 documentation/build/html/_sources/index.txt create mode 100644 documentation/build/html/_sources/introduction.txt create mode 100644 documentation/build/html/_sources/parametertree.txt create mode 100644 documentation/build/html/_sources/plotting.txt create mode 100644 documentation/build/html/_sources/region_of_interest.txt create mode 100644 documentation/build/html/_sources/style.txt create mode 100644 documentation/build/html/_sources/widgets/checktable.txt create mode 100644 documentation/build/html/_sources/widgets/colorbutton.txt create mode 100644 documentation/build/html/_sources/widgets/datatreewidget.txt create mode 100644 documentation/build/html/_sources/widgets/dockarea.txt create mode 100644 documentation/build/html/_sources/widgets/filedialog.txt create mode 100644 documentation/build/html/_sources/widgets/gradientwidget.txt create mode 100644 documentation/build/html/_sources/widgets/graphicslayoutwidget.txt create mode 100644 documentation/build/html/_sources/widgets/graphicsview.txt create mode 100644 documentation/build/html/_sources/widgets/histogramlutwidget.txt create mode 100644 documentation/build/html/_sources/widgets/imageview.txt create mode 100644 documentation/build/html/_sources/widgets/index.txt create mode 100644 documentation/build/html/_sources/widgets/joystickbutton.txt create mode 100644 documentation/build/html/_sources/widgets/multiplotwidget.txt create mode 100644 documentation/build/html/_sources/widgets/parametertree.txt create mode 100644 documentation/build/html/_sources/widgets/plotwidget.txt create mode 100644 documentation/build/html/_sources/widgets/progressdialog.txt create mode 100644 documentation/build/html/_sources/widgets/rawimagewidget.txt create mode 100644 documentation/build/html/_sources/widgets/spinbox.txt create mode 100644 documentation/build/html/_sources/widgets/tablewidget.txt create mode 100644 documentation/build/html/_sources/widgets/treewidget.txt create mode 100644 documentation/build/html/_sources/widgets/verticallabel.txt create mode 100644 documentation/build/html/_static/basic.css create mode 100644 documentation/build/html/_static/default.css create mode 100644 documentation/build/html/_static/doctools.js create mode 100644 documentation/build/html/_static/file.png create mode 100644 documentation/build/html/_static/jquery.js create mode 100644 documentation/build/html/_static/minus.png create mode 100644 documentation/build/html/_static/plus.png create mode 100644 documentation/build/html/_static/pygments.css create mode 100644 documentation/build/html/_static/searchtools.js create mode 100644 documentation/build/html/_static/sidebar.js create mode 100644 documentation/build/html/_static/underscore.js create mode 100644 documentation/build/html/apireference.html create mode 100644 documentation/build/html/functions.html create mode 100644 documentation/build/html/genindex.html create mode 100644 documentation/build/html/graphicsItems/arrowitem.html create mode 100644 documentation/build/html/graphicsItems/axisitem.html create mode 100644 documentation/build/html/graphicsItems/buttonitem.html create mode 100644 documentation/build/html/graphicsItems/curvearrow.html create mode 100644 documentation/build/html/graphicsItems/curvepoint.html create mode 100644 documentation/build/html/graphicsItems/gradienteditoritem.html create mode 100644 documentation/build/html/graphicsItems/gradientlegend.html create mode 100644 documentation/build/html/graphicsItems/graphicslayout.html create mode 100644 documentation/build/html/graphicsItems/graphicsobject.html create mode 100644 documentation/build/html/graphicsItems/graphicswidget.html create mode 100644 documentation/build/html/graphicsItems/griditem.html create mode 100644 documentation/build/html/graphicsItems/histogramlutitem.html create mode 100644 documentation/build/html/graphicsItems/imageitem.html create mode 100644 documentation/build/html/graphicsItems/index.html create mode 100644 documentation/build/html/graphicsItems/infiniteline.html create mode 100644 documentation/build/html/graphicsItems/labelitem.html create mode 100644 documentation/build/html/graphicsItems/linearregionitem.html create mode 100644 documentation/build/html/graphicsItems/plotcurveitem.html create mode 100644 documentation/build/html/graphicsItems/plotdataitem.html create mode 100644 documentation/build/html/graphicsItems/plotitem.html create mode 100644 documentation/build/html/graphicsItems/roi.html create mode 100644 documentation/build/html/graphicsItems/scalebar.html create mode 100644 documentation/build/html/graphicsItems/scatterplotitem.html create mode 100644 documentation/build/html/graphicsItems/uigraphicsitem.html create mode 100644 documentation/build/html/graphicsItems/viewbox.html create mode 100644 documentation/build/html/graphicsItems/vtickgroup.html create mode 100644 documentation/build/html/graphicswindow.html create mode 100644 documentation/build/html/how_to_use.html create mode 100644 documentation/build/html/images.html create mode 100644 documentation/build/html/index.html create mode 100644 documentation/build/html/introduction.html create mode 100644 documentation/build/html/objects.inv create mode 100644 documentation/build/html/parametertree.html create mode 100644 documentation/build/html/plotting.html create mode 100644 documentation/build/html/py-modindex.html create mode 100644 documentation/build/html/region_of_interest.html create mode 100644 documentation/build/html/search.html create mode 100644 documentation/build/html/searchindex.js create mode 100644 documentation/build/html/style.html create mode 100644 documentation/build/html/widgets/checktable.html create mode 100644 documentation/build/html/widgets/colorbutton.html create mode 100644 documentation/build/html/widgets/datatreewidget.html create mode 100644 documentation/build/html/widgets/dockarea.html create mode 100644 documentation/build/html/widgets/filedialog.html create mode 100644 documentation/build/html/widgets/gradientwidget.html create mode 100644 documentation/build/html/widgets/graphicslayoutwidget.html create mode 100644 documentation/build/html/widgets/graphicsview.html create mode 100644 documentation/build/html/widgets/histogramlutwidget.html create mode 100644 documentation/build/html/widgets/imageview.html create mode 100644 documentation/build/html/widgets/index.html create mode 100644 documentation/build/html/widgets/joystickbutton.html create mode 100644 documentation/build/html/widgets/multiplotwidget.html create mode 100644 documentation/build/html/widgets/parametertree.html create mode 100644 documentation/build/html/widgets/plotwidget.html create mode 100644 documentation/build/html/widgets/progressdialog.html create mode 100644 documentation/build/html/widgets/rawimagewidget.html create mode 100644 documentation/build/html/widgets/spinbox.html create mode 100644 documentation/build/html/widgets/tablewidget.html create mode 100644 documentation/build/html/widgets/treewidget.html create mode 100644 documentation/build/html/widgets/verticallabel.html create mode 100644 documentation/make.bat create mode 100644 documentation/source/apireference.rst create mode 100644 documentation/source/conf.py create mode 100644 documentation/source/functions.rst create mode 100644 documentation/source/graphicsItems/arrowitem.rst create mode 100644 documentation/source/graphicsItems/axisitem.rst create mode 100644 documentation/source/graphicsItems/buttonitem.rst create mode 100644 documentation/source/graphicsItems/curvearrow.rst create mode 100644 documentation/source/graphicsItems/curvepoint.rst create mode 100644 documentation/source/graphicsItems/gradienteditoritem.rst create mode 100644 documentation/source/graphicsItems/gradientlegend.rst create mode 100644 documentation/source/graphicsItems/graphicslayout.rst create mode 100644 documentation/source/graphicsItems/graphicsobject.rst create mode 100644 documentation/source/graphicsItems/graphicswidget.rst create mode 100644 documentation/source/graphicsItems/griditem.rst create mode 100644 documentation/source/graphicsItems/histogramlutitem.rst create mode 100644 documentation/source/graphicsItems/imageitem.rst create mode 100644 documentation/source/graphicsItems/index.rst create mode 100644 documentation/source/graphicsItems/infiniteline.rst create mode 100644 documentation/source/graphicsItems/labelitem.rst create mode 100644 documentation/source/graphicsItems/linearregionitem.rst create mode 100644 documentation/source/graphicsItems/make create mode 100644 documentation/source/graphicsItems/plotcurveitem.rst create mode 100644 documentation/source/graphicsItems/plotdataitem.rst create mode 100644 documentation/source/graphicsItems/plotitem.rst create mode 100644 documentation/source/graphicsItems/roi.rst create mode 100644 documentation/source/graphicsItems/scalebar.rst create mode 100644 documentation/source/graphicsItems/scatterplotitem.rst create mode 100644 documentation/source/graphicsItems/uigraphicsitem.rst create mode 100644 documentation/source/graphicsItems/viewbox.rst create mode 100644 documentation/source/graphicsItems/vtickgroup.rst create mode 100644 documentation/source/graphicswindow.rst create mode 100644 documentation/source/how_to_use.rst create mode 100644 documentation/source/images.rst create mode 100644 documentation/source/images/plottingClasses.png create mode 100644 documentation/source/images/plottingClasses.svg create mode 100644 documentation/source/index.rst create mode 100644 documentation/source/internals.rst create mode 100644 documentation/source/introduction.rst create mode 100644 documentation/source/parametertree.rst create mode 100644 documentation/source/plotting.rst create mode 100644 documentation/source/region_of_interest.rst create mode 100644 documentation/source/style.rst create mode 100644 documentation/source/widgets/checktable.rst create mode 100644 documentation/source/widgets/colorbutton.rst create mode 100644 documentation/source/widgets/datatreewidget.rst create mode 100644 documentation/source/widgets/dockarea.rst create mode 100644 documentation/source/widgets/filedialog.rst create mode 100644 documentation/source/widgets/gradientwidget.rst create mode 100644 documentation/source/widgets/graphicslayoutwidget.rst create mode 100644 documentation/source/widgets/graphicsview.rst create mode 100644 documentation/source/widgets/histogramlutwidget.rst create mode 100644 documentation/source/widgets/imageview.rst create mode 100644 documentation/source/widgets/index.rst create mode 100644 documentation/source/widgets/joystickbutton.rst create mode 100644 documentation/source/widgets/make create mode 100644 documentation/source/widgets/multiplotwidget.rst create mode 100644 documentation/source/widgets/parametertree.rst create mode 100644 documentation/source/widgets/plotwidget.rst create mode 100644 documentation/source/widgets/progressdialog.rst create mode 100644 documentation/source/widgets/rawimagewidget.rst create mode 100644 documentation/source/widgets/spinbox.rst create mode 100644 documentation/source/widgets/tablewidget.rst create mode 100644 documentation/source/widgets/treewidget.rst create mode 100644 documentation/source/widgets/verticallabel.rst rename examples/{test_Arrow.py => Arrow.py} (77%) create mode 100644 examples/CLIexample.py create mode 100644 examples/DataSlicing.py rename examples/{test_draw.py => Draw.py} (80%) mode change 100755 => 100644 create mode 100644 examples/Flowchart.py create mode 100644 examples/GradientEditor.py create mode 100755 examples/GraphicsLayout.py create mode 100644 examples/GraphicsScene.py create mode 100644 examples/HistogramLUT.py rename examples/{test_ImageItem.py => ImageItem.py} (53%) mode change 100755 => 100644 rename examples/{test_ImageView.py => ImageView.py} (100%) mode change 100755 => 100644 rename examples/{test_MultiPlotWidget.py => MultiPlotWidget.py} (94%) mode change 100755 => 100644 create mode 100644 examples/PlotSpeedTest.py rename examples/{test_PlotWidget.py => PlotWidget.py} (76%) mode change 100755 => 100644 create mode 100644 examples/Plotting.py rename examples/{test_ROItypes.py => ROItypes.py} (58%) mode change 100755 => 100644 create mode 100755 examples/ScatterPlot.py create mode 100644 examples/VideoSpeedTest.py create mode 100644 examples/VideoTemplate.py create mode 100644 examples/VideoTemplate.ui rename examples/{test_viewBox.py => ViewBox.py} (83%) create mode 100644 examples/__init__.py create mode 100644 examples/__main__.py create mode 100644 examples/exampleLoaderTemplate.py create mode 100644 examples/exampleLoaderTemplate.ui delete mode 100755 examples/test_scatterPlot.py create mode 100644 flowchart/Flowchart.py create mode 100644 flowchart/FlowchartCtrlTemplate.py create mode 100644 flowchart/FlowchartCtrlTemplate.ui create mode 100644 flowchart/FlowchartGraphicsView.py create mode 100644 flowchart/FlowchartTemplate.py create mode 100644 flowchart/FlowchartTemplate.ui create mode 100644 flowchart/Node.py create mode 100644 flowchart/Terminal.py create mode 100644 flowchart/__init__.py create mode 100644 flowchart/eq.py create mode 100644 flowchart/library/Data.py create mode 100644 flowchart/library/Display.py create mode 100644 flowchart/library/EventDetection.py create mode 100644 flowchart/library/Filters.py create mode 100644 flowchart/library/Operators.py create mode 100644 flowchart/library/__init__.py create mode 100644 flowchart/library/common.py create mode 100644 functions.pyc delete mode 100644 graphicsItems.py create mode 100644 graphicsItems/ArrowItem.py create mode 100644 graphicsItems/AxisItem.py create mode 100644 graphicsItems/ButtonItem.py create mode 100644 graphicsItems/CurvePoint.py create mode 100644 graphicsItems/GradientEditorItem.py create mode 100644 graphicsItems/GradientLegend.py create mode 100644 graphicsItems/GraphicsItemMethods.py create mode 100644 graphicsItems/GraphicsLayout.py create mode 100644 graphicsItems/GraphicsObject.py create mode 100644 graphicsItems/GraphicsWidget.py create mode 100644 graphicsItems/GridItem.py create mode 100644 graphicsItems/HistogramLUTItem.py create mode 100644 graphicsItems/ImageItem.old create mode 100644 graphicsItems/ImageItem.py create mode 100644 graphicsItems/InfiniteLine.py create mode 100644 graphicsItems/ItemGroup.py create mode 100644 graphicsItems/LabelItem.py create mode 100644 graphicsItems/LinearRegionItem.py rename MultiPlotItem.py => graphicsItems/MultiPlotItem.py (74%) create mode 100644 graphicsItems/PlotCurveItem.py create mode 100644 graphicsItems/PlotDataItem.py create mode 100644 graphicsItems/PlotItem/PlotItem.py create mode 100644 graphicsItems/PlotItem/__init__.py create mode 100644 graphicsItems/PlotItem/auto.png create mode 100644 graphicsItems/PlotItem/ctrl.png create mode 100644 graphicsItems/PlotItem/icons.svg create mode 100644 graphicsItems/PlotItem/lock.png create mode 100644 graphicsItems/PlotItem/plotConfigTemplate.py create mode 100644 graphicsItems/PlotItem/plotConfigTemplate.ui rename widgets.py => graphicsItems/ROI.py (67%) create mode 100644 graphicsItems/ScaleBar.py create mode 100644 graphicsItems/ScatterPlotItem.py create mode 100644 graphicsItems/UIGraphicsItem.py create mode 100644 graphicsItems/VTickGroup.py create mode 100644 graphicsItems/ViewBox.pyc.renamed1 create mode 100644 graphicsItems/ViewBox/ViewBox.py create mode 100644 graphicsItems/ViewBox/ViewBoxMenu.py create mode 100644 graphicsItems/ViewBox/__init__.py create mode 100644 graphicsItems/ViewBox/axisCtrlTemplate.py create mode 100644 graphicsItems/ViewBox/axisCtrlTemplate.ui create mode 100644 graphicsItems/__init__.py create mode 100644 graphicsWindows.pyc rename ImageView.py => imageview/ImageView.py (76%) rename ImageViewTemplate.py => imageview/ImageViewTemplate.py (80%) create mode 100644 imageview/ImageViewTemplate.ui create mode 100644 imageview/__init__.py create mode 100644 parametertree/Parameter.py create mode 100644 parametertree/ParameterItem.py create mode 100644 parametertree/ParameterTree.py create mode 100644 parametertree/__init__.py create mode 100644 parametertree/__main__.py create mode 100644 parametertree/default.png create mode 100644 parametertree/parameterTypes.py delete mode 100644 plotConfigTemplate.py delete mode 100644 plotConfigTemplate.ui create mode 100644 ptime.pyc create mode 100644 widgets/CheckTable.py rename ColorButton.py => widgets/ColorButton.py (83%) create mode 100644 widgets/DataTreeWidget.py create mode 100644 widgets/FileDialog.py create mode 100644 widgets/GradientWidget.py create mode 100644 widgets/GraphicsLayoutWidget.py rename GraphicsView.py => widgets/GraphicsView.py (81%) create mode 100644 widgets/HistogramLUTWidget.py create mode 100644 widgets/JoystickButton.py rename MultiPlotWidget.py => widgets/MultiPlotWidget.py (88%) rename PlotWidget.py => widgets/PlotWidget.py (95%) create mode 100644 widgets/ProgressDialog.py create mode 100644 widgets/RawImageWidget.py create mode 100644 widgets/SpinBox.py create mode 100644 widgets/TableWidget.py create mode 100644 widgets/TreeWidget.py create mode 100644 widgets/VerticalLabel.py create mode 100644 widgets/__init__.py diff --git a/GradientWidget.py b/GradientWidget.py deleted file mode 100644 index 033c62db..00000000 --- a/GradientWidget.py +++ /dev/null @@ -1,452 +0,0 @@ -# -*- coding: utf-8 -*- -from PyQt4 import QtGui, QtCore -import weakref - -class TickSlider(QtGui.QGraphicsView): - def __init__(self, parent=None, orientation='bottom', allowAdd=True, **kargs): - QtGui.QGraphicsView.__init__(self, parent) - #self.orientation = orientation - self.allowAdd = allowAdd - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor) - self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) - self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing) - self.length = 100 - self.tickSize = 15 - self.orientations = { - 'left': (270, 1, -1), - 'right': (270, 1, 1), - 'top': (0, 1, -1), - 'bottom': (0, 1, 1) - } - - self.scene = QtGui.QGraphicsScene() - self.setScene(self.scene) - - self.ticks = {} - self.maxDim = 20 - self.setOrientation(orientation) - self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) - self.setBackgroundRole(QtGui.QPalette.NoRole) - self.setMouseTracking(True) - - def keyPressEvent(self, ev): - ev.ignore() - - def setMaxDim(self, mx=None): - if mx is None: - mx = self.maxDim - else: - self.maxDim = mx - - if self.orientation in ['bottom', 'top']: - self.setFixedHeight(mx) - self.setMaximumWidth(16777215) - else: - self.setFixedWidth(mx) - self.setMaximumHeight(16777215) - - def setOrientation(self, ort): - self.orientation = ort - self.resetTransform() - self.rotate(self.orientations[ort][0]) - self.scale(*self.orientations[ort][1:]) - self.setMaxDim() - - def addTick(self, x, color=None, movable=True): - if color is None: - color = QtGui.QColor(255,255,255) - tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize) - self.ticks[tick] = x - self.scene.addItem(tick) - return tick - - def removeTick(self, tick): - del self.ticks[tick] - self.scene.removeItem(tick) - - def tickMoved(self, tick, pos): - #print "tick changed" - ## Correct position of tick if it has left bounds. - newX = min(max(0, pos.x()), self.length) - pos.setX(newX) - tick.setPos(pos) - self.ticks[tick] = float(newX) / self.length - - def tickClicked(self, tick, ev): - if ev.button() == QtCore.Qt.RightButton: - self.removeTick(tick) - - def widgetLength(self): - if self.orientation in ['bottom', 'top']: - return self.width() - else: - return self.height() - - def resizeEvent(self, ev): - wlen = max(40, self.widgetLength()) - self.setLength(wlen-self.tickSize) - bounds = self.scene.itemsBoundingRect() - bounds.setLeft(min(-self.tickSize*0.5, bounds.left())) - bounds.setRight(max(self.length + self.tickSize, bounds.right())) - #bounds.setTop(min(bounds.top(), self.tickSize)) - #bounds.setBottom(max(0, bounds.bottom())) - self.setSceneRect(bounds) - self.fitInView(bounds, QtCore.Qt.KeepAspectRatio) - - def setLength(self, newLen): - for t, x in self.ticks.items(): - t.setPos(x * newLen, t.pos().y()) - self.length = float(newLen) - - def mousePressEvent(self, ev): - QtGui.QGraphicsView.mousePressEvent(self, ev) - self.ignoreRelease = False - if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks - self.ignoreRelease = True - - def mouseReleaseEvent(self, ev): - QtGui.QGraphicsView.mouseReleaseEvent(self, ev) - if self.ignoreRelease: - return - - pos = self.mapToScene(ev.pos()) - if pos.x() < 0 or pos.x() > self.length: - return - if pos.y() < 0 or pos.y() > self.tickSize: - return - - if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: - pos.setX(min(max(pos.x(), 0), self.length)) - self.addTick(pos.x()/self.length) - elif ev.button() == QtCore.Qt.RightButton: - self.showMenu(ev) - - - def showMenu(self, ev): - pass - - def setTickColor(self, tick, color): - tick = self.getTick(tick) - tick.color = color - tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color))) - - def setTickValue(self, tick, val): - tick = self.getTick(tick) - val = min(max(0.0, val), 1.0) - x = val * self.length - pos = tick.pos() - pos.setX(x) - tick.setPos(pos) - self.ticks[tick] = val - - def tickValue(self, tick): - tick = self.getTick(tick) - return self.ticks[tick] - - def getTick(self, tick): - if type(tick) is int: - tick = self.listTicks()[tick][0] - return tick - - def mouseMoveEvent(self, ev): - QtGui.QGraphicsView.mouseMoveEvent(self, ev) - #print ev.pos(), ev.buttons() - - def listTicks(self): - ticks = self.ticks.items() - ticks.sort(lambda a,b: cmp(a[1], b[1])) - return ticks - - -class GradientWidget(TickSlider): - - sigGradientChanged = QtCore.Signal(object) - - def __init__(self, *args, **kargs): - TickSlider.__init__(self, *args, **kargs) - self.currentTick = None - self.currentTickColor = None - self.rectSize = 15 - self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) - self.colorMode = 'rgb' - self.colorDialog = QtGui.QColorDialog() - self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) - self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) - #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged) - self.colorDialog.currentColorChanged.connect(self.currentColorChanged) - #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected) - self.colorDialog.rejected.connect(self.currentColorRejected) - - #self.gradient = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(100,0)) - self.scene.addItem(self.gradRect) - self.addTick(0, QtGui.QColor(0,0,0), True) - self.addTick(1, QtGui.QColor(255,0,0), True) - - self.setMaxDim(self.rectSize + self.tickSize) - self.updateGradient() - - #self.btn = QtGui.QPushButton('RGB') - #self.btnProxy = self.scene.addWidget(self.btn) - #self.btnProxy.setFlag(self.btnProxy.ItemIgnoresTransformations) - #self.btnProxy.scale(0.7, 0.7) - #self.btnProxy.translate(-self.btnProxy.sceneBoundingRect().width()+self.tickSize/2., 0) - #if self.orientation == 'bottom': - #self.btnProxy.translate(0, -self.rectSize) - - def setColorMode(self, cm): - if cm not in ['rgb', 'hsv']: - raise Exception("Unknown color mode %s" % str(cm)) - self.colorMode = cm - self.updateGradient() - - def updateGradient(self): - self.gradient = self.getGradient() - self.gradRect.setBrush(QtGui.QBrush(self.gradient)) - #self.emit(QtCore.SIGNAL('gradientChanged'), self) - self.sigGradientChanged.emit(self) - - def setLength(self, newLen): - TickSlider.setLength(self, newLen) - self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize) - self.updateGradient() - - def currentColorChanged(self, color): - if color.isValid() and self.currentTick is not None: - self.setTickColor(self.currentTick, color) - self.updateGradient() - - def currentColorRejected(self): - self.setTickColor(self.currentTick, self.currentTickColor) - self.updateGradient() - - def tickClicked(self, tick, ev): - if ev.button() == QtCore.Qt.LeftButton: - if not tick.colorChangeAllowed: - return - self.currentTick = tick - self.currentTickColor = tick.color - self.colorDialog.setCurrentColor(tick.color) - self.colorDialog.open() - #color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) - #if color.isValid(): - #self.setTickColor(tick, color) - #self.updateGradient() - elif ev.button() == QtCore.Qt.RightButton: - if not tick.removeAllowed: - return - if len(self.ticks) > 2: - self.removeTick(tick) - self.updateGradient() - - def tickMoved(self, tick, pos): - TickSlider.tickMoved(self, tick, pos) - self.updateGradient() - - - def getGradient(self): - g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) - if self.colorMode == 'rgb': - ticks = self.listTicks() - g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) - elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop - ticks = self.listTicks() - stops = [] - stops.append((ticks[0][1], ticks[0][0].color)) - for i in range(1,len(ticks)): - x1 = ticks[i-1][1] - x2 = ticks[i][1] - dx = (x2-x1) / 10. - for j in range(1,10): - x = x1 + dx*j - stops.append((x, self.getColor(x))) - stops.append((x2, self.getColor(x2))) - g.setStops(stops) - return g - - def getColor(self, x): - ticks = self.listTicks() - if x <= ticks[0][1]: - return QtGui.QColor(ticks[0][0].color) # always copy colors before handing them out - if x >= ticks[-1][1]: - return QtGui.QColor(ticks[-1][0].color) - - x2 = ticks[0][1] - for i in range(1,len(ticks)): - x1 = x2 - x2 = ticks[i][1] - if x1 <= x and x2 >= x: - break - - dx = (x2-x1) - if dx == 0: - f = 0. - else: - f = (x-x1) / dx - c1 = ticks[i-1][0].color - c2 = ticks[i][0].color - if self.colorMode == 'rgb': - r = c1.red() * (1.-f) + c2.red() * f - g = c1.green() * (1.-f) + c2.green() * f - b = c1.blue() * (1.-f) + c2.blue() * f - a = c1.alpha() * (1.-f) + c2.alpha() * f - return QtGui.QColor(r, g, b,a) - elif self.colorMode == 'hsv': - h1,s1,v1,_ = c1.getHsv() - h2,s2,v2,_ = c2.getHsv() - h = h1 * (1.-f) + h2 * f - s = s1 * (1.-f) + s2 * f - v = v1 * (1.-f) + v2 * f - c = QtGui.QColor() - c.setHsv(h,s,v) - return c - - - - def mouseReleaseEvent(self, ev): - TickSlider.mouseReleaseEvent(self, ev) - self.updateGradient() - - def addTick(self, x, color=None, movable=True): - if color is None: - color = self.getColor(x) - t = TickSlider.addTick(self, x, color=color, movable=movable) - t.colorChangeAllowed = True - t.removeAllowed = True - return t - - def saveState(self): - ticks = [] - for t in self.ticks: - c = t.color - ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha()))) - state = {'mode': self.colorMode, 'ticks': ticks} - return state - - def restoreState(self, state): - self.setColorMode(state['mode']) - for t in self.ticks.keys(): - self.removeTick(t) - for t in state['ticks']: - c = QtGui.QColor(*t[1]) - self.addTick(t[0], c) - self.updateGradient() - - - -class BlackWhiteSlider(GradientWidget): - def __init__(self, parent): - GradientWidget.__init__(self, parent) - self.getTick(0).colorChangeAllowed = False - self.getTick(1).colorChangeAllowed = False - self.allowAdd = False - self.setTickColor(self.getTick(1), QtGui.QColor(255,255,255)) - self.setOrientation('right') - - def getLevels(self): - return (self.tickValue(0), self.tickValue(1)) - - def setLevels(self, black, white): - self.setTickValue(0, black) - self.setTickValue(1, white) - - - - -class GammaWidget(TickSlider): - pass - - -class Tick(QtGui.QGraphicsPolygonItem): - def __init__(self, view, pos, color, movable=True, scale=10): - #QObjectWorkaround.__init__(self) - self.movable = movable - self.view = weakref.ref(view) - self.scale = scale - self.color = color - #self.endTick = endTick - self.pg = QtGui.QPolygonF([QtCore.QPointF(0,0), QtCore.QPointF(-scale/3**0.5,scale), QtCore.QPointF(scale/3**0.5,scale)]) - QtGui.QGraphicsPolygonItem.__init__(self, self.pg) - self.setPos(pos[0], pos[1]) - self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemIsSelectable) - self.setBrush(QtGui.QBrush(QtGui.QColor(self.color))) - if self.movable: - self.setZValue(1) - else: - self.setZValue(0) - - #def x(self): - #return self.pos().x()/100. - - def mouseMoveEvent(self, ev): - #print self, "move", ev.scenePos() - if not self.movable: - return - if not ev.buttons() & QtCore.Qt.LeftButton: - return - - - newPos = ev.scenePos() + self.mouseOffset - newPos.setY(self.pos().y()) - #newPos.setX(min(max(newPos.x(), 0), 100)) - self.setPos(newPos) - self.view().tickMoved(self, newPos) - self.movedSincePress = True - #self.emit(QtCore.SIGNAL('tickChanged'), self) - ev.accept() - - def mousePressEvent(self, ev): - self.movedSincePress = False - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - self.mouseOffset = self.pos() - ev.scenePos() - self.pressPos = ev.scenePos() - elif ev.button() == QtCore.Qt.RightButton: - ev.accept() - #if self.endTick: - #return - #self.view.tickChanged(self, delete=True) - - def mouseReleaseEvent(self, ev): - #print self, "release", ev.scenePos() - if not self.movedSincePress: - self.view().tickClicked(self, ev) - - #if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos: - #color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) - #if color.isValid(): - #self.color = color - #self.setBrush(QtGui.QBrush(QtGui.QColor(self.color))) - ##self.emit(QtCore.SIGNAL('tickChanged'), self) - #self.view.tickChanged(self) - - - - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - w = QtGui.QMainWindow() - w.show() - w.resize(400,400) - cw = QtGui.QWidget() - w.setCentralWidget(cw) - - l = QtGui.QGridLayout() - l.setSpacing(0) - cw.setLayout(l) - - w1 = GradientWidget(orientation='top') - w2 = GradientWidget(orientation='right', allowAdd=False) - w2.setTickColor(1, QtGui.QColor(255,255,255)) - w3 = GradientWidget(orientation='bottom') - w4 = TickSlider(orientation='left') - - l.addWidget(w1, 0, 1) - l.addWidget(w2, 1, 2) - l.addWidget(w3, 2, 1) - l.addWidget(w4, 1, 0) - - - \ No newline at end of file diff --git a/GraphicsScene.py b/GraphicsScene.py new file mode 100644 index 00000000..e9316796 --- /dev/null +++ b/GraphicsScene.py @@ -0,0 +1,747 @@ +from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL, QtSvg +import weakref +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +import pyqtgraph.ptime as ptime +import debug + +try: + import sip + HAVE_SIP = True +except: + HAVE_SIP = False + + +__all__ = ['GraphicsScene'] + +class GraphicsScene(QtGui.QGraphicsScene): + """ + Extension of QGraphicsScene that implements a complete, parallel mouse event system. + (It would have been preferred to just alter the way QGraphicsScene creates and delivers + events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent + is private) + + - Generates MouseClicked events in addition to the usual press/move/release events. + (This works around a problem where it is impossible to have one item respond to a + drag if another is watching for a click.) + - Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects + - Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu. + - Allows items to decide _before_ a mouse click which item will be the recipient of mouse events. + This lets us indicate unambiguously to the user which item they are about to click/drag on + - Eats mouseMove events that occur too soon after a mouse press. + - Reimplements items() and itemAt() to circumvent PyQt bug + + Mouse interaction is as follows: + 1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents + as well as custom HoverEvents. + 2) Items are sent HoverEvents in Z-order and each item may optionally call event.acceptClicks(button), + acceptDrags(button) or both. If this method call returns True, this informs the item that _if_ + the user clicks/drags the specified mouse button, the item is guaranteed to be the + recipient of click/drag events (the item may wish to change its appearance to indicate this). + If the call to acceptClicks/Drags returns False, then the item is guaranteed to NOT receive + the requested event (because another item has already accepted it). + 3) If the mouse is clicked, a mousePressEvent is generated as usual. If any items accept this press event, then + No click/drag events will be generated and mouse interaction proceeds as defined by Qt. This allows + items to function properly if they are expecting the usual press/move/release sequence of events. + (It is recommended that items do NOT accept press events, and instead use click/drag events) + Note: The default implementation of QGraphicsItem.mousePressEvent will ACCEPT the event if the + item is has its Selectable or Movable flags enabled. You may need to override this behavior. + 3) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events. + If the mouse is moved a sufficient distance (or moved slowly enough) before the button is released, + then a mouseDragEvent is generated. + If no drag events are generated before the button is released, then a mouseClickEvent is generated. + 4) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent + in step 1. If no such items exist, then the scene attempts to deliver the events to items near the event. + ClickEvents may be delivered in this way even if no + item originally claimed it could accept the click. DragEvents may only be delivered this way if it is the initial + 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. + + @classmethod + def registerObject(cls, obj): + """ + Workaround for PyQt bug in qgraphicsscene.items() + All subclasses of QGraphicsObject must register themselves with this function. + (otherwise, mouse interaction with those objects will likely fail) + """ + if HAVE_SIP and isinstance(obj, sip.wrapper): + cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj + + + def __init__(self, clickRadius=2, moveDistance=5): + QtGui.QGraphicsScene.__init__(self) + self.setClickRadius(clickRadius) + self.setMoveDistance(moveDistance) + self.clickEvents = [] + self.dragButtons = [] + self.mouseGrabber = None + self.dragItem = None + self.lastDrag = None + self.hoverItems = weakref.WeakKeyDictionary() + self.lastHoverEvent = None + #self.searchRect = QtGui.QGraphicsRectItem() + #self.searchRect.setPen(fn.mkPen(200,0,0)) + #self.addItem(self.searchRect) + + + def setClickRadius(self, r): + """ + Set the distance away from mouse clicks to search for interacting items. + When clicking, the scene searches first for items that directly intersect the click position + followed by any other items that are within a rectangle that extends r pixels away from the + click position. + """ + self._clickRadius = r + + def setMoveDistance(self, d): + """ + Set the distance the mouse must move after a press before mouseMoveEvents will be delivered. + This ensures that clicks with a small amount of movement are recognized as clicks instead of + drags. + """ + self._moveDistance = d + + def mousePressEvent(self, ev): + #print 'scenePress' + QtGui.QGraphicsScene.mousePressEvent(self, ev) + #print "mouseGrabberItem: ", self.mouseGrabberItem() + if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events + self.clickEvents.append(MouseClickEvent(ev)) + #else: + #addr = sip.unwrapinstance(sip.cast(self.mouseGrabberItem(), QtGui.QGraphicsItem)) + #item = GraphicsScene._addressCache.get(addr, self.mouseGrabberItem()) + #print "click grabbed by:", item + + def mouseMoveEvent(self, ev): + self.sigMouseMoved.emit(ev.scenePos()) + + ## First allow QGraphicsScene to deliver hoverEnter/Move/ExitEvents + QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + + + ## Next deliver our own HoverEvents + self.sendHoverEvents(ev) + + + if int(ev.buttons()) != 0: ## button is pressed; send mouseMoveEvents and mouseDragEvents + QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + if self.mouseGrabberItem() is None: + now = ptime.time() + init = False + ## keep track of which buttons are involved in dragging + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]: + if int(ev.buttons() & btn) == 0: + continue + if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet + cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0] + dist = Point(ev.screenPos() - cev.screenPos()) + if dist.length() < self._moveDistance and now - cev.time() < 0.5: + continue + init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True + self.dragButtons.append(int(btn)) + + ## If we have dragged buttons, deliver a drag event + if len(self.dragButtons) > 0: + if self.sendDragEvent(ev, init=init): + ev.accept() + + def leaveEvent(self, ev): ## inform items that mouse is gone + if len(self.dragButtons) == 0: + self.sendHoverEvents(ev, exitOnly=True) + + + def mouseReleaseEvent(self, ev): + #print 'sceneRelease' + if self.mouseGrabberItem() is None: + #print "sending click/drag event" + if ev.button() in self.dragButtons: + if self.sendDragEvent(ev, final=True): + #print "sent drag event" + ev.accept() + self.dragButtons.remove(ev.button()) + else: + cev = [e for e in self.clickEvents if int(e.button()) == int(ev.button())] + if self.sendClickEvent(cev[0]): + #print "sent click event" + ev.accept() + self.clickEvents.remove(cev[0]) + + if int(ev.buttons()) == 0: + self.dragItem = None + self.dragButtons = [] + self.clickEvents = [] + self.lastDrag = None + QtGui.QGraphicsScene.mouseReleaseEvent(self, ev) + + self.sendHoverEvents(ev) ## let items prepare for next click/drag + + def mouseDoubleClickEvent(self, ev): + QtGui.QGraphicsScene.mouseDoubleClickEvent(self, ev) + if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events + self.clickEvents.append(MouseClickEvent(ev, double=True)) + + def sendHoverEvents(self, ev, exitOnly=False): + ## if exitOnly, then just inform all previously hovered items that the mouse has left. + + if exitOnly: + acceptable=False + items = [] + event = HoverEvent(None, acceptable) + else: + acceptable = int(ev.buttons()) == 0 ## if we are in mid-drag, do not allow items to accept the hover event. + event = HoverEvent(ev, acceptable) + items = self.itemsNearEvent(event) + self.sigMouseHover.emit(items) + + prevItems = self.hoverItems.keys() + + for item in items: + if hasattr(item, 'hoverEvent'): + event.currentItem = item + if item not in self.hoverItems: + self.hoverItems[item] = None + event.enter = True + else: + prevItems.remove(item) + event.enter = False + + try: + item.hoverEvent(event) + except: + debug.printExc("Error sending hover event:") + + event.enter = False + event.exit = True + for item in prevItems: + event.currentItem = item + try: + item.hoverEvent(event) + except: + debug.printExc("Error sending hover exit event:") + finally: + del self.hoverItems[item] + + if hasattr(ev, 'buttons') and int(ev.buttons()) == 0: + self.lastHoverEvent = event ## save this so we can ask about accepted events later. + + + def sendDragEvent(self, ev, init=False, final=False): + ## Send a MouseDragEvent to the current dragItem or to + ## items near the beginning of the drag + event = MouseDragEvent(ev, self.clickEvents[0], self.lastDrag, start=init, finish=final) + #print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem + if init and self.dragItem is None: + if self.lastHoverEvent is not None: + acceptedItem = self.lastHoverEvent.dragItems().get(event.button(), None) + else: + acceptedItem = None + + if acceptedItem is not None: + #print "Drag -> pre-selected item:", acceptedItem + self.dragItem = acceptedItem + event.currentItem = self.dragItem + try: + self.dragItem.mouseDragEvent(event) + except: + debug.printExc("Error sending drag event:") + + else: + #print "drag -> new item" + for item in self.itemsNearEvent(event): + #print "check item:", item + if hasattr(item, 'mouseDragEvent'): + event.currentItem = item + try: + item.mouseDragEvent(event) + except: + debug.printExc("Error sending drag event:") + if event.isAccepted(): + #print " --> accepted" + self.dragItem = item + break + elif self.dragItem is not None: + event.currentItem = self.dragItem + try: + self.dragItem.mouseDragEvent(event) + except: + debug.printExc("Error sending hover exit event:") + + self.lastDrag = event + + return event.isAccepted() + + + def sendClickEvent(self, ev): + ## if we are in mid-drag, click events may only go to the dragged item. + if self.dragItem is not None and hasattr(self.dragItem, 'mouseClickEvent'): + ev.currentItem = self.dragItem + self.dragItem.mouseClickEvent(ev) + + ## otherwise, search near the cursor + else: + if self.lastHoverEvent is not None: + acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None) + else: + acceptedItem = None + + if acceptedItem is not None: + ev.currentItem = acceptedItem + try: + acceptedItem.mouseClickEvent(ev) + except: + debug.printExc("Error sending click event:") + else: + for item in self.itemsNearEvent(ev): + if hasattr(item, 'mouseClickEvent'): + ev.currentItem = item + try: + item.mouseClickEvent(ev) + except: + debug.printExc("Error sending click event:") + + if ev.isAccepted(): + break + if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton: + #print "GraphicsScene emitting sigSceneContextMenu" + self.sigMouseClicked.emit(ev) + ev.accept() + return ev.isAccepted() + + #def claimEvent(self, item, button, eventType): + #key = (button, eventType) + #if key in self.claimedEvents: + #return False + #self.claimedEvents[key] = item + #print "event", key, "claimed by", item + #return True + + + def items(self, *args): + #print 'args:', args + items = QtGui.QGraphicsScene.items(self, *args) + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + items2 = map(self.translateGraphicsItem, items) + #if HAVE_SIP and isinstance(self, sip.wrapper): + #items2 = [] + #for i in items: + #addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem)) + #i2 = GraphicsScene._addressCache.get(addr, i) + ##print i, "==>", i2 + #items2.append(i2) + #print 'items:', items + return items2 + + def selectedItems(self, *args): + items = QtGui.QGraphicsScene.selectedItems(self, *args) + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + #if HAVE_SIP and isinstance(self, sip.wrapper): + #items2 = [] + #for i in items: + #addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem)) + #i2 = GraphicsScene._addressCache.get(addr, i) + ##print i, "==>", i2 + #items2.append(i2) + items2 = map(self.translateGraphicsItem, items) + + #print 'items:', items + return items2 + + def itemAt(self, *args): + item = QtGui.QGraphicsScene.itemAt(self, *args) + + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + #if HAVE_SIP and isinstance(self, sip.wrapper): + #addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem)) + #item = GraphicsScene._addressCache.get(addr, item) + #return item + return self.translateGraphicsItem(item) + + def itemsNearEvent(self, event, selMode=QtCore.Qt.IntersectsItemShape, sortOrder=QtCore.Qt.DescendingOrder): + """ + Return an iterator that iterates first through the items that directly intersect point (in Z order) + followed by any other items that are within the scene's click radius. + """ + #tr = self.getViewWidget(event.widget()).transform() + view = self.views()[0] + tr = view.viewportTransform() + r = self._clickRadius + rect = view.mapToScene(QtCore.QRect(0, 0, 2*r, 2*r)).boundingRect() + + seen = set() + if hasattr(event, 'buttonDownScenePos'): + point = event.buttonDownScenePos() + else: + point = event.scenePos() + w = rect.width() + h = rect.height() + rgn = QtCore.QRectF(point.x()-w, point.y()-h, 2*w, 2*h) + #self.searchRect.setRect(rgn) + + + items = self.items(point, selMode, sortOrder, tr) + + ## remove items whose shape does not contain point (scene.items() apparently sucks at this) + items2 = [] + for item in items: + shape = item.shape() + if shape is None: + continue + if item.mapToScene(shape).contains(point): + items2.append(item) + + ## Sort by descending Z-order (don't trust scene.itms() to do this either) + ## use 'absolute' z value, which is the sum of all item/parent ZValues + def absZValue(item): + if item is None: + return 0 + return item.zValue() + absZValue(item.parentItem()) + + items2.sort(lambda a,b: cmp(absZValue(b), absZValue(a))) + + return items2 + + #for item in items: + ##seen.add(item) + + #shape = item.mapToScene(item.shape()) + #if not shape.contains(point): + #continue + #yield item + #for item in self.items(rgn, selMode, sortOrder, tr): + ##if item not in seen: + #yield item + + def getViewWidget(self, widget): + ## same pyqt bug -- mouseEvent.widget() doesn't give us the original python object. + ## [[doesn't seem to work correctly]] + if HAVE_SIP and isinstance(self, sip.wrapper): + addr = sip.unwrapinstance(sip.cast(widget, QtGui.QWidget)) + #print "convert", widget, addr + for v in self.views(): + addr2 = sip.unwrapinstance(sip.cast(v, QtGui.QWidget)) + #print " check:", v, addr2 + if addr2 == addr: + return v + else: + return widget + + def addParentContextMenus(self, item, menu, event): + """ + Can be called by any item in the scene to expand its context menu to include parent context menus. + Parents may implement getContextMenus to add new menus / actions to the existing menu. + getContextMenus must accept 1 argument (the event that generated the original menu) and + return a single QMenu or a list of QMenus. + + The final menu will look like: + + Original Item 1 + Original Item 2 + ... + Original Item N + ------------------ + Parent Item 1 + Parent Item 2 + ... + Grandparent Item 1 + ... + + + Arguments: + item - The item that initially created the context menu + (This is probably the item making the call to this function) + menu - The context menu being shown by the item + event - The original event that triggered the menu to appear. + """ + + #items = self.itemsNearEvent(ev) + menusToAdd = [] + while item.parentItem() is not None: + item = item.parentItem() + #for item in items: + #if item is sender: + #continue + if not hasattr(item, "getContextMenus"): + continue + + + subMenus = item.getContextMenus(event) + if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus + subMenus = [subMenus] + + for sm in subMenus: + menusToAdd.append(sm) + + if len(menusToAdd) > 0: + menu.addSeparator() + + for m in menusToAdd: + menu.addMenu(m) + + return menu + + @staticmethod + def translateGraphicsItem(item): + ## for fixing pyqt bugs where the wrong item is returned + if HAVE_SIP and isinstance(item, sip.wrapper): + addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem)) + item = GraphicsScene._addressCache.get(addr, item) + return item + + @staticmethod + def translateGraphicsItems(items): + return map(GraphicsScene.translateGraphicsItem, items) + + +class MouseDragEvent: + def __init__(self, moveEvent, pressEvent, lastEvent, start=False, finish=False): + self.start = start + self.finish = finish + self.accepted = False + self.currentItem = None + self._buttonDownScenePos = {} + self._buttonDownScreenPos = {} + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]: + self._buttonDownScenePos[int(btn)] = moveEvent.buttonDownScenePos(btn) + self._buttonDownScreenPos[int(btn)] = moveEvent.buttonDownScreenPos(btn) + self._scenePos = moveEvent.scenePos() + self._screenPos = moveEvent.screenPos() + if lastEvent is None: + self._lastScenePos = pressEvent.scenePos() + self._lastScreenPos = pressEvent.screenPos() + else: + self._lastScenePos = lastEvent.scenePos() + self._lastScreenPos = lastEvent.screenPos() + self._buttons = moveEvent.buttons() + self._button = pressEvent.button() + self._modifiers = moveEvent.modifiers() + + def accept(self): + self.accepted = True + self.acceptedItem = self.currentItem + + def ignore(self): + self.accepted = False + + def isAccepted(self): + return self.accepted + + def scenePos(self): + return Point(self._scenePos) + + def screenPos(self): + return Point(self._screenPos) + + def buttonDownScenePos(self, btn=None): + if btn is None: + btn = self.button() + return Point(self._buttonDownScenePos[int(btn)]) + + def buttonDownScreenPos(self, btn=None): + if btn is None: + btn = self.button() + return Point(self._buttonDownScreenPos[int(btn)]) + + def lastScenePos(self): + return Point(self._lastScenePos) + + def lastScreenPos(self): + return Point(self._lastScreenPos) + + def buttons(self): + return self._buttons + + def button(self): + """Return the button that initiated the drag (may be different from the buttons currently pressed)""" + return self._button + + def pos(self): + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def buttonDownPos(self, btn=None): + if btn is None: + btn = self.button() + return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[int(btn)])) + + def isStart(self): + return self.start + + def isFinish(self): + return self.finish + + def __repr__(self): + lp = self.lastPos() + p = self.pos() + return "<MouseDragEvent (%g,%g)->(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish())) + + def modifiers(self): + return self._modifiers + + + +class MouseClickEvent: + def __init__(self, pressEvent, double=False): + self.accepted = False + self.currentItem = None + self._double = double + self._scenePos = pressEvent.scenePos() + self._screenPos = pressEvent.screenPos() + self._button = pressEvent.button() + self._buttons = pressEvent.buttons() + self._modifiers = pressEvent.modifiers() + self._time = ptime.time() + + + def accept(self): + self.accepted = True + self.acceptedItem = self.currentItem + + def ignore(self): + self.accepted = False + + def isAccepted(self): + return self.accepted + + def scenePos(self): + return Point(self._scenePos) + + def screenPos(self): + return Point(self._screenPos) + + def buttons(self): + return self._buttons + + def button(self): + return self._button + + def double(self): + return self._double + + def pos(self): + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def modifiers(self): + return self._modifiers + + def __repr__(self): + p = self.pos() + return "<MouseClickEvent (%g,%g) button=%d>" % (p.x(), p.y(), int(self.button())) + + def time(self): + return self._time + + + +class HoverEvent: + """ + This event class both informs items that the mouse cursor is nearby and allows items to + communicate with one another about whether each item will accept _potential_ mouse events. + + It is common for multiple overlapping items to receive hover events and respond by changing + their appearance. This can be misleading to the user since, in general, only one item will + respond to mouse events. To avoid this, items make calls to event.acceptClicks(button) + and/or acceptDrags(button). + + Each item may make multiple calls to acceptClicks/Drags, each time for a different button. + If the method returns True, then the item is guaranteed to be + the recipient of the claimed event IF the user presses the specified mouse button before + moving. If claimEvent returns False, then this item is guaranteed NOT to get the specified + event (because another has already claimed it) and the item should change its appearance + accordingly. + + event.isEnter() returns True if the mouse has just entered the item's shape; + event.isExit() returns True if the mouse has just left. + """ + def __init__(self, moveEvent, acceptable): + self.enter = False + self.acceptable = acceptable + self.exit = False + self.__clickItems = weakref.WeakValueDictionary() + self.__dragItems = weakref.WeakValueDictionary() + self.currentItem = None + if moveEvent is not None: + self._scenePos = moveEvent.scenePos() + self._screenPos = moveEvent.screenPos() + self._lastScenePos = moveEvent.lastScenePos() + self._lastScreenPos = moveEvent.lastScreenPos() + self._buttons = moveEvent.buttons() + self._modifiers = moveEvent.modifiers() + else: + self.exit = True + + + + def isEnter(self): + return self.enter + + def isExit(self): + return self.exit + + def acceptClicks(self, button): + """""" + if not self.acceptable: + return False + if button not in self.__clickItems: + self.__clickItems[button] = self.currentItem + return True + return False + + def acceptDrags(self, button): + if not self.acceptable: + return False + if button not in self.__dragItems: + self.__dragItems[button] = self.currentItem + return True + return False + + def scenePos(self): + return Point(self._scenePos) + + def screenPos(self): + return Point(self._screenPos) + + def lastScenePos(self): + return Point(self._lastScenePos) + + def lastScreenPos(self): + return Point(self._lastScreenPos) + + def buttons(self): + return self._buttons + + def pos(self): + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def __repr__(self): + lp = self.lastPos() + p = self.pos() + return "<HoverEvent (%g,%g)->(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit())) + + def modifiers(self): + return self._modifiers + + def clickItems(self): + return self.__clickItems + + def dragItems(self): + return self.__dragItems + + + \ No newline at end of file diff --git a/GraphicsScene.pyc b/GraphicsScene.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35431aa08b042fbceb3feb069a506181b5f0638b GIT binary patch literal 27489 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 DI$c~i literal 0 HcmV?d00001 diff --git a/ImageViewTemplate.ui b/ImageViewTemplate.ui deleted file mode 100644 index 98d58a1f..00000000 --- a/ImageViewTemplate.ui +++ /dev/null @@ -1,283 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>Form</class> - <widget class="QWidget" name="Form"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>726</width> - <height>588</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="spacing"> - <number>0</number> - </property> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QSplitter" name="splitter"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <widget class="QWidget" name="layoutWidget"> - <layout class="QGridLayout" name="gridLayout"> - <property name="margin"> - <number>0</number> - </property> - <property name="spacing"> - <number>0</number> - </property> - <item row="1" column="0" rowspan="3"> - <widget class="GraphicsView" name="graphicsView" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>10</horstretch> - <verstretch>10</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item row="3" column="3"> - <widget class="QPushButton" name="roiBtn"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>1</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>30</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string>R</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="3"> - <widget class="GradientWidget" name="gradientWidget" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>100</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item row="2" column="3"> - <widget class="QPushButton" name="normBtn"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>1</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>30</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string>N</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="0" colspan="4"> - <widget class="QGroupBox" name="normGroup"> - <property name="title"> - <string>Normalization</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <property name="margin"> - <number>0</number> - </property> - <property name="spacing"> - <number>0</number> - </property> - <item row="0" column="2"> - <widget class="QRadioButton" name="normSubtractRadio"> - <property name="text"> - <string>Subtract</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QRadioButton" name="normDivideRadio"> - <property name="text"> - <string>Divide</string> - </property> - <property name="checked"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_5"> - <property name="font"> - <font> - <weight>75</weight> - <bold>true</bold> - </font> - </property> - <property name="text"> - <string>Operation:</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_3"> - <property name="font"> - <font> - <weight>75</weight> - <bold>true</bold> - </font> - </property> - <property name="text"> - <string>Mean:</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_4"> - <property name="font"> - <font> - <weight>75</weight> - <bold>true</bold> - </font> - </property> - <property name="text"> - <string>Blur:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QCheckBox" name="normROICheck"> - <property name="text"> - <string>ROI</string> - </property> - </widget> - </item> - <item row="2" column="2"> - <widget class="QDoubleSpinBox" name="normXBlurSpin"/> - </item> - <item row="2" column="1"> - <widget class="QLabel" name="label_8"> - <property name="text"> - <string>X</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="2" column="3"> - <widget class="QLabel" name="label_9"> - <property name="text"> - <string>Y</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="2" column="4"> - <widget class="QDoubleSpinBox" name="normYBlurSpin"/> - </item> - <item row="2" column="5"> - <widget class="QLabel" name="label_10"> - <property name="text"> - <string>T</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="0" column="3"> - <widget class="QRadioButton" name="normOffRadio"> - <property name="text"> - <string>Off</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="3"> - <widget class="QCheckBox" name="normTimeRangeCheck"> - <property name="text"> - <string>Time range</string> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QCheckBox" name="normFrameCheck"> - <property name="text"> - <string>Frame</string> - </property> - </widget> - </item> - <item row="2" column="6"> - <widget class="QDoubleSpinBox" name="normTBlurSpin"/> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <widget class="PlotWidget" name="roiPlot" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>0</width> - <height>40</height> - </size> - </property> - </widget> - </widget> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>GradientWidget</class> - <extends>QWidget</extends> - <header>pyqtgraph.GradientWidget</header> - <container>1</container> - </customwidget> - <customwidget> - <class>GraphicsView</class> - <extends>QWidget</extends> - <header>GraphicsView</header> - <container>1</container> - </customwidget> - <customwidget> - <class>PlotWidget</class> - <extends>QWidget</extends> - <header>PlotWidget</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/PlotItem.py b/PlotItem.py deleted file mode 100644 index d53d2bfc..00000000 --- a/PlotItem.py +++ /dev/null @@ -1,1284 +0,0 @@ -# -*- coding: utf-8 -*- -""" -PlotItem.py - Graphics item implementing a scalable ViewBox with plotting powers. -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -This class is one of the workhorses of pyqtgraph. It implements a graphics item with -plots, labels, and scales which can be viewed inside a QGraphicsScene. If you want -a widget that can be added to your GUI, see PlotWidget instead. - -This class is very heavily featured: - - Automatically creates and manages PlotCurveItems - - Fast display and update of plots - - Manages zoom/pan ViewBox, scale, and label elements - - Automatic scaling when data changes - - Control panel with a huge feature set including averaging, decimation, - display, power spectrum, svg/png export, plot linking, and more. -""" - -from graphicsItems import * -from plotConfigTemplate import * -from PyQt4 import QtGui, QtCore, QtSvg -from functions import * -#from ObjectWorkaround import * -#tryWorkaround(QtCore, QtGui) -import weakref -import numpy as np -#import debug - -try: - from WidgetGroup import * - HAVE_WIDGETGROUP = True -except: - HAVE_WIDGETGROUP = False - -try: - from metaarray import * - HAVE_METAARRAY = True -except: - HAVE_METAARRAY = False - - -class PlotItem(QtGui.QGraphicsWidget): - - sigYRangeChanged = QtCore.Signal(object, object) - sigXRangeChanged = QtCore.Signal(object, object) - sigRangeChanged = QtCore.Signal(object, object) - - """Plot graphics item that can be added to any graphics scene. Implements axis titles, scales, interactive viewbox.""" - lastFileDir = None - managers = {} - - def __init__(self, parent=None, name=None, labels=None, **kargs): - QtGui.QGraphicsWidget.__init__(self, parent) - - ## Set up control buttons - - self.ctrlBtn = QtGui.QToolButton() - self.ctrlBtn.setText('?') - self.autoBtn = QtGui.QToolButton() - self.autoBtn.setText('A') - self.autoBtn.hide() - self.proxies = [] - for b in [self.ctrlBtn, self.autoBtn]: - proxy = QtGui.QGraphicsProxyWidget(self) - proxy.setWidget(b) - proxy.setAcceptHoverEvents(False) - b.setStyleSheet("background-color: #000000; color: #888; font-size: 6pt") - self.proxies.append(proxy) - #QtCore.QObject.connect(self.ctrlBtn, QtCore.SIGNAL('clicked()'), self.ctrlBtnClicked) - self.ctrlBtn.clicked.connect(self.ctrlBtnClicked) - #QtCore.QObject.connect(self.autoBtn, QtCore.SIGNAL('clicked()'), self.enableAutoScale) - self.autoBtn.clicked.connect(self.enableAutoScale) - - - self.layout = QtGui.QGraphicsGridLayout() - self.layout.setContentsMargins(1,1,1,1) - self.setLayout(self.layout) - self.layout.setHorizontalSpacing(0) - self.layout.setVerticalSpacing(0) - - self.vb = ViewBox() - #QtCore.QObject.connect(self.vb, QtCore.SIGNAL('xRangeChanged'), self.xRangeChanged) - self.vb.sigXRangeChanged.connect(self.xRangeChanged) - #QtCore.QObject.connect(self.vb, QtCore.SIGNAL('yRangeChanged'), self.yRangeChanged) - self.vb.sigYRangeChanged.connect(self.yRangeChanged) - #QtCore.QObject.connect(self.vb, QtCore.SIGNAL('rangeChangedManually'), self.enableManualScale) - self.vb.sigRangeChangedManually.connect(self.enableManualScale) - - #QtCore.QObject.connect(self.vb, QtCore.SIGNAL('viewChanged'), self.viewChanged) - self.vb.sigRangeChanged.connect(self.viewRangeChanged) - - self.layout.addItem(self.vb, 2, 1) - self.alpha = 1.0 - self.autoAlpha = True - self.spectrumMode = False - - self.autoScale = [True, True] - - ## Create and place scale items - self.scales = { - 'top': {'item': ScaleItem(orientation='top', linkView=self.vb), 'pos': (1, 1)}, - 'bottom': {'item': ScaleItem(orientation='bottom', linkView=self.vb), 'pos': (3, 1)}, - 'left': {'item': ScaleItem(orientation='left', linkView=self.vb), 'pos': (2, 0)}, - 'right': {'item': ScaleItem(orientation='right', linkView=self.vb), 'pos': (2, 2)} - } - for k in self.scales: - self.layout.addItem(self.scales[k]['item'], *self.scales[k]['pos']) - - ## Create and place label items - #self.labels = { - #'title': {'item': LabelItem('title', size='11pt'), 'pos': (0, 2), 'text': ''}, - #'top': {'item': LabelItem('top'), 'pos': (1, 2), 'text': '', 'units': '', 'unitPrefix': ''}, - #'bottom': {'item': LabelItem('bottom'), 'pos': (5, 2), 'text': '', 'units': '', 'unitPrefix': ''}, - #'left': {'item': LabelItem('left'), 'pos': (3, 0), 'text': '', 'units': '', 'unitPrefix': ''}, - #'right': {'item': LabelItem('right'), 'pos': (3, 4), 'text': '', 'units': '', 'unitPrefix': ''} - #} - #self.labels['left']['item'].setAngle(-90) - #self.labels['right']['item'].setAngle(-90) - #for k in self.labels: - #self.layout.addItem(self.labels[k]['item'], *self.labels[k]['pos']) - self.titleLabel = LabelItem('', size='11pt') - self.layout.addItem(self.titleLabel, 0, 1) - self.setTitle(None) ## hide - - - for i in range(4): - self.layout.setRowPreferredHeight(i, 0) - self.layout.setRowMinimumHeight(i, 0) - self.layout.setRowSpacing(i, 0) - self.layout.setRowStretchFactor(i, 1) - - for i in range(3): - self.layout.setColumnPreferredWidth(i, 0) - self.layout.setColumnMinimumWidth(i, 0) - self.layout.setColumnSpacing(i, 0) - self.layout.setColumnStretchFactor(i, 1) - self.layout.setRowStretchFactor(2, 100) - self.layout.setColumnStretchFactor(1, 100) - - - ## Wrap a few methods from viewBox - for m in ['setXRange', 'setYRange', 'setRange', 'autoRange', 'viewRect', 'setMouseEnabled']: - setattr(self, m, getattr(self.vb, m)) - - self.items = [] - self.curves = [] - self.dataItems = [] - self.paramList = {} - self.avgCurves = {} - - ### Set up context menu - - w = QtGui.QWidget() - self.ctrl = c = Ui_Form() - c.setupUi(w) - dv = QtGui.QDoubleValidator(self) - self.ctrlMenu = QtGui.QMenu() - self.menuAction = QtGui.QWidgetAction(self) - self.menuAction.setDefaultWidget(w) - self.ctrlMenu.addAction(self.menuAction) - - if HAVE_WIDGETGROUP: - self.stateGroup = WidgetGroup(self.ctrlMenu) - - self.fileDialog = None - - self.xLinkPlot = None - self.yLinkPlot = None - self.linksBlocked = False - - - #self.ctrlBtn.setFixedWidth(60) - self.setAcceptHoverEvents(True) - - ## Connect control widgets - #QtCore.QObject.connect(c.xMinText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale) - c.xMinText.editingFinished.connect(self.setManualXScale) - #QtCore.QObject.connect(c.xMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale) - c.xMaxText.editingFinished.connect(self.setManualXScale) - #QtCore.QObject.connect(c.yMinText, QtCore.SIGNAL('editingFinished()'), self.setManualYScale) - c.yMinText.editingFinished.connect(self.setManualYScale) - #QtCore.QObject.connect(c.yMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualYScale) - c.yMaxText.editingFinished.connect(self.setManualYScale) - - #QtCore.QObject.connect(c.xManualRadio, QtCore.SIGNAL('clicked()'), self.updateXScale) - c.xManualRadio.clicked.connect(lambda: self.updateXScale()) - #QtCore.QObject.connect(c.yManualRadio, QtCore.SIGNAL('clicked()'), self.updateYScale) - c.yManualRadio.clicked.connect(lambda: self.updateYScale()) - - #QtCore.QObject.connect(c.xAutoRadio, QtCore.SIGNAL('clicked()'), self.updateXScale) - c.xAutoRadio.clicked.connect(self.updateXScale) - #QtCore.QObject.connect(c.yAutoRadio, QtCore.SIGNAL('clicked()'), self.updateYScale) - c.yAutoRadio.clicked.connect(self.updateYScale) - - #QtCore.QObject.connect(c.xAutoPercentSpin, QtCore.SIGNAL('valueChanged(int)'), self.replot) - c.xAutoPercentSpin.valueChanged.connect(self.replot) - #QtCore.QObject.connect(c.yAutoPercentSpin, QtCore.SIGNAL('valueChanged(int)'), self.replot) - c.yAutoPercentSpin.valueChanged.connect(self.replot) - - #QtCore.QObject.connect(c.xLogCheck, QtCore.SIGNAL('toggled(bool)'), self.setXLog) - #QtCore.QObject.connect(c.yLogCheck, QtCore.SIGNAL('toggled(bool)'), self.setYLog) - - #QtCore.QObject.connect(c.alphaGroup, QtCore.SIGNAL('toggled(bool)'), self.updateAlpha) - c.alphaGroup.toggled.connect(self.updateAlpha) - #QtCore.QObject.connect(c.alphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateAlpha) - c.alphaSlider.valueChanged.connect(self.updateAlpha) - #QtCore.QObject.connect(c.autoAlphaCheck, QtCore.SIGNAL('toggled(bool)'), self.updateAlpha) - c.autoAlphaCheck.toggled.connect(self.updateAlpha) - - #QtCore.QObject.connect(c.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid) - c.gridGroup.toggled.connect(self.updateGrid) - #QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateGrid) - c.gridAlphaSlider.valueChanged.connect(self.updateGrid) - - #QtCore.QObject.connect(c.powerSpectrumGroup, QtCore.SIGNAL('toggled(bool)'), self.updateSpectrumMode) - c.powerSpectrumGroup.toggled.connect(self.updateSpectrumMode) - #QtCore.QObject.connect(c.saveSvgBtn, QtCore.SIGNAL('clicked()'), self.saveSvgClicked) - c.saveSvgBtn.clicked.connect(self.saveSvgClicked) - #QtCore.QObject.connect(c.saveImgBtn, QtCore.SIGNAL('clicked()'), self.saveImgClicked) - c.saveImgBtn.clicked.connect(self.saveImgClicked) - #QtCore.QObject.connect(c.saveCsvBtn, QtCore.SIGNAL('clicked()'), self.saveCsvClicked) - c.saveCsvBtn.clicked.connect(self.saveCsvClicked) - - #QtCore.QObject.connect(c.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid) - #QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateGrid) - - #QtCore.QObject.connect(self.ctrl.xLinkCombo, QtCore.SIGNAL('currentIndexChanged(int)'), self.xLinkComboChanged) - self.ctrl.xLinkCombo.currentIndexChanged.connect(self.xLinkComboChanged) - #QtCore.QObject.connect(self.ctrl.yLinkCombo, QtCore.SIGNAL('currentIndexChanged(int)'), self.yLinkComboChanged) - self.ctrl.yLinkCombo.currentIndexChanged.connect(self.yLinkComboChanged) - - #QtCore.QObject.connect(c.downsampleSpin, QtCore.SIGNAL('valueChanged(int)'), self.updateDownsampling) - c.downsampleSpin.valueChanged.connect(self.updateDownsampling) - - #QtCore.QObject.connect(self.ctrl.avgParamList, QtCore.SIGNAL('itemClicked(QListWidgetItem*)'), self.avgParamListClicked) - self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked) - #QtCore.QObject.connect(self.ctrl.averageGroup, QtCore.SIGNAL('toggled(bool)'), self.avgToggled) - self.ctrl.averageGroup.toggled.connect(self.avgToggled) - - #QtCore.QObject.connect(self.ctrl.pointsGroup, QtCore.SIGNAL('toggled(bool)'), self.updatePointMode) - #QtCore.QObject.connect(self.ctrl.autoPointsCheck, QtCore.SIGNAL('toggled(bool)'), self.updatePointMode) - - #QtCore.QObject.connect(self.ctrl.maxTracesCheck, QtCore.SIGNAL('toggled(bool)'), self.updateDecimation) - self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation) - #QtCore.QObject.connect(self.ctrl.maxTracesSpin, QtCore.SIGNAL('valueChanged(int)'), self.updateDecimation) - self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation) - #QtCore.QObject.connect(c.xMouseCheck, QtCore.SIGNAL('toggled(bool)'), self.mouseCheckChanged) - c.xMouseCheck.toggled.connect(self.mouseCheckChanged) - #QtCore.QObject.connect(c.yMouseCheck, QtCore.SIGNAL('toggled(bool)'), self.mouseCheckChanged) - c.yMouseCheck.toggled.connect(self.mouseCheckChanged) - - self.xLinkPlot = None - self.yLinkPlot = None - self.linksBlocked = False - self.manager = None - - #self.showLabel('right', False) - #self.showLabel('top', False) - #self.showLabel('title', False) - #self.showLabel('left', False) - #self.showLabel('bottom', False) - self.showScale('right', False) - self.showScale('top', False) - self.showScale('left', True) - self.showScale('bottom', True) - - 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 len(kargs) > 0: - self.plot(**kargs) - - #def paint(self, *args): - #prof = debug.Profiler('PlotItem.paint', disabled=True) - #QtGui.QGraphicsWidget.paint(self, *args) - #prof.finish() - - - def close(self): - #print "delete", self - ## Most of this crap is needed to avoid PySide trouble. - ## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets) - ## the solution is to manually remove all widgets before scene.clear() is called - if self.ctrlMenu is None: ## already shut down - return - self.ctrlMenu.setParent(None) - self.ctrlMenu = None - - self.ctrlBtn.setParent(None) - self.ctrlBtn = None - self.autoBtn.setParent(None) - self.autoBtn = None - - for k in self.scales: - i = self.scales[k]['item'] - i.close() - - self.scales = None - self.scene().removeItem(self.vb) - self.vb = None - - ## causes invalid index errors: - #for i in range(self.layout.count()): - #self.layout.removeAt(i) - - for p in self.proxies: - try: - p.setWidget(None) - except RuntimeError: - break - self.scene().removeItem(p) - self.proxies = [] - - self.menuAction.releaseWidget(self.menuAction.defaultWidget()) - self.menuAction.setParent(None) - self.menuAction = None - - if self.manager is not None: - self.manager.sigWidgetListChanged.disconnect(self.updatePlotList) - self.manager.removeWidget(self.name) - #else: - #print "no manager" - - def registerPlot(self, name): - self.name = name - win = str(self.window()) - #print "register", name, win - if win not in PlotItem.managers: - PlotItem.managers[win] = PlotWidgetManager() - self.manager = PlotItem.managers[win] - self.manager.addWidget(self, name) - #QtCore.QObject.connect(self.manager, QtCore.SIGNAL('widgetListChanged'), self.updatePlotList) - self.manager.sigWidgetListChanged.connect(self.updatePlotList) - self.updatePlotList() - - def updatePlotList(self): - """Update the list of all plotWidgets in the "link" combos""" - #print "update plot list", self - try: - for sc in [self.ctrl.xLinkCombo, self.ctrl.yLinkCombo]: - current = str(sc.currentText()) - sc.clear() - sc.addItem("") - if self.manager is not None: - for w in self.manager.listWidgets(): - #print w - if w == self.name: - continue - sc.addItem(w) - except: - import gc - refs= gc.get_referrers(self) - print " error during update of", self - print " Referrers are:", refs - raise - - def updateGrid(self, *args): - g = self.ctrl.gridGroup.isChecked() - if g: - g = self.ctrl.gridAlphaSlider.value() - for k in self.scales: - self.scales[k]['item'].setGrid(g) - - def viewGeometry(self): - """return the screen geometry of the viewbox""" - v = self.scene().views()[0] - b = self.vb.mapRectToScene(self.vb.boundingRect()) - wr = v.mapFromScene(b).boundingRect() - pos = v.mapToGlobal(v.pos()) - wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) - return wr - - - - - def viewRangeChanged(self, vb, range): - #self.emit(QtCore.SIGNAL('viewChanged'), *args) - self.sigRangeChanged.emit(self, range) - - def blockLink(self, b): - self.linksBlocked = b - - def xLinkComboChanged(self): - self.setXLink(str(self.ctrl.xLinkCombo.currentText())) - - def yLinkComboChanged(self): - self.setYLink(str(self.ctrl.yLinkCombo.currentText())) - - def setXLink(self, plot=None): - """Link this plot's X axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)""" - if isinstance(plot, basestring): - if self.manager is None: - return - if self.xLinkPlot is not None: - self.manager.unlinkX(self, self.xLinkPlot) - plot = self.manager.getWidget(plot) - if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'): - plot = plot.getPlotItem() - self.xLinkPlot = plot - if plot is not None: - self.setManualXScale() - self.manager.linkX(self, plot) - - def setYLink(self, plot=None): - """Link this plot's Y axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)""" - if isinstance(plot, basestring): - if self.manager is None: - return - if self.yLinkPlot is not None: - self.manager.unlinkY(self, self.yLinkPlot) - plot = self.manager.getWidget(plot) - if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'): - plot = plot.getPlotItem() - self.yLinkPlot = plot - if plot is not None: - self.setManualYScale() - self.manager.linkY(self, plot) - - def linkXChanged(self, plot): - """Called when a linked plot has changed its X scale""" - #print "update from", plot - if self.linksBlocked: - return - pr = plot.vb.viewRect() - pg = plot.viewGeometry() - if pg is None: - #print " return early" - return - sg = self.viewGeometry() - upp = float(pr.width()) / pg.width() - x1 = pr.left() + (sg.x()-pg.x()) * upp - x2 = x1 + sg.width() * upp - plot.blockLink(True) - self.setManualXScale() - self.setXRange(x1, x2, padding=0) - plot.blockLink(False) - self.replot() - - def linkYChanged(self, plot): - """Called when a linked plot has changed its Y scale""" - if self.linksBlocked: - return - pr = plot.vb.viewRect() - pg = plot.vb.boundingRect() - sg = self.vb.boundingRect() - upp = float(pr.height()) / pg.height() - y1 = pr.bottom() + (sg.y()-pg.y()) * upp - y2 = y1 + sg.height() * upp - plot.blockLink(True) - self.setManualYScale() - self.setYRange(y1, y2, padding=0) - plot.blockLink(False) - self.replot() - - def avgToggled(self, b): - if b: - self.recomputeAverages() - for k in self.avgCurves: - self.avgCurves[k][1].setVisible(b) - - def avgParamListClicked(self, item): - name = str(item.text()) - self.paramList[name] = (item.checkState() == QtCore.Qt.Checked) - self.recomputeAverages() - - def recomputeAverages(self): - if not self.ctrl.averageGroup.isChecked(): - return - for k in self.avgCurves: - self.removeItem(self.avgCurves[k][1]) - #Qwt.QwtPlotCurve.detach(self.avgCurves[k][1]) - self.avgCurves = {} - for c in self.curves: - self.addAvgCurve(c) - self.replot() - - def addAvgCurve(self, curve): - """Add a single curve into the pool of curves averaged together""" - - ## If there are plot parameters, then we need to determine which to average together. - remKeys = [] - addKeys = [] - if self.ctrl.avgParamList.count() > 0: - - ### First determine the key of the curve to which this new data should be averaged - for i in range(self.ctrl.avgParamList.count()): - item = self.ctrl.avgParamList.item(i) - if item.checkState() == QtCore.Qt.Checked: - remKeys.append(str(item.text())) - else: - addKeys.append(str(item.text())) - - if len(remKeys) < 1: ## In this case, there would be 1 average plot for each data plot; not useful. - return - - p = curve.meta().copy() - for k in p: - if type(k) is tuple: - p['.'.join(k)] = p[k] - del p[k] - for rk in remKeys: - if rk in p: - del p[rk] - for ak in addKeys: - if ak not in p: - p[ak] = None - key = tuple(p.items()) - - ### Create a new curve if needed - if key not in self.avgCurves: - plot = PlotCurveItem() - plot.setPen(mkPen([0, 200, 0])) - plot.setShadowPen(mkPen([0, 0, 0, 100], 3)) - plot.setAlpha(1.0, False) - plot.setZValue(100) - self.addItem(plot) - #Qwt.QwtPlotCurve.attach(plot, self) - self.avgCurves[key] = [0, plot] - self.avgCurves[key][0] += 1 - (n, plot) = self.avgCurves[key] - - ### Average data together - (x, y) = curve.getData() - if plot.yData is not None: - newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n) - plot.setData(plot.xData, newData) - else: - plot.setData(x, y) - - - def mouseCheckChanged(self): - state = [self.ctrl.xMouseCheck.isChecked(), self.ctrl.yMouseCheck.isChecked()] - self.vb.setMouseEnabled(*state) - - def xRangeChanged(self, _, range): - if any(np.isnan(range)) or any(np.isinf(range)): - raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender()))) - self.ctrl.xMinText.setText('%0.5g' % range[0]) - self.ctrl.xMaxText.setText('%0.5g' % range[1]) - - ## automatically change unit scale - maxVal = max(abs(range[0]), abs(range[1])) - (scale, prefix) = siScale(maxVal) - #for l in ['top', 'bottom']: - #if self.getLabel(l).isVisible(): - #self.setLabel(l, unitPrefix=prefix) - #self.getScale(l).setScale(scale) - #else: - #self.setLabel(l, unitPrefix='') - #self.getScale(l).setScale(1.0) - - #self.emit(QtCore.SIGNAL('xRangeChanged'), self, range) - self.sigXRangeChanged.emit(self, range) - - def yRangeChanged(self, _, range): - if any(np.isnan(range)) or any(np.isinf(range)): - raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender()))) - self.ctrl.yMinText.setText('%0.5g' % range[0]) - self.ctrl.yMaxText.setText('%0.5g' % range[1]) - - ## automatically change unit scale - maxVal = max(abs(range[0]), abs(range[1])) - (scale, prefix) = siScale(maxVal) - #for l in ['left', 'right']: - #if self.getLabel(l).isVisible(): - #self.setLabel(l, unitPrefix=prefix) - #self.getScale(l).setScale(scale) - #else: - #self.setLabel(l, unitPrefix='') - #self.getScale(l).setScale(1.0) - #self.emit(QtCore.SIGNAL('yRangeChanged'), self, range) - self.sigYRangeChanged.emit(self, range) - - - def enableAutoScale(self): - self.ctrl.xAutoRadio.setChecked(True) - self.ctrl.yAutoRadio.setChecked(True) - self.autoBtn.hide() - self.updateXScale() - self.updateYScale() - self.replot() - - def updateXScale(self): - """Set plot to autoscale or not depending on state of radio buttons""" - if self.ctrl.xManualRadio.isChecked(): - self.setManualXScale() - else: - self.setAutoXScale() - self.replot() - - def updateYScale(self, b=False): - """Set plot to autoscale or not depending on state of radio buttons""" - if self.ctrl.yManualRadio.isChecked(): - self.setManualYScale() - else: - self.setAutoYScale() - self.replot() - - def enableManualScale(self, v=[True, True]): - if v[0]: - self.autoScale[0] = False - self.ctrl.xManualRadio.setChecked(True) - #self.setManualXScale() - if v[1]: - self.autoScale[1] = False - self.ctrl.yManualRadio.setChecked(True) - #self.setManualYScale() - self.autoBtn.show() - #self.replot() - - def setManualXScale(self): - self.autoScale[0] = False - x1 = float(self.ctrl.xMinText.text()) - x2 = float(self.ctrl.xMaxText.text()) - self.ctrl.xManualRadio.setChecked(True) - self.setXRange(x1, x2, padding=0) - self.autoBtn.show() - #self.replot() - - def setManualYScale(self): - self.autoScale[1] = False - y1 = float(self.ctrl.yMinText.text()) - y2 = float(self.ctrl.yMaxText.text()) - self.ctrl.yManualRadio.setChecked(True) - self.setYRange(y1, y2, padding=0) - self.autoBtn.show() - #self.replot() - - def setAutoXScale(self): - self.autoScale[0] = True - self.ctrl.xAutoRadio.setChecked(True) - #self.replot() - - def setAutoYScale(self): - self.autoScale[1] = True - self.ctrl.yAutoRadio.setChecked(True) - #self.replot() - - def addItem(self, item, *args): - self.items.append(item) - self.vb.addItem(item, *args) - - def removeItem(self, item): - if not item in self.items: - return - self.items.remove(item) - if item in self.dataItems: - self.dataItems.remove(item) - - if item.scene() is not None: - self.vb.removeItem(item) - if item in self.curves: - self.curves.remove(item) - self.updateDecimation() - self.updateParamList() - #item.connect(item, QtCore.SIGNAL('plotChanged'), self.plotChanged) - item.sigPlotChanged.connect(self.plotChanged) - - def clear(self): - for i in self.items[:]: - self.removeItem(i) - self.avgCurves = {} - - def clearPlots(self): - for i in self.curves[:]: - self.removeItem(i) - self.avgCurves = {} - - - def plot(self, data=None, data2=None, x=None, y=None, clear=False, params=None, pen=None): - """Add a new plot curve. Data may be specified a few ways: - plot(yVals) # x vals will be integers - plot(xVals, yVals) - plot(y=yVals, x=xVals) - """ - if y is not None: - data = y - if data2 is not None: - x = data - data = data2 - - if clear: - self.clear() - if params is None: - params = {} - if HAVE_METAARRAY and isinstance(data, MetaArray): - curve = self._plotMetaArray(data, x=x) - elif isinstance(data, np.ndarray): - curve = self._plotArray(data, x=x) - elif isinstance(data, list): - if x is not None: - x = np.array(x) - curve = self._plotArray(np.array(data), x=x) - elif data is None: - curve = PlotCurveItem() - else: - raise Exception('Not sure how to plot object of type %s' % type(data)) - - #print data, curve - self.addCurve(curve, params) - if pen is not None: - curve.setPen(mkPen(pen)) - - return curve - - def scatterPlot(self, *args, **kargs): - sp = ScatterPlotItem(*args, **kargs) - self.addDataItem(sp) - return sp - - def addDataItem(self, item): - self.addItem(item) - self.dataItems.append(item) - - def addCurve(self, c, params=None): - if params is None: - params = {} - c.setMeta(params) - self.curves.append(c) - #Qwt.QwtPlotCurve.attach(c, self) - self.addItem(c) - - ## configure curve for this plot - (alpha, auto) = self.alphaState() - c.setAlpha(alpha, auto) - c.setSpectrumMode(self.ctrl.powerSpectrumGroup.isChecked()) - c.setDownsampling(self.downsampleMode()) - c.setPointMode(self.pointMode()) - - ## Hide older plots if needed - self.updateDecimation() - - ## Add to average if needed - self.updateParamList() - if self.ctrl.averageGroup.isChecked(): - self.addAvgCurve(c) - - #c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged) - c.sigPlotChanged.connect(self.plotChanged) - self.plotChanged() - - def plotChanged(self, curve=None): - ## Recompute auto range if needed - for ax in [0, 1]: - if self.autoScale[ax]: - percentScale = [self.ctrl.xAutoPercentSpin.value(), self.ctrl.yAutoPercentSpin.value()][ax] * 0.01 - mn = None - mx = None - for c in self.curves + [c[1] for c in self.avgCurves.values()] + self.dataItems: - if not c.isVisible(): - continue - cmn, cmx = c.getRange(ax, percentScale) - if mn is None or cmn < mn: - mn = cmn - if mx is None or cmx > mx: - mx = cmx - if mn is None or mx is None or any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])): - continue - if mn == mx: - mn -= 1 - mx += 1 - self.setRange(ax, mn, mx) - #print "Auto range:", ax, mn, mx - - def replot(self): - self.plotChanged() - self.update() - - def updateParamList(self): - self.ctrl.avgParamList.clear() - ## Check to see that each parameter for each curve is present in the list - #print "\nUpdate param list", self - #print "paramList:", self.paramList - for c in self.curves: - #print " curve:", c - for p in c.meta().keys(): - #print " param:", p - if type(p) is tuple: - p = '.'.join(p) - - ## If the parameter is not in the list, add it. - matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly) - #print " matches:", matches - if len(matches) == 0: - i = QtGui.QListWidgetItem(p) - if p in self.paramList and self.paramList[p] is True: - #print " set checked" - i.setCheckState(QtCore.Qt.Checked) - else: - #print " set unchecked" - i.setCheckState(QtCore.Qt.Unchecked) - self.ctrl.avgParamList.addItem(i) - else: - i = matches[0] - - self.paramList[p] = (i.checkState() == QtCore.Qt.Checked) - #print "paramList:", self.paramList - - - ## This is bullshit. - def writeSvg(self, fileName=None): - if fileName is None: - fileName = QtGui.QFileDialog.getSaveFileName() - if isinstance(fileName, tuple): - raise Exception("Not implemented yet..") - fileName = str(fileName) - PlotItem.lastFileDir = os.path.dirname(fileName) - - rect = self.vb.viewRect() - xRange = rect.left(), rect.right() - - svg = "" - fh = open(fileName, 'w') - - dx = max(rect.right(),0) - min(rect.left(),0) - ymn = min(rect.top(), rect.bottom()) - ymx = max(rect.top(), rect.bottom()) - dy = max(ymx,0) - min(ymn,0) - sx = 1. - sy = 1. - while dx*sx < 10: - sx *= 1000 - while dy*sy < 10: - sy *= 1000 - sy *= -1 - - #fh.write('<svg viewBox="%f %f %f %f">\n' % (rect.left()*sx, rect.top()*sx, rect.width()*sy, rect.height()*sy)) - fh.write('<svg>\n') - fh.write('<path fill="none" stroke="#000000" stroke-opacity="0.5" stroke-width="1" d="M%f,0 L%f,0"/>\n' % (rect.left()*sx, rect.right()*sx)) - fh.write('<path fill="none" stroke="#000000" stroke-opacity="0.5" stroke-width="1" d="M0,%f L0,%f"/>\n' % (rect.top()*sy, rect.bottom()*sy)) - - - for item in self.curves: - if isinstance(item, PlotCurveItem): - color = colorStr(item.pen.color()) - opacity = item.pen.color().alpha() / 255. - color = color[:6] - x, y = item.getData() - mask = (x > xRange[0]) * (x < xRange[1]) - mask[:-1] += mask[1:] - m2 = mask.copy() - mask[1:] += m2[:-1] - x = x[mask] - y = y[mask] - - x *= sx - y *= sy - - #fh.write('<g fill="none" stroke="#%s" stroke-opacity="1" stroke-width="1">\n' % color) - fh.write('<path fill="none" stroke="#%s" stroke-opacity="%f" stroke-width="1" d="M%f,%f ' % (color, opacity, x[0], y[0])) - for i in xrange(1, len(x)): - fh.write('L%f,%f ' % (x[i], y[i])) - - fh.write('"/>') - #fh.write("</g>") - for item in self.dataItems: - if isinstance(item, ScatterPlotItem): - - pRect = item.boundingRect() - vRect = pRect.intersected(rect) - - for point in item.points(): - pos = point.pos() - if not rect.contains(pos): - continue - color = colorStr(point.brush.color()) - opacity = point.brush.color().alpha() / 255. - color = color[:6] - x = pos.x() * sx - y = pos.y() * sy - - fh.write('<circle cx="%f" cy="%f" r="1" fill="#%s" stroke="none" fill-opacity="%f"/>\n' % (x, y, color, opacity)) - #fh.write('<path fill="none" stroke="#%s" stroke-opacity="%f" stroke-width="1" d="M%f,%f ' % (color, opacity, x[0], y[0])) - #for i in xrange(1, len(x)): - #fh.write('L%f,%f ' % (x[i], y[i])) - - #fh.write('"/>') - - ## get list of curves, scatter plots - - - fh.write("</svg>\n") - - - - #def writeSvg(self, fileName=None): - #if fileName is None: - #fileName = QtGui.QFileDialog.getSaveFileName() - #fileName = str(fileName) - #PlotItem.lastFileDir = os.path.dirname(fileName) - - #self.svg = QtSvg.QSvgGenerator() - #self.svg.setFileName(fileName) - #res = 120. - #view = self.scene().views()[0] - #bounds = view.viewport().rect() - #bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height()) - - #self.svg.setResolution(res) - #self.svg.setViewBox(bounds) - - #self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height())) - - #painter = QtGui.QPainter(self.svg) - #view.render(painter, bounds) - - #painter.end() - - ### Workaround to set pen widths correctly - #import re - #data = open(fileName).readlines() - #for i in range(len(data)): - #line = data[i] - #m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line) - #if m is not None: - ##print "Matched group:", line - #g = m.groups() - #matrix = map(float, g[2].split(',')) - ##print "matrix:", matrix - #scale = max(abs(matrix[0]), abs(matrix[3])) - #if scale == 0 or scale == 1.0: - #continue - #data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n' - ##print "old line:", line - ##print "new line:", data[i] - #open(fileName, 'w').write(''.join(data)) - - - def writeImage(self, fileName=None): - if fileName is None: - fileName = QtGui.QFileDialog.getSaveFileName() - if isinstance(fileName, tuple): - raise Exception("Not implemented yet..") - fileName = str(fileName) - PlotItem.lastFileDir = os.path.dirname(fileName) - self.png = QtGui.QImage(int(self.size().width()), int(self.size().height()), QtGui.QImage.Format_ARGB32) - painter = QtGui.QPainter(self.png) - painter.setRenderHints(painter.Antialiasing | painter.TextAntialiasing) - self.scene().render(painter, QtCore.QRectF(), self.mapRectToScene(self.boundingRect())) - painter.end() - self.png.save(fileName) - - def writeCsv(self, fileName=None): - if fileName is None: - fileName = QtGui.QFileDialog.getSaveFileName() - fileName = str(fileName) - PlotItem.lastFileDir = os.path.dirname(fileName) - - fd = open(fileName, 'w') - data = [c.getData() for c in self.curves] - i = 0 - while True: - done = True - for d in data: - if i < len(d[0]): - fd.write('%g,%g,'%(d[0][i], d[1][i])) - done = False - else: - fd.write(' , ,') - fd.write('\n') - if done: - break - i += 1 - fd.close() - - - def saveState(self): - if not HAVE_WIDGETGROUP: - raise Exception("State save/restore requires WidgetGroup class.") - state = self.stateGroup.state() - state['paramList'] = self.paramList.copy() - #print "\nSAVE %s:\n" % str(self.name), state - #print "Saving state. averageGroup.isChecked(): %s state: %s" % (str(self.ctrl.averageGroup.isChecked()), str(state['averageGroup'])) - return state - - def restoreState(self, state): - if not HAVE_WIDGETGROUP: - raise Exception("State save/restore requires WidgetGroup class.") - if 'paramList' in state: - self.paramList = state['paramList'].copy() - - self.stateGroup.setState(state) - self.updateSpectrumMode() - self.updateDownsampling() - self.updateAlpha() - self.updateDecimation() - - self.stateGroup.setState(state) - self.updateXScale() - self.updateYScale() - self.updateParamList() - - #print "\nRESTORE %s:\n" % str(self.name), state - #print "Restoring state. averageGroup.isChecked(): %s state: %s" % (str(self.ctrl.averageGroup.isChecked()), str(state['averageGroup'])) - #avg = self.ctrl.averageGroup.isChecked() - #if avg != state['averageGroup']: - #print " WARNING: avgGroup is %s, should be %s" % (str(avg), str(state['averageGroup'])) - - - def widgetGroupInterface(self): - return (None, PlotItem.saveState, PlotItem.restoreState) - - def updateSpectrumMode(self, b=None): - if b is None: - b = self.ctrl.powerSpectrumGroup.isChecked() - for c in self.curves: - c.setSpectrumMode(b) - self.enableAutoScale() - self.recomputeAverages() - - - def updateDownsampling(self): - ds = self.downsampleMode() - for c in self.curves: - c.setDownsampling(ds) - self.recomputeAverages() - #for c in self.avgCurves.values(): - #c[1].setDownsampling(ds) - - - def downsampleMode(self): - if self.ctrl.decimateGroup.isChecked(): - if self.ctrl.manualDecimateRadio.isChecked(): - ds = self.ctrl.downsampleSpin.value() - else: - ds = True - else: - ds = False - return ds - - def updateDecimation(self): - if self.ctrl.maxTracesCheck.isChecked(): - numCurves = self.ctrl.maxTracesSpin.value() - else: - numCurves = -1 - - curves = self.curves[:] - split = len(curves) - numCurves - for i in range(len(curves)): - if numCurves == -1 or i >= split: - curves[i].show() - else: - if self.ctrl.forgetTracesCheck.isChecked(): - curves[i].free() - self.removeItem(curves[i]) - else: - curves[i].hide() - - - def updateAlpha(self, *args): - (alpha, auto) = self.alphaState() - for c in self.curves: - c.setAlpha(alpha**2, auto) - - #self.replot(autoRange=False) - - def alphaState(self): - enabled = self.ctrl.alphaGroup.isChecked() - auto = self.ctrl.autoAlphaCheck.isChecked() - alpha = float(self.ctrl.alphaSlider.value()) / self.ctrl.alphaSlider.maximum() - if auto: - alpha = 1.0 ## should be 1/number of overlapping plots - if not enabled: - auto = False - alpha = 1.0 - return (alpha, auto) - - def pointMode(self): - if self.ctrl.pointsGroup.isChecked(): - if self.ctrl.autoPointsCheck.isChecked(): - mode = None - else: - mode = True - else: - mode = False - return mode - - def wheelEvent(self, ev): - # disables default panning the whole scene by mousewheel - ev.accept() - - def resizeEvent(self, ev): - if self.ctrlBtn is None: ## already closed down - return - self.ctrlBtn.move(0, self.size().height() - self.ctrlBtn.size().height()) - self.autoBtn.move(self.ctrlBtn.width(), self.size().height() - self.autoBtn.size().height()) - - def hoverMoveEvent(self, ev): - self.mousePos = ev.pos() - self.mouseScreenPos = ev.screenPos() - - def ctrlBtnClicked(self): - self.ctrlMenu.popup(self.mouseScreenPos) - - def getLabel(self, key): - pass - - def _checkScaleKey(self, key): - if key not in self.scales: - raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(self.scales.keys()))) - - def getScale(self, key): - self._checkScaleKey(key) - return self.scales[key]['item'] - - def setLabel(self, key, text=None, units=None, unitPrefix=None, **args): - self.getScale(key).setLabel(text=text, units=units, unitPrefix=unitPrefix, **args) - - def showLabel(self, key, show=True): - self.getScale(key).showLabel(show) - - def setTitle(self, title=None, **args): - if title is None: - self.titleLabel.setVisible(False) - self.layout.setRowFixedHeight(0, 0) - self.titleLabel.setMaximumHeight(0) - else: - self.titleLabel.setMaximumHeight(30) - self.layout.setRowFixedHeight(0, 30) - self.titleLabel.setVisible(True) - self.titleLabel.setText(title, **args) - - def showScale(self, key, show=True): - s = self.getScale(key) - p = self.scales[key]['pos'] - if show: - s.show() - else: - s.hide() - - def _plotArray(self, arr, x=None): - if arr.ndim != 1: - raise Exception("Array must be 1D to plot (shape is %s)" % arr.shape) - if x is None: - x = np.arange(arr.shape[0]) - if x.ndim != 1: - raise Exception("X array must be 1D to plot (shape is %s)" % x.shape) - c = PlotCurveItem(arr, x=x) - return c - - - - def _plotMetaArray(self, arr, x=None, autoLabel=True): - inf = arr.infoCopy() - if arr.ndim != 1: - raise Exception('can only automatically plot 1 dimensional arrays.') - ## create curve - try: - xv = arr.xvals(0) - #print 'xvals:', xv - except: - if x is None: - xv = arange(arr.shape[0]) - else: - xv = x - c = PlotCurveItem() - c.setData(x=xv, y=arr.view(np.ndarray)) - - if autoLabel: - name = arr._info[0].get('name', None) - units = arr._info[0].get('units', None) - self.setLabel('bottom', text=name, units=units) - - name = arr._info[1].get('name', None) - units = arr._info[1].get('units', None) - self.setLabel('left', text=name, units=units) - - return c - - def saveSvgClicked(self): - fileName = QtGui.QFileDialog.getSaveFileName() - self.writeSvg(fileName) - - ## QFileDialog seems to be broken under OSX - #self.fileDialog = QtGui.QFileDialog() - ##if PlotItem.lastFileDir is not None: - ##self.fileDialog.setDirectory(PlotItem.lastFileDir) - #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - #if PlotItem.lastFileDir is not None: - #self.fileDialog.setDirectory(PlotItem.lastFileDir) - - #self.fileDialog.show() - ##QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeSvg) - #self.fileDialog.fileSelected.connect(self.writeSvg) - - #def svgFileSelected(self, fileName): - ##PlotWidget.lastFileDir = os.path.split(fileName)[0] - #self.writeSvg(str(fileName)) - - def saveImgClicked(self): - self.fileDialog = QtGui.QFileDialog() - #if PlotItem.lastFileDir is not None: - #self.fileDialog.setDirectory(PlotItem.lastFileDir) - if PlotItem.lastFileDir is not None: - self.fileDialog.setDirectory(PlotItem.lastFileDir) - self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - self.fileDialog.show() - #QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeImage) - self.fileDialog.fileSelected.connect(self.writeImage) - - def saveCsvClicked(self): - self.fileDialog = QtGui.QFileDialog() - #if PlotItem.lastFileDir is not None: - #self.fileDialog.setDirectory(PlotItem.lastFileDir) - if PlotItem.lastFileDir is not None: - self.fileDialog.setDirectory(PlotItem.lastFileDir) - self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - self.fileDialog.show() - #QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeCsv) - self.fileDialog.fileSelected.connect(self.writeCsv) - #def imgFileSelected(self, fileName): - ##PlotWidget.lastFileDir = os.path.split(fileName)[0] - #self.writeImage(str(fileName)) - - -class PlotWidgetManager(QtCore.QObject): - - sigWidgetListChanged = QtCore.Signal(object) - - """Used for managing communication between PlotWidgets""" - def __init__(self): - QtCore.QObject.__init__(self) - self.widgets = weakref.WeakValueDictionary() # Don't keep PlotWidgets around just because they are listed here - - def addWidget(self, w, name): - self.widgets[name] = w - #self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys()) - self.sigWidgetListChanged.emit(self.widgets.keys()) - - def removeWidget(self, name): - if name in self.widgets: - del self.widgets[name] - #self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys()) - self.sigWidgetListChanged.emit(self.widgets.keys()) - else: - print "plot %s not managed" % name - - - def listWidgets(self): - return self.widgets.keys() - - def getWidget(self, name): - if name not in self.widgets: - return None - else: - return self.widgets[name] - - def linkX(self, p1, p2): - #QtCore.QObject.connect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged) - p1.sigXRangeChanged.connect(p2.linkXChanged) - #QtCore.QObject.connect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged) - p2.sigXRangeChanged.connect(p1.linkXChanged) - p1.linkXChanged(p2) - #p2.setManualXScale() - - def unlinkX(self, p1, p2): - #QtCore.QObject.disconnect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged) - p1.sigXRangeChanged.disconnect(p2.linkXChanged) - #QtCore.QObject.disconnect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged) - p2.sigXRangeChanged.disconnect(p1.linkXChanged) - - def linkY(self, p1, p2): - #QtCore.QObject.connect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged) - p1.sigYRangeChanged.connect(p2.linkYChanged) - #QtCore.QObject.connect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged) - p2.sigYRangeChanged.connect(p1.linkYChanged) - p1.linkYChanged(p2) - #p2.setManualYScale() - - def unlinkY(self, p1, p2): - #QtCore.QObject.disconnect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged) - p1.sigYRangeChanged.disconnect(p2.linkYChanged) - #QtCore.QObject.disconnect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged) - p2.sigYRangeChanged.disconnect(p1.linkYChanged) diff --git a/Point.py b/Point.py index b98dfad0..c16d4df6 100644 --- a/Point.py +++ b/Point.py @@ -5,7 +5,7 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -from PyQt4 import QtCore +from Qt import QtCore import numpy as np def clip(x, mn, mx): @@ -23,12 +23,12 @@ class Point(QtCore.QPointF): if isinstance(args[0], QtCore.QSizeF): QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height())) return + elif isinstance(args[0], float) or isinstance(args[0], int): + QtCore.QPointF.__init__(self, float(args[0]), float(args[0])) + return elif hasattr(args[0], '__getitem__'): QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1])) return - elif type(args[0]) in [float, int]: - QtCore.QPointF.__init__(self, float(args[0]), float(args[0])) - return elif len(args) == 2: QtCore.QPointF.__init__(self, args[0], args[1]) return @@ -101,6 +101,10 @@ class Point(QtCore.QPointF): """Returns the vector length of this Point.""" return (self[0]**2 + self[1]**2) ** 0.5 + def norm(self): + """Returns a vector in the same direction with unit length.""" + return self / self.length() + def angle(self, a): """Returns the angle in degrees between this vector and the vector a.""" n1 = self.length() @@ -139,4 +143,7 @@ class Point(QtCore.QPointF): return max(self[0], self[1]) def copy(self): - return Point(self) \ No newline at end of file + return Point(self) + + def toQPoint(self): + return QtCore.QPoint(*self) \ No newline at end of file diff --git a/Point.pyc b/Point.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47869a3a385e284b53b7514a215d11f5d5c94b26 GIT binary patch literal 6601 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 literal 0 HcmV?d00001 diff --git a/Qt.py b/Qt.py new file mode 100644 index 00000000..e1d4b28a --- /dev/null +++ b/Qt.py @@ -0,0 +1,5 @@ +## Do all Qt imports from here to allow easier PyQt / PySide compatibility + +from PyQt4 import QtGui, QtCore, QtOpenGL, QtSvg +if not hasattr(QtCore, 'Signal'): + QtCore.Signal = QtCore.pyqtSignal diff --git a/Qt.pyc b/Qt.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a46429d8504f92f1ef55df29f6c149de83342e4 GIT binary patch literal 367 zcmYjM!A`?440Y0OLz58yz^SL^vLiy1K-vWj+KNjrQ`SapSkiWl3-ruya^w$?xPntA zfn`5G%g>Gze4a1AKHj$loGhTcrf|0eB{>5FU>W2LEQg$f4ImG|hLDHA)C?4dlEWJZ zxAy#L?s2z(hK2*kqL3EpZ?cKujLHNzgU0#9ZrWbq*+lO<)okugitM?X6q&9XX||D< zOs$78!Bt8Xq|5{N;LpYdK`os`>~TrF8;|%qp86vqihtl9sT6f<l`z9WiI=uN2rC=y zc1G?+(UwZc;<yq<@5O-H2*0oDWmgOJDs5+!6LIYQuaRKfjnj9oY+DXSUHi+NfBuqP Fupb@fQilKl literal 0 HcmV?d00001 diff --git a/SignalProxy.py b/SignalProxy.py index 6ac25193..95d94ba8 100644 --- a/SignalProxy.py +++ b/SignalProxy.py @@ -1,31 +1,38 @@ # -*- coding: utf-8 -*- -from PyQt4 import QtCore +from Qt import QtCore from ptime import time +__all__ = ['SignalProxy'] + class SignalProxy(QtCore.QObject): """Object which collects rapid-fire signals and condenses them into a single signal. Used, for example, to prevent a SpinBox from generating multiple signals when the mouse wheel is rolled - over it.""" + over it. + + Emits sigDelayed after input signals have stopped for a certain period of time. + """ + + sigDelayed = QtCore.Signal(object) - def __init__(self, source, signal, delay=0.3): + def __init__(self, signal, delay=0.3, slot=None): """Initialization arguments: - source - Any QObject that will emit signal, or None if signal is new style - signal - Output of QtCore.SIGNAL(...), or obj.signal for new style - delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s)""" + signal - a bound Signal or pyqtSignal instance + delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s) + slot - Optional function to connect sigDelayed to. + """ QtCore.QObject.__init__(self) - if source is None: - signal.connect(self.signalReceived) - self.signal = QtCore.SIGNAL('signal') - else: - source.connect(source, signal, self.signalReceived) - self.signal = signal + signal.connect(self.signalReceived) + self.signal = signal self.delay = delay self.args = None self.timer = QtCore.QTimer() self.timer.timeout.connect(self.flush) self.block = False + self.slot = slot + if slot is not None: + self.sigDelayed.connect(slot) def setDelay(self, delay): self.delay = delay @@ -42,28 +49,39 @@ class SignalProxy(QtCore.QObject): """If there is a signal queued up, send it now.""" if self.args is None or self.block: return False - self.emit(self.signal, *self.args) + #self.emit(self.signal, *self.args) + self.sigDelayed.emit(self.args) self.args = None + self.timer.stop() return True def disconnect(self): self.block = True - + try: + self.signal.disconnect(self.signalReceived) + except: + pass + try: + self.sigDelayed.disconnect(self.slot) + except: + pass + + -def proxyConnect(source, signal, slot, delay=0.3): - """Connect a signal to a slot with delay. Returns the SignalProxy - object that was created. Be sure to store this object so it is not - garbage-collected immediately.""" - sp = SignalProxy(source, signal, delay) - if source is None: - sp.connect(sp, QtCore.SIGNAL('signal'), slot) - else: - sp.connect(sp, signal, slot) - return sp +#def proxyConnect(source, signal, slot, delay=0.3): + #"""Connect a signal to a slot with delay. Returns the SignalProxy + #object that was created. Be sure to store this object so it is not + #garbage-collected immediately.""" + #sp = SignalProxy(source, signal, delay) + #if source is None: + #sp.connect(sp, QtCore.SIGNAL('signal'), slot) + #else: + #sp.connect(sp, signal, slot) + #return sp if __name__ == '__main__': - from PyQt4 import QtGui + from Qt import QtGui app = QtGui.QApplication([]) win = QtGui.QMainWindow() spin = QtGui.QSpinBox() @@ -71,8 +89,12 @@ if __name__ == '__main__': win.show() def fn(*args): - print "Got signal:", args + print "Raw signal:", args + def fn2(*args): + print "Delayed signal:", args - proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn) + spin.valueChanged.connect(fn) + #proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn) + proxy = SignalProxy(spin.valueChanged, delay=0.5, slot=fn2) \ No newline at end of file diff --git a/SignalProxy.pyc b/SignalProxy.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42390dfa615e249f5f33e7c89c8d75fd048061e9 GIT binary patch literal 3604 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 literal 0 HcmV?d00001 diff --git a/Transform.py b/Transform.py index 9938a190..91614f1d 100644 --- a/Transform.py +++ b/Transform.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from PyQt4 import QtCore, QtGui +from Qt import QtCore, QtGui from Point import Point import numpy as np @@ -108,8 +108,8 @@ class Transform(QtGui.QTransform): def saveState(self): p = self._state['pos'] s = self._state['scale'] - if s[0] == 0: - raise Exception('Invalid scale') + #if s[0] == 0: + #raise Exception('Invalid scale: %s' % str(s)) return {'pos': (p[0], p[1]), 'scale': (s[0], s[1]), 'angle': self._state['angle']} def restoreState(self, state): diff --git a/Transform.pyc b/Transform.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bb1ce46bb5ab0d7334093a3c975e466b7cab671 GIT binary patch literal 9368 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 F{U0*kgaiNp literal 0 HcmV?d00001 diff --git a/WidgetGroup.py b/WidgetGroup.py new file mode 100644 index 00000000..32b952e6 --- /dev/null +++ b/WidgetGroup.py @@ -0,0 +1,282 @@ +# -*- coding: utf-8 -*- +""" +WidgetGroup.py - WidgetGroup class for easily managing lots of Qt widgets +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +This class addresses the problem of having to save and restore the state +of a large group of widgets. +""" + +from Qt import QtCore, QtGui +import weakref, inspect + + +__all__ = ['WidgetGroup'] + +def splitterState(w): + s = str(w.saveState().toPercentEncoding()) + return s + +def restoreSplitter(w, s): + if type(s) is list: + w.setSizes(s) + elif type(s) is str: + w.restoreState(QtCore.QByteArray.fromPercentEncoding(s)) + else: + print "Can't configure QSplitter using object of type", type(s) + if w.count() > 0: ## make sure at least one item is not collapsed + for i in w.sizes(): + if i > 0: + return + w.setSizes([50] * w.count()) + +def comboState(w): + ind = w.currentIndex() + data = w.itemData(ind) + #if not data.isValid(): + if data is not None: + try: + if not data.isValid(): + data = None + else: + data = data.toInt()[0] + except AttributeError: + pass + if data is None: + return unicode(w.itemText(ind)) + else: + return data + +def setComboState(w, v): + if type(v) is int: + #ind = w.findData(QtCore.QVariant(v)) + ind = w.findData(v) + if ind > -1: + w.setCurrentIndex(ind) + return + w.setCurrentIndex(w.findText(str(v))) + + +class WidgetGroup(QtCore.QObject): + """This class takes a list of widgets and keeps an internal record of their state which is always up to date. Allows reading and writing from groups of widgets simultaneously.""" + + ## List of widget types which can be handled by WidgetGroup. + ## The value for each type is a tuple (change signal function, get function, set function, [auto-add children]) + ## The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just + ## when it is changed by user interaction. (for example, 'clicked' is not a valid signal here) + ## If the change signal is None, the value of the widget is not cached. + ## Custom widgets not in this list can be made to work with WidgetGroup by giving them a 'widgetGroupInterface' method + ## which returns the tuple. + classes = { + QtGui.QSpinBox: + (lambda w: w.valueChanged, + QtGui.QSpinBox.value, + QtGui.QSpinBox.setValue), + QtGui.QDoubleSpinBox: + (lambda w: w.valueChanged, + QtGui.QDoubleSpinBox.value, + QtGui.QDoubleSpinBox.setValue), + QtGui.QSplitter: + (None, + splitterState, + restoreSplitter, + True), + QtGui.QCheckBox: + (lambda w: w.stateChanged, + QtGui.QCheckBox.isChecked, + QtGui.QCheckBox.setChecked), + QtGui.QComboBox: + (lambda w: w.currentIndexChanged, + comboState, + setComboState), + QtGui.QGroupBox: + (lambda w: w.toggled, + QtGui.QGroupBox.isChecked, + QtGui.QGroupBox.setChecked, + True), + QtGui.QLineEdit: + (lambda w: w.editingFinished, + lambda w: str(w.text()), + QtGui.QLineEdit.setText), + QtGui.QRadioButton: + (lambda w: w.toggled, + QtGui.QRadioButton.isChecked, + QtGui.QRadioButton.setChecked), + QtGui.QSlider: + (lambda w: w.valueChanged, + QtGui.QSlider.value, + QtGui.QSlider.setValue), + } + + sigChanged = QtCore.Signal(str, object) + + + def __init__(self, widgetList=None): + """Initialize WidgetGroup, adding specified widgets into this group. + widgetList can be: + - a list of widget specifications (widget, [name], [scale]) + - a dict of name: widget pairs + - any QObject, and all compatible child widgets will be added recursively. + + The 'scale' parameter for each widget allows QSpinBox to display a different value than the value recorded + in the group state (for example, the program may set a spin box value to 100e-6 and have it displayed as 100 to the user) + """ + QtCore.QObject.__init__(self) + self.widgetList = weakref.WeakKeyDictionary() # Make sure widgets don't stick around just because they are listed here + self.scales = weakref.WeakKeyDictionary() + self.cache = {} ## name:value pairs + self.uncachedWidgets = weakref.WeakKeyDictionary() + if isinstance(widgetList, QtCore.QObject): + self.autoAdd(widgetList) + elif isinstance(widgetList, list): + for w in widgetList: + self.addWidget(*w) + elif isinstance(widgetList, dict): + for name, w in widgetList.iteritems(): + self.addWidget(w, name) + elif widgetList is None: + return + else: + raise Exception("Wrong argument type %s" % type(widgetList)) + + def addWidget(self, w, name=None, scale=None): + if not self.acceptsType(w): + raise Exception("Widget type %s not supported by WidgetGroup" % type(w)) + if name is None: + name = str(w.objectName()) + if name == '': + raise Exception("Cannot add widget '%s' without a name." % str(w)) + self.widgetList[w] = name + self.scales[w] = scale + self.readWidget(w) + + if type(w) in WidgetGroup.classes: + signal = WidgetGroup.classes[type(w)][0] + else: + signal = w.widgetGroupInterface()[0] + + if signal is not None: + if inspect.isfunction(signal) or inspect.ismethod(signal): + signal = signal(w) + signal.connect(self.mkChangeCallback(w)) + else: + self.uncachedWidgets[w] = None + + def findWidget(self, name): + for w in self.widgetList: + if self.widgetList[w] == name: + return w + return None + + def interface(self, obj): + t = type(obj) + if t in WidgetGroup.classes: + return WidgetGroup.classes[t] + else: + return obj.widgetGroupInterface() + + def checkForChildren(self, obj): + """Return true if we should automatically search the children of this object for more.""" + iface = self.interface(obj) + return (len(iface) > 3 and iface[3]) + + def autoAdd(self, obj): + ## Find all children of this object and add them if possible. + accepted = self.acceptsType(obj) + if accepted: + #print "%s auto add %s" % (self.objectName(), obj.objectName()) + self.addWidget(obj) + + if not accepted or self.checkForChildren(obj): + for c in obj.children(): + self.autoAdd(c) + + def acceptsType(self, obj): + for c in WidgetGroup.classes: + if isinstance(obj, c): + return True + if hasattr(obj, 'widgetGroupInterface'): + return True + return False + #return (type(obj) in WidgetGroup.classes) + + def setScale(self, widget, scale): + val = self.readWidget(widget) + self.scales[widget] = scale + self.setWidget(widget, val) + #print "scaling %f to %f" % (val, self.readWidget(widget)) + + + def mkChangeCallback(self, w): + return lambda *args: self.widgetChanged(w, *args) + + def widgetChanged(self, w, *args): + #print "widget changed" + n = self.widgetList[w] + v1 = self.cache[n] + v2 = self.readWidget(w) + if v1 != v2: + #print "widget", n, " = ", v2 + self.emit(QtCore.SIGNAL('changed'), self.widgetList[w], v2) + self.sigChanged.emit(self.widgetList[w], v2) + + def state(self): + for w in self.uncachedWidgets: + self.readWidget(w) + + #cc = self.cache.copy() + #if 'averageGroup' in cc: + #val = cc['averageGroup'] + #w = self.findWidget('averageGroup') + #self.readWidget(w) + #if val != self.cache['averageGroup']: + #print " AverageGroup did not match cached value!" + #else: + #print " AverageGroup OK" + return self.cache.copy() + + def setState(self, s): + #print "SET STATE", self, s + for w in self.widgetList: + n = self.widgetList[w] + #print " restore %s?" % n + if n not in s: + continue + #print " restore state", w, n, s[n] + self.setWidget(w, s[n]) + + def readWidget(self, w): + if type(w) in WidgetGroup.classes: + getFunc = WidgetGroup.classes[type(w)][1] + else: + getFunc = w.widgetGroupInterface()[1] + + if getFunc is None: + return None + + val = getFunc(w) + if self.scales[w] is not None: + val /= self.scales[w] + #if isinstance(val, QtCore.QString): + #val = str(val) + n = self.widgetList[w] + self.cache[n] = val + return val + + def setWidget(self, w, v): + v1 = v + if self.scales[w] is not None: + v *= self.scales[w] + + if type(w) in WidgetGroup.classes: + setFunc = WidgetGroup.classes[type(w)][2] + else: + setFunc = w.widgetGroupInterface()[2] + setFunc(w, v) + #name = self.widgetList[w] + #if name in self.cache and (self.cache[name] != v1): + #print "%s: Cached value %s != set value %s" % (name, str(self.cache[name]), str(v1)) + + + \ No newline at end of file diff --git a/WidgetGroup.pyc b/WidgetGroup.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1743d3041eb7cf343cd14cd0a04c7dc72b48e741 GIT binary patch literal 9737 zcmb_iO>i7X74F$x{p?zbWJ|Uq;)KD8?F}Td9Uu^65**2PTqH^~YvLec#Av2>HPY^k zJUwe^Q+5tG6*y3EqKYFYPTYVhIB|d~F5IBt%8erj4ip#Q`(Dq?N~%ENjb%@(r~CEm z?)U%RsQUNm#;^Zz?ae^tKUMtx3Lf)5noy}Vw2lfZs_Up;MXgm-ud3Fn%He2Lb!%#^ zCJ^Y4skJc`)>Ow)YvU>$Qy*acgbK%{GpWJ}>C{y?DV+v^bwzC*QEgzWbMS#uUtgOd z>Q=MCbDVONYTSy#j?S*9Nq=W?XW%~Xy8A}lpz9mswv*J=zKOa6x97)xCyG06H_42f zwB6Or-4zt3v6So#(x|hUxi4I}c)@jF?{8~&$?xs>ojB?GjpfK>X|&PLbm;cuP^a!2 zH*TJP=i)`T8wENxdeLoZUA|;{ne5z4Qtd`@JL&mZl*EgT#?8&h<VW+vFxAFr<7S)M z-AR*;uI}+%oBmxMGfQ0K-_@=khb{m!;v%HU{7g3h=eu1$?P#|{3UFb5n?<(~{Tn~c z3P_cWqgl<CusR!)?)84Ok{v;JAGHF|QxTlc5*~93O{Ua6rM4W>f2+dwfurs_>Yf8W zaK%cqN-#E{Pgdt|9J$34W=A=dyrokRD7zX5Nr+q7GjZ*1#?~~^C~3?&^z<Hx-qq&( z;v)a=4DMu|)Zf`WzwfYMb~eqmJKZSDbSm3%H&a8SoX4Gj*Wju6Ea5S~Lc{CeE{?*^ z+mk9iEw`)WgHHVAeGpZFcuOE^3Y6O#S6dS~C{Q@@3<dmpiSznvB-JE|1ijd<sq`0W z?-^NN=iu_~6Y4(psua`jQ;}MC-dDG{5W`ifNQq=#HW6)WHF>WUv&hT%@wv<mlDHjp z`VffKR!#)BZzwd$#+D8;3PU#7(Ggat=mj*QHW~47TsmFIbT)|*qqA1@o;Dt-l}(|^ z#Z3t7;az3ay46<)nZA;y{veyfYdcMPhqx;e79{;Rlb#_~5fP74=uHq4xgL$k^%<-! z&X(W%IEKtT8s!{wX7QY=%%GifrkqJ9;AXrPC4UKz`92!bSZoSK8GR9GkUM9OQrmTv zcGTcma%-zf?wMD}Eda!`GFTHnJ8A(#x9)UQ4fow{s5DTH<KX?B7~6oSzy(43kXp!{ z_6bl6(=E)mL?c#&Q%!P17!dT+6k2lwre;rg6lJ=%>_e}}!j&Y}8HpL0xBYGu3bC`~ z2FU&xMz3VKt-6|~Nh*N-ID-D^64gzh9Wq7~IEq6-9%7df*+U@-lHNulf^!;g=5aI% zyqKv>IaU0wSLU5*X9B&sN~xv1lW@mJr_`uiORos!3N6if1O<kM*{LciO>H4HNp}z$ zV`xQjt;%D*0Lck17E;8LW-BLwRfsldA;hM<K|7WXbAtdRp=iuLI#S&3dQYGok%D5@ zuwhBW&+-IRNr*QDdrF8G@SQ&t>sRrZ-(hDBzY2$mwxS}qQ-_{YS4oDgv7$4s+SR=O zBJ5NSF;PIEQ$kFr7sJPut}ES88qOGA8OWwES}oR1tF6am6rGs@^E4o80x_%f(PH!% zM#c9N$y~7xotb^e=Kv&efei`>I<K~l7dYlIEkOb&P82xMDfd1B2#FX7ak97-I_0fS zsu$OAs|B_738kM<`jmQr0Om=BtA-8r0fLmLq>tAl>H|!DQiW5z&6=yiY3Y1Qg^x++ zv<hdW^Rx<QrSpsmk4oo^3Xe(WtP1C(b54cx(rK#jxOARX;R)${T7@U2^BLu}nhRv; zPtoj)6*7MtaRMAZZ1z4+FR{V4);pMs5oo{}#(o#kLXf1PIDt(ar8Z)4cQ>P8(?$5< zcX$1PaS<~h#DKaZc)8N;Cc6d;eL87DyPHNC`!w@5#E_sVk1<>m_4?h+k9E>F-N9l& z(Jvm+W!fhyAx#`7R-_f{cGvIr_0pyvcXTMD;vPm+8QRRtUB9;x`Y+!=-{4XI?{U0m z0Q_-};GF@IH}E{v)<OxxNa+z#LXPqFLmz{#e80PSl&3+S@Q_525FzPwx*uOMgdX2~ z=o8T9>riC>S`<fS^AVkdM*E<El(F_Krs(#dEfQ<PQx!Tx7#o+>2t>C{W%?KjBV-AI z<KSV4^?&HCN9}whTX!CM>qn-e+vV0ID+$U+ZtdXyU;*flP#lb4uz;8VzVm@FW_^qy z!tixmAz}mE`ozKUNrfy3A&12U*OG1uUmInX@xub*2t7~wBenN{bIZ5qBml)3rWnwC z59XA~L97e-X@UQ<+G`OWju$6ezI{r)TT%C`2iD$0%v!<npJVx10wWe+9Qs#wTG=d9 z5Ts|Az%@_c`L=+xB$6>_K%5&f6ab;+dwPFP{hW)Ol|I4j=pbrGNQMh{0N;}!JB4?V zY;>{V=06tTb^5IUK59c>a?3fS?1x=ifjW?c*|<%+{yFy>vES3T@!JG`SKog2Ad)b$ zNe_W8<%e>2{3taC;NpS1`le(^xC+v0zuQGFg)BddkbJwrX4D;CX*a^`hUT4dQTWt; zYNERu&aT8+c5WgsKPM+Uhiy}w0<JhOKEUbPOK>}SS>$X{{A#i%?l(df>kmMOFlx6o zgML@sBZvudG2geZ)~D;R+%mGWHu)Dn;x?t)VKeCGWG$)+NS}MCeGFU#ve?F8Z+8Qm z=es2C#S0ho^DhcLP<cSTBEwnaRJgWpIM0*vD1D>TA>~)h38>GlG-1S$cKSUYp6R7~ z#x##%%BGGq={eOU|5#s#S<TkhB{A96{&uR{QY^TI{#W&28N5WX#7_s3bq)ncN*O|A zV-rn3*wh*Abw3W+4{zCWj;xE24}&amU{fW(pCwnq&|{7wg$l6Mg4?VIo60Z}Ed)Ng zXFO(>GIVt>&^tVm$4o(zaam(@x9y$BkXU0GqWp~($9T^H5lymj(Wu6(GlPG1JWOk6 z5ckicU2sk!2dGs78ZJivr2+c_444hKtXiY6f&>cso)xK6uIL9c(;EuMrZpL(HGw5y ziVH-xN&)i}3zljQHZ&I&h#Mlab<|~;9KJ|`dzrn-+`KsD{-EVnLv4?%^tYrd`sLI@ zSVJmZlk6IYfn`MD!w%g;A<<^!XB~u0Thj_7IEVQcGi6vo`<hNW=-8_h5$=GrXGRW% zlwB+xRypxB(eZ#{|1RUD)g&QT%xBPAi715Fjg!nZ{hb|T(a?pB!G2>13lC<UMYH6` z1cX2pihk~lIfs6>ne?f1l!`_83kVO@LW*yGK%p`>p)TGRF(|SnGH2aA<<u5)R<PQN z*#$1<1mkpjRF9raG)d~6C=<?OV0Ty<MNvSfZ9mrns02)RmMcgbdPBLkWdQ@DwV8w# zSrW&DV`S3XwjtpX%-4n=Y<n+YP-y18gq{e$cL_aP^PzdRni_SGjEtxap9j7l;4!Ds zD3Sar=Qw140h)k+mE+DQDzhjmJjJ<TDWMAJC9b1h?lp6jSib#a;UGo07a$PZkZyQo zRAKHk)Gd(%#y~4l9<cPL1+dw@T$Fc|+m3c8Av!AumUBM=HZzGvLBHo%tRgFUe*Ts_ zd=4FmSuWL5JcLdoK%o+yrDif%si=h(Ky0DPmXz#8YFKW?iWt3D*n9=e@JdqkfRq6f zb{g?oG>VdXQp%XO=C?x8$2sy5oOu=?RKR@~!~VDi^pk-~O(ozPED*|TwmN27SmNod zpTg&*eYhypx={#+dcj}Qva`Mp!9$hbXg`I=rMD7y4v%URVT2}mag<(?mHEYp)VBQo zDtbbVu8xQF@osRK<{{$cMkElG2;M2!zLum*3^8z~pJB|Ft5MYv&XZewg!6554$%k{ zNzce&QLl8Y5uP$%lHtP6ryEjuE+j$%%c35}$PiZW&TEJa^V+hN72eUi%!Zoht)LOB zP|(n$EQ;&1N-u<=^jbTnLlL21GztmwUjmt}bb%t{(Crh@ic%*^%DfCP)YYLDh}D;G zFHo<oAE5+^PJ@r4)7G^?j5vIW>}`RUdBN3cg4F+p+VNQ+$10RwAZ20*H+|!yoGLPK z&F>n0NI1MzK#zE9lKVG!^7<aV{3M>56HqRB4E`2*xPZ<9c>qJfZy^G7Up%hxKM(!G zVbz5=JW|ag5(#J!30fO8WT;qd>o&Y=gyS-Vh@)LiM2DXh=nQ@jOooJ}8K1T+CCT}4 z7eoE{4*ol;En5E}56{_^0F6|Y2zL^Xz<yVZ$9JIyN_}sbDvJ$c0o7h(e;q@3iV)RU zWiQ>9-0oq%J4y2VM;-)E&UX=?gD4Me^H4in1>!$&2jW$hT%;c5;W{2OKvT-X0#d(R z45|`B@ujc?_s19o-NUDes!IC;Cy^YYJ!*vz(3;@|06oC>Tdu~3E{jtPw9FuxEX;-Q z#l{GE0gv&$^_Lgfqo@hJbuY?n2EgODZd_lv^1Apy6LkvaaAsc=!FrJxk{But`MYR; zy)c>-dS3^QKjF#E3Ihj}&?`^d??T*xa<xQ=MKpU)<#ZL3X{l4%S_TQ0wz;E~V#tcs zAVx%1$4COXeSj~@hs0pCTiB?Ib;7>@cdkgY&e0>K*P!i|OVEk!Eti*g>0r!5Nx^@3 z*a?-MSGW*(n#)dMI~HIno&OpSeGT}BCc_iP3kTOZfyXuvBkrZ8;06B%42JQos>~w9 zn}QpVOY(vxr+$o=GAM$u<l)fyJRB;g78pM;ossb0GA$osaACj9y~z$g#pN-U6?~`- z))E9C0>3R(gI?ItOCV!TH_3dMwhWV>nq)$`^@?bb<eT0{MPWA9r%fIz&!MPV)C_;( za1C*~cZ-mt6I#UcpTHqWyFx5&<L4U4Jm<_;sT|^W4iNi37Jxdrk+B9Lr_1w&2eP9t z+03y_>EJ+6En}eiZeAfPrz~g6Y)?{8;S4B=|4=#c<nlew`hkeL+7wX}f-x}vD4HIk zpLd#Y-$EmlghL`FM-x<!!hZxDHIJdAoN-dkW+Bc>lgYb?^7=ZHJrp-6#_Q|7B<y$j zN?loB50hYh-4jPOiGiXJBq}cTm_i-i<`)wAn7g{1^iiA2OGZ{`xYlDP>CwK5OE3Qp zz`iXp+(d~<-XbHfh|zc`P$7Tyo+OH=OBC|eu~@V!e^*n!y&lE-DyrfIA^G)FzF@3+ z_+*s4+RrlNk-X(<3tyylYSRt8+rL0*Nit|tTk_F{;vUQMQu(l9=WBeW6_Wr|!a^IA zzQZwkMzQH$z#il<H@ae%@HV0ik{tTWSZ>}$GwzJntCew-Ugq+)iZ9ny*rQ^s=+|Wo zf>6%XofFk#)u~f6C+p+&iTYUmNWG5V)q0~ogXdV2;fWWa5y!S_{i6tk_dKVraLURG wvKh$Akkuu#JQgbC%uRGgyt4?}Wh<U9*MUYNt8oO~k5}uJdaXWPKU#162XND=Hvj+t literal 0 HcmV?d00001 diff --git a/__init__.py b/__init__.py index 618436ac..bf371d2f 100644 --- a/__init__.py +++ b/__init__.py @@ -1,37 +1,102 @@ # -*- coding: utf-8 -*- ### import all the goodies and add some helper functions for easy CLI use -from functions import * -from graphicsItems import * -from graphicsWindows import * -#import PlotWidget -#import ImageView -from PyQt4 import QtGui +## 'Qt' is a local module; it is intended mainly to cover up the differences +## between PyQt4 and PySide. +from Qt import QtGui + +## not really safe. +#if QtGui.QApplication.instance() is None: + #app = QtGui.QApplication([]) + +CONFIG_OPTIONS = { + 'leftButtonPan': True ## if false, left button drags a rubber band for zooming in viewbox +} + +def setConfigOption(opt, value): + CONFIG_OPTIONS[opt] = value + +def getConfigOption(opt): + return CONFIG_OPTIONS[opt] + +## Import almost everything to make it available from a single namespace +## don't import the more complex systems--canvas, parametertree, flowchart, dockarea +## these must be imported separately. + +import os +def importAll(path): + d = os.path.join(os.path.split(__file__)[0], path) + 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 != '__init__.py': + files.append(f[:-3]) + + for modName in files: + mod = __import__(path+"."+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): + globals()[k] = getattr(mod, k) + +importAll('graphicsItems') +importAll('widgets') + +from imageview import * +from WidgetGroup import * from Point import Point from Transform import Transform +from functions import * +from graphicsWindows import * +from SignalProxy import * + + + + +## Convenience functions for command-line use + + plots = [] images = [] QAPP = None def plot(*args, **kargs): + """ + | Create and return a PlotWindow (this is just a window with PlotWidget inside), plot data in it. + | Accepts a *title* argument to set the title of the window. + | All other arguments are used to plot data. (see :func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`) + """ mkQApp() if 'title' in kargs: w = PlotWindow(title=kargs['title']) del kargs['title'] else: w = PlotWindow() - w.plot(*args, **kargs) + if len(args)+len(kargs) > 0: + w.plot(*args, **kargs) plots.append(w) w.show() return w -def show(*args, **kargs): +def image(*args, **kargs): + """ + | Create and return an ImageWindow (this is just a window with ImageView widget inside), show image data inside. + | Will show 2D or 3D image data. + | Accepts a *title* argument to set the title of the window. + | All other arguments are used to show data. (see :func:`ImageView.setImage() <pyqtgraph.ImageView.setImage>`) + """ mkQApp() w = ImageWindow(*args, **kargs) images.append(w) w.show() return w +show = image ## for backward compatibility + def mkQApp(): if QtGui.QApplication.instance() is None: diff --git a/__init__.pyc b/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aaddcfeb79965db6e4aae3ca71070c9ab6555efd GIT binary patch literal 3585 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 literal 0 HcmV?d00001 diff --git a/canvas/Canvas.py b/canvas/Canvas.py new file mode 100644 index 00000000..9bd9e863 --- /dev/null +++ b/canvas/Canvas.py @@ -0,0 +1,554 @@ +# -*- coding: utf-8 -*- +if __name__ == '__main__': + import sys, os + md = os.path.dirname(os.path.abspath(__file__)) + sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path + #print md + +from CanvasTemplate import * +#from pyqtgraph.GraphicsView import GraphicsView +#import pyqtgraph.graphicsItems as graphicsItems +#from pyqtgraph.PlotWidget import PlotWidget +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.graphicsItems.ROI import ROI +from pyqtgraph.graphicsItems.ViewBox import ViewBox +from pyqtgraph.graphicsItems.GridItem import GridItem +#import DataManager +import numpy as np +import debug +#import pyqtgraph as pg +import weakref +from CanvasManager import CanvasManager +#import items +from CanvasItem import CanvasItem, GroupCanvasItem + +class Canvas(QtGui.QWidget): + + sigSelectionChanged = QtCore.Signal(object, object) + sigItemTransformChanged = QtCore.Signal(object, object) + sigItemTransformChangeFinished = QtCore.Signal(object, object) + + def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_Form() + self.ui.setupUi(self) + #self.view = self.ui.view + self.view = ViewBox() + self.ui.view.setCentralItem(self.view) + self.itemList = self.ui.itemList + self.itemList.setSelectionMode(self.itemList.ExtendedSelection) + self.allowTransforms = allowTransforms + self.multiSelectBox = SelectBox() + self.view.addItem(self.multiSelectBox) + self.multiSelectBox.hide() + self.multiSelectBox.setZValue(1e6) + self.ui.mirrorSelectionBtn.hide() + self.ui.resetTransformsBtn.hide() + + self.redirect = None ## which canvas to redirect items to + self.items = [] + + #self.view.enableMouse() + self.view.setAspectLocked(True) + self.view.invertY() + + grid = GridItem() + self.grid = CanvasItem(grid, name='Grid', movable=False) + self.addItem(self.grid) + + self.hideBtn = QtGui.QPushButton('>', self) + self.hideBtn.setFixedWidth(20) + self.hideBtn.setFixedHeight(20) + self.ctrlSize = 200 + self.sizeApplied = False + self.hideBtn.clicked.connect(self.hideBtnClicked) + self.ui.splitter.splitterMoved.connect(self.splitterMoved) + + self.ui.itemList.itemChanged.connect(self.treeItemChanged) + self.ui.itemList.sigItemMoved.connect(self.treeItemMoved) + self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected) + self.ui.autoRangeBtn.clicked.connect(self.autoRange) + self.ui.storeSvgBtn.clicked.connect(self.storeSvg) + self.ui.storePngBtn.clicked.connect(self.storePng) + self.ui.redirectCheck.toggled.connect(self.updateRedirect) + self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect) + self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged) + self.multiSelectBox.sigRegionChangeFinished.connect(self.multiSelectBoxChangeFinished) + self.ui.mirrorSelectionBtn.clicked.connect(self.mirrorSelectionClicked) + self.ui.resetTransformsBtn.clicked.connect(self.resetTransformsClicked) + + self.resizeEvent() + if hideCtrl: + self.hideBtnClicked() + + if name is not None: + self.registeredName = CanvasManager.instance().registerCanvas(self, name) + self.ui.redirectCombo.setHostName(self.registeredName) + + def storeSvg(self): + self.ui.view.writeSvg() + + def storePng(self): + self.ui.view.writeImage() + + def splitterMoved(self): + self.resizeEvent() + + def hideBtnClicked(self): + ctrlSize = self.ui.splitter.sizes()[1] + if ctrlSize == 0: + cs = self.ctrlSize + w = self.ui.splitter.size().width() + if cs > w: + cs = w - 20 + self.ui.splitter.setSizes([w-cs, cs]) + self.hideBtn.setText('>') + else: + self.ctrlSize = ctrlSize + self.ui.splitter.setSizes([100, 0]) + self.hideBtn.setText('<') + self.resizeEvent() + + def autoRange(self): + self.view.autoRange() + + def resizeEvent(self, ev=None): + if ev is not None: + QtGui.QWidget.resizeEvent(self, ev) + self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0) + + if not self.sizeApplied: + self.sizeApplied = True + s = min(self.width(), max(100, min(200, self.width()*0.25))) + s2 = self.width()-s + self.ui.splitter.setSizes([s2, s]) + + + def updateRedirect(self, *args): + ### Decide whether/where to redirect items and make it so + cname = str(self.ui.redirectCombo.currentText()) + man = CanvasManager.instance() + if self.ui.redirectCheck.isChecked() and cname != '': + redirect = man.getCanvas(cname) + else: + redirect = None + + if self.redirect is redirect: + return + + self.redirect = redirect + if redirect is None: + self.reclaimItems() + else: + self.redirectItems(redirect) + + + def redirectItems(self, canvas): + for i in self.items: + if i is self.grid: + continue + li = i.listItem + parent = li.parent() + if parent is None: + tree = li.treeWidget() + if tree is None: + print "Skipping item", i, i.name + continue + tree.removeTopLevelItem(li) + else: + parent.removeChild(li) + canvas.addItem(i) + + + def reclaimItems(self): + items = self.items + #self.items = {'Grid': items['Grid']} + #del items['Grid'] + self.items = [self.grid] + items.remove(self.grid) + + for i in items: + i.canvas.removeItem(i) + self.addItem(i) + + def treeItemChanged(self, item, col): + #gi = self.items.get(item.name, None) + #if gi is None: + #return + try: + citem = item.canvasItem + except AttributeError: + return + if item.checkState(0) == QtCore.Qt.Checked: + for i in range(item.childCount()): + item.child(i).setCheckState(0, QtCore.Qt.Checked) + citem.show() + else: + for i in range(item.childCount()): + item.child(i).setCheckState(0, QtCore.Qt.Unchecked) + citem.hide() + + def treeItemSelected(self): + sel = self.selectedItems() + #sel = [] + #for listItem in self.itemList.selectedItems(): + #if hasattr(listItem, 'canvasItem') and listItem.canvasItem is not None: + #sel.append(listItem.canvasItem) + #sel = [self.items[item.name] for item in sel] + + if len(sel) == 0: + #self.selectWidget.hide() + return + + multi = len(sel) > 1 + for i in self.items: + #i.ctrlWidget().hide() + ## updated the selected state of every item + i.selectionChanged(i in sel, multi) + + if len(sel)==1: + #item = sel[0] + #item.ctrlWidget().show() + self.multiSelectBox.hide() + self.ui.mirrorSelectionBtn.hide() + self.ui.resetTransformsBtn.hide() + elif len(sel) > 1: + self.showMultiSelectBox() + + #if item.isMovable(): + #self.selectBox.setPos(item.item.pos()) + #self.selectBox.setSize(item.item.sceneBoundingRect().size()) + #self.selectBox.show() + #else: + #self.selectBox.hide() + + #self.emit(QtCore.SIGNAL('itemSelected'), self, item) + self.sigSelectionChanged.emit(self, sel) + + def selectedItems(self): + """ + Return list of all selected canvasItems + """ + return [item.canvasItem for item in self.itemList.selectedItems() if item.canvasItem is not None] + + #def selectedItem(self): + #sel = self.itemList.selectedItems() + #if sel is None or len(sel) < 1: + #return + #return self.items.get(sel[0].name, None) + + def selectItem(self, item): + li = item.listItem + #li = self.getListItem(item.name()) + #print "select", li + self.itemList.setCurrentItem(li) + + + + def showMultiSelectBox(self): + ## Get list of selected canvas items + items = self.selectedItems() + + rect = self.view.itemBoundingRect(items[0].graphicsItem()) + for i in items: + if not i.isMovable(): ## all items in selection must be movable + return + br = self.view.itemBoundingRect(i.graphicsItem()) + rect = rect|br + + self.multiSelectBox.blockSignals(True) + self.multiSelectBox.setPos([rect.x(), rect.y()]) + self.multiSelectBox.setSize(rect.size()) + self.multiSelectBox.setAngle(0) + self.multiSelectBox.blockSignals(False) + + self.multiSelectBox.show() + + self.ui.mirrorSelectionBtn.show() + self.ui.resetTransformsBtn.show() + #self.multiSelectBoxBase = self.multiSelectBox.getState().copy() + + def mirrorSelectionClicked(self): + for ci in self.selectedItems(): + ci.mirrorY() + self.showMultiSelectBox() + + def resetTransformsClicked(self): + for i in self.selectedItems(): + i.resetTransformClicked() + self.showMultiSelectBox() + + def multiSelectBoxChanged(self): + self.multiSelectBoxMoved() + + def multiSelectBoxChangeFinished(self): + for ci in self.selectedItems(): + ci.applyTemporaryTransform() + ci.sigTransformChangeFinished.emit(ci) + + def multiSelectBoxMoved(self): + transform = self.multiSelectBox.getGlobalTransform() + for ci in self.selectedItems(): + ci.setTemporaryTransform(transform) + ci.sigTransformChanged.emit(ci) + + + def addGraphicsItem(self, item, **opts): + """Add a new GraphicsItem to the scene at pos. + Common options are name, pos, scale, and z + """ + citem = CanvasItem(item, **opts) + self.addItem(citem) + return citem + + + def addGroup(self, name, **kargs): + group = GroupCanvasItem(name=name) + self.addItem(group, **kargs) + return group + + + def addItem(self, citem): + """ + Add an item to the canvas. + """ + + ## Check for redirections + if self.redirect is not None: + name = self.redirect.addItem(citem) + self.items.append(citem) + return name + + if not self.allowTransforms: + citem.setMovable(False) + + citem.sigTransformChanged.connect(self.itemTransformChanged) + citem.sigTransformChangeFinished.connect(self.itemTransformChangeFinished) + citem.sigVisibilityChanged.connect(self.itemVisibilityChanged) + + + ## Determine name to use in the item list + name = citem.opts['name'] + if name is None: + name = 'item' + newname = name + + ## If name already exists, append a number to the end + ## NAH. Let items have the same name if they really want. + #c=0 + #while newname in self.items: + #c += 1 + #newname = name + '_%03d' %c + #name = newname + + ## find parent and add item to tree + #currentNode = self.itemList.invisibleRootItem() + insertLocation = 0 + #print "Inserting node:", name + + + ## determine parent list item where this item should be inserted + parent = citem.parentItem() + if parent in (None, self.view.childGroup): + parent = self.itemList.invisibleRootItem() + else: + parent = parent.listItem + + ## set Z value above all other siblings if none was specified + siblings = [parent.child(i).canvasItem for i in xrange(parent.childCount())] + z = citem.zValue() + if z is None: + zvals = [i.zValue() for i in siblings] + if len(zvals) == 0: + z = 0 + else: + z = max(zvals)+10 + citem.setZValue(z) + + ## determine location to insert item relative to its siblings + for i in range(parent.childCount()): + ch = parent.child(i) + zval = ch.canvasItem.graphicsItem().zValue() ## should we use CanvasItem.zValue here? + if zval < z: + insertLocation = i + break + else: + insertLocation = i+1 + + node = QtGui.QTreeWidgetItem([name]) + flags = node.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsDragEnabled + if not isinstance(citem, GroupCanvasItem): + flags = flags & ~QtCore.Qt.ItemIsDropEnabled + node.setFlags(flags) + if citem.opts['visible']: + node.setCheckState(0, QtCore.Qt.Checked) + else: + node.setCheckState(0, QtCore.Qt.Unchecked) + + node.name = name + #if citem.opts['parent'] != None: + ## insertLocation is incorrect in this case + parent.insertChild(insertLocation, node) + #else: + #root.insertChild(insertLocation, node) + + citem.name = name + citem.listItem = node + node.canvasItem = citem + self.items.append(citem) + + ctrl = citem.ctrlWidget() + ctrl.hide() + self.ui.ctrlLayout.addWidget(ctrl) + + ## inform the canvasItem that its parent canvas has changed + citem.setCanvas(self) + + ## Autoscale to fit the first item added (not including the grid). + if len(self.items) == 2: + self.autoRange() + + + #for n in name: + #nextnode = None + #for x in range(currentNode.childCount()): + #ch = currentNode.child(x) + #if hasattr(ch, 'name'): ## check Z-value of current item to determine insert location + #zval = ch.canvasItem.zValue() + #if zval > z: + ###print " ->", x + #insertLocation = x+1 + #if n == ch.text(0): + #nextnode = ch + #break + #if nextnode is None: ## If name doesn't exist, create it + #nextnode = QtGui.QTreeWidgetItem([n]) + #nextnode.setFlags((nextnode.flags() | QtCore.Qt.ItemIsUserCheckable) & ~QtCore.Qt.ItemIsDropEnabled) + #nextnode.setCheckState(0, QtCore.Qt.Checked) + ### Add node to correct position in list by Z-value + ###print " ==>", insertLocation + #currentNode.insertChild(insertLocation, nextnode) + + #if n == name[-1]: ## This is the leaf; add some extra properties. + #nextnode.name = name + + #if n == name[0]: ## This is the root; make the item movable + #nextnode.setFlags(nextnode.flags() | QtCore.Qt.ItemIsDragEnabled) + #else: + #nextnode.setFlags(nextnode.flags() & ~QtCore.Qt.ItemIsDragEnabled) + + #currentNode = nextnode + return citem + + def treeItemMoved(self, item, parent, index): + ##Item moved in tree; update Z values + if parent is self.itemList.invisibleRootItem(): + item.canvasItem.setParentItem(self.view.childGroup) + else: + item.canvasItem.setParentItem(parent.canvasItem) + siblings = [parent.child(i).canvasItem for i in xrange(parent.childCount())] + + zvals = [i.zValue() for i in siblings] + zvals.sort(reverse=True) + + for i in range(len(siblings)): + item = siblings[i] + item.setZValue(zvals[i]) + #item = self.itemList.topLevelItem(i) + + ##ci = self.items[item.name] + #ci = item.canvasItem + #if ci is None: + #continue + #if ci.zValue() != zvals[i]: + #ci.setZValue(zvals[i]) + + #if self.itemList.topLevelItemCount() < 2: + #return + #name = item.name + #gi = self.items[name] + #if index == 0: + #next = self.itemList.topLevelItem(1) + #z = self.items[next.name].zValue()+1 + #else: + #prev = self.itemList.topLevelItem(index-1) + #z = self.items[prev.name].zValue()-1 + #gi.setZValue(z) + + + + + + + def itemVisibilityChanged(self, item): + listItem = item.listItem + checked = listItem.checkState(0) == QtCore.Qt.Checked + vis = item.isVisible() + if vis != checked: + if vis: + listItem.setCheckState(0, QtCore.Qt.Checked) + else: + listItem.setCheckState(0, QtCore.Qt.Unchecked) + + def removeItem(self, item): + if isinstance(item, CanvasItem): + item.setCanvas(None) + #self.view.scene().removeItem(item.item) + self.itemList.removeTopLevelItem(item.listItem) + #del self.items[item.name] + self.items.remove(item) + else: + self.view.removeItem(item) + + ## disconnect signals, remove from list, etc.. + + + def addToScene(self, item): + self.view.addItem(item) + + def removeFromScene(self, item): + self.view.removeItem(item) + + + def listItems(self): + """Return a dictionary of name:item pairs""" + return self.items + + def getListItem(self, name): + return self.items[name] + + #def scene(self): + #return self.view.scene() + + def itemTransformChanged(self, item): + #self.emit(QtCore.SIGNAL('itemTransformChanged'), self, item) + self.sigItemTransformChanged.emit(self, item) + + def itemTransformChangeFinished(self, item): + #self.emit(QtCore.SIGNAL('itemTransformChangeFinished'), self, item) + self.sigItemTransformChangeFinished.emit(self, item) + + + +class SelectBox(ROI): + def __init__(self, scalable=False): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, [0,0], [1,1]) + center = [0.5, 0.5] + + if scalable: + self.addScaleHandle([1, 1], center, lockAspect=True) + self.addScaleHandle([0, 0], center, lockAspect=True) + self.addRotateHandle([0, 1], center) + self.addRotateHandle([1, 0], center) + + + + + + + + + + + \ No newline at end of file diff --git a/canvas/CanvasItem.py b/canvas/CanvasItem.py new file mode 100644 index 00000000..3900af2d --- /dev/null +++ b/canvas/CanvasItem.py @@ -0,0 +1,490 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore, QtSvg +from pyqtgraph.graphicsItems.ROI import ROI +import pyqtgraph as pg +import TransformGuiTemplate +import debug + +class SelectBox(ROI): + def __init__(self, scalable=False): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, [0,0], [1,1], invertible=True) + center = [0.5, 0.5] + + if scalable: + self.addScaleHandle([1, 1], center, lockAspect=True) + self.addScaleHandle([0, 0], center, lockAspect=True) + self.addRotateHandle([0, 1], center) + self.addRotateHandle([1, 0], center) + +class CanvasItem(QtCore.QObject): + + sigResetUserTransform = QtCore.Signal(object) + sigTransformChangeFinished = QtCore.Signal(object) + sigTransformChanged = QtCore.Signal(object) + + """CanvasItem takes care of managing an item's state--alpha, visibility, z-value, transformations, etc. and + provides a control widget""" + + sigVisibilityChanged = QtCore.Signal(object) + transformCopyBuffer = None + + def __init__(self, item, **opts): + defOpts = {'name': None, 'z': None, 'movable': True, 'scalable': False, 'visible': True, 'parent':None} #'pos': [0,0], 'scale': [1,1], 'angle':0, + defOpts.update(opts) + self.opts = defOpts + self.selectedAlone = False ## whether this item is the only one selected + + QtCore.QObject.__init__(self) + self.canvas = None + self._graphicsItem = item + + parent = self.opts['parent'] + if parent is not None: + self._graphicsItem.setParentItem(parent.graphicsItem()) + self._parentItem = parent + else: + self._parentItem = None + + z = self.opts['z'] + if z is not None: + item.setZValue(z) + + self.ctrl = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.layout.setContentsMargins(0,0,0,0) + self.ctrl.setLayout(self.layout) + + self.alphaLabel = QtGui.QLabel("Alpha") + self.alphaSlider = QtGui.QSlider() + self.alphaSlider.setMaximum(1023) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setValue(1023) + self.layout.addWidget(self.alphaLabel, 0, 0) + self.layout.addWidget(self.alphaSlider, 0, 1) + self.resetTransformBtn = QtGui.QPushButton('Reset Transform') + self.copyBtn = QtGui.QPushButton('Copy') + self.pasteBtn = QtGui.QPushButton('Paste') + + self.transformWidget = QtGui.QWidget() + self.transformGui = TransformGuiTemplate.Ui_Form() + self.transformGui.setupUi(self.transformWidget) + self.layout.addWidget(self.transformWidget, 3, 0, 1, 2) + self.transformGui.mirrorImageBtn.clicked.connect(self.mirrorY) + + self.layout.addWidget(self.resetTransformBtn, 1, 0, 1, 2) + self.layout.addWidget(self.copyBtn, 2, 0, 1, 1) + self.layout.addWidget(self.pasteBtn, 2, 1, 1, 1) + self.alphaSlider.valueChanged.connect(self.alphaChanged) + self.alphaSlider.sliderPressed.connect(self.alphaPressed) + self.alphaSlider.sliderReleased.connect(self.alphaReleased) + #self.canvas.sigSelectionChanged.connect(self.selectionChanged) + self.resetTransformBtn.clicked.connect(self.resetTransformClicked) + self.copyBtn.clicked.connect(self.copyClicked) + self.pasteBtn.clicked.connect(self.pasteClicked) + + self.setMovable(self.opts['movable']) ## update gui to reflect this option + + + if 'transform' in self.opts: + self.baseTransform = self.opts['transform'] + else: + self.baseTransform = pg.Transform() + if 'pos' in self.opts and self.opts['pos'] is not None: + self.baseTransform.translate(self.opts['pos']) + if 'angle' in self.opts and self.opts['angle'] is not None: + self.baseTransform.rotate(self.opts['angle']) + if 'scale' in self.opts and self.opts['scale'] is not None: + self.baseTransform.scale(self.opts['scale']) + + ## create selection box (only visible when selected) + tr = self.baseTransform.saveState() + if 'scalable' not in opts and tr['scale'] == (1,1): + self.opts['scalable'] = True + + ## every CanvasItem implements its own individual selection box + ## so that subclasses are free to make their own. + self.selectBox = SelectBox(scalable=self.opts['scalable']) + #self.canvas.scene().addItem(self.selectBox) + self.selectBox.hide() + self.selectBox.setZValue(1e6) + self.selectBox.sigRegionChanged.connect(self.selectBoxChanged) ## calls selectBoxMoved + self.selectBox.sigRegionChangeFinished.connect(self.selectBoxChangeFinished) + + ## set up the transformations that will be applied to the item + ## (It is not safe to use item.setTransform, since the item might count on that not changing) + self.itemRotation = QtGui.QGraphicsRotation() + self.itemScale = QtGui.QGraphicsScale() + self._graphicsItem.setTransformations([self.itemRotation, self.itemScale]) + + self.tempTransform = pg.Transform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done. + self.userTransform = pg.Transform() ## stores the total transform of the object + self.resetUserTransform() + + ## now happens inside resetUserTransform -> selectBoxToItem + # self.selectBoxBase = self.selectBox.getState().copy() + + + #print "Created canvas item", self + #print " base:", self.baseTransform + #print " user:", self.userTransform + #print " temp:", self.tempTransform + #print " bounds:", self.item.sceneBoundingRect() + + def setMovable(self, m): + self.opts['movable'] = m + + if m: + self.resetTransformBtn.show() + self.copyBtn.show() + self.pasteBtn.show() + else: + self.resetTransformBtn.hide() + self.copyBtn.hide() + self.pasteBtn.hide() + + def setCanvas(self, canvas): + ## Called by canvas whenever the item is added. + ## It is our responsibility to add all graphicsItems to the canvas's scene + ## The canvas will automatically add our graphicsitem, + ## so we just need to take care of the selectbox. + if canvas is self.canvas: + return + + if canvas is None: + self.canvas.removeFromScene(self._graphicsItem) + self.canvas.removeFromScene(self.selectBox) + else: + canvas.addToScene(self._graphicsItem) + canvas.addToScene(self.selectBox) + self.canvas = canvas + + def graphicsItem(self): + """Return the graphicsItem for this canvasItem.""" + return self._graphicsItem + + def parentItem(self): + return self._parentItem + + def setParentItem(self, parent): + self._parentItem = parent + if parent is not None: + if isinstance(parent, CanvasItem): + parent = parent.graphicsItem() + self.graphicsItem().setParentItem(parent) + + #def name(self): + #return self.opts['name'] + + def copyClicked(self): + CanvasItem.transformCopyBuffer = self.saveTransform() + + def pasteClicked(self): + t = CanvasItem.transformCopyBuffer + if t is None: + return + else: + self.restoreTransform(t) + + def mirrorY(self): + if not self.isMovable(): + return + + #flip = self.transformGui.mirrorImageCheck.isChecked() + #tr = self.userTransform.saveState() + + inv = pg.Transform() + inv.scale(-1, 1) + self.userTransform = self.userTransform * inv + self.updateTransform() + self.selectBoxFromUser() + #if flip: + #if tr['scale'][0] < 0 xor tr['scale'][1] < 0: + #return + #else: + #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) + #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) + #self.userTransform.setRotate(-tr['angle']) + #self.updateTransform() + #self.selectBoxFromUser() + #return + #elif not flip: + #if tr['scale'][0] > 0 and tr['scale'][1] > 0: + #return + #else: + #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) + #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) + #self.userTransform.setRotate(-tr['angle']) + #self.updateTransform() + #self.selectBoxFromUser() + #return + + def hasUserTransform(self): + #print self.userRotate, self.userTranslate + return not self.userTransform.isIdentity() + + def ctrlWidget(self): + return self.ctrl + + def alphaChanged(self, val): + alpha = val / 1023. + self._graphicsItem.setOpacity(alpha) + + def isMovable(self): + return self.opts['movable'] + + + def selectBoxMoved(self): + """The selection box has moved; get its transformation information and pass to the graphics item""" + self.userTransform = self.selectBox.getGlobalTransform(relativeTo=self.selectBoxBase) + self.updateTransform() + + def scale(self, x, y): + self.userTransform.scale(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def rotate(self, ang): + self.userTransform.rotate(ang) + self.selectBoxFromUser() + self.updateTransform() + + def translate(self, x, y): + self.userTransform.translate(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def setTranslate(self, x, y): + self.userTransform.setTranslate(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def setRotate(self, angle): + self.userTransform.setRotate(angle) + self.selectBoxFromUser() + self.updateTransform() + + def setScale(self, x, y): + self.userTransform.setScale(x, y) + self.selectBoxFromUser() + self.updateTransform() + + + def setTemporaryTransform(self, transform): + self.tempTransform = transform + self.updateTransform() + + def applyTemporaryTransform(self): + """Collapses tempTransform into UserTransform, resets tempTransform""" + self.userTransform = self.userTransform * self.tempTransform ## order is important! + self.resetTemporaryTransform() + self.selectBoxFromUser() ## update the selection box to match the new userTransform + + #st = self.userTransform.saveState() + + #self.userTransform = self.userTransform * self.tempTransform ## order is important! + + #### matrix multiplication affects the scale factors, need to reset + #if st['scale'][0] < 0 or st['scale'][1] < 0: + #nst = self.userTransform.saveState() + #self.userTransform.setScale([-nst['scale'][0], -nst['scale'][1]]) + + #self.resetTemporaryTransform() + #self.selectBoxFromUser() + #self.selectBoxChangeFinished() + + + + def resetTemporaryTransform(self): + self.tempTransform = pg.Transform() ## don't use Transform.reset()--this transform might be used elsewhere. + self.updateTransform() + + def transform(self): + return self._graphicsItem.transform() + + def updateTransform(self): + """Regenerate the item position from the base, user, and temp transforms""" + transform = self.baseTransform * self.userTransform * self.tempTransform ## order is important + + s = transform.saveState() + self._graphicsItem.setPos(*s['pos']) + + self.itemRotation.setAngle(s['angle']) + self.itemScale.setXScale(s['scale'][0]) + self.itemScale.setYScale(s['scale'][1]) + + self.displayTransform(transform) + + def displayTransform(self, transform): + """Updates transform numbers in the ctrl widget.""" + + tr = transform.saveState() + + self.transformGui.translateLabel.setText("Translate: (%f, %f)" %(tr['pos'][0], tr['pos'][1])) + self.transformGui.rotateLabel.setText("Rotate: %f degrees" %tr['angle']) + self.transformGui.scaleLabel.setText("Scale: (%f, %f)" %(tr['scale'][0], tr['scale'][1])) + #self.transformGui.mirrorImageCheck.setChecked(False) + #if tr['scale'][0] < 0: + # self.transformGui.mirrorImageCheck.setChecked(True) + + + def resetUserTransform(self): + #self.userRotate = 0 + #self.userTranslate = pg.Point(0,0) + self.userTransform.reset() + self.updateTransform() + + self.selectBox.blockSignals(True) + self.selectBoxToItem() + self.selectBox.blockSignals(False) + self.sigTransformChanged.emit(self) + self.sigTransformChangeFinished.emit(self) + + def resetTransformClicked(self): + self.resetUserTransform() + self.sigResetUserTransform.emit(self) + + def restoreTransform(self, tr): + try: + #self.userTranslate = pg.Point(tr['trans']) + #self.userRotate = tr['rot'] + self.userTransform = pg.Transform(tr) + self.updateTransform() + + self.selectBoxFromUser() ## move select box to match + self.sigTransformChanged.emit(self) + self.sigTransformChangeFinished.emit(self) + except: + #self.userTranslate = pg.Point([0,0]) + #self.userRotate = 0 + self.userTransform = pg.Transform() + debug.printExc("Failed to load transform:") + #print "set transform", self, self.userTranslate + + def saveTransform(self): + """Return a dict containing the current user transform""" + #print "save transform", self, self.userTranslate + #return {'trans': list(self.userTranslate), 'rot': self.userRotate} + return self.userTransform.saveState() + + def selectBoxFromUser(self): + """Move the selection box to match the current userTransform""" + ## user transform + #trans = QtGui.QTransform() + #trans.translate(*self.userTranslate) + #trans.rotate(-self.userRotate) + + #x2, y2 = trans.map(*self.selectBoxBase['pos']) + + self.selectBox.blockSignals(True) + self.selectBox.setState(self.selectBoxBase) + self.selectBox.applyGlobalTransform(self.userTransform) + #self.selectBox.setAngle(self.userRotate) + #self.selectBox.setPos([x2, y2]) + self.selectBox.blockSignals(False) + + + def selectBoxToItem(self): + """Move/scale the selection box so it fits the item's bounding rect. (assumes item is not rotated)""" + self.itemRect = self._graphicsItem.boundingRect() + rect = self._graphicsItem.mapRectToParent(self.itemRect) + self.selectBox.blockSignals(True) + self.selectBox.setPos([rect.x(), rect.y()]) + self.selectBox.setSize(rect.size()) + self.selectBox.setAngle(0) + self.selectBoxBase = self.selectBox.getState().copy() + self.selectBox.blockSignals(False) + + def zValue(self): + return self.opts['z'] + + def setZValue(self, z): + self.opts['z'] = z + if z is not None: + self._graphicsItem.setZValue(z) + + #def selectionChanged(self, canvas, items): + #self.selected = len(items) == 1 and (items[0] is self) + #self.showSelectBox() + + + def selectionChanged(self, sel, multi): + """ + Inform the item that its selection state has changed. + Arguments: + sel: bool, whether the item is currently selected + multi: bool, whether there are multiple items currently selected + """ + self.selectedAlone = sel and not multi + self.showSelectBox() + if self.selectedAlone: + self.ctrlWidget().show() + else: + self.ctrlWidget().hide() + + def showSelectBox(self): + """Display the selection box around this item if it is selected and movable""" + if self.selectedAlone and self.isMovable() and self.isVisible(): #and len(self.canvas.itemList.selectedItems())==1: + self.selectBox.show() + else: + self.selectBox.hide() + + def hideSelectBox(self): + self.selectBox.hide() + + + def selectBoxChanged(self): + self.selectBoxMoved() + #self.updateTransform(self.selectBox) + #self.emit(QtCore.SIGNAL('transformChanged'), self) + self.sigTransformChanged.emit(self) + + def selectBoxChangeFinished(self): + #self.emit(QtCore.SIGNAL('transformChangeFinished'), self) + self.sigTransformChangeFinished.emit(self) + + def alphaPressed(self): + """Hide selection box while slider is moving""" + self.hideSelectBox() + + def alphaReleased(self): + self.showSelectBox() + + def show(self): + if self.opts['visible']: + return + self.opts['visible'] = True + self._graphicsItem.show() + self.showSelectBox() + self.sigVisibilityChanged.emit(self) + + def hide(self): + if not self.opts['visible']: + return + self.opts['visible'] = False + self._graphicsItem.hide() + self.hideSelectBox() + self.sigVisibilityChanged.emit(self) + + def setVisible(self, vis): + if vis: + self.show() + else: + self.hide() + + def isVisible(self): + return self.opts['visible'] + + +class GroupCanvasItem(CanvasItem): + """ + Canvas item used for grouping others + """ + + def __init__(self, **opts): + defOpts = {'movable': False, 'scalable': False} + defOpts.update(opts) + item = pg.ItemGroup() + CanvasItem.__init__(self, item, **defOpts) + diff --git a/canvas/CanvasManager.py b/canvas/CanvasManager.py new file mode 100644 index 00000000..0c64e274 --- /dev/null +++ b/canvas/CanvasManager.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +if not hasattr(QtCore, 'Signal'): + QtCore.Signal = QtCore.pyqtSignal +import weakref + +class CanvasManager(QtCore.QObject): + SINGLETON = None + + sigCanvasListChanged = QtCore.Signal() + + def __init__(self): + if CanvasManager.SINGLETON is not None: + raise Exception("Can only create one canvas manager.") + CanvasManager.SINGLETON = self + QtCore.QObject.__init__(self) + self.canvases = weakref.WeakValueDictionary() + + @classmethod + def instance(cls): + return CanvasManager.SINGLETON + + def registerCanvas(self, canvas, name): + n2 = name + i = 0 + while n2 in self.canvases: + n2 = "%s_%03d" % (name, i) + i += 1 + self.canvases[n2] = canvas + self.sigCanvasListChanged.emit() + return n2 + + def unregisterCanvas(self, name): + c = self.canvases[name] + del self.canvases[name] + self.sigCanvasListChanged.emit() + + def listCanvases(self): + return self.canvases.keys() + + def getCanvas(self, name): + return self.canvases[name] + + +manager = CanvasManager() + + +class CanvasCombo(QtGui.QComboBox): + def __init__(self, parent=None): + QtGui.QComboBox.__init__(self, parent) + man = CanvasManager.instance() + man.sigCanvasListChanged.connect(self.updateCanvasList) + self.hostName = None + self.updateCanvasList() + + def updateCanvasList(self): + canvases = CanvasManager.instance().listCanvases() + canvases.insert(0, "") + if self.hostName in canvases: + canvases.remove(self.hostName) + + sel = self.currentText() + if sel in canvases: + self.blockSignals(True) ## change does not affect current selection; block signals during update + self.clear() + for i in canvases: + self.addItem(i) + if i == sel: + self.setCurrentIndex(self.count()) + + self.blockSignals(False) + + def setHostName(self, name): + self.hostName = name + self.updateCanvasList() + diff --git a/canvas/CanvasTemplate.py b/canvas/CanvasTemplate.py new file mode 100644 index 00000000..c525b705 --- /dev/null +++ b/canvas/CanvasTemplate.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'CanvasTemplate.ui' +# +# Created: Sun Dec 18 20:04:41 2011 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(466, 422) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.view = GraphicsView(self.splitter) + self.view.setObjectName(_fromUtf8("view")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout_2.setMargin(0) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) + self.autoRangeBtn.setSizePolicy(sizePolicy) + self.autoRangeBtn.setObjectName(_fromUtf8("autoRangeBtn")) + self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2) + self.itemList = TreeWidget(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(100) + sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) + self.itemList.setSizePolicy(sizePolicy) + self.itemList.setHeaderHidden(True) + self.itemList.setObjectName(_fromUtf8("itemList")) + self.itemList.headerItem().setText(0, _fromUtf8("1")) + self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2) + self.ctrlLayout = QtGui.QGridLayout() + self.ctrlLayout.setSpacing(0) + self.ctrlLayout.setObjectName(_fromUtf8("ctrlLayout")) + self.gridLayout_2.addLayout(self.ctrlLayout, 10, 0, 1, 2) + self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget) + self.storeSvgBtn.setObjectName(_fromUtf8("storeSvgBtn")) + self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1) + self.storePngBtn = QtGui.QPushButton(self.layoutWidget) + self.storePngBtn.setObjectName(_fromUtf8("storePngBtn")) + self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.redirectCheck = QtGui.QCheckBox(self.layoutWidget) + self.redirectCheck.setObjectName(_fromUtf8("redirectCheck")) + self.horizontalLayout.addWidget(self.redirectCheck) + self.redirectCombo = CanvasCombo(self.layoutWidget) + self.redirectCombo.setObjectName(_fromUtf8("redirectCombo")) + self.horizontalLayout.addWidget(self.redirectCombo) + self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2) + self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.mirrorSelectionBtn.setObjectName(_fromUtf8("mirrorSelectionBtn")) + self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 8, 0, 1, 1) + self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget) + self.resetTransformsBtn.setObjectName(_fromUtf8("resetTransformsBtn")) + self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 1, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) + self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8)) + self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) + self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.GraphicsView import GraphicsView +from CanvasManager import CanvasCombo +from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/canvas/CanvasTemplate.ui b/canvas/CanvasTemplate.ui new file mode 100644 index 00000000..b104c84c --- /dev/null +++ b/canvas/CanvasTemplate.ui @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>466</width> + <height>422</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="GraphicsView" name="view"/> + <widget class="QWidget" name="layoutWidget"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="3" column="0" colspan="2"> + <widget class="QPushButton" name="autoRangeBtn"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Auto Range</string> + </property> + </widget> + </item> + <item row="7" column="0" colspan="2"> + <widget class="TreeWidget" name="itemList"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>100</verstretch> + </sizepolicy> + </property> + <property name="headerHidden"> + <bool>true</bool> + </property> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + </widget> + </item> + <item row="10" column="0" colspan="2"> + <layout class="QGridLayout" name="ctrlLayout"> + <property name="spacing"> + <number>0</number> + </property> + </layout> + </item> + <item row="1" column="0"> + <widget class="QPushButton" name="storeSvgBtn"> + <property name="text"> + <string>Store SVG</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="storePngBtn"> + <property name="text"> + <string>Store PNG</string> + </property> + </widget> + </item> + <item row="6" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="redirectCheck"> + <property name="toolTip"> + <string>Check to display all local items in a remote canvas.</string> + </property> + <property name="text"> + <string>Redirect</string> + </property> + </widget> + </item> + <item> + <widget class="CanvasCombo" name="redirectCombo"/> + </item> + </layout> + </item> + <item row="8" column="0"> + <widget class="QPushButton" name="mirrorSelectionBtn"> + <property name="text"> + <string>Mirror Selection</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QPushButton" name="resetTransformsBtn"> + <property name="text"> + <string>Reset Transforms</string> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>TreeWidget</class> + <extends>QTreeWidget</extends> + <header>pyqtgraph.widgets.TreeWidget</header> + </customwidget> + <customwidget> + <class>GraphicsView</class> + <extends>QGraphicsView</extends> + <header>pyqtgraph.widgets.GraphicsView</header> + </customwidget> + <customwidget> + <class>CanvasCombo</class> + <extends>QComboBox</extends> + <header>CanvasManager</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/canvas/TransformGuiTemplate.py b/canvas/TransformGuiTemplate.py new file mode 100644 index 00000000..5ffc3f08 --- /dev/null +++ b/canvas/TransformGuiTemplate.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'TransformGuiTemplate.ui' +# +# Created: Sun Dec 18 20:04:40 2011 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(169, 82) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.verticalLayout = QtGui.QVBoxLayout(Form) + self.verticalLayout.setSpacing(1) + self.verticalLayout.setMargin(0) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.translateLabel = QtGui.QLabel(Form) + self.translateLabel.setObjectName(_fromUtf8("translateLabel")) + self.verticalLayout.addWidget(self.translateLabel) + self.rotateLabel = QtGui.QLabel(Form) + self.rotateLabel.setObjectName(_fromUtf8("rotateLabel")) + self.verticalLayout.addWidget(self.rotateLabel) + self.scaleLabel = QtGui.QLabel(Form) + self.scaleLabel.setObjectName(_fromUtf8("scaleLabel")) + self.verticalLayout.addWidget(self.scaleLabel) + self.mirrorImageBtn = QtGui.QPushButton(Form) + self.mirrorImageBtn.setToolTip(_fromUtf8("")) + self.mirrorImageBtn.setObjectName(_fromUtf8("mirrorImageBtn")) + self.verticalLayout.addWidget(self.mirrorImageBtn) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8)) + self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8)) + self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorImageBtn.setText(QtGui.QApplication.translate("Form", "Mirror", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/canvas/TransformGuiTemplate.ui b/canvas/TransformGuiTemplate.ui new file mode 100644 index 00000000..c8c24a95 --- /dev/null +++ b/canvas/TransformGuiTemplate.ui @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>169</width> + <height>82</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>1</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="translateLabel"> + <property name="text"> + <string>Translate:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="rotateLabel"> + <property name="text"> + <string>Rotate:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="scaleLabel"> + <property name="text"> + <string>Scale:</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="mirrorImageBtn"> + <property name="toolTip"> + <string extracomment="Mirror the item across the global Y axis"/> + </property> + <property name="text"> + <string>Mirror</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/canvas/__init__.py b/canvas/__init__.py new file mode 100644 index 00000000..d7d3058e --- /dev/null +++ b/canvas/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from Canvas import * +from CanvasItem import * \ No newline at end of file diff --git a/debug.py b/debug.py index 2a2157db..13fdbee4 100644 --- a/debug.py +++ b/debug.py @@ -8,7 +8,7 @@ Distributed under MIT/X11 license. See license.txt for more infomation. import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile import ptime from numpy import ndarray -from PyQt4 import QtCore, QtGui +from Qt import QtCore, QtGui __ftraceDepth = 0 def ftrace(func): diff --git a/debug.pyc b/debug.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b44753ef934601e96d6d22a76eff91270cefea97 GIT binary patch literal 29902 zcmd6wdvILWec#XB#hU;DK0$~Qbw!E-D3PF~$fiw$lqJ9-l}M0uAsaF&1YYc301GU3 zfxQa?aLGim=!cxzY2!3$`beGr6FYVtk3D(WaqHBn>$cNQnob)}{%9weOePsSZ8Mqa zcskQ*Ki}WEcXugKvXd#%QryFH&pr1%e&_c-zjKuT%YnY{%>AS3qD%jD@$b9&CHLl> zD>-+CG;pPys|4-}8K2L&E4eJ6cUSURzQbMV$nu@;N@teua#y;te7C#Oo#lJnm7Xl$ z>#p=>`9620FU#+7SN2#w?<)OnZNOa_aDm3r;VOIG+Mv5K=+=hZl_5$yU16Uqb-9}i ze7`Hv56$<wn~e8>E72q+G}iBKGU|hEB?DG+$k9S+ue-@y54+N!=3N@H&yTp$J{OO; zGVq{~zx&-yrg)bt9kA|>y3#?*+^x6qJ!(qnA$Jp~9CIZae8`oCRp(Xb-hbs@SEA9= zt~Amt3SEizXo)uNu@T*;K1;`JM8{p}UhCn6^&eW{{jPMME1hwr<IPr1+BE$jPgpAt zxYGUh<)|y2w9J?*Jz$wrt~6?y2VH5*G7K^sf9aHcep<7>a>l0fp!GVgIx}PALX=OD z^p)a;&86}6o$yTf)MmBVDA%e<*r<htaw#lVLwmhct}carleP7odU<KN5kC6xBM*n+ zGn=b%I9XU*FDzASl|tWCIce0(3!9C&6mC{aaXozY=@-ww^vEM&rCf}wNjx6T#BuYX zvE2w4YxQugR*&gvv9?xF=i`0l|IMH2v4GJvx=E^~LcLzt83V0dLs<SoW0G$gotC|{ zS!RyT6;)qV^vm;04v>7*xkbbMk2rTDaMuIot_sOI$y8jd)e8;Au$eGs8%&|Ts>wE% z<8UEn-c>yns--aIS6d7hQy7KCLZy<7_l5j<VzXg`SJ4hrDaF-B5?13xkZ7xCq}oBp z8u`5%#rsJhh=y|qZ{*zdocow_D}ftkjfO8>rnr=M=L*h!@POW2=WpOX$xQNY77F+8 z-@*Hr+CJ^jr`PjLRNYJ`nr;{-0$i9h3iU==kV^D2EY+&9hwA{3^Ye?1dZ8Fk#p{jb zMvs+L3v2QG{Fp}FP*SKbB?kFb%L&lyi`xx#QorWcOw|OYG5&SQOC)EPYise@3ixt% zt5#n<yH=={lle+v;cT&1iq95`8;_l>lo!r!Hp-Q=>pL5brFvm~`E0xma#iBw?D)9; zwq9g7+t5t5_N$vA5|`@>hJwBz9~{W-A!h@y=|#P~jrvG@nZC2KJ3BnV+=Fy4xkP|* z9fNzy$|M5a^)M_7bXp7*PV!445|$P`c$KBjxg}fh?T3XQD=-Jj12;8yue;IVSmzaZ zIJg4}fgHovS$<vz8^=MBz^zyn3TDRC-*lpx_Zi$m!;gj_W2?SUD6TdHBIp28oR7DQ zw$RCXrQ9&OE3B_W1%?JIwQ|*euQ_^@w>s<fc(Ls9p|N1!SIVLU^;_}4CwGU@LStHr z8}HdJ8kK2MF6a-sg2TZ*0ib2Z8qf8jU?R9TVZW$%MrHsT_zl0B6SV<gz7X*90zSZA z%(=stE|&mr0N^24$)7HFa2P@5>F@S$d-N8;HZ!JCB_AevzFw|2;4xubuZthXNm5vf zVMmopZ3_;vu%nkA#yqkv&?lTSv=tM<8W$RSak-qd_!D`WWxe<WcD@iBC*onN+*pRO z`)1CE3zb@NH93{B38BN)lgV5do=p0Z9-dBKK7aDr^CxGbejW^N7s|)T+7}lu+CLK$ z(}rKsu<8h!jq+M-ECiXbs0YtN1n{+FDH6>_V$cnhFqYjy4b5m>00%i9AfFg=<PQW# zf{~yzFpLm2YFLwc{~C!Htx?@y2NkwYXH?qb3HPW7w;gjJS&{9f%Nx%^w6{U2UPGEb zkY<mDbe`m|h%_+%;%aDg?%DtMS?B03`nYA8jd3&=+m={uG;uIG%2RZ=l0gz<4$(b& zY^}8bYhy4J5o6e`W!Ea(vcD*LIm=TbbauJ^oEH8tzoKTSUy~VVGUA~O+KB&E7y~5` z`<}4HQlleOGoX4B*vDfeaoSK1DKj<rx5Q(zT3+9!sYfBB5XRWrxxgs+1(Q0$aj{Cn zbFoEu8S`E3b@g{h>NICPH3!bZ0`n3<RDvniN0k53oZA?2Gq=_qwYnW{rBmQSDo_z{ zt#sRW@5#P<kDH;hR2>7x?iuvQ5EUw{Ev&>vG$_;r7@;x1^_|8t9B-*;>?<r*3JDq( zx(SsUMfllLSdW+DZLg3$1+z@T(HF+Y$47nLI1w8y#fyc_N@IL_Ol-iAa2gHBw3nq~ zL&SyHm{0pRVOUPeRTi&W^tDp8(>nEhZf8A?gfX`GCh%+^#@8A<>jv)aU0Q2nxD~+o z+`>w7kwKZ(=koo*>0DV;E_RcMJT+BY_FqQ*kb=?et|P$qxYZt4Kc+Qa3fzrOR|P4s z|9trm$(p#)r9w(5XL7&}1q8}qg(sS&v~}xqhkpjYOwB!V@J5fjZU*Ez<?^onGY%ez zx<XR{9RSIdE_)Di@xTC6#pjs2(W|bn_o`8bvD)qGU(ln_@eA$-)?zli>wVVo_HPH| zrsjS#(5G~0pRQoF_Pf<CSMLip{!G)_IqO(;dSn7qbNk&5+Ua%IZTf0_IH1S+JAy5% zJzG8Kf*_y<3U?>ThcH$GlyN?B2FaVj^=`LfjQ~%+4PXkq<AD#PsKNBDEocPwVHLvq z*>o(-;YPpPc|mY#&UWO+fV)1Rsv~@{+Ue@w4fNdYMyR|Uy6gQ6{E)k`SM2Bd-nMRj zH<)FHv(<gBqcPxC`&`}S7`+DE0$0C*i$*9CD#W=4Z2%9Z#0b0H5$y5?<~7f~*)%ZE zS%p{gd4@nOzFs128})=1bVx6laXQT#IVR5E?c;vTcWKNiw8&WFUX#6<o2A|??`Fot zSCe;>OvR13zJ>%bj<H^7EGIJ0Jn6y<7UOyh(V`I5Yir)_fheR2p%RfZZN_NAXm+s< zFER4T3_RoBqNOq&$w&nEQ$BB2X&t2&W6GF-x;O2&j3r!;y)I-HmbGhYRMu|v7|o2C zkQgOryr*0;c9HBPrjF0Ug#3UcFpHJAP>;kxtluJPA>5HA^vuepT94~VbcPaRMb=oM zx)aGLvWirCo-xi~pI4V`nm-cnyhrM$F}+3jW#p?EG@dF{lGs|Q#MLqJ3;V_<4D4*G z9*mY<D{Ma#SEIM7o>*ZL^YnHy(YdUM0VH1ElZ35%yFT8c(<FL0tJJ8+-l{Jaip#Na z&Rvs|PcVqf{E`I{m+Qh@(0{$C21ER}7t^vUH-K^ZSTNK#kUK<;d~lM|JX&LC&`sT; zU_T~Cf37R9clxR?7zpm-*O43I{n=n7w?C&=?#>kj23daH{EQKQm6DxL%Q}JFAQR79 zS%js$v8XY@*1wuzk;nmi3F03UUicRh-W{%95|_nNS%J}EN5bn&j|pk@@C^Fp>t0!L z^}Zl&6^==(Uv6u)(^~zUD3|sybi%_kWRA^Lzbn>E7A(?7e<Tj!hq+?3p5_NMAz4f_ zo}0i!RQ*H2>qkBrz*w3fSb@*jEX0ug6pXi-!^BGis)#S25MQ<q;6-ot{hNF{-~6_U z({Il$pVRBgkfRf8JLUIGs@VnI&b)*WNjcMFtY#E9HtW@}5GKozSBO6hy(1~s%NT9f zVpx%A(40+>Xv(n4BJ@OrHc=`U8{ywLnVd#>xpC2m;B+E!-F$Q7V);>37wQ~8nNaVz z|1@=!VY%`5qS4s=JQO@XpNLybP>yM*hUYFOQr^=Np466}_-@)WCvg*F0oY2+80)<T z>(DqCE44zyUNM*GLDkcH4LlMmM`CVPv>L<Fq<SwD64<3CF6V}jhWcnnp4^k=0yzy~ zxscFw-E%RGaDLuYJv>k%7V8uiZ&0c2mQtm0(5Yvh@|h;qb1okog~A7+`lGqK?BD)e z5C4Xie|PTj+<owYq9|QW^zbuEA7&~ZfuPqBFbKyaITY;e{}~`ZnH_jXIaGXP6>hmd zC)t~G=j0=V6zt*12*#*?8o{1Kj-ajqAap5U4|ye<*(1sr)b<rI3{g_vt2Pib3<aH| z$4KbM)gzUlD~L{hCAf;IZHLb>K-Bz=uSi8w7j)+-_-cT_qApBDU0e;+B%k(~Cc|TE z+->uc+HxRJN1e>c+NR-bF5PbXUFu?L?qMh0kQRH;cDhX;x!*ox=snJc1><4b8}|qP zuQZ$#27Z9w4R#*1COi}Ulzk45qPy1Iwnj8DuNk2aq8a@XijiqX+fR6Sz1@bw)HRe2 z8_KU(TWCYHWtM&WfPN*|u)i7*nqK{T0RU8<N%z{Mk;^N4#adv_dO6Fh)G72iFy#`w zLQF&&h!mSbX$8X-KM|N{#!*>s5vyL$38jLZ4$Z_h=k;i{02?Y)M(vF`h-5A$;aE97 z9g4`zQ*Sm@ayk^HQjCvVex~g>IYlc4Iq2nt@=6qzQl?*_M#)27jq%}+ml>`%fX%~s z^<-(z)P?zZu>_cpuz$Syl-JYA#FV0|^m`s}Q~Dtpy$v4^$E3PMb4n(aJgKC~Y6KMH zA&pJ^!;vf<d$h4fFRHFQ>(P(ug?Q78_08B+s~6!pruRhe)H_+fNuyNT^vx|+Hk0Ks z4cD40E*D^aqF?X7ESs`cd&K|qsI^?IRU2M|5reAzkX2vZCDs%@PM=@U_@+r*ehB?z z1f66re5orq7Mu)*gWlX3p7vXb^bvF!-VNji;Z6O)LCVojto)$Ld%8&bf_*_zJX)(D z{;f4gF0eKrJAP@U&@(B_hSUTi=KfNrF`+K;4mg@v8>9-#V{c$gTsHeL5FN-2%(Lmy z?RE}2@epHp0slbqv(;0wt6)P24=CKfq&06mk&!s2q8VGT2-F^oxPL4*Gy_lJ8H$|P zIUwa1BJ<y4`OQ{`F@6S(#J_D_bjX%eGcr$@-ag}F{lU(#OpwNY6|Nl6OwQ^1jeao( z(w9Wbs9NvLH^<jtB8O!8Xe>W1)>og9`G?koYmfTdzo*uw<}S-ZZ5(!3q^L^*4^x0q zdQNI)VP1wO4g7>P&_@H<pw@^Cu{}1JjY$l;A2+*-fe+ij_eoOJzkF-ASADn7%bZlV zjJ)5Ce7~+;PGh&O``zuj|BBg@jU#Sl#CVu`K4Ty4$tK(zH11M=fNyH<d+J}7<z9nO zl-oF;11gQ9>4*>@f(sKjn;otxK<;GK+FsF|%wW*$ZW+To)<2zAL%wN5_oR(X&E03F zFpch2Jt6Bcw~D*ri#bZ9I({*CW6%~FPHYlMHu(A653oMXmC(!|&A$O$8;|ChB7aQ? z$mK5u&y<Htn>hKyWjO2FW^oypKbxTD<@ek@#CgAo<7p{5%@<=>rpvX`nJqTO*5ms1 zrz(Y|>rXf0waMi|bt%67ob3i&f0nJZrW;?xdonqHved?$RVL1P-kW98x30zYg&L-w z>0{dU^ZYk_$Q(zK63KbvqJBHpcy`9f$F#TNxiC!C>v_-GNX2BKB<J2+U8u1W{JPR7 zH_Meqx!P7LN9j}P#;Sd+oq@DkdwKMby41jnwYBv!tbbGm;_zsW%h3!OZw9|ZIb-qS z=GfCo`Gaw7F)FHDa>VpdlP&gnsn94y^D6%UiRr9<DQ5W2OBC_+XhLt{gJ`?nh^1+P z$x9Lp_2P1LL3PyaQ*tV3K3<w#rN}@Zf%Yz{W!36&akHKv@4<ZpskspJRnqnx$+>Bs zbnAUNS+-$oR?*994a9fRnws=7t=`yKkDgZHWs=>JM<1lGKjxP}XxO*+2ZQ(?dvks0 z%6<Ho4<5+%BW?720GZQgQfQd+ZfxcwNS=O5kMr*k5@;}eet>@u2Kxpj)B5>N8+p>Y zyZBM3>m2_cr*$M=2lgepQ=z_q<9Jq{;kd3Pw?7i((j$_0k;KGBK<=b)-oV`?W7~Yn zn1!qi9?=eag$uOPGTknk88ZmqOLL}HP1dTYYGvtW(s$UHD?rN%CRnajO0xB~;=*c6 zRkP@dREzWO>Ss|i2pq5=1T-rK&0ftG9uP)vdRC_MEf+8#7x1oxsTU~RiO~q_VYSxq zP1%Uv<M++a+k*lWmbStR;jK^^JJt5~>L@)-gRG--R3v}9caLT-A8mU(Zi5{=6^6}1 z%UDtr#PySEG|4Zyn?yVq-2f?sd1t%Z$^d)^hg9upzf~wQ7v#`!8{!AE<lk4QY{pYf zC>sqh+{E-vwhlChq-~oet}N~n>ZclSxmrf!eU@qX-F^_%wiLxO^fM^jM@BjdfFP4| z6Ux~R6AuWXpbZrIDEgR^*OW*h^V>`x*P~d90YSR`Zp~CfDwIkuv?e)ln@Jv|R&$2T z7V+LbFD-QR36i$i{7sc<O1m`^@muVZSU%ph`T6&1M#5F+t@aLb%|$a?B12s%s%XH+ zFkWH2vfLlC$;z}26)?bro1qs{1+^EJx3%54-vTo(QG-B$9I6$+%jmi@hW3egcDlLS zGXB^U+cIuO3{q<U$yRIQ5j)`<m_u9|x3HM0xPr61`Q>?Y20bHRRNB`0X=7>TV>Sur zaZOlf(xi@haxM0_CzwRSJuf!nLdjHsLWTBB{_NJ;No6tS^O9CgbfNGcNr53`$UZ!` zMJ;9aGs4q)r#Ok{CF3@Rikhqs6>3gSNU>Y9@tBA*;W1I1GsQ&Ps`L)AKE^mdzg8=4 zR*1<;{d=WaaehAfNnRN_MW0mer<ME+No)L;yQASw(TC~q-5R;{xvu`Mptpnn^1XYE zIX+*nEuylu<grNWNq)%_B(IalUKqz-5)%Vzl2|gB_)dhej}yf{={Bj{BdOe@ruNbO zOj%K5_(FVt+90ZJNe-{Yg=7<CHaE&5syAw=YAQU%f@SL0*!XxWL8>;9kl9qmMl<bn zv?Wf%5C<!fb$EI=7Z>%=6pi|Pe2oypvC$IYl~&<)U)D_Wf8X!hs<pLZov@=*#+Bhm zRR%IH`Z<y}$|6%4jjcf#^JXD+Ad66m&gFQ7IjNi!8;96Ypo;?Pw`ujly#Icqf1Y)* z+I;Rq(Rgn=6L<qI!QkL>67k|7(>@4A#7StPkpJSvi{Xqm)Z&uIcn#O<$XeJ^IVmhu zV$W=_ch+KyH!)94<6-~LBNlGbCQmH-<nQuziwf>60f-9a07~|gQN-C`t`~;XffgWF zi$#hUR}$(sC5t$|=Sp>Uas}Zm+62MELg-yyE&wEkAnbwM!B#9D6oSywHG)=};5FU> zWCiP!OKVB_PwCBv<Om+o&nr2q#9;LI$-N$KRz|r8-W`RTi4u{;D}tT4oP&LL0J9Tb z5DQP5;F{p!O~94G>Jm^hQ2BxusM;VjLNgN+6Ctr8P)b}n&E9aMP%&n3{s#rz-=mS1 z)QENyW->xNm48%F6Om+4YwF!im`Q&|?@lTE9inUlg#hbm(a)kubf!a_oZltd$)s6a znV<iJCZwUcz?`ur{i46abM&)HWI20}u!+5YpvQlx<QGU<<F(u!MO@>l#B!t(uMgj8 zZm_?<ySK|scX47Q-h6$tY958+CL)I*_iM35))hsaQe&FSWS)bJ8K*ikybbEt170uo zLM%uIl9!#Jho)l5gV$)1VcMLmt*zCnXRs;F6fLGA%M?8m$61N_6Es30)H+-I4IkeP zGbSfuOxVVp_b<e$ZI8h}`v_o7Lvh@Z^mn6WePQiZH`h!(w69w$b4M4&FewH7-)=G# zcHjrm3sXy_hmrR3o8*`LGZGjETKo2w#95~1ep!%)UI3|}&b}Iq#OLv6n}ZqOBziPH zNY)46cA96G2|xFH-Nt}z_OM)h*olFM^!$)40QmP|`jU_FLxL5{_bO;EKPV5jC8r({ zo5VEz6WfFLE@FJ)+R<WHiecG?+c^eIKeI;2LZfMu^Tz58=!@k#(JMf}c`7oWn6`DJ z=asymL~G)mS_9<JKj+ger4$Pkic&XP^C5HUCuwfK)Rc=puQrxRZ12OzjnvAe=&XuF z#*N(y{EogrZ=cqhD}2=<dI#_~^#}Kp>m!n5K>i~BE$ZzpLgfktEVy?}C<p`Vj)l2_ zKaLc&2oePF-i+lRR?Cy*h4jIk-01_*_pxg%rHWFrEYL?YI6#oIeLYM&*-qi}KEg3G z@5YFUu56n7h&?FVnbQXRsnhcHpsZ#uwGC%&l^Y4(j`1<MO<KzuozkPg5;a|-(Rm(Q z`{_%?O7umQ@Y@YxFDJMV4YfRrwEUu$EHmNc-WwoDW&l^;8%?;7uK}rO2ueF5oYOkl zrV`~W5_X-I)?DWqxU1R5<r1JKmY(3!r>9Pb<wdeY!m}0$L81E0Jj&6plJk#WB4->W zU6|d1lDKiDR$JX%|C)fKM}n2XWn;<DEvzVzd9}@&5*L1wU-B7}c54a*mNm84#d#NV zvd!EO69DigFHC1w23snh+EOL&^CURZ`#jmE^7hsq7qfk1CKs5^VLmpl^zB|dsYix* zojxlWQh&tK+@u=E`skZ8eVpC%r};RtN9BVu+Jp?rUv#9dRGU+BOAN3-NG2>#Hmj%c zqOK!bWX!3E4O%up2<?p<Tk?t~)xtVaH;v>xt`T;xGh!-m!mL=23m6xt3C1yZh~MpI zKP6%Hu?W4DW@jjTbTNA{Y>${*c5$;}QdF_)c04TIupu&UH5m_I)KDxUd8x)|aN#bg zvv5qYKO$HBxV3WeR7fn&k^&W}QMd++w19kT&`d<3Wo=7GJqS6hqKl^Ee=CcW)-QxD zbdV=DJP|eus|+LqhxiOz3<N|ra5Eda3xa9uP3x2|-_&x&zg#LWE?V={vp**=u^DP& z{HHDYGRa_VTq^~4$aYo{k5ePuD(GZNxlzVv^uf5atKG9m4Lb*;C1}p6eF)j)n*W{v z9g|x7R0U4CX;?=g*%lEP%^CYyHm7GN8p9{1r@VJgxM1%43@Etozfb2O0UL{UP7j|y zOITzHF;#Tb$Jl<6qd|=rA+l533=3?xwXnW5I^k(tYIjkz-1T<0GLhTAF|Ih!=bIEU zHkt;Mje=AsPC$DKY`c{_o6+~py5U$F;byNgafJe?<u`{NxOdA?nxKA*4BfY;L(D}| z=6oqW%@+O2844&Q4z<WBwhGx$Zr>8fx9PT7-$(8(#8B9nht$=F=R{)yh)%PzI9tq% zRVXzhlDC#?tT!EJ(K$x(wr19bwO)j+n+_t@J}EXRp#!r<gs~BsW`Zn}%QE>aWA;8; zJKhykL%xD1+fIT-Q><7+=rg=Pmr-ge46d6c)J*yyXq&=53s1KU-v`_Gr=?SKBNms2 z^lNruHps-r$B9+q0|Gozu}bneXe!edn{E4^EOxyS5!&m`2R%))58Q};n0mHD*IG8K z)<Zem$TmH_0R9ctlq!Rk0PGX5y~qdeH$$W>WR^v};YIbwRi>d_+a)1wUf2I3@qCzG zvA*&4*nhbL!5Ordp<p=I)L`<|uw|AlF8DPU$dt-dN0c6#DJ>9n%A#L)$mRB=Fj|eb zM&FVO#tlxkM51&LZmmfRe$lud`R#@#sP)xArsJ*uCMZh(a(`8b75L__3$Yd&{vd#h zzOJsmO7i~*UVG{KRu=u2ZD1pN>p5aM`8=XQs4wS5&Nvih=AN{21?6z+0!s#L^en>6 zT!o|t8~1uD#}$df2y&~E-_$ZT5Ns2-qZSTiEnpU#i5zSc{HO4Z)~7tsQM$b;lgM;* z*&R(@8{wH>TS&LOQ+*|5b58O$BLujrDSm{16zb)KBVpQThe>eo73m~S%&~k)cP{{% zjZadJyeQmqRG-f!s!v;xws?5!lq_e@Hu;30oCF46skEH<)853IVCd0rl6ZQm)v;+{ zX3@J;B%Z#xPEd)D>Cm2oW~TRF;g$EqieFiDhB30DambIU>}gfuw+l2F{ZqR6p0LPV z)}4o7T1POw{2m(K5@D5;bj?WKpX5(jxLdHwPzo$UV&N&-j#Aw62K3b26BYxA$iYv7 zPs8t%f@eCx1j=+}yWiw4s9mI#6?-gv$Zb62X46LTkeF5ehDKkpKEbbB8?}PlH2R9$ zc*V_XyxlhH9=AQM@lDM=rEoxn*cq~Gyx-0q3Hs%6K%2Q#7$Lxre<n7LH4E_5sNma< zzq2WcDT)~q^P)DBzr?rs$(;^NkT$(Q>eYLJI4UXkUh~=Fi-X%W>Ni-JY1ZgZ_(pp& z*OD6Hfd%l5V))th5dK)mYlI~}YSY#6I8JWvaiE}1tZ~^2!1io`@$YdXM|i`(6C1xD z%&L<2kXTF~%D_kAP1xmm_Yoh;xjpO8bv>t8J$*Sq%po{{=j&2#;~|CqVQ8i=4s-TP zft!0dM~_L0MuTt7BKD3P97bdCQGPlh{ZMWet5dJ-h@rMW7|Ng_4NwT&ClqHu=q5FM zRCcolE7qz(I3YC*B&{|8W~;^?n3Og$lh2W~_z0Yb8%~Vnh{gEu>y`;>+{xQoDbq3* zrrmnIs%I*pN?SZ7+wf1howss1y9RIjes*id8k-|b9$~y^T7N>*y_&YNw@^$)J0G@c zK@E6u#gz5KAa~MJl0zhT!?a9zR`idKEa(OqF^2S#7O6JHwRL0ZrhpYJU9dXl0W3Z; zMx8r+>q8GcOjHVM3#G!v3(O+1E;XH#rF28gpZe?5P~Jw;n*JH?J1B)&q=bi2(~f9| z)0s23{c_TK7g8(t&Az;Nn?7&-($*y$YbhqDZtZ;9hZ0WEkO`mYy~=$<i4-}LKGE+e z_b-%4S;18)|NQ!ni5_#yOH_SE?<bYasFL`*M?3S$*w#;^LX%L@H<e@gy;6?(D!tvD zzP=BE)MKi>cv;n-R$}|h8UgW$K8n5(eU?YV2SbqPU-NipcLdbGh_zQ(Ar;$o9wOlI zFz&Q{IEIh3avchPmz#Jn*q1xhkqJW`<~!1XSj>0w%@Be0{pL<q!2RCf6k&`9_(DGu zm>CHPyh(*bJc_^Ot4w2u+8)?YZ@obP_y_Mr#t7nLq``&l`>dHB`1E0ONt>5h?@b}Q zurZzP1$fHrrCX08(nxIWJ&Fh?BH35b*OZuBSS#D6)M`h>FA$kKyEe9G_l(v>+N}fj z_8;3dLoM&G7>vR4BpKL|pD}R32fTj2jJ{?GQi-jZj6LbYwt!$w*mEhpC{_}So!zCG z+OAtU!J2(vq5eM2SZIR1qxF)Y5w+PYU!=suwDuuVWbne)HX?lV1*$xg21&e9ZCela zpsfec1;n67jw7J(!`xd6+VASSskv{ZXEUTTp$DPGdTGe93N>ny5oHJyUfO^?CLfSY zW%o&e`JZ(gAJ8V3PFeK3l>r&{^oy=DTjiR!K%M~}?1%>X19BDZ=$2kMap8M0Z9^oo z&noeudfjj1;9#lT8sPc?b?$W%w2WbCi5&7^5V|sj!xtU}lrb|zM??_)^h-rfjI=Jq zx=(QSr>NxyZQqGW%?#Y2fD9*$mL?I>J<&48&r4E-q4&SHqUtCeleBExd-+v8zEg>I zT#V9;&o0i}cIopfv|Tvclrxh2k1G2JiScQ)1&fFDVeT@Q;i54I5u@KyJ)_@$tDMlr zloijnMb6QqN?uWNS&4CQabq7ZuFbjVOG<v9#D+!NG`&mIV4IG_Ub{pG#T`ee`|zD8 z{CkLP4$p=Ac<$$rWmit&;y#32CE6J}mOFYI{&0e(SZpvzmzdlu&HzKu=AGw}QaW1> zs}5?n+)XWZ*v1J3oZW)RH|Xnwf{4bY=%HKGG2;vm@S-)p<C@Itrl_kH{{Nk#l#(B7 zPLYK!YObB>&!#AXfTb}9ZL8yCFwW;6>r-y$aLVpskZ__`g8<GpyYXzsaXK9Nm#T0x z&oQBY#YKqKPDj5+>KAvET=eUDROg;*n$+N3j92xRpHJ?nf?^|Kb1V~rM?v<({049+ z8iXVsdidq}C9u>Mgj!pmIdNmy)@zmmmF=$zSElB^EW2Fn6^UWIkW{yayoyT*vk-gB zUskfx3N}-XVWdKp6JtQoB#IA80GhIcf67i0^Qzkk<HTmUf<(b6W|jpXqdPM>Vc7gd z3}G}NT09FMF$NAd&$|&RFxLJsmH138%H~%K7;p#?v&b=5Qwy`BX<-&6E~e(LO3q+C z>SL^6x-&yXS=y%v-hWsJ>NxgQ2xQoriHYl*C><RsWbm?{86y|Iouj5^r1N$gdxBYj z<6}#wZ4>q?y6;@rk`A}fsF_UnutqJD2{g^jCS~a)MMfW(ApsNpOA?Qk-_qm1(xbf) z&mQA<hgGnDLl3_w?6a<<%F7z+)!)P}4D5}jDKrOvYk%C3)(fI<^VY({Ba=oS)T0?D zpHXffiHWnd!nzq7ZIbG;-bt|GYeV=ZmS+oh>*=W^6@k~hm=ZbqkP?BW82x)a|DKZL zBnG7x(C#d?{yRGSDZ%SOGGf%+`C(?m2>FAyORos@Bf*0_A10LH1aC+E8Hpag=;CL* zu1*G25t3(0+7fw-HxjiPMna?8mpmmuZjnLeR~0>bq62B=RYODaRBmJk;fo-}A;l!| z=Xy^X)iT{!P`5w_J5?85)8h>#rdw!(#WP5a*fU5imTeH2sI=uNmlDq{pVm967k(wk z++n2sIz4<=i=dGz@=;5m5U<04dfKaE9lU9a8#H}sRApxL{G@Wft>m98kta0z4@!Q4 z!~*ziQ{SJyj7&3|(Ccp~ktFjJ@T77=iRkMjt?66t&LW~z#&1FnNl2#afG(aH%JmKr zbl=&#uXnPyZ?K~`<f*HF&){7qNP1O~pP^sOr{EasvyY_#rwC@mun_;V7&DchG4mm> zLTXZu#(`7E`}U=rH%mRfP9QPTur}pDN3&lMNUe&dp-({U@)-Y)IPlcmZ%G=_wwDI< zBP-*V+{SC_xmk`)qrAxzRrq5GEtwVEZDHlQGYUr`76fhkk6Z6#=w#LmB%+HbPTLh0 z-VibS0udz%cOoETN3NSqNmei&BP~%@2iJLJ-ONH+Ng+VuXfR%s*w;+j-^6SCyXK{n z=@6R-vE>f*H(2R5#jQe}C>0B>ka^O&0wQxaDuiN3`yj*sc!S0EpV_-86uI$=tBk#4 z@redO3hTsYDBPr)5T{@+$PLaO+x4NxvwiCJdnBma*6?u~uk8&??i9Jkx#hq-AKF>r ztMhXfu|35}AYH9>KJ**GUQQUDwJac!gg;<(K{=zGKTz&*B@0S~m_~c;JMY3;ZwS?t zlf~c<(q2|=F){;JT=K?@3fK(QjaO#a0OegYZ2pkme=P@5nqku^gC)C1O1H(xWi()? zSC7J~yxiDtb$Zx9K42%9iV&vxVQI#b>Nzq<ij<VVK#<;S8~M|@37irHf}PCZk|opf zpo1B7A%F32@|;>SCnXqVS8fv;thgpwl$=DdONqE5ajjtSNE6wuhZn7aVi^>8B?Kzt zlg7cB_`_w*irS^K+k?bjyw7D_gO=+0bb5v2i}V%A|K=mQ<#_aZPgA(J*yo+)oYGkS zK#SBvMuByMT=8XK7xKs^xWq4cjfFr_D;d*iXL@gkOTG!8gK?tg@|ra<**mdh>~w05 ziwp5oP;BgvD&jLw>rodGJZ)REhjgpV)Z9QIEdbTn)M=QWIPssWK}tpt)Bkrm|7O<t zH`Vz@L#=~LLaILTMcA9>pc9JeOTU9NYA^i++g`%6`SRHSo_7;H26k?Y`E^Zju>-Zx z!~t^FtiSqjLuE4>1GoK9hw<RHp=a&+%vcn2^0yG`&nNL_sdmQV<O&?QCVV!#UauLa zC*Ij)uk6pHoHb>Q^`%W614~+Gq~WCJ<<?=7CJo=pn5*3&xV2p7q6WC5_s}PH)So_@ z+b+5eod%xBcC1s9Vh6|Kce4&#dp>lgKQVXRsR{@D>wK%b{b1|5YR7iHN7FDO-SDUG z;qux?g1v=0v_0DF-?!$QOZz6YqFiJTnvg9n3$ml>YC^$*^kD5|+w9g>3m9o*{L^4g z#$2tsm_-)p*FLh>kAe>?w|tmIKHg?hif>~|=>gXC!m`^1h?Q??5d+~iAiB*!YR%7p z*KOX{so+D~wBWa^P;XYqe1(F1+rrpKmZV4TTX^LWC1ST^jE_!-qYXvVz3~s>WPTH{ z31TdwY;Vsavax8^uWgjKY5jEg@Ed!fbp>|<W`bl5$80(6gh1Lj*r!d(+pM+1QjL;? zgBg6@qG!6VG$)z%+=`)W2El>~!erYkYwmlppZZq4FttZ$zFup#?;&Zycp@DriR-Jv zhtnIYZdoRUO}6ImD?Z{YY9Mx*)$0e6ev{&dg&+ccI?CW+|9U%g8Ho`cCNVi85uWEK zA(z}o;>9=I6?V$)$&v%67|+PVpkL~wlBD*B@!n6)Z_$maOfBx}h6S9K?W?%j&pdnt zI%2XfXJfE!-FnUcQ=hhr$$t=??~Ep4o^Mn9B!_)Gji~_UjFYb8eb#1FeoK#kqI&#% zaz}LoWFFJ?SGhp8e<Us8QFVQ21m#Q$+(W@Yr!Lg;Pva<W`_f-;r*b{~bb7I<ucX51 zH_6YmFz~={>LA6@42a6we!EaXau=ud4@<XG6pSH<v;qI1U7s{2gj=?p+c=b78K=e* z(~JGZlNpb)8)TatgzMrmUXKJTu8xQA_4*&&SE0jO1x}g3T>SMjqLie0KLAAK89+70 zZt<Xrll0PnmUb^B^<JR=L@wRwklZ!I2H%MgF=xSUEu>UWxTsyj^Bm5l5#jM(3Z?LQ zXTcDgx<;<jT7~bnObcXKrdMrsv__`Q$s^-USwiS8c#q86Ec(q<x)C%s)D>0>-OTH4 zELi=dbitNmbmOGs?<{3$#OGu5mBlYM_o6(HA@XdQ+nG!Q$AIxspvYglVBBBXU|UQN zsflY!zzY|Nru=0Mrizd2@oP#xro?D!S-D+waGQ;$&_$MERI6Z48#^2=mrY-Ah~v>k z24!oYVcR-<noJq+6QuMhEeLeOBCsq|b02aq<4`tl1&%1d=^1Ns&TSkvhk-dE<b448 z-nyq6x;frqjC5(2<@7m3^~J@&OO{tYg-@oQ3TTfK%17iuMpxzXw5(`?hgqD?>1{xl z8R;`R(&cYwkaf<{fYy~9TuFfWL;#|hUcBT)k^<tdl?xWBs4$|2UDcS3+eR_}FCHBo zowxrPQuLo`-kdzXdG2q-*0LD$NBs?sY)W!}<4K6JhpPVOt8Q}XaL~hnB&SO3+=5+> zXq>y2m@#e3Q&>pkgoyqtRs9V-q6ZpC{J~C^O-~q?{Hwu@k=YG?e2{L$)S|e=X(0I2 zvvvbU@)#pZ^itO~-wkK(0*w^7*v}=1=Is&$^fP4oG8vGe&i|o{Mdau_2<7S7U(W}F z!aQSvg&tU27Yy~Vv)Lx;NKs0bwXpyw8*=CFLLo$_0;ja^a4tVFTRqAZgP1eiBKnGL zO4&X=btbeGp57A!LJEoRF&hbM(R-DsX$U$j&(g*xQ~qvJxDW1Ws>W=gvI=t5Ynw~U zVSxigoIya7dI7?c<-qYK{?9y1I(20KQv|8|%ki4GI$lJ}S>LR$i>H|Og#)Q+H<72Z zXq1x-_;NB9B&BdEuIdhS9883x6_RBeNqKE8E|n=INS2rWt}5`FYb(|k_<wsWg`4Z% z%2-3m1NZzOOv}Y0;W!IN0{l2yl7>6mRjjier_GdOeB<GmtSC+mH6HhNK^o0vTPV~1 zaH}!BCs|^zgouN;T?hAW9*wN{Uqw_(qP0Ovwt!^{xEFzAOAj&GWdliCceQyR?^(4Z z60nwbi3FnW(cxqsbibR7<Bm>wjwHi+@g(Kpodbj+9pE0~rYvjn(1*Yr@le!^KM@ZV zpOlB;+o@E8BvQu)v_UKfBw*f4m?GH7ROO`jyu`UL2hE4ciKjX@EP}{$)#gPD*2rgs zpbEBmmsW!?e9e?l^xm!|9~Q}Md|re?s#`UozDqQD2{p`rqF7l1IkFbvxuVYR*v_4& zEhsV%!F<PU9ME6gq)R*s!xKFt2MA1IhhY0&Jxt9Fi*h~Lj$Fc1aS7UlxAzBa>|847 zr54hs&M(6VA+}7jjN;5{0{d}`ndt@qaZv2M&`6QgZol<cx<OKYJ8jy>*!?**{bhvG zjf@GWt@>CIF>Mu0>N<QA>!zD}%G6b7hNlF=gU{A&gZp@`nayh1LQF>0gOI1S9q081 z0G)<}nBM&*JvMjLb_uHqz(tK^elV`c+>0*@#qX!ov6FEC)Ryb?7ziF(+Rb&g`-8hs z-gIegYm`1G%Jla`m`bX~{S_yA?f1Ckko3_tk_xyu*VXl3sPq?={5%Orhxd4Mjd)2i z{s8+6jYumLN!5-Rql@+^8CG(yk`qcoB?j?Flrt9egmQCA%*AaC%C<*8tg@d|@;N16 zP-4p5uPgT(O8!uZ@wGow?z>71W&Ve9wyOV2xg2AA9Wd^!fz(5<6JE2pC_ZEVtC_Cc zHj6dHekWXM-(dIt-u-*}I|loX{7CQ7-nSom`M~Ib2lo&55A_c8_a6A~`|t1V={?uG Ow|Ag-aQ~66;Qs(Npy)yX literal 0 HcmV?d00001 diff --git a/dockarea/Container.py b/dockarea/Container.py new file mode 100644 index 00000000..a254d474 --- /dev/null +++ b/dockarea/Container.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +import weakref + +class Container(object): + #sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject. + + def __init__(self, area): + object.__init__(self) + self.area = area + self._container = None + self._stretch = (10, 10) + self.stretches = weakref.WeakKeyDictionary() + + def container(self): + return self._container + + def containerChanged(self, c): + self._container = c + + def type(self): + return None + + def insert(self, new, pos=None, neighbor=None): + if not isinstance(new, list): + new = [new] + if neighbor is None: + if pos == 'before': + index = 0 + else: + index = self.count() + else: + index = self.indexOf(neighbor) + if index == -1: + index = 0 + if pos == 'after': + index += 1 + + for n in new: + #print "change container", n, " -> ", self + n.containerChanged(self) + #print "insert", n, " -> ", self, index + self._insertItem(n, index) + index += 1 + n.sigStretchChanged.connect(self.childStretchChanged) + #print "child added", self + self.updateStretch() + + def apoptose(self, propagate=True): + ##if there is only one (or zero) item in this container, disappear. + cont = self._container + c = self.count() + if c > 1: + return + if self.count() == 1: ## if there is one item, give it to the parent container (unless this is the top) + if self is self.area.topContainer: + return + self.container().insert(self.widget(0), 'before', self) + #print "apoptose:", self + self.close() + if propagate and cont is not None: + cont.apoptose() + + def close(self): + self.area = None + self._container = None + self.setParent(None) + + def childEvent(self, ev): + ch = ev.child() + if ev.removed() and hasattr(ch, 'sigStretchChanged'): + #print "Child", ev.child(), "removed, updating", self + try: + ch.sigStretchChanged.disconnect(self.childStretchChanged) + except: + pass + self.updateStretch() + + def childStretchChanged(self): + #print "child", QtCore.QObject.sender(self), "changed shape, updating", self + self.updateStretch() + + def setStretch(self, x=None, y=None): + #print "setStretch", self, x, y + self._stretch = (x, y) + self.sigStretchChanged.emit() + + def updateStretch(self): + ###Set the stretch values for this container to reflect its contents + pass + + + def stretch(self): + """Return the stretch factors for this container""" + return self._stretch + + +class SplitContainer(Container, QtGui.QSplitter): + """Horizontal or vertical splitter with some changes: + - save/restore works correctly + """ + sigStretchChanged = QtCore.Signal() + + def __init__(self, area, orientation): + QtGui.QSplitter.__init__(self) + self.setOrientation(orientation) + Container.__init__(self, area) + #self.splitterMoved.connect(self.restretchChildren) + + def _insertItem(self, item, index): + self.insertWidget(index, item) + item.show() ## need to show since it may have been previously hidden by tab + + def saveState(self): + sizes = self.sizes() + if all([x == 0 for x in sizes]): + sizes = [10] * len(sizes) + return {'sizes': sizes} + + def restoreState(self, state): + sizes = state['sizes'] + self.setSizes(sizes) + for i in range(len(sizes)): + self.setStretchFactor(i, sizes[i]) + + def childEvent(self, ev): + QtGui.QSplitter.childEvent(self, ev) + Container.childEvent(self, ev) + + #def restretchChildren(self): + #sizes = self.sizes() + #tot = sum(sizes) + + + + +class HContainer(SplitContainer): + def __init__(self, area): + SplitContainer.__init__(self, area, QtCore.Qt.Horizontal) + + def type(self): + return 'horizontal' + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + #print "updateStretch", self + x = 0 + y = 0 + sizes = [] + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + x += wx + y = max(y, wy) + sizes.append(wx) + #print " child", self.widget(i), wx, wy + self.setStretch(x, y) + #print sizes + + tot = float(sum(sizes)) + if tot == 0: + scale = 1.0 + else: + scale = self.width() / tot + self.setSizes([int(s*scale) for s in sizes]) + + + +class VContainer(SplitContainer): + def __init__(self, area): + SplitContainer.__init__(self, area, QtCore.Qt.Vertical) + + def type(self): + return 'vertical' + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + #print "updateStretch", self + x = 0 + y = 0 + sizes = [] + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + y += wy + x = max(x, wx) + sizes.append(wy) + #print " child", self.widget(i), wx, wy + self.setStretch(x, y) + + #print sizes + tot = float(sum(sizes)) + if tot == 0: + scale = 1.0 + else: + scale = self.height() / tot + self.setSizes([int(s*scale) for s in sizes]) + + +class TContainer(Container, QtGui.QWidget): + sigStretchChanged = QtCore.Signal() + def __init__(self, area): + QtGui.QWidget.__init__(self) + Container.__init__(self, area) + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.layout.setContentsMargins(0,0,0,0) + self.setLayout(self.layout) + + self.hTabLayout = QtGui.QHBoxLayout() + self.hTabBox = QtGui.QWidget() + self.hTabBox.setLayout(self.hTabLayout) + self.hTabLayout.setSpacing(2) + self.hTabLayout.setContentsMargins(0,0,0,0) + self.layout.addWidget(self.hTabBox, 0, 1) + + self.stack = QtGui.QStackedWidget() + self.layout.addWidget(self.stack, 1, 1) + self.stack.childEvent = self.stackChildEvent + + + self.setLayout(self.layout) + for n in ['count', 'widget', 'indexOf']: + setattr(self, n, getattr(self.stack, n)) + + + def _insertItem(self, item, index): + if not isinstance(item, Dock.Dock): + raise Exception("Tab containers may hold only docks, not other containers.") + self.stack.insertWidget(index, item) + self.hTabLayout.insertWidget(index, item.label) + #QtCore.QObject.connect(item.label, QtCore.SIGNAL('clicked'), self.tabClicked) + item.label.sigClicked.connect(self.tabClicked) + self.tabClicked(item.label) + + def tabClicked(self, tab, ev=None): + if ev is None or ev.button() == QtCore.Qt.LeftButton: + for i in range(self.count()): + w = self.widget(i) + if w is tab.dock: + w.label.setDim(False) + self.stack.setCurrentIndex(i) + else: + w.label.setDim(True) + + def type(self): + return 'tab' + + def saveState(self): + return {'index': self.stack.currentIndex()} + + def restoreState(self, state): + self.stack.setCurrentIndex(state['index']) + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + x = 0 + y = 0 + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + x = max(x, wx) + y = max(y, wy) + self.setStretch(x, y) + + def stackChildEvent(self, ev): + QtGui.QStackedWidget.childEvent(self.stack, ev) + Container.childEvent(self, ev) + +import Dock diff --git a/dockarea/Dock.py b/dockarea/Dock.py new file mode 100644 index 00000000..3b058ba4 --- /dev/null +++ b/dockarea/Dock.py @@ -0,0 +1,350 @@ +from pyqtgraph.Qt import QtCore, QtGui + +from DockDrop import * +from pyqtgraph.widgets.VerticalLabel import VerticalLabel + +class Dock(QtGui.QWidget, DockDrop): + + sigStretchChanged = QtCore.Signal() + + def __init__(self, name, area=None, size=(10, 10)): + QtGui.QWidget.__init__(self) + DockDrop.__init__(self) + self.area = area + self.label = DockLabel(name, self) + self.labelHidden = False + self.moveLabel = True ## If false, the dock is no longer allowed to move the label. + self.autoOrient = True + self.orientation = 'horizontal' + #self.label.setAlignment(QtCore.Qt.AlignHCenter) + self.topLayout = QtGui.QGridLayout() + self.topLayout.setContentsMargins(0, 0, 0, 0) + self.topLayout.setSpacing(0) + self.setLayout(self.topLayout) + self.topLayout.addWidget(self.label, 0, 1) + self.widgetArea = QtGui.QWidget() + self.topLayout.addWidget(self.widgetArea, 1, 1) + self.layout = QtGui.QGridLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(0) + self.widgetArea.setLayout(self.layout) + self.widgetArea.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.widgets = [] + self.currentRow = 0 + #self.titlePos = 'top' + self.raiseOverlay() + self.hStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-top-width: 0px; + }""" + self.vStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + border-left-width: 0px; + }""" + self.nStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + }""" + self.dragStyle = """ + Dock > QWidget { + border: 4px solid #00F; + border-radius: 5px; + }""" + self.setAutoFillBackground(False) + self.widgetArea.setStyleSheet(self.hStyle) + + self.setStretch(*size) + + def setStretch(self, x=None, y=None): + #print "setStretch", self, x, y + #self._stretch = (x, y) + if x is None: + x = 0 + if y is None: + y = 0 + #policy = self.sizePolicy() + #policy.setHorizontalStretch(x) + #policy.setVerticalStretch(y) + #self.setSizePolicy(policy) + self._stretch = (x, y) + self.sigStretchChanged.emit() + #print "setStretch", self, x, y, self.stretch() + + def stretch(self): + #policy = self.sizePolicy() + #return policy.horizontalStretch(), policy.verticalStretch() + return self._stretch + + #def stretch(self): + #return self._stretch + + def hideTitleBar(self): + self.label.hide() + self.labelHidden = True + if 'center' in self.allowedAreas: + self.allowedAreas.remove('center') + self.updateStyle() + + def showTitleBar(self): + self.label.show() + self.labelHidden = False + self.allowedAreas.add('center') + self.updateStyle() + + def setOrientation(self, o='auto', force=False): + #print self.name(), "setOrientation", o, force + if o == 'auto': + if self.container().type() == 'tab': + o = 'horizontal' + elif self.width() > self.height()*1.5: + o = 'vertical' + else: + o = 'horizontal' + if force or self.orientation != o: + self.orientation = o + self.label.setOrientation(o) + self.updateStyle() + + def updateStyle(self): + #print self.name(), "update style:", self.orientation, self.moveLabel, self.label.isVisible() + if self.labelHidden: + self.widgetArea.setStyleSheet(self.nStyle) + elif self.orientation == 'vertical': + self.label.setOrientation('vertical') + if self.moveLabel: + #print self.name(), "reclaim label" + self.topLayout.addWidget(self.label, 1, 0) + self.widgetArea.setStyleSheet(self.vStyle) + else: + self.label.setOrientation('horizontal') + if self.moveLabel: + #print self.name(), "reclaim label" + self.topLayout.addWidget(self.label, 0, 1) + self.widgetArea.setStyleSheet(self.hStyle) + + def resizeEvent(self, ev): + self.setOrientation() + self.resizeOverlay(self.size()) + + def name(self): + return str(self.label.text()) + + def container(self): + return self._container + + def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1): + if row is None: + row = self.currentRow + self.currentRow = max(row+1, self.currentRow) + self.widgets.append(widget) + self.layout.addWidget(widget, row, col, rowspan, colspan) + self.raiseOverlay() + + + def startDrag(self): + self.drag = QtGui.QDrag(self) + mime = QtCore.QMimeData() + #mime.setPlainText("asd") + self.drag.setMimeData(mime) + self.widgetArea.setStyleSheet(self.dragStyle) + self.update() + action = self.drag.exec_() + self.updateStyle() + + def float(self): + self.area.floatDock(self) + + def containerChanged(self, c): + #print self.name(), "container changed" + self._container = c + if c.type() != 'tab': + self.moveLabel = True + self.label.setDim(False) + else: + self.moveLabel = False + + self.setOrientation(force=True) + + def __repr__(self): + return "<Dock %s %s>" % (self.name(), self.stretch()) + +class DockLabel(VerticalLabel): + + sigClicked = QtCore.Signal(object, object) + + def __init__(self, text, dock): + self.dim = False + self.fixedWidth = False + VerticalLabel.__init__(self, text, orientation='horizontal', forceWidth=False) + self.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter) + self.dock = dock + self.updateStyle() + self.setAutoFillBackground(False) + + #def minimumSizeHint(self): + ##sh = QtGui.QWidget.minimumSizeHint(self) + #return QtCore.QSize(20, 20) + + def updateStyle(self): + r = '3px' + if self.dim: + fg = '#aaa' + bg = '#44a' + border = '#339' + else: + fg = '#fff' + bg = '#66c' + border = '#55B' + + if self.orientation == 'vertical': + self.vStyle = """DockLabel { + background-color : %s; + color : %s; + border-top-right-radius: 0px; + border-top-left-radius: %s; + border-bottom-right-radius: 0px; + border-bottom-left-radius: %s; + border-width: 0px; + border-right: 2px solid %s; + padding-top: 3px; + padding-bottom: 3px; + }""" % (bg, fg, r, r, border) + self.setStyleSheet(self.vStyle) + else: + self.hStyle = """DockLabel { + background-color : %s; + color : %s; + border-top-right-radius: %s; + border-top-left-radius: %s; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; + border-width: 0px; + border-bottom: 2px solid %s; + padding-left: 3px; + padding-right: 3px; + }""" % (bg, fg, r, r, border) + self.setStyleSheet(self.hStyle) + + def setDim(self, d): + if self.dim != d: + self.dim = d + self.updateStyle() + + def setOrientation(self, o): + VerticalLabel.setOrientation(self, o) + self.updateStyle() + + def mousePressEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + self.pressPos = ev.pos() + self.startedDrag = False + ev.accept() + + def mouseMoveEvent(self, ev): + if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance(): + self.dock.startDrag() + ev.accept() + #print ev.pos() + + def mouseReleaseEvent(self, ev): + if not self.startedDrag: + #self.emit(QtCore.SIGNAL('clicked'), self, ev) + self.sigClicked.emit(self, ev) + ev.accept() + + def mouseDoubleClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + self.dock.float() + + #def paintEvent(self, ev): + #p = QtGui.QPainter(self) + ##p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) + #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) + #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) + + #VerticalLabel.paintEvent(self, ev) + + + +#class DockLabel(QtGui.QWidget): + #def __init__(self, text, dock): + #QtGui.QWidget.__init__(self) + #self._text = text + #self.dock = dock + #self.orientation = None + #self.setOrientation('horizontal') + + #def text(self): + #return self._text + + #def mousePressEvent(self, ev): + #if ev.button() == QtCore.Qt.LeftButton: + #self.pressPos = ev.pos() + #self.startedDrag = False + #ev.accept() + + #def mouseMoveEvent(self, ev): + #if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance(): + #self.dock.startDrag() + #ev.accept() + ##print ev.pos() + + #def mouseReleaseEvent(self, ev): + #ev.accept() + + #def mouseDoubleClickEvent(self, ev): + #if ev.button() == QtCore.Qt.LeftButton: + #self.dock.float() + + #def setOrientation(self, o): + #if self.orientation == o: + #return + #self.orientation = o + #self.update() + #self.updateGeometry() + + #def paintEvent(self, ev): + #p = QtGui.QPainter(self) + #p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) + #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) + #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) + + #p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255))) + + #if self.orientation == 'vertical': + #p.rotate(-90) + #rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width()) + #else: + #rgn = self.rect() + #align = QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter + + #self.hint = p.drawText(rgn, align, self.text()) + #p.end() + + #if self.orientation == 'vertical': + #self.setMaximumWidth(self.hint.height()) + #self.setMaximumHeight(16777215) + #else: + #self.setMaximumHeight(self.hint.height()) + #self.setMaximumWidth(16777215) + + #def sizeHint(self): + #if self.orientation == 'vertical': + #if hasattr(self, 'hint'): + #return QtCore.QSize(self.hint.height(), self.hint.width()) + #else: + #return QtCore.QSize(19, 50) + #else: + #if hasattr(self, 'hint'): + #return QtCore.QSize(self.hint.width(), self.hint.height()) + #else: + #return QtCore.QSize(50, 19) diff --git a/dockarea/DockArea.py b/dockarea/DockArea.py new file mode 100644 index 00000000..88e699d5 --- /dev/null +++ b/dockarea/DockArea.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +from Container import * +from DockDrop import * +import pyqtgraph.debug as debug +import weakref + +## TODO: +# - containers should be drop areas, not docks. (but every slot within a container must have its own drop areas?) +# - drop between tabs +# - nest splitters inside tab boxes, etc. + + + + +class DockArea(Container, QtGui.QWidget, DockDrop): + def __init__(self, temporary=False, home=None): + Container.__init__(self, self) + QtGui.QWidget.__init__(self) + DockDrop.__init__(self, allowedAreas=['left', 'right', 'top', 'bottom']) + self.layout = QtGui.QVBoxLayout() + self.layout.setContentsMargins(0,0,0,0) + self.layout.setSpacing(0) + self.setLayout(self.layout) + self.docks = weakref.WeakValueDictionary() + self.topContainer = None + self.raiseOverlay() + self.temporary = temporary + self.tempAreas = [] + self.home = home + + def type(self): + return "top" + + def addDock(self, dock, position='bottom', relativeTo=None): + """Adds a dock to this area. + position may be: bottom, top, left, right, over, under + If relativeTo specifies an existing dock, the new dock is added adjacent to it""" + + ## Determine the container to insert this dock into. + ## If there is no neighbor, then the container is the top. + if relativeTo is None or relativeTo is self: + if self.topContainer is None: + container = self + neighbor = None + else: + container = self.topContainer + neighbor = None + else: + if isinstance(relativeTo, basestring): + relativeTo = self.docks[relativeTo] + container = self.getContainer(relativeTo) + neighbor = relativeTo + + ## what container type do we need? + neededContainer = { + 'bottom': 'vertical', + 'top': 'vertical', + 'left': 'horizontal', + 'right': 'horizontal', + 'above': 'tab', + 'below': 'tab' + }[position] + + ## Can't insert new containers into a tab container; insert outside instead. + if neededContainer != container.type() and container.type() == 'tab': + neighbor = container + container = container.container() + + ## Decide if the container we have is suitable. + ## If not, insert a new container inside. + if neededContainer != container.type(): + if neighbor is None: + container = self.addContainer(neededContainer, self.topContainer) + else: + container = self.addContainer(neededContainer, neighbor) + + ## Insert the new dock before/after its neighbor + insertPos = { + 'bottom': 'after', + 'top': 'before', + 'left': 'before', + 'right': 'after', + 'above': 'before', + 'below': 'after' + }[position] + #print "request insert", dock, insertPos, neighbor + container.insert(dock, insertPos, neighbor) + dock.area = self + self.docks[dock.name()] = dock + + def getContainer(self, obj): + if obj is None: + return self + return obj.container() + + def makeContainer(self, typ): + if typ == 'vertical': + new = VContainer(self) + elif typ == 'horizontal': + new = HContainer(self) + elif typ == 'tab': + new = TContainer(self) + return new + + def addContainer(self, typ, obj): + """Add a new container around obj""" + new = self.makeContainer(typ) + + container = self.getContainer(obj) + container.insert(new, 'before', obj) + #print "Add container:", new, " -> ", container + if obj is not None: + new.insert(obj) + self.raiseOverlay() + return new + + def insert(self, new, pos=None, neighbor=None): + if self.topContainer is not None: + self.topContainer.containerChanged(None) + self.layout.addWidget(new) + self.topContainer = new + #print self, "set top:", new + new._container = self + self.raiseOverlay() + #print "Insert top:", new + + def count(self): + if self.topContainer is None: + return 0 + return 1 + + def moveDock(self, dock, position, neighbor): + old = dock.container() + ## Moving to the edge of a tabbed dock causes a drop outside the tab box + if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab': + neighbor = neighbor.container() + self.addDock(dock, position, neighbor) + old.apoptose() + + #def paintEvent(self, ev): + #self.drawDockOverlay() + + def resizeEvent(self, ev): + self.resizeOverlay(self.size()) + + def addTempArea(self): + if self.home is None: + area = DockArea(temporary=True, home=self) + self.tempAreas.append(area) + win = QtGui.QMainWindow() + win.setCentralWidget(area) + area.win = win + win.show() + else: + area = self.home.addTempArea() + #print "added temp area", area, area.window() + return area + + def floatDock(self, dock): + area = self.addTempArea() + area.win.resize(dock.size()) + area.moveDock(dock, 'top', None) + + + def removeTempArea(self, area): + self.tempAreas.remove(area) + #print "close window", area.window() + area.window().close() + + def saveState(self): + state = {'main': self.childState(self.topContainer), 'float': []} + for a in self.tempAreas: + geo = a.win.geometry() + geo = (geo.x(), geo.y(), geo.width(), geo.height()) + state['float'].append((a.saveState(), geo)) + return state + + def childState(self, obj): + if isinstance(obj, Dock): + return ('dock', obj.name(), {}) + else: + childs = [] + for i in range(obj.count()): + childs.append(self.childState(obj.widget(i))) + return (obj.type(), childs, obj.saveState()) + + + def restoreState(self, state): + ## 1) make dict of all docks and list of existing containers + containers, docks = self.findAll() + oldTemps = self.tempAreas[:] + #print "found docks:", docks + + ## 2) create container structure, move docks into new containers + self.buildFromState(state['main'], docks, self) + + ## 3) create floating areas, populate + for s in state['float']: + a = self.addTempArea() + a.buildFromState(s[0]['main'], docks, a) + a.win.setGeometry(*s[1]) + + ## 4) Add any remaining docks to the bottom + for d in docks.itervalues(): + self.moveDock(d, 'below', None) + + #print "\nKill old containers:" + ## 5) kill old containers + for c in containers: + c.close() + for a in oldTemps: + a.apoptose() + + + def buildFromState(self, state, docks, root, depth=0): + typ, contents, state = state + pfx = " " * depth + if typ == 'dock': + obj = docks[contents] + del docks[contents] + else: + obj = self.makeContainer(typ) + + root.insert(obj, 'after') + #print pfx+"Add:", obj, " -> ", root + + if typ != 'dock': + for o in contents: + self.buildFromState(o, docks, obj, depth+1) + obj.apoptose(propagate=False) + obj.restoreState(state) ## this has to be done later? + + + def findAll(self, obj=None, c=None, d=None): + if obj is None: + obj = self.topContainer + + ## check all temp areas first + if c is None: + c = [] + d = {} + for a in self.tempAreas: + c1, d1 = a.findAll() + c.extend(c1) + d.update(d1) + + if isinstance(obj, Dock): + d[obj.name()] = obj + else: + c.append(obj) + for i in range(obj.count()): + o2 = obj.widget(i) + c2, d2 = self.findAll(o2) + c.extend(c2) + d.update(d2) + return (c, d) + + def apoptose(self): + #print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count() + if self.temporary and self.topContainer.count() == 0: + self.topContainer = None + self.home.removeTempArea(self) + #self.close() + + + \ No newline at end of file diff --git a/dockarea/DockDrop.py b/dockarea/DockDrop.py new file mode 100644 index 00000000..339b28fd --- /dev/null +++ b/dockarea/DockDrop.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui + +class DockDrop(object): + """Provides dock-dropping methods""" + def __init__(self, allowedAreas=None): + object.__init__(self) + if allowedAreas is None: + allowedAreas = ['center', 'right', 'left', 'top', 'bottom'] + self.allowedAreas = set(allowedAreas) + self.setAcceptDrops(True) + self.dropArea = None + self.overlay = DropAreaOverlay(self) + self.overlay.raise_() + + def resizeOverlay(self, size): + self.overlay.resize(size) + + def raiseOverlay(self): + self.overlay.raise_() + + def dragEnterEvent(self, ev): + if isinstance(ev.source(), Dock.Dock): + #print "drag enter accept" + ev.accept() + else: + #print "drag enter ignore" + ev.ignore() + + def dragMoveEvent(self, ev): + #print "drag move" + ld = ev.pos().x() + rd = self.width() - ld + td = ev.pos().y() + bd = self.height() - td + + mn = min(ld, rd, td, bd) + if mn > 30: + self.dropArea = "center" + elif (ld == mn or td == mn) and mn > self.height()/3.: + self.dropArea = "center" + elif (rd == mn or ld == mn) and mn > self.width()/3.: + self.dropArea = "center" + + elif rd == mn: + self.dropArea = "right" + elif ld == mn: + self.dropArea = "left" + elif td == mn: + self.dropArea = "top" + elif bd == mn: + self.dropArea = "bottom" + + if ev.source() is self and self.dropArea == 'center': + #print " no self-center" + self.dropArea = None + ev.ignore() + elif self.dropArea not in self.allowedAreas: + #print " not allowed" + self.dropArea = None + ev.ignore() + else: + #print " ok" + ev.accept() + self.overlay.setDropArea(self.dropArea) + + def dragLeaveEvent(self, ev): + self.dropArea = None + self.overlay.setDropArea(self.dropArea) + + def dropEvent(self, ev): + area = self.dropArea + if area is None: + return + if area == 'center': + area = 'above' + self.area.moveDock(ev.source(), area, self) + self.dropArea = None + self.overlay.setDropArea(self.dropArea) + + + +class DropAreaOverlay(QtGui.QWidget): + """Overlay widget that draws drop areas during a drag-drop operation""" + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.dropArea = None + self.hide() + self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) + + def setDropArea(self, area): + self.dropArea = area + if area is None: + self.hide() + else: + ## Resize overlay to just the region where drop area should be displayed. + ## This works around a Qt bug--can't display transparent widgets over QGLWidget + prgn = self.parent().rect() + rgn = QtCore.QRect(prgn) + w = min(30, prgn.width()/3.) + h = min(30, prgn.height()/3.) + + if self.dropArea == 'left': + rgn.setWidth(w) + elif self.dropArea == 'right': + rgn.setLeft(rgn.left() + prgn.width() - w) + elif self.dropArea == 'top': + rgn.setHeight(h) + elif self.dropArea == 'bottom': + rgn.setTop(rgn.top() + prgn.height() - h) + elif self.dropArea == 'center': + rgn.adjust(w, h, -w, -h) + self.setGeometry(rgn) + self.show() + + self.update() + + def paintEvent(self, ev): + if self.dropArea is None: + return + p = QtGui.QPainter(self) + rgn = self.rect() + + p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 255, 50))) + p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 150), 3)) + p.drawRect(rgn) + +import Dock \ No newline at end of file diff --git a/dockarea/__init__.py b/dockarea/__init__.py new file mode 100644 index 00000000..88d12393 --- /dev/null +++ b/dockarea/__init__.py @@ -0,0 +1,2 @@ +from DockArea import DockArea +from Dock import Dock \ No newline at end of file diff --git a/dockarea/__main__.py b/dockarea/__main__.py new file mode 100644 index 00000000..86e8c9d1 --- /dev/null +++ b/dockarea/__main__.py @@ -0,0 +1,83 @@ +import sys + +## Make sure pyqtgraph is importable +p = os.path.dirname(os.path.abspath(__file__)) +p = os.path.join(p, '..', '..') +sys.path.insert(0, p) + +from pyqtgraph.Qt import QtCore, QtGui + +from DockArea import * +from Dock import * + +app = QtGui.QApplication([]) +win = QtGui.QMainWindow() +area = DockArea() +win.setCentralWidget(area) +win.resize(800,800) +from Dock import Dock +d1 = Dock("Dock1", size=(200,200)) +d2 = Dock("Dock2", size=(100,100)) +d3 = Dock("Dock3", size=(1,1)) +d4 = Dock("Dock4", size=(50,50)) +d5 = Dock("Dock5", size=(100,100)) +d6 = Dock("Dock6", size=(300,300)) +area.addDock(d1, 'left') +area.addDock(d2, 'right') +area.addDock(d3, 'bottom') +area.addDock(d4, 'right') +area.addDock(d5, 'left', d1) +area.addDock(d6, 'top', d4) + +area.moveDock(d6, 'above', d4) +d3.hideTitleBar() + +print "===build complete====" + +for d in [d1, d2, d3, d4, d5]: + w = QtGui.QWidget() + l = QtGui.QVBoxLayout() + w.setLayout(l) + btns = [] + for i in range(4): + btns.append(QtGui.QPushButton("%s Button %d"%(d.name(), i))) + l.addWidget(btns[-1]) + d.w = (w, l, btns) + d.addWidget(w) + + + +import pyqtgraph as pg +p = pg.PlotWidget() +d6.addWidget(p) + +print "===widgets added===" + + +#s = area.saveState() + + +#print "\n\n-------restore----------\n\n" +#area.restoreState(s) +s = None +def save(): + global s + s = area.saveState() + +def load(): + global s + area.restoreState(s) + + +#d6.container().setCurrentIndex(0) +#d2.label.setTabPos(40) + +#win2 = QtGui.QMainWindow() +#area2 = DockArea() +#win2.setCentralWidget(area2) +#win2.resize(800,800) + + +win.show() +#win2.show() + diff --git a/documentation/Makefile b/documentation/Makefile new file mode 100644 index 00000000..15b77d38 --- /dev/null +++ b/documentation/Makefile @@ -0,0 +1,130 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyqtgraph.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyqtgraph.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pyqtgraph" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyqtgraph" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/documentation/build/doctrees/apireference.doctree b/documentation/build/doctrees/apireference.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1620ca32384d7d38e0620151f1b3adcdd4c5aa82 GIT binary patch literal 3040 zcmcIm>3<wY5mjVM+FeVsd<Se1=Q0XJnh*>L<}@J*Mlb~9G6p;hy)(VrgXXH~9v#4t z7>JV%A%y$B@B6-Q{xV+A&Pwb3nU8$YZ?#)p_3G8D<B91Y%!1NHv0h8FQ0k8TH<797 z@GbXT<LQd`tx!7^i8MR8)V#mqnJWuZQ&S?3P8YH#3z-IzXI)K)blKG=GVqqAekgmI z=PGW|VlE0T{X}Xl`f$<qxI>FUS>UPhbygN|aKNQbD1)pJ_BGGDbXb%o>t&fqGb4R% zL}7Sg{T7<DV4_g-V#Np5EG+=Z>K>9MO|3vhaR~2x$m7G432GVG5FI{({Ek+94EYse ztHE)crf3#`0gX?nX_{@mzje}0A@ib4qX49J_yt$4f)Yn%iFD#}LpobovEd{ljkF(& zu4Ann5%5_jj=GMe+sSv>^b3(IXT*4&xigICAgSG))(UO-g->4NlNF!p(wq@}%K<Ob zT$2pHh&sZUBI=d~4BuAq=@n{)Wp3>>-|o>|5DTsG`3{d-T@h@+yXHGR+GnY}%j3J% zlAxKP{TYv6tY$??`{O8;ys|zGXN@lS9yP7N>q}N98A0h|aPd;mdT+(6E9!VHl=F{W zIDK(fC~NA3I;w6}C)F~|)Gydkz<b|{y4_QEc<N41-Q}q>Ua1gPHNjps8S9eVhC=VZ zNr0Dwr?VBm0^yYnc5m4gWq0yDDlBUDJN!!EeW2n8E#5Y&_)wM_R9~OtSB+t9@4n!3 z7VoQXhIju6?`zck6TV&xSg))2^%j=(`@l_M1zD13Wg2$)4M26i;y1#crp9ozMkf4_ zh4H2a20u&(O%|9!%GE8XDwhR6BF4#dN7x<>bgrUwdrgGlu;rVF9ey){Ua0sj!=P}# z4i%+k(nXht{MLawLG$Cn<hN0$uA$D-c!%8q?eE7~m)}0HHAE$cB|QWAfzcv@{2uf8 z9V>24POnVsPSkwyI=|Dsas=epHHiX!!N^2AQ5wo^zI2_*tt-71g?(v8-(SAY?~*jD zqA*0OUm4JRlm>B$!4t)Z^lp#ev!X7lOX{+uRwA}TnH$CLU7s$&=Xq!t5}a#t=c{!+ zB>q~$>3}Ijs(V?HfJo1!h223^_s#n%_Nj%B!+v>x#e$afPSVX{e`Kk->v2GbZWv?r z>(FCKM<>kv`lp`9eQF1hsfU4P<#A*SNAsh3S$E_IFdlai?oHMWk7Elf&k6(FiA$|0 zY({5_(YY{=MRVqHP79-8LjYWHK}#SW<0mjarmkh2wa129S{f6jea+=q^Y@CnVZzR3 z5tmpXyqWP<#oH@%ctnM72M&O5Xx;&gBXFG+Ayz~#QQ<VO!uX&|^F=R!DRxC!#QY&T zsf@|bIgZIPtfxqt-da}l9c7Z(;SW<A3<T1;<_caHbD7&5h8s!qM_gLe5MA!0W$Z@N z{5T4Uf{(#@f@Z8Tcla7D#3JpN*if(!C4ZErp{kEjI}{kHMkG0Z+(N9$^<%jyV}25Y zaUN-4>!riJbscL=iP$s-Pp!|;LIa|izfZf$ap{ySimdRFdlrIGfB3HwX&9q$16s0a zX*^K~7$Z%7hGs(9E&KclmzE@2(iV0HLRV56es)01S!wdp{4bxM#3}<Df#K%>3AIIE z``cFG{3)<^Ff5bY-_?}n=fM?bew6mI#_ZFU<SkL87BGBCk??0+I%avdEoZCdqi=!N zNd7Ff@Wble_ni&-b96{<N7(g{b%~8wllOUWGaF=SFY5Cbnp#0|n~{!8R`3_$Ifyf{ z;mbrsv5#q0;DUfszVtLLBa>ve)Ap+F>)dkn<pJ%J=_Y^0ZSu;sEJNgx#QarD-*%os zWf2B_k&0OJ*U(Rv6I?L$JZm!k`hX4$_pxu)%ijP@tI;u)eG?%ji05z7Olf+n5AwIE z(>%9={tjM8>3E6wyVR~NS2alZ9v$?<NQ*A0Y<T&;OKo&;j)2iN{{VxjZA90{9K<C1 z;W}=s8+F7#q8YmM2txmu=0>O0Pw49Z^)mS>wfi!axZF^-pCQRwN_*t~9BpGYvfJ6e zpxc_x-rCw)%Xf^*(z>%lx#_@CJN!#Jbt6?19r7#n94*>u=I_z-YnqNi{te<|5!zd% zsp4-3baL2(GM4oq0D(sNql@TwP<lgntz*r<_vm=7=qS~)sLxrM+PZ}N2Q<L}oQr6) z;TM0z7YlX9yVLPcZrP<{lk<Ie!Tx!DYIt(}rQ*M;C3RFodE<BUaHIUK;=ik9Onxlk pf7Cm^UZ|sU6z3C8B^*iA9?e+I|Md7@G;5`Ti|*eQ|EKJ({TImUWv~DM literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/environment.pickle b/documentation/build/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..eee3550a391102da9f44347003ad2e3b52f874e3 GIT binary patch literal 77056 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 IpKq=EABBxf{Qv*} literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/functions.doctree b/documentation/build/doctrees/functions.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9d40ecc8d9b3fe9c0d672be881267dd431003c40 GIT binary patch literal 54676 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/arrowitem.doctree b/documentation/build/doctrees/graphicsItems/arrowitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8a90ddb68c5aa439d7ce3e5422a617ade3cab882 GIT binary patch literal 5249 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 AO8@`> literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/axisitem.doctree b/documentation/build/doctrees/graphicsItems/axisitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b0deac26328c4cda3a687dc224f471c3dc599c0a GIT binary patch literal 11126 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/buttonitem.doctree b/documentation/build/doctrees/graphicsItems/buttonitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ce962738e59669da725827d5eef5e4acde6a8794 GIT binary patch literal 5775 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/curvearrow.doctree b/documentation/build/doctrees/graphicsItems/curvearrow.doctree new file mode 100644 index 0000000000000000000000000000000000000000..532f70172cbd9438946c890aa9c68e8299c26643 GIT binary patch literal 6107 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/curvepoint.doctree b/documentation/build/doctrees/graphicsItems/curvepoint.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c98a291658a6fb2ed20c70781757701030c86fab GIT binary patch literal 6727 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/gradienteditoritem.doctree b/documentation/build/doctrees/graphicsItems/gradienteditoritem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b1029004df6603a1e48d00bac85ce8ddf469fd13 GIT binary patch literal 6649 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 Ll4U%@(&+yHTGYz) literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/gradientlegend.doctree b/documentation/build/doctrees/graphicsItems/gradientlegend.doctree new file mode 100644 index 0000000000000000000000000000000000000000..912cb974046a488635ffcaf71a0cbdae1df61a9b GIT binary patch literal 7223 zcmc&(2bdeh6}Hd5BH4Ft7-OFeq8M~wpD3o84#vjVKAIOq5n{DFx><X*t2evry8|}h zBowERP8#VwB!u*kN)l2@CA}vkg!F_|l92MhS*=cz&-w`8myhrKt+g}n&HLxIdHd$Z z9?SNvD0JLl)bni-Wcjrwyf90%M|M+BOmp%^#xrXooT`g}=El^UFCIK=)~xyzh2zSs z%h65Bv>mW)730FQN6Tf$bHZ|&`nJq29@3365aCv{BHV!bL3%(JQW^lk5C|Yq%W}0W z%8W(Eq9CdSp%Y>kzE`$IHK1%v^NgXosRB{1i6Ah?0eyan28|&rQVLkhfgdSL(9oEX zwT0y?leuZZn6cQ5Lci+!q34I99E7F{Y2lXHMn5aTu>)EZ)8f4B!(TuC2Jn|LhB|O9 ziHreDI<5^_v|ovqcB(lL77NVM{?PKWm=1uJl{wKswcMCx^Z`DA(hAvQ^fktZJC{+S zt|HH|V7e@=EV|H8LHadOaHE}~FyX76g_`L(LD@Ac1*R&P7Og9|PNl#`FEou<NbRom zP)Otzw0#{b*c^&lz;7+OA5}p}t8Us&2gY<z#pn;san=y!jQ+;v=wKsjhM{sQQ3(AX z64Rl1W5AB;Oc&5$C8OVR%^<+<)g@z~Vp=;DTkr6aF^3g>M2YfpiD~pE^dluYQfAD^ znCm*8prf|*V6ld`(i+(#q35;vP7T2^T<B&LYA(cdbY3pkezd90XXzNZLN1fT@<5q0 zdNm6jD*^bpygaNVSC{1BC3!?ij+7z^xw0W`U8k_^ViJLK{7e!~fTm82=_CM<EUge# z%&1}ob?JDMqo!>q;pcd^n9S12fV)1XQ$TIZO0bPFG(AnHGPKk3j0m_AfFsZ*+(kJe zDADP8xQyeCyMoRr9tx*u-)=6i^`3LQT+;zY*pov#)5H$P8XKGeA5w&srLzF<?3m8! zfH#+CX#=A>H;;hv6Sy1q7=5;1GaZjM6?=4)?*-kj?BE1L=M{TFpyAPG*cS`x@$=>8 z7GSg`znA8iv>Z5a!Z1>>_*P)t7SkA8oU>573i7)gF|j5WIM<3bIlo#(DjI>F&z8eR z=2XrA@T}A60_c3GePOzgw|5aMpDvaIrqNgP?Ffp#M6N7m^yV6Qk+#F+MogDhTCGHv zG1EXba$OxR#hIMta-%m<!4*gZy;X-p?aDo^3b<+?3NSsSEgi#IDnSER$5dtw^rc$O zVVB$B>AbZCdTY}xbPey#oWbW)kD!V?zGW@4uths2kvhWFGkbL+2R@F#&r%h*#$%F< zE8D8E(H3m;TY1P~U^}`2+X-Opi;)X(wV1pV+;|so2{IqZ>M<ehESHzJfXQeu+Fpv0 z1l^E?K*C2=yTefgG`nJ&NYT{0ph4<~rb3}yCqP1(1gxo;u1#4}`z_Y6+KA~o#&>-; zeDOY9r@2m+o&te4#B?JB7UyCs94t`BRKstQkZ97=ZkGAfZZe++oumuV)1ir*V!Aoq z&J9`<l7-$9)2*pcj?HPIQVZGBO~^AELYR@B2_esl>Dj4}Tc(BF7SnTB2**Sc`ki8) z+fB^#AZD7Co)1AUi0Or?pxd+{5moSS<|JkLUWinkr59nxx5xD2loi=W$7SOC4KjKO zYxkwy5WQ@#Q;%&k&c#`lUJfC5#PkZ@vBHiwaG8-abC&P=DhDEcrsot25|dAioay@B zcy7W8WiG^dmP6@)5-n#7WEPw$xloGSF4K)b8Rmkzu$-!6<+62T`w$CjZl+0>9;H`8 zzqiZb;`I>!1j`e>nPw%jsqOS0txv5-x(;yo0iIu-KGZr&Q+gGWUZ$xVdNphIH8W^- z%k1=se=UstBx&t+aIDwI^adDCvIg%+JeW&^H^%g))M=ntlm=4Q1aIzA6TBra&nT{e z1=9LFKa$HgkN*x$<&b@(Z;x=98`4|bivr2V|D7?ttpkES_}`wF8~4b4ob&QL9XZM3 zbeB9|-qm`jy(2IA)V@2Wce1x01+X2KZepML_`Wj<AgRyjcj+_w-LlV=d!NnkfpXs) z)BD=b=JzvkvAAsCg5?!B7}}5G59B4E8~4QYK}J2$qGDL>?gk`%ZhUA4Uuzz%ABL;7 zGSf$Z@uM+)ELHP8i39LyaBoZ>Plr<pI`e;`n}kp1b&|a=rcW`_G$8}!y$zH)$$q-q z8lPz-SvCND76?BV)8|tt_az89liwfH7g8aOOx`Bsi`|5LsUbvX@-IWkS7Q2VD&+oY zArHj#H5M|Rjyw1A^=@Lm0Ws68^i2r*R!rYc1wEhzC7E1uuKrF;-%Te|SJT<~dtIdH zgFtfBem|xkr0kpyZ-%ElndxwJ`uqEbX$JXGmkjb_&LC?6y2<yrQV711L;A@KDdfSJ ze%hiB$slwN`Pt*oArB#kJk**)exBrzhhzE$)-De39m><_-4XgF9Ol8+tnn+IHGVDo zXUZDCfrtDyrr)(^jo&kIQTH!8(p6K`3~A#JN!oZMrav-J{&bioUm+iE%^QE};$QS< zcySvy{RJ@p8q?oW<vuc<I3A7Z@9A(VLMM)YbQAGUoi`qf>0b<WHlVuD+oP>X<KNx* z{{x7c>!#gm7JkHPHl8Vt$22^A4I|Vz&Qy;knbSg&nuw!K&D9foeV3<+RP|zBqU=$9 zdY%4co#JJHy+KsbI|$O6CiHV=y)I0Z^SIK%D;F+FWfWMZD@Jo?S(d1CU6ZpNE9C0p zI<AV=CnZpvtDE>bQv)n=#SR7zrLZG&ZJ{z+^j=7t<%P;J!}0%DX!9TfYM+n)Vl}7% zE#J|=%L-~yev<0&G`a0y%nOz21yx_wgi=Ew9Ww@-jWO=js0Dh_g?JhR7~l`2KrPaP zi+ONiFjcGgZaL)&)DjGr)P6j<sIAea*ITM*_vhLDI#|n^V3{U3fC(~|lYHm~YPlX= zfhU)3#{9I$$9*C-jA3;3ntN$B1l3B-whB*1U-uP0xYU7~e891j)}J~E@96#wwt@{* zPO~1&tb-|z1mPhVi`Ah#vk>hMhwJJxnuy~f4AfzoZ8c90AlSK_4%FdzZ7JZZN`D^M z*-N>`4zlV9%=;>jXRJn;)>xeCv&=m+>~d$Ij>HUbE`dB>*=U2-(Tf$Hg?^Aa3Ugz| zd{wo;fR<>aT(t&c#;^>-`uaj4^nEmd9T8SXeKlT?Va+Y5wM>zL-YlV8bEAobp}MH^ zRt36~78Wn)#fP~1u9q#p#?9kE9j!+PL!U*a7?0r<d$~(BrH<v_gDyTfB6MfLXbW{5 zkM#&ot>YgV+eF(iG;4KrJl=T&+DK(r>=Ldz0b^(Z&c{~K{YBRY?NoK5Cd^9H4I5-9 zF<GxM*ua<c8`a57CyyO7R>I`IDq}0Y4QrH>S7~~-i=XR3W-R0l1Yneq??M~u6dvxi zMI{<nr}9e1LQ$&-c9|(x1X74lordu}#wtGw>ruF`^-t%8`WTuOsx$aykb9?IR4Y$% z+^I7$2CFWxqguV(bm2gqg>gh9zIq(5>NkkbPKbvlOy%)ju&yv`>KskGl+9LkTy6a_ z`nlK&{Cxu^jRDN9U48UATb;|Vi^L@Q_t1P8p>eF0y^)vhHTv*PT6M<NCcUJ#IlOz9 z6F8x-)OmVzspb0S&ayzi&n=^`snCCin{3A9t;Q;Bwbo1r({5A_>a5uFLA1x1BfMQ| zi(V|*cs&r2ja}AUwN(!<6q9uyj*Kr(p*+(K)HXf7n6-!N0NufDXk|>VIWOsGmpNF} z1)6w><DtcoFVv$8IxMCx!m!aBh1Inusf+n{wy_jP(Iv@Bj@N2rwVlZ_nq@6IlFH!s z1!da_%nA(9P~xSUH-k)52MT9WUB+Y_a%QDG;n<;6d}wYNFoxQlR9()5y~g&9fOG}_ z?r&UYs4Mx+eS$@0+D!kSt^m|kEHZ<eAAvg*ctHv8W9XW)$4=YlYCSriov@jL%FKCC zBLz)NOyD~nADx~~LAKac0C7-I*D%$}b`5ugsR@!DI(&vWO_!%tT4ClM$5s~q7{U%) zUBkxU9%DF34Z;;VbwTx^EH^GDlmH0=tzk1AK!K`auw?A7qsj3Cq4dp@@YuSx8pkYR zb{_gHi2GzrCS!wIE6vRB@ZwR$SlW5k>02kYgPCUOyAZV#zhmX%$yZNz<KpRIH0-y@ zC30D0EN;62OHSdMX7KPtF|vk>5nnT-7}3`T>75d;JaD~%iwz@_o+^Q=gS4awPos~0 Q(+-rvORNGsqsr)i0T7jdB>(^b literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/graphicslayout.doctree b/documentation/build/doctrees/graphicsItems/graphicslayout.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c22d0900709741831dfabf99843a688c9f40c7a2 GIT binary patch literal 8137 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/graphicsobject.doctree b/documentation/build/doctrees/graphicsItems/graphicsobject.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b892464b888bcf221db4ff86d90884e2b0cbdb1c GIT binary patch literal 17301 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 G%>Mu<k>#HN literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/graphicswidget.doctree b/documentation/build/doctrees/graphicsItems/graphicswidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c6218a6b168c675314eb61b6b70629f5e2d4e4c5 GIT binary patch literal 5637 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 HR4e@#%p$HK literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/griditem.doctree b/documentation/build/doctrees/graphicsItems/griditem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..41a92766b66309acce61b26650cf60e175c20210 GIT binary patch literal 4913 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/histogramlutitem.doctree b/documentation/build/doctrees/graphicsItems/histogramlutitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1560ff90c2a249792f5005a301555726e7c05c3a GIT binary patch literal 4895 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/imageitem.doctree b/documentation/build/doctrees/graphicsItems/imageitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c4bf51cffd060de2f8dd6d4369e407d283400319 GIT binary patch literal 16931 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 Jhu?ZO^*^iH>%;&6 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/index.doctree b/documentation/build/doctrees/graphicsItems/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ce964372e07ccdc124c94c7e289cb261eb6a2963 GIT binary patch literal 4809 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 bCi<;b(?9C;Pc~{Z25%<*)un%DjoJSIS$77U literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/infiniteline.doctree b/documentation/build/doctrees/graphicsItems/infiniteline.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a39e2dbcb07baa1db254b8a582feb803e0b02dde GIT binary patch literal 11533 zcmeHNd6*nkwNFB3GSkUS781ajgcK^!3CVO693d<cz+f00KvLL%ZCl-4(^WIoReeuY zWoDwK1VxD(uDId~Zn*EFh`YF=;)*M-xT0_IExzq{PE~bR_ssOi<GoM(!%V)es(a6V z&b{Z{<*F6cnpX`2+wsR-uV(r={%n|TkW<UI9a1YIbwpktbd81?%r{M6t&CJ(zIfEK zWy_lLDyWmuw8r+iQ?_dd=7qLvs{V=P#Wm=*lSYRdS4|eTE{QVco53Ep?wD!-xIN&? zWoA<@t3kkNJ?Wj}g>KDPxrybWzTOWjeqaaC(sRo-bIMn%Beg~!ZW`p9<%a3|MjfgR zmDI34Tn&k!e%bdzs+wx;q@JspRgVnDR_iA94MrGvQ=S*NUSO8}z@R{_pIEN1Vk6mL zx*<|W=B<AGt<u+Ym|>$efWJZf<*Zz&4^%DNsezl?R8mKEJHj`s%rd8rh98cJ)Pvv$ zGG-IGM)YNRKNNtdYSdbx_b2U}yUCE*q|mjiaBfa*DNe&|1<Pxg1t**_3$q^06dHzW z`(?+d6d0;tRMja3$F3AOED9|*7t+WOOB7;$1&IK=>hBB8hF{2PJ4Sw>j=koP+8QaX zqOS^!I$KB`r>{y>QxDd2Mi7u)2?N;wA(47$ULUB1O@{N;@g;p#)iHb@?<bV>fr?R` zA@<&hCH)9CbiSmvSsM+#FNWV<QYTrfj8I=GF}Gu41&Afltae%}ELeFg-)$cRh6BqK zVB}qqdRX2Xk?`$^>4#gR)-l#*YpZpf-X{p^WDDY+lDCd8StpdN6H8XUWNj~n7Pwjo zuSayV+HcNbSdA~F|43Nr)JUBM<`(nX6;_O}QuUjb?aqxEwOTwhZMSC5<<xG7dU~YJ z0JOd=mTZy*lWuh;3wBnX<$+YOc-vt~1Pa-vUs8|CBUo%1Z)X>eLpWqr)$(F-&33hx z<F|1v2WpQ2?IsiD7Lg8<M>VI;fpF(W>bx%DR&uX;G|RR(kD=nl!k&Lv@2`0c!*<nU ziYsJ9FA&_TO!L&E_7(d8AmXVD;8_rq_6h5Pj9}`*`~tf%XxX<BenCj^^<yF9WTY-) zU+?WQK)&huXHI&qxkEG9MrWS3Ylqe}-K4GI9WW|r-D@~LKq@lLNZ&Si;#_O9K6nPt z4~V<7)Wz`1a3^zh37g<?>>hQg)o<wi4X+kr>f3K^DGrJOl37*h2#U)h_4rEGkE+N> z15=^n$Y8pBVK7~x_r*rK5>rIql+DBN35T;LDlK6m!^M;)qdTXb2pg3n^(3}Ye`<FP zI!<C7uwrXPu{Me_ShLC^BO;9%Rkg-5v{VfWnvt4HL#4cU)W>`<1~4h)R2_0zk+NB? zT-IJ=8QIokanv*mHq%Qm2ZD7HQw<2`M#@Wtvw8>@OV)&BDpCXt>xzspgCdN0%V$Y~ zUXp~6gtKRMOsWHrWj0cCsVu68EJz8kQ(%%~`<QIzA=JT0Jvl{9y|+Px>Ht<xVd<h? z(mi#Fz-iIRsi%SARgpRbhQ$H)Pcocfkb_CzNmqpoJYPw&O|8L�n7RfQd1j=Z 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 Q=F=bX5z(LU8&<~t2Lxxf82|tP literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/labelitem.doctree b/documentation/build/doctrees/graphicsItems/labelitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c40e04fc5cb0e83b4c3a7d6563e6765622664f31 GIT binary patch literal 10554 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 QWyBxQi0O}bMwQ|J1z5RdssI20 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/linearregionitem.doctree b/documentation/build/doctrees/graphicsItems/linearregionitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..40deb4cc264d585f8cd14738e4348202cdc635ae GIT binary patch literal 8537 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/plotcurveitem.doctree b/documentation/build/doctrees/graphicsItems/plotcurveitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f85da9c37e7fe11eed12fcbf034ae47f4849ffbf GIT binary patch literal 9555 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/plotdataitem.doctree b/documentation/build/doctrees/graphicsItems/plotdataitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..11222a9dc252b6c71bb98406044c1eb8c13b9faf GIT binary patch literal 30382 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%0D2e>yX^_c>wz!q`@mTT_WI}nVqD1C+nPxhf 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 W-#qWY<GY=59x`~>0>91S-v0wVWdXSW literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/plotitem.doctree b/documentation/build/doctrees/graphicsItems/plotitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c80871dc71a470b7015aef6171feb3bcf574ef48 GIT binary patch literal 31151 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 N!wu#g@W0&G`G2C*dQ1QS literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/roi.doctree b/documentation/build/doctrees/graphicsItems/roi.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ad83ccd5272de86afceed00c3bdd418b5dd12ab4 GIT binary patch literal 21560 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 CvFO?W literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/scalebar.doctree b/documentation/build/doctrees/graphicsItems/scalebar.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3a56a7bf36f16f0068e1beb3da1d156a6c45f0f4 GIT binary patch literal 6287 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 CJUTl7 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/scatterplotitem.doctree b/documentation/build/doctrees/graphicsItems/scatterplotitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c36093d474e8758b98f9e5e47211ca0fd4ca50c0 GIT binary patch literal 17644 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 A!vFvP literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/uigraphicsitem.doctree b/documentation/build/doctrees/graphicsItems/uigraphicsitem.doctree new file mode 100644 index 0000000000000000000000000000000000000000..bc9776971897c6cfa4b67b336627ac587e6336c6 GIT binary patch literal 15483 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/viewbox.doctree b/documentation/build/doctrees/graphicsItems/viewbox.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7a2e13a6fd15e6e0ccccba6bb49bb9c4df1069fa GIT binary patch literal 27898 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 HkM;i#1E}~O literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicsItems/vtickgroup.doctree b/documentation/build/doctrees/graphicsItems/vtickgroup.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9ecc872bd0f4ace1a28c677782eca5d308a041f7 GIT binary patch literal 5951 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 aE=0?EFMdTo`=lG`^YD`D^YNQh#{UOY>;>Zh literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/graphicswindow.doctree b/documentation/build/doctrees/graphicswindow.doctree new file mode 100644 index 0000000000000000000000000000000000000000..24ef9fc734c2fc6fc99a89f8c9dd9bb3de18b0de GIT binary patch literal 3501 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/how_to_use.doctree b/documentation/build/doctrees/how_to_use.doctree new file mode 100644 index 0000000000000000000000000000000000000000..5b0c58ecb3e66892fdfee880e3f7fa6b42cb1f1c GIT binary patch literal 9236 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 LrcdE-+OB^Y2)B-A literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/images.doctree b/documentation/build/doctrees/images.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8f127e33bd00e7463177e169e98899dbc4bcd1fd GIT binary patch literal 11808 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/index.doctree b/documentation/build/doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e1879d0ce48a54022363186d745193dbdb778e42 GIT binary patch literal 6042 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/introduction.doctree b/documentation/build/doctrees/introduction.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8ed0f5f19ec7749312cbf2f979c082c9d111d8b8 GIT binary patch literal 13863 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 OK1bi<@5c<RTlinxV0u&l literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/parametertree.doctree b/documentation/build/doctrees/parametertree.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2adac6ee1f82d081633ca2fe5c6731568453cd60 GIT binary patch literal 3458 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 T|4B7dD;Tl=Rq(&_&dPrPIG^{q literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/plotting.doctree b/documentation/build/doctrees/plotting.doctree new file mode 100644 index 0000000000000000000000000000000000000000..5a3604feb0645eba883be551c3808b755a7e5d8c GIT binary patch literal 26167 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 O<M86s@%XKeWd9%E%Bq_H literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/region_of_interest.doctree b/documentation/build/doctrees/region_of_interest.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f7ba7dec6ad639d83ce6cb1a161c46dacadef1a8 GIT binary patch literal 4508 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/style.doctree b/documentation/build/doctrees/style.doctree new file mode 100644 index 0000000000000000000000000000000000000000..bf141c9eb2864d1a239d2652603cf3c30b407ae6 GIT binary patch literal 5421 zcmd5=cbpqX6+WMRMLOU49NS62G2V1txH~x^kPu2j0)}vjh{0n_KrE}>k!HDVZ)Wy% z3M|AxLJR~5J@n9f@4ffl3BC8;d)}MX>U2Vo{E__NAN_V$GjG1{du8U$rK7GFyQ%gA zwHU=7S7mw)d8Es*VeSx&X0WlsN|6(C-ATBDO&N?G+%YmT68I6Hx0-$s%v(<6S#BJ} z62@zaZB=PQX<wt&II=z7R4|djB%4Vbskj|-rJNR;D%GLPX53UtT+UW;DqRj!HCFbx z8%u{~!{!=0!bx@9jAI?en%hb{Qp5Dp1~x&Ud{4nl23y29+eU~M2+#uJS;+CsDmLi~ zKk#r3*jk4p2I(s9(n4j}hHSTIumjmjr`<(!BpYGl2pa*yQDT&hcfTJ!Xh!lxrjhTW zgk`wdL52cVMI7>KkS_CTJC@7U(20Cy2Tr3(qpFSzr&j~NQ6=%KNe6UGI*F(j`Wyb$ ze7&mDoq#V&rQzn69fF-19MfPE+G&vlu!~J}S->q=+0k11jZ~w+yECvVY|={;8mHjc zI-76<M=5+huFfVKj=L<$J;&GCMpE*GI-DqG9X6JaKdBBUi*YAqn@C!yERCYEZg{|) z7!@e!DV0Gf(DMO`a7)y4K7)mdIMT$+1twnh_TuF{6EBP6D6w4}Eq01sY|PArszBU( zD&p9>IIb>^uZt7v;-q>iaCy;P^whyM_w!W@*wg;YLbpQQPS4=h2uIMO^J&9L8-sSx z)sAsP8E%94&&c3R!rw-BUg$&}tC>b7T$BlU%n1>D%JL(t=ReDFU7l#mku8%(%2Ek; z{bt9HT9y{vA}KB7PpgvfXfDs8;!8uOO5nFx<_Wb3x9wA?)WZj7kxI84rqZTDrQ3_s z{-?<AfRf)agR@C;VsVC%T#&HmWN;@E_G}b(FLFg;6z{3KCf>Y}tQiME+$I56oFL`O z3Z0JC;8v=*cNUzxCXg_yoHtCB@j{h5qbfJXvUfptdo#Ezu_FlQ3=OD88U$S1fv+$= z?}p6J&*1Jjg^jGCW>bBmV2K=iL4}RyAK}7-8&ITEC<`zb@}$VqVX~l9`?nmV^=x1B zunhOWMK8)=AIW_lT2iQEz!zMBq3&ufG4doyOBD>A5NUcb#nY-Z=B?JeCFd=7-U{cf z&b+leZ?)&(o(TT@Zq}B1Yr(w;`u-v6*9xS}c>h2K_a@oM%tg7G+;azq%w3wKGIJmF z+Kq+&V)Wd7Gq_&?;ehcRRs%s=)ibz1X|)@*svva~pfN}JSiInNH)rWI2{_nmItWAl z0YeD%$UBC7BLkOKrn&Wg^4^fSOC=)Dk^BvT(?nve3`AkRv5uIZ!2^ldalIh0T9S8^ z>jXScEUU6-agViNow~SZ4weVG1w-)lxP^vWltE03(_FuwTQX$sQkl2`a=U?uB+^qE z=wgLv9lbPz6{2@B`OS|!zKSvHtomUZcHLz)(TjAL2yt&k)<MU@6rqELDa|rYTOyBq zWmObkt4C_lkF?pZ=Ab<&%jytnJy|+NmP;~t5P_z-X}>HF9x``nie$-9mYbN#Lr~6# zW^ieN?2>hI9?IZhB<DEFxi8<VA3jL^5kuhjs9$ENU!K7uX#tvhsGs^3L*_1RChCWg zdf%##LYR-v;4y`j%h$oYGK0qwOp9P%#3QR}?K_wDP^&Gt<d%~pGESuLXkH$K|F|I# zdhj1_;9r%&6UaPe-}RV>C!+l;`zbzYi2bE$qWI))LSLWBry%#IX7DuPPUf#FxN|I4 zw2xk$!P5!f6^O4EYo`%ljSgcZ?#3Zjs8tJ(82MqvoGftfbe)LyPVOil6QGg$fyRwA zPXTU)%P3-H4rL6NjgI)dcId(UY5Q7O`}E1mGT`at-xa)jvRs5`AkyxcE-&lP945q; zVo!J$it&Gi|JkVeb24}?sZLz3HmV!FugTzfr1t^T+ayJ<rxU1{MD15ITs8wLc$h1y z2R#e5()*5b=1Q6`mJtx1Kd9adhN-u;Q16AP-c5++MX1G#Gk6JULCCH#T44A&bcSJ5 zS~`)!x*Bo`FRiiZUY}<AikD^ZayE&Nc|Y-r3|`4*RVQr3fnAJL@Txkznr&Uz<(b*9 zsl#j8_JQHH`RjFccs(n*zBXNwf;ZIRjid<MT(py>V|f#Hk3EEIjpv)|@D_rV#1iX2 zytT$AeXrYDz70Fe>3%E<-d=}yu<2s49Kbs>co&<+ZN`__K-$=pwzyXC?mE1OO)9Qw zPf_sRzJpm)#*yZcS3Y3Quw{5(4DZk20~K~eA%#s183JEc@Ii#J4Nb?=!wwpg*yEAw zW8e0n8rv+JE)K<3I+X!@nC%o=CudcwI*zf6_qlE^#<EovIt;4t5mrJ4Tu!FpqiA&| z;R$iT>KM6b@MASLLr1{G#*#sezk-jWBXvw`6mUMl#_%86flspOz=>KZHt;y!<M1gq zik<bRS;@l^i_LkMz-I`e5!Vj*3J>73*f?%R8h9xX8Hd5=u*(-tfCpnU;PXpkY`P1g z+n;`+CaN`d43{#NHX@BZRMgI21y-E^9rr~xOAr-K5x8K0G~r8Z%;Sx;1z)bQSspex zITo8|B{<jcmBVaToa!Xi|H<Z8vByU>Tn*PEB=l{RhW08&ID8G&+v25RV)tT7!PikM z?1}xT8Fz<$gG6pSG9tiei#s8Fv&MFi?sz+4yk_Ik9Pw%n-(r*a$HIwwPWRy3Y%5>& z@hSwvI>oC3qs@0vn{hXentltu+w~Pj+={P!jTbK8L(6UuJIgi?9Y3%g53f2*hyVR6 z*e+xe_A+hF>$XZrs~;R@8+o(>Kdg0mC5opW@(2U?5y`ij#295*zPOCA#=(y<pGXtD z;4!^=m+?;yv#I=|0vnxZ8Gec|DMtIE>}R;dfbj5hHkRt<!kO?3R_;!xfc_=U_M?6m z!LL}!j9gGC!mrsD+w&Ffbrga|m*3P_2{RZQn5Mr9zs15-!oARL`<@o?yCuAOS|3O7 zdp5@Q@5QD6z$S{Dr#~`w(_Gp7iIrMBqJt*->(9u}#L$|mf5CjA;OM3FU)k<%O1InX z#iWC`43SA`kFQj5q^iQ-*fHxh8c2=5i)+~ol`(rwmw&KP--CbR`Y~Q&;3ZAheg8Vl zcIL^)18yn+DyN0r-O%L-HlP^gU9n9JE4cwb*4dFJp#4a3X)gPCL~ilqDE^CSFoid1 zm}vR8jrq4}!@ekyauXR(8*Im5oi`VKa*QU8nA<5ij?bB#z+W*dwkr(9{(DBV#mGrB zrbJ_Q;fAm~lVyB1JE&=(iyJ(=aKg(a?4wz!sJ=>0;n2F=jK6H0LdjEd8ecLwgTHBG F@j7T&e%1g0 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/checktable.doctree b/documentation/build/doctrees/widgets/checktable.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d64858a9d00470e951d0f210c8d040cca12c2ca9 GIT binary patch literal 4775 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/colorbutton.doctree b/documentation/build/doctrees/widgets/colorbutton.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e9352b2bf15d4fe1369cc6faec90133ecdd4df05 GIT binary patch literal 5607 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/datatreewidget.doctree b/documentation/build/doctrees/widgets/datatreewidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e9673c847cb734c4b6d099d63bd38b54f296de0a GIT binary patch literal 6995 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 V{2Kinm3E+Z;w4rtexvf(zX8Q%IO6~S literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/dockarea.doctree b/documentation/build/doctrees/widgets/dockarea.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9131d6a138603600dcf1490289f73706bcabc079 GIT binary patch literal 2862 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 DnwcKC literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/filedialog.doctree b/documentation/build/doctrees/widgets/filedialog.doctree new file mode 100644 index 0000000000000000000000000000000000000000..17e770c9cf535b209befd0b13fdf625fb7ad9cb5 GIT binary patch literal 4763 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/gradientwidget.doctree b/documentation/build/doctrees/widgets/gradientwidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6d09b2c6e0f444eb359d0edfc92cdce636abb0e0 GIT binary patch literal 5742 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 MCD9k)H?EA|51$LLIRF3v literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/graphicslayoutwidget.doctree b/documentation/build/doctrees/widgets/graphicslayoutwidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6fc16da9f247b1fc085089824fa13e51cfafbfb7 GIT binary patch literal 5215 zcmcgwcYGX26_#a7x;quya!F!au@bkH6YEk8gpe2lq#<)OPjUf{W$$)Qv(et}y_sE0 z0yc?(goqM41cK>=8X(lrd+)vX-b?8Ho7r3GbdfIq@sob{+wx}K_r6#5y}72h=7zO6 z@_aQJgf3SFecRwcRG>8z$EY`<wHC_<PJ>7DO|EENLVZ?wUr$d@bDpAU=`_XU#roH) zsl(2E7)O_T?lg}kZQBdH$hN6}YE5}F<c%z(d?Ph$q-da2KKV}fqTJM)m~B*XRYhI| zuwh`kysjvpP=Sp!9jUn8;7U2uKsZ>TAvRKrrGyo>3S(L0G+bf@m)An+=+!h*VxvwR zh4nCuf-vH?iX0iy`l&T+K-=oMiZ&#)(Gvam8(^DP1V1WrA{Vn<O?bWwZM3OEn>+bb zyrxwaXbb4vn$QWLQ#!LPTqm*~)(-;!l}-}9tiSbqTjw^CH)R}nHTbAN+si&kD~hne zi+()Ai?gAeDK?zIQ?~C^i(0Da)aYQ*_o_wh-QuFRikX8A6U9_tF?EM3u4Lz=R1xjC z_85&NbaIsqM9#G4h<37pmT@%B3QiPBuNp`2z$poxYO$OfH?^FiT@^M^^Btvdy}QD4 zRi`#1wg2{1*ji2X-U{s#qYmp!<*f=$hyf>N>wGWZw127>o3+A{P7}QXJfCiLGKA3Z z!Oa2CJeg3@5+|ClTPZl{3~`d!Dz=F+v6J<gg><F>?6WMft0Hz+#GZ=STM<?z7SJwQ zI?wJ@d4$g)PY$lG=PqFFt_j@@V1(8=8CRXST2n}uU~bZJ-8ARCz~yrVItS42p3u3F zW<4pwk_KpA(>*l6^DK=E=u(&#m__JFkE;ruZy|iVVA|(&s62rHS;_Qq%Xnhk3&s~i zVnWApME7)HZK-AK3Mf$>Y6ZF%Kwgm0y*ogz({JfM8smK}BvF`xzHou{yJ5re0=lT& zYcl-~287isb#0-G%Y6_q@aPhF92=VFmx@c;fYJS|JMgDPZRH^>qgWyarhxHqLig7( zuzwXItTgM+FNFa=eSH7^8And539x>F*v9hb>FR)(J4D(8Bg+-g1GJeB)UKchiJZgw z8=)J6_6LjY<-9SnRT}9b2!JCAv1+@|=rX-1SC4()B+})p2T+;yrG#98LeW?EbSge{ zp`DJ0twM(rps<-7FVMrmM<t<0Xg>Ngj>lo^VdQ`|aJ?~bX$jfd!bh%<4Ox7UBM!Fh zMO7GDOUTW_qOx+Nr+N_gC=3PSz*SFZTH`9T^ETOrZK!<>2@TBa2JA`zTfQ010Gyvt zBLi3O0xm@s09lw&6Dvfy4NTsEF<v8$M0P`>fJA@!R?0()fGJMsXoe~5f(fAtz9KGt zPodz<0^D3e^V!nOi=zhIGGTg@M)~M&C?B&*04?rQpacr9O6Y1REDvfwnGcpp9dI+( z3ZgVi7MIaATJ^DRsvir2I`*T-ftSZ8bZutbRfZR#AzYWx6EuVYKxifGl6qm(UEfXJ z0@Sswr6)qwlM;Gzrs_JQipN#_uUunlVGx<#=qZ46Lqbo@mZE5wL`s9TB}Pxv3_QIX zq-R({SN&%u^sH>F8^kuGq%h_F*<H&0b1ZSFd={w4IxTBr+*+*ZD>h~02qF=>6S}L3 z=()@5z0j5Y#)O{N0mRhw=Ud{!1+hvA7Z;mKkZw*liA%*z?G@?;me2+HctS74*5$KT z$%w^{an;IvV@k3x<@ZIV{JvQ9JK~OI_a*S`OA~t8^0NDKEqr--?9Kp)E0FWctL!T* zp-aNe3B6L=kZYqIA$Yt6%#?&zb@TVD5rXZD=`}$5+Js)0k$!U;?mES9N$B<2<4l7| z=QniI@J7qz%B=~#Nh9q@FMz$J1=i%to4alGmKD<Tt-$-Xgx;P3yLBl&Z%gPMT4h^` zb*g)3H+ApoNY8ge)q4_pZ>H+Dw)7M_0pFL<`?Hr>Y?ze$Ko>P8yVD%UucKQnHW*1K zQ1wtYxTFu3*zn?9VA|9VCG=sI!=<kKiawIiN7)87->8PZok=SCScN{$HZGeg%<4~6 z=#y-7hq!HCeX2sAX8D?zey0_Erb3_9{TCa`#uw87e-6{iBEqFX=kpc%f`-)$B|Z)G z#S+VTZflbH5+<49b}Wj%T%oV9;Y?Wy;HwFJjcq`y=1GhxHijUKZi>EMp>MF9;!%Wl zQqectdhNP2CD1Kg=J+wjMfz4q-%jW|78}i|uyubA;4_N83mBWQbtqj-Y)y_nH>jam zrSFy4kgV4riqS2WK7F5U7g5waw^)qA5W}d)qxxhhr;8$L_(l2w%Y%U$*IUyMvFk{a zH?<8Mqf-qV{;0%86dK{CT?-p}epU2iI5G;gjttIESfB2e=jo?x*mr{I7y~2ZT~0q^ zy_nyA&hoB<2_|wHP5Om~XvnpFew6$4OUxldFatv<8cjV${t8p2aQvtx@aw5QHrxWy z8h3tE5@(j!4lZRVZ6FO@Flwi_!U<d-j{7a!pdn%~Rssrrm`T55eJ-!Y)Aaii+rS%D zt{pq?b0s*B=no5Q43l;<j#k_Aj~E5OMlGV-0SUeh;)Xq^Bb@#O_SU;`qiHY3l%hX_ zE3~^_P!C&Tf6*k*Ix^6Ju@`q5^w$#GqIs`-zVVtJ@dofloc_jgcyVCQ83$eZJKM<T zF#3bWD8@i)$omJl>Bo1m?oHD_TfRcX9rctKg_8b-oojyR%-9^mf^TCqk{IaW*neNo z#$ZWfG1Hd3ZmXu|>OTu?Ef0>;e@iX9nu^CR>}dG1XH8F!W^k?<BG5d1y=CC|O0F@_ zM>R7zRxlG_YPamgn*}zQ&c(J4W4YD{kLY+^ia5E>JYC=62ib?mtS^r02hNfG`nu5C z3Q;Zx(w)4Z-E}gj#qws!0ZcJ6ukY8}uBV(Th)!9P1+zAf6mJ5BHzx<R7#2oO)yDiD z2|0u(Q#m%WJly247VKk3E&`+xeLc`R7Ln`qo&WzlNRS(}%KS7BIL=~1DL3LC@n8aS z$v>m!>5vY#tVGC7dgabmm6)BKoovpdn+Z&naQV?9gjG>))>1o`GrT<E<Q9mC+u4Y& zkM<IuTlLCb&y^?WixH$ldt97|hYM_bn%>;!rpAExNZ75j4S5nokVh?tnJiIq8y;5J zR+BPbptv;0S3J<Jb>()vMWPMjL<p&zKHHH#8#a_=MOcpM&EqQD()lGZM_2M>y{N|= zr^ubSPUJX#_4(CtF^aQ}oH!h_(Pc+y=4&QTF~X<f7ft7BXjtVgvk(Ui=A<DzlEPUD k&P8w{!tz<6S8_K*D{>EhS-%cSSIND&N#s8K#?{IH0T3`NssI20 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/graphicsview.doctree b/documentation/build/doctrees/widgets/graphicsview.doctree new file mode 100644 index 0000000000000000000000000000000000000000..179bef42902ae1ad6d85143a1dd876e643acfc86 GIT binary patch literal 12436 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 S!+~1yXaFA}4dNM;M*an&=J?+L literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/histogramlutwidget.doctree b/documentation/build/doctrees/widgets/histogramlutwidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..09c8c0f1b71283572d7f05ae028e9003b0a55db4 GIT binary patch literal 5455 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 WgZO0wIy7A+&%{e2&%$q9oBSU)BVYai literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/imageview.doctree b/documentation/build/doctrees/widgets/imageview.doctree new file mode 100644 index 0000000000000000000000000000000000000000..cec322081c9c56fadc81cdbbb9e753d5eef032cf GIT binary patch literal 12149 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/index.doctree b/documentation/build/doctrees/widgets/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4f113d6def817a500583698574297206c413200d GIT binary patch literal 4236 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 XYWjyq|74SvD|m4Jt4sgR8w>vhpe!$k literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/joystickbutton.doctree b/documentation/build/doctrees/widgets/joystickbutton.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4e22f97e174bae06d02076c8f3627aefcc26bae8 GIT binary patch literal 4859 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/multiplotwidget.doctree b/documentation/build/doctrees/widgets/multiplotwidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c548d00196cfbe220ccaba342825a83200f9ae2a GIT binary patch literal 5295 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/parametertree.doctree b/documentation/build/doctrees/widgets/parametertree.doctree new file mode 100644 index 0000000000000000000000000000000000000000..56b8107738508625a28d18ead11cbd1317e7ffc7 GIT binary patch literal 2912 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 K*ZiL<-1sj_1vL=> literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/plotwidget.doctree b/documentation/build/doctrees/widgets/plotwidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..29b5c88905e3db6f5b0e86ac7636e56aa0bc8adf GIT binary patch literal 5476 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 VFB{XL=qPzAUSfF~exusle*yIBbF=^e literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/progressdialog.doctree b/documentation/build/doctrees/widgets/progressdialog.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0c6a7f2c6f99217919fef5e111cd73a7fa39f810 GIT binary patch literal 10374 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 RO6(RM!b3#A!8I&o{u^RKDUSdE literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/rawimagewidget.doctree b/documentation/build/doctrees/widgets/rawimagewidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8bcfeb1b13bc4713c391f267bbcf7a3cc8793c4a GIT binary patch literal 7770 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/spinbox.doctree b/documentation/build/doctrees/widgets/spinbox.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f3b88a281239619f3f08a306c07cf7a54ed2346d GIT binary patch literal 12210 zcmeHNd6XPg)z3CtXJ)dHL?APv5rLjKnNC<VEJ2A7#lWzoVrv-M>h7AU>Qs02yH%Z; zQAz<t39h)~hWjpxxQmLS;)*NoyP}A@xGU=K-dELKJ=5J^4u9~S<MGU#s;>9${_eeR zyO%F3mb_v;a9n?)>Xj@%#h(?c8l=?Hu`ATFP%Y0G168wP1@kq_S1Ur*lgS;qWXY1+ zyb5N>tl1M6*PQB3Z*C%=cdAa1&#T_uOFMx0R<PG}>z3*RNFS*9z%6)ls(&)KrX5i= z0Kn09JrkgH0eLlxf_aq!)W)_@PBpNoMpmF!0_M23FjmP4oa)Rji0|80>Ffj0Uk&bF zS~u4C^@1Nb0W9QI^CfHAS3{v1Hil~^`BuJS`Mx;=v#gp@tBv7eod~FT->Xy6QfnrS zRLLrOWHPo|J87&n>w!1zc|p|+th^tX6sW^?FE#quz)s0m>q50YWB1~(4}bkeUu>TZ z_5l7;_*;p;L3^-n^cQW%ErFqWz?3?C;W+wMk-4VS5g5iJL$wjZNak!L+K92l=!Fif z=%~HS=#APpEhIx$jp|jW2;WGlqjGuJFKc@hE9=(xTiIEU_Gc?5%$9e}LYASjW>KA# zb)7<%y(-)Al5Ff)VvMZjmz{M=GgjbdBQ+<;4^;Z9E7Z}UdSJom3(Og|j5-E}(E6wc z87VUe$SKqV*!|d09hWisOZ6JV`Re#7qp#?izK`FTDWkt&7WWg6+Srt_oDIBrN<G-# zU>ZFdetb%8vHQ)svBGt#mYUeT48)=cP+5DK4byJTEHnxn!i71u!LlcW>cosaBEB5O zfjY?^wU4wn*+<*Q7(D{3PPQTGLo)X9Q+8&`9-Fc^Pub&BbsG%rh|NP6a@k|eAquy* zkUj;rIW<(Lfu+rCPN)~mdZFmoY^ORmVU|idh@EQ5noFtEA>fWsodIZLi56;-Ws+|7 zFqY}z85RWsY01W6KllVWmOrJ=%)l=Y7p|pt=0-5`k%H-rm^kfJ(~aRBWB&})BTVQt z8L8ApFl3lwN}UC{&JNYC1#+$6UiC;8>rolRgs0^^=b+JB@+ziNRgcaslK@^MvsWl{ zep0)0Jpd5#)VUZ+5R~@w>~j->smaVDlQC%Cci?Y9oiMoPL&gh2bs-P#SX1|m4w@a4 zUey{;AK+ZJW9!7$@$}}+`)5s>@yB52v+YgB!08<8@TD`<MX>pBD}8k_Yq^JwuMB&@ zG<qvusSZQ$wU5dTh>lTqRgb}t=R)<^LUNSU;~1%by6(CXAp2Up<PxJtn_voI-ZSlR zR9$*7X@GnO1DI80CW+9LdOR#(hN{38=#8zKhI$vkw^^rEqEiz^6<MiL8!wMNLRt3q z1T8fU-DW~%$BqDBZs*RL51bn@o>C6vDu-%6%aux+Y9b-qU=l~UELf$RU{wg#N=$hW zt`;g43s>$UoR*9rnIEbE81^LzVFpAP8K^o-a-f?evyg;m#yFeIL5%rOJs}nYGg3zz z!8u_fOn9#2BX>Lz@>~|GFh-3BZmr1E0<50If<3vLV3&9BnFgJdx&jQZ4AoP>Ft?J2 zMrIfZ7MSC*$ZvEkG|<#jne|oOSU(LmSwK%c9d@}oR0m__t`xi2EW$ORdIpQYfz$}m zrU14ud}cR>&jQ01Z1rrgdrqjH8?(En$?n=vJ&)P-gI$~k7IJ%jH*PNgw<NN9A(*`= zR4<O1T`SD2dIA6KGgr~WI)_(nFM<Boh3cg-Dsqtos}6_=OudXv{_<`zy<*X<#{(t{ zzLa_;m|P#KSFzT|EM2nX!gyxT^Lg5<rhRy!hZ%PweWvSrvwqsQTs?)Sz3FrnYfN&A zrmIuvczV_e>@+4kGSkzRq+tKl)Ry$c=cKWAn|9{Xjl8raz038?KqLAZktc7H(q^@k z#)8YKfG+sgu(q5@XSQuQWjwuY>y}fHrMGS6H-1<_abgS%&ad$*DLw5`x{ift9TR6M zB8}d&EZ3YzgUk!ApU#+CmrC6YoSJK;vHo>R>7s2`u?p0(cvW{E2C$;m2zN}q8ZL32 zy(wqIOYY+mDqbx`)}E-%pNa{_$8-%nVb#WL952b$YmlY~8Uv?Z%ftS<Hp9MqX}k=1 zJ$&(hLwaaK`UZsQ8$<Obgn`Y&d%eyHyzF{&sNNEXAVwX_E?d@HZ|$<)dRxZcnL8Hl z5pSe2W9iJIF-;@E+g@pmcL{-dd+UPB=B3pQp?b#x-m<8AXU0C~pxt2&W$%`WSrb<` z+UMCfCKn9v%GkUjx+zre=Ab_TQpR4`kT#L+J1&iG(CNsQ_0M}`{qtVC*R&U}d)^1* zzCTnSXkGVwkb(E*R=44APhrw+UFLi!WAmJRbErPdQge=trNwaF6v-lU@<-Z4MPu3W z(I`R3$m$jd_pwlYJT~*qdgOV+y){&yh?`>$GP!=T8;4J2WahdpRG(%^7ff~#_SQ&P znY%vIjnro$>;ioCImr9@P<<g5?6&6Yb$h73$Shl?yM+wD)Q#bn!LS8eeFf~k8mh0w z>~3$eyCYOzXLiZ?ZXvgCbmR8TXe{EHR(%W1z8$LX#LVsxW_q%;c{==VsJ<6>#{MT0 z;P<=mk-669Q2s%vei);30uSLu_g4a+XnQpAqd1%VxJx$q31^dSkhL9&?wJM03xWD+ zo6K@&sD73(h0TIUYWex0r<S{rTJB1wmS5=9a(AeH3EH_$iO_B@I^5;XWE%OEq>*3S zgYDADZ{Quj4b|^j)5z}`cwcf;+t$);Qpz86O1URge`IObbuchwygQj*{?x_Q)Suzf zod~JFK!Lx8>Tj{#@6oZuspjvY`bXRxbCXo_&o11wV{=^G8>)ZB=&?a1F8<vGot9v| zXJY|KOYtM5Wq7h7_>AssoTH^}(@#%FCuyf))fcVQCTKY$j@tLaEbKSD7NHe_z4%ll zUXD-?BW*tf>b(HG>I&+^E2MrQJ5tsx%Pgr_4I3xq1&(SO5WtiMl+zK8ZGsl}TWmLu zCTS(ah+>)sMWoFg4rORax_8P+W`jWYaK?#YK*PXW9MQB2%^|H8qWX{sW++GUaA?yS z={v-Q1FZ$LeO{Sgi5REDga}p;v<|;QS})%txx*5{p3ue~Ewf;WAg2w2v3ZIfz#S{w zggPCL9=N~}c!hK%o-Hv?8zaPha?%{HUK(5%$2yGwL&BX#h2|#t)>CB@Z?w!hC4!xf z61YWP9WN&+&G358Y>=U&(Fo}uh*wCw0*^wo%)A>hPY;s*V-x)e?i~Ddob+|%-iUuX zUPxsUq!LEwL_lNRNzrEXOD=e@kQ&#da+hLMFS2lsR@GZ6-kSv2+)bpT)?KFSTWLqP zrX3$giP)z*rEzSi9Q0UoMN4nYT41oHtYAto@!rCmN6V~2(_bZ}Oo$MR)Nkx?D9bq8 z?++ojf)j>x8(txuAaaeABgxrf3v<$m0(g=JY~-Zi{>({<OBFg9g2Y+rAtF@Atn^Un z-kHll)&z;}c?q`}0c{6no4j-innOBO$Tss*B$4E$)1>cE^U~>nMqc7qGB52ABAl1b zz^{-VCf}`#kfc5}TsX<z2FJW*mNA*49xlj8QD<^Ts$GiOi5_^wBk&68EIeCN)Y%c@ zzLvz85TH$x+65e)q#h|$A0^*<3z5VeEwhTrG<A+ZE{c>!ntC+D>%ByR5bZ`YbT}8U zkj@htGD2nM-%M1K(tm!UKfzxT)dkYmng4~-j{`lrNN8QGY2{YH$#@_z1k5B-i8n^H zM?gB0G#Gw+g97cvH*|RnULoZKS?j_awJyy6vC{uI-Je?y{zzAC*zXgF&g?HiKiEeE zXbPX;ekood<po!BXMV!Hxr?F43zV6FO8B2->w@%k=3Yd<Wb2ZUvNWmOiLEiq>9}0? z1342bdX*Zk0}77o1oQfQD?aO^X(l&XX1S1pqtKKTJR_7B3DQi#Hsfr+&zv2g!)|4~ zLfS9PM#_;mtlPr8>Iz^*12*!ig5LMrS`PuVmoJN`3Mt~G>WM@hlWI-6^^s7F-#w$^ z(l{Umv^E))&>WI4G@BVU5=Sy>Abp3LQR{$4M&(yBqaF|<oKa`-E2KI39?6{!YsYJ@ zhBEqaUf-qFYb8wVJN8f=7i%Rxc)gf!<J=vxSm1oi8YJ`WypTq|eFAs%wad3pL=QaW zGQ2_x@odevPl^y*50ql@+9ca213@R-%Z20>@~uxZ8fc?sRy~<%uN1IF0nx~`Phog{ z2M|-Cr=ks_UximlPZI(<IWXsDnti(TU!CYrFqbrYQ2IJEzef5w%{@bCJyX+?>#*hk z!T!uDnQWgWfSn0Ho8k4fT9Xbv2d&WSxp;+it&q@qG52Q5eV+6`U-!#-R>DEr2=fAg z>@3U+(GOwxU@Sq1UW8^y^J2V0dWq1`(lGy~G}lT0OLf2O9~OCN8<}1v@SSCPIr<?J z7rrbaN3TF9Bzq-ZAzd$&v}7!cNS2r!UL{bkPCzAsMJ9*WNMC1JUMu~)uYaA;dcCHV zyM^NxoA|)C5{r2olZ21!m5Frf9IWCSeQ5UT0k8MvF2x^D<3wQAaou#mN>{uCxJwB< zf#(Yw3$u*CnKU2MVXAsZ5Lh!7rTO}PJi#F*R`e{?*a$i7p;CHd;R~Qy4C?XC5+6WJ z&>Nr(#)qwhsVd5OGF81%)Ldki=2Z12#@YVgVTLzD7r4}0@CxazqWMTUvJH>A_ILJy zw+YDGHHe&wV3;L{d2DpT#;8}t-ci|8v%l_RU@`$a3I6$R4MS}OTw|4L`ieP?eO$v& z_(Be6M+f42v(gyd0QT)i?HwZ6;!*33Zn5Z{jJo~*OYj@P9d>>fULoBivW}D^ZP>Dj z=+!Zdr^z)s1`cY>st=3ddX(M`&`D#cQ7^`YO!OWh`d&PZel&1Vflu#~#`kmMYJa{` z@Z5Zi<<keyJVhVm&NVIdXF~5o(t9)au3Lbb7l027z(*Khpy=r05T8COt+(LGCN~D- z(l@?|rH`Q*#cmB<8g+8?aY4HkPnN#s5nO;iA>jQ^Nms<tC-IG<xS^!5K7C41Z)4P< zSPm`Wr_mPDXSioIN`@T5J#!wlbzE6SpB1#vac6(zgFby8pNaeJX_DLXQfe<R(fkyB z0sS7`j%P?;WLRTuY|lKGltIh=K79#2kn=F`^QeUCz?y~Y<7yGbQ}ku@O&WtVT?7Iu z1?%L}SI}l`w1c2_YBn2q9%?QfE0~_}XeMh16*o&?WrzXTt!PQ*+SfFP;hI%rRV~!( z6+!$CAwJ9(zqLG;=v=Sq)7PbSDDaqNEaNwrVh@*e&C@se_mGRb>N<)RfmpKWTimwH zs?xXl#{e(EP?lJ!(Rc983dkVkUF(45(s$8@3b{e3ilRtVO5y69z9)bw+jN5n?E4JX zV+=*|>2f^!0mIoRPZ}c_a*y&*#Va8r<n<@6&`U0U{t#fsYF5Apq7D8otU*8G=AM#O zsL#-knUb;EsuV1CnR(Yj)ut8DPtbnQ7{vrss|TIv|C9;!vS`JC?&OalE-gesLVk|p zj(&zVjOwaVy;93JT-c|dqaD$R3wNhF?M1+MY2aa$epOi)(6!78{X$^Z^RP`ju8e*@ zuqsdm{(d()jehiPId<zwCHf_QuCeA&ISb1Nb(B(yv46#+dyHO`Y)m^d^lKq0V-D{= z;P_795&cG5*B4O{u|IF2M$XNn2oTv81(5VxbY5+YLe)wm9W>o2@7LI{zXQ-gW4Tp5 zK))AaTJf51)l1N_;?f_ad9^iH^WeyGV2Ne9PxnasTDBf)lBCWtvdSNY=1N`Tnde}k zKMC+K$3w!A|17Pm7K|AE1<giJJ(%9Io&L(dQxPeSqQ7ZMPBl5m^mhgu5R@&b)}Vjz z_o{r!@y!ATATr`V1$O|MMpu>4y$r@7XBP6Pt_W=U7g~1r8^f(mO8;iS9%Iigh_nPn zh(=#j<x5NXD}Nt1l5wr+|5X%A%b3f+jD_rK^1v_0_av$c*-4v2vO-!1+3y-zr-w0* zin7k^>@2EAP(M(WtW&ZMWC84Fsh6QfT8+9OGW!5hmWUbV%$#ppv7eEbIVBq4AHz^8 z=?y6~9yB)UY=9yl$yTs-VCSP!U0Mkc#8xy+5;HyxqH)UDAOYl5ebjj)-5{)aKua`) zUWDmN)HFBRhIQMJSf!EdS26K=!C1d=dXxHVTFppHq&k?^;CD!C@#G@xo9sTlliPrD z-T{<7rt}foN`2rzXy08o*0vOg>)H4)p|TE7Z1l5yEVW)JV!a}J{kYm11;QCA12zWY d*~O;~0L3K-o<=Vs1r=0uI6gu;0?&G3;y;iQU!wp3 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/tablewidget.doctree b/documentation/build/doctrees/widgets/tablewidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..21539df76c1e7391e6eb7963f1f8facf975c44aa GIT binary patch literal 11304 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/treewidget.doctree b/documentation/build/doctrees/widgets/treewidget.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3d17105765b1a75a0e515527823f7e27573d5da1 GIT binary patch literal 7154 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 literal 0 HcmV?d00001 diff --git a/documentation/build/doctrees/widgets/verticallabel.doctree b/documentation/build/doctrees/widgets/verticallabel.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c4d66a574b2c32db97c8db7d2ed317a335ba0e85 GIT binary patch literal 5483 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	iqFJJK8 ztR`?nlv4*1G0}0yX!bl^G{UFh7boRCIA!b8%|zVJm}{Es>JB$dxJ|-6lE`P<L8Q-s ZXjz|$Uopr5=|*}#UQ&G)ev``de*uH4ZvFrO literal 0 HcmV?d00001 diff --git a/documentation/build/html/.buildinfo b/documentation/build/html/.buildinfo new file mode 100644 index 00000000..1fd6f9ff --- /dev/null +++ b/documentation/build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: ba0221080f2c4e6cd48402ae7f991103 +tags: fbb0d17656682115ca4d033fb2f83ba1 diff --git a/documentation/build/html/_images/plottingClasses.png b/documentation/build/html/_images/plottingClasses.png new file mode 100644 index 0000000000000000000000000000000000000000..7c8325a5bbe0809aa3b40ac3a8f76972b4d802a9 GIT binary patch literal 68667 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 literal 0 HcmV?d00001 diff --git a/documentation/build/html/_modules/index.html b/documentation/build/html/_modules/index.html new file mode 100644 index 00000000..4438a15a --- /dev/null +++ b/documentation/build/html/_modules/index.html @@ -0,0 +1,89 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Overview: module code — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <h1>All modules for which code is available</h1> +<ul><li><a href="pyqtgraph.html">pyqtgraph</a></li> +</ul> + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/_modules/pyqtgraph.html b/documentation/build/html/_modules/pyqtgraph.html new file mode 100644 index 00000000..7779bd3a --- /dev/null +++ b/documentation/build/html/_modules/pyqtgraph.html @@ -0,0 +1,192 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>pyqtgraph — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Module code" href="index.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="index.html" accesskey="U">Module code</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <h1>Source code for pyqtgraph</h1><div class="highlight"><pre> +<span class="c"># -*- coding: utf-8 -*-</span> +<span class="c">### import all the goodies and add some helper functions for easy CLI use</span> + +<span class="c">## 'Qt' is a local module; it is intended mainly to cover up the differences</span> +<span class="c">## between PyQt4 and PySide.</span> +<span class="kn">from</span> <span class="nn">Qt</span> <span class="kn">import</span> <span class="n">QtGui</span> + + +<span class="n">CONFIG_OPTIONS</span> <span class="o">=</span> <span class="p">{</span> + <span class="s">'leftButtonPan'</span><span class="p">:</span> <span class="bp">True</span> +<span class="p">}</span> + +<span class="k">def</span> <span class="nf">setConfigOption</span><span class="p">(</span><span class="n">opt</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> + <span class="n">CONFIG_OPTIONS</span><span class="p">[</span><span class="n">opt</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> + +<span class="k">def</span> <span class="nf">getConfigOption</span><span class="p">(</span><span class="n">opt</span><span class="p">):</span> + <span class="k">return</span> <span class="n">CONFIG_OPTIONS</span><span class="p">[</span><span class="n">opt</span><span class="p">]</span> + +<span class="c">## Import almost everything to make it available from a single namespace</span> +<span class="c">## don't import the more complex systems--canvas, parametertree, flowchart, dockarea</span> +<span class="c">## these must be imported separately.</span> + +<span class="kn">import</span> <span class="nn">os</span> +<span class="k">def</span> <span class="nf">importAll</span><span class="p">(</span><span class="n">path</span><span class="p">):</span> + <span class="n">d</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">__file__</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span> <span class="n">path</span><span class="p">)</span> + <span class="n">files</span> <span class="o">=</span> <span class="p">[]</span> + <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">d</span><span class="p">):</span> + <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="n">f</span><span class="p">)):</span> + <span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> + <span class="k">elif</span> <span class="n">f</span><span class="p">[</span><span class="o">-</span><span class="mi">3</span><span class="p">:]</span> <span class="o">==</span> <span class="s">'.py'</span> <span class="ow">and</span> <span class="n">f</span> <span class="o">!=</span> <span class="s">'__init__.py'</span><span class="p">:</span> + <span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">f</span><span class="p">[:</span><span class="o">-</span><span class="mi">3</span><span class="p">])</span> + + <span class="k">for</span> <span class="n">modName</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span> + <span class="n">mod</span> <span class="o">=</span> <span class="nb">__import__</span><span class="p">(</span><span class="n">path</span><span class="o">+</span><span class="s">"."</span><span class="o">+</span><span class="n">modName</span><span class="p">,</span> <span class="nb">globals</span><span class="p">(),</span> <span class="nb">locals</span><span class="p">(),</span> <span class="n">fromlist</span><span class="o">=</span><span class="p">[</span><span class="s">'*'</span><span class="p">])</span> + <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="s">'__all__'</span><span class="p">):</span> + <span class="n">names</span> <span class="o">=</span> <span class="n">mod</span><span class="o">.</span><span class="n">__all__</span> + <span class="k">else</span><span class="p">:</span> + <span class="n">names</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="n">mod</span><span class="p">)</span> <span class="k">if</span> <span class="n">n</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="s">'_'</span><span class="p">]</span> + <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">names</span><span class="p">:</span> + <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="n">k</span><span class="p">):</span> + <span class="nb">globals</span><span class="p">()[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span> + +<span class="n">importAll</span><span class="p">(</span><span class="s">'graphicsItems'</span><span class="p">)</span> +<span class="n">importAll</span><span class="p">(</span><span class="s">'widgets'</span><span class="p">)</span> + +<span class="kn">from</span> <span class="nn">imageview</span> <span class="kn">import</span> <span class="o">*</span> +<span class="kn">from</span> <span class="nn">WidgetGroup</span> <span class="kn">import</span> <span class="o">*</span> +<span class="kn">from</span> <span class="nn">Point</span> <span class="kn">import</span> <span class="n">Point</span> +<span class="kn">from</span> <span class="nn">Transform</span> <span class="kn">import</span> <span class="n">Transform</span> +<span class="kn">from</span> <span class="nn">functions</span> <span class="kn">import</span> <span class="o">*</span> +<span class="kn">from</span> <span class="nn">graphicsWindows</span> <span class="kn">import</span> <span class="o">*</span> +<span class="kn">from</span> <span class="nn">SignalProxy</span> <span class="kn">import</span> <span class="o">*</span> + + + + +<span class="c">## Convenience functions for command-line use</span> + + + +<span class="n">plots</span> <span class="o">=</span> <span class="p">[]</span> +<span class="n">images</span> <span class="o">=</span> <span class="p">[]</span> +<span class="n">QAPP</span> <span class="o">=</span> <span class="bp">None</span> + +<div class="viewcode-block" id="plot"><a class="viewcode-back" href="../functions.html#pyqtgraph.plot">[docs]</a><span class="k">def</span> <span class="nf">plot</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kargs</span><span class="p">):</span> + <span class="sd">"""</span> +<span class="sd"> | Create and return a PlotWindow (this is just a window with PlotWidget inside), plot data in it.</span> +<span class="sd"> | Accepts a *title* argument to set the title of the window.</span> +<span class="sd"> | All other arguments are used to plot data. (see :func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`)</span> +<span class="sd"> """</span> + <span class="n">mkQApp</span><span class="p">()</span> + <span class="k">if</span> <span class="s">'title'</span> <span class="ow">in</span> <span class="n">kargs</span><span class="p">:</span> + <span class="n">w</span> <span class="o">=</span> <span class="n">PlotWindow</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="n">kargs</span><span class="p">[</span><span class="s">'title'</span><span class="p">])</span> + <span class="k">del</span> <span class="n">kargs</span><span class="p">[</span><span class="s">'title'</span><span class="p">]</span> + <span class="k">else</span><span class="p">:</span> + <span class="n">w</span> <span class="o">=</span> <span class="n">PlotWindow</span><span class="p">()</span> + <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span><span class="o">+</span><span class="nb">len</span><span class="p">(</span><span class="n">kargs</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span><span class="p">:</span> + <span class="n">w</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kargs</span><span class="p">)</span> + <span class="n">plots</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">w</span><span class="p">)</span> + <span class="n">w</span><span class="o">.</span><span class="n">show</span><span class="p">()</span> + <span class="k">return</span> <span class="n">w</span> + </div> +<div class="viewcode-block" id="image"><a class="viewcode-back" href="../functions.html#pyqtgraph.image">[docs]</a><span class="k">def</span> <span class="nf">image</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kargs</span><span class="p">):</span> + <span class="sd">"""</span> +<span class="sd"> | Create and return an ImageWindow (this is just a window with ImageView widget inside), show image data inside.</span> +<span class="sd"> | Will show 2D or 3D image data.</span> +<span class="sd"> | Accepts a *title* argument to set the title of the window.</span> +<span class="sd"> | All other arguments are used to show data. (see :func:`ImageView.setImage() <pyqtgraph.ImageView.setImage>`)</span> +<span class="sd"> """</span> + <span class="n">mkQApp</span><span class="p">()</span> + <span class="n">w</span> <span class="o">=</span> <span class="n">ImageWindow</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kargs</span><span class="p">)</span> + <span class="n">images</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">w</span><span class="p">)</span> + <span class="n">w</span><span class="o">.</span><span class="n">show</span><span class="p">()</span> + <span class="k">return</span> <span class="n">w</span></div> +<span class="n">show</span> <span class="o">=</span> <span class="n">image</span> <span class="c">## for backward compatibility</span> + + +<span class="k">def</span> <span class="nf">mkQApp</span><span class="p">():</span> + <span class="k">if</span> <span class="n">QtGui</span><span class="o">.</span><span class="n">QApplication</span><span class="o">.</span><span class="n">instance</span><span class="p">()</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span> + <span class="k">global</span> <span class="n">QAPP</span> + <span class="n">QAPP</span> <span class="o">=</span> <span class="n">QtGui</span><span class="o">.</span><span class="n">QApplication</span><span class="p">([])</span> +</pre></div> + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="index.html" >Module code</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/_sources/apireference.txt b/documentation/build/html/_sources/apireference.txt new file mode 100644 index 00000000..ab4ec666 --- /dev/null +++ b/documentation/build/html/_sources/apireference.txt @@ -0,0 +1,11 @@ +API Reference +============= + +Contents: + +.. toctree:: + :maxdepth: 2 + + functions + graphicsItems/index + widgets/index diff --git a/documentation/build/html/_sources/functions.txt b/documentation/build/html/_sources/functions.txt new file mode 100644 index 00000000..3d56a4d9 --- /dev/null +++ b/documentation/build/html/_sources/functions.txt @@ -0,0 +1,53 @@ +Pyqtgraph's Helper Functions +============================ + +Simple Data Display Functions +----------------------------- + +.. autofunction:: pyqtgraph.plot + +.. autofunction:: pyqtgraph.image + + + +Color, Pen, and Brush Functions +------------------------------- + +Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and fill shapes. These classes are highly capable but somewhat awkward to use. Pyqtgraph offers the functions :func:`~pyqtgraph.mkColor`, :func:`~pyqtgraph.mkPen`, and :func:`~pyqtgraph.mkBrush` to simplify the process of creating these classes. In most cases, however, it will be unnecessary to call these functions directly--any function or method that accepts *pen* or *brush* arguments will make use of these functions for you. For example, the following three lines all have the same effect:: + + pg.plot(xdata, ydata, pen='r') + pg.plot(xdata, ydata, pen=pg.mkPen('r')) + pg.plot(xdata, ydata, pen=QPen(QColor(255, 0, 0))) + + +.. autofunction:: pyqtgraph.mkColor + +.. autofunction:: pyqtgraph.mkPen + +.. autofunction:: pyqtgraph.mkBrush + +.. autofunction:: pyqtgraph.hsvColor + +.. autofunction:: pyqtgraph.intColor + +.. autofunction:: pyqtgraph.colorTuple + +.. autofunction:: pyqtgraph.colorStr + + +Data Slicing +------------ + +.. autofunction:: pyqtgraph.affineSlice + + + +SI Unit Conversion Functions +---------------------------- + +.. autofunction:: pyqtgraph.siFormat + +.. autofunction:: pyqtgraph.siScale + +.. autofunction:: pyqtgraph.siEval + diff --git a/documentation/build/html/_sources/graphicsItems/arrowitem.txt b/documentation/build/html/_sources/graphicsItems/arrowitem.txt new file mode 100644 index 00000000..250957a5 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/arrowitem.txt @@ -0,0 +1,8 @@ +ArrowItem +========= + +.. autoclass:: pyqtgraph.ArrowItem + :members: + + .. automethod:: pyqtgraph.ArrowItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/axisitem.txt b/documentation/build/html/_sources/graphicsItems/axisitem.txt new file mode 100644 index 00000000..8f76d130 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/axisitem.txt @@ -0,0 +1,8 @@ +AxisItem +======== + +.. autoclass:: pyqtgraph.AxisItem + :members: + + .. automethod:: pyqtgraph.AxisItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/buttonitem.txt b/documentation/build/html/_sources/graphicsItems/buttonitem.txt new file mode 100644 index 00000000..44469db6 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/buttonitem.txt @@ -0,0 +1,8 @@ +ButtonItem +========== + +.. autoclass:: pyqtgraph.ButtonItem + :members: + + .. automethod:: pyqtgraph.ButtonItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/curvearrow.txt b/documentation/build/html/_sources/graphicsItems/curvearrow.txt new file mode 100644 index 00000000..4c7f11ab --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/curvearrow.txt @@ -0,0 +1,8 @@ +CurveArrow +========== + +.. autoclass:: pyqtgraph.CurveArrow + :members: + + .. automethod:: pyqtgraph.CurveArrow.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/curvepoint.txt b/documentation/build/html/_sources/graphicsItems/curvepoint.txt new file mode 100644 index 00000000..f19791f7 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/curvepoint.txt @@ -0,0 +1,8 @@ +CurvePoint +========== + +.. autoclass:: pyqtgraph.CurvePoint + :members: + + .. automethod:: pyqtgraph.CurvePoint.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/gradienteditoritem.txt b/documentation/build/html/_sources/graphicsItems/gradienteditoritem.txt new file mode 100644 index 00000000..02d40956 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/gradienteditoritem.txt @@ -0,0 +1,8 @@ +GradientEditorItem +================== + +.. autoclass:: pyqtgraph.GradientEditorItem + :members: + + .. automethod:: pyqtgraph.GradientEditorItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/gradientlegend.txt b/documentation/build/html/_sources/graphicsItems/gradientlegend.txt new file mode 100644 index 00000000..f47031c0 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/gradientlegend.txt @@ -0,0 +1,8 @@ +GradientLegend +============== + +.. autoclass:: pyqtgraph.GradientLegend + :members: + + .. automethod:: pyqtgraph.GradientLegend.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/graphicslayout.txt b/documentation/build/html/_sources/graphicsItems/graphicslayout.txt new file mode 100644 index 00000000..f45dfd87 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/graphicslayout.txt @@ -0,0 +1,8 @@ +GraphicsLayout +============== + +.. autoclass:: pyqtgraph.GraphicsLayout + :members: + + .. automethod:: pyqtgraph.GraphicsLayout.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/graphicsobject.txt b/documentation/build/html/_sources/graphicsItems/graphicsobject.txt new file mode 100644 index 00000000..736d941e --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/graphicsobject.txt @@ -0,0 +1,8 @@ +GraphicsObject +============== + +.. autoclass:: pyqtgraph.GraphicsObject + :members: + + .. automethod:: pyqtgraph.GraphicsObject.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/graphicswidget.txt b/documentation/build/html/_sources/graphicsItems/graphicswidget.txt new file mode 100644 index 00000000..7cf23bbe --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/graphicswidget.txt @@ -0,0 +1,8 @@ +GraphicsWidget +============== + +.. autoclass:: pyqtgraph.GraphicsWidget + :members: + + .. automethod:: pyqtgraph.GraphicsWidget.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/griditem.txt b/documentation/build/html/_sources/graphicsItems/griditem.txt new file mode 100644 index 00000000..aa932766 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/griditem.txt @@ -0,0 +1,8 @@ +GridItem +======== + +.. autoclass:: pyqtgraph.GridItem + :members: + + .. automethod:: pyqtgraph.GridItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/histogramlutitem.txt b/documentation/build/html/_sources/graphicsItems/histogramlutitem.txt new file mode 100644 index 00000000..db0e18cb --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/histogramlutitem.txt @@ -0,0 +1,8 @@ +HistogramLUTItem +================ + +.. autoclass:: pyqtgraph.HistogramLUTItem + :members: + + .. automethod:: pyqtgraph.HistogramLUTItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/imageitem.txt b/documentation/build/html/_sources/graphicsItems/imageitem.txt new file mode 100644 index 00000000..49a981dc --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/imageitem.txt @@ -0,0 +1,8 @@ +ImageItem +========= + +.. autoclass:: pyqtgraph.ImageItem + :members: + + .. automethod:: pyqtgraph.ImageItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/index.txt b/documentation/build/html/_sources/graphicsItems/index.txt new file mode 100644 index 00000000..46f5a938 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/index.txt @@ -0,0 +1,37 @@ +Pyqtgraph's Graphics Items +========================== + +Since pyqtgraph relies on Qt's GraphicsView framework, most of its graphics functionality is implemented as QGraphicsItem subclasses. This has two important consequences: 1) virtually anything you want to draw can be easily accomplished using the functionality provided by Qt. 2) Many of pyqtgraph's GraphicsItem classes can be used in any normal QGraphicsScene. + + +Contents: + +.. toctree:: + :maxdepth: 2 + + plotdataitem + plotcurveitem + scatterplotitem + plotitem + imageitem + viewbox + linearregionitem + infiniteline + roi + graphicslayout + axisitem + arrowitem + curvepoint + curvearrow + griditem + scalebar + labelitem + vtickgroup + gradienteditoritem + histogramlutitem + gradientlegend + buttonitem + graphicsobject + graphicswidget + uigraphicsitem + diff --git a/documentation/build/html/_sources/graphicsItems/infiniteline.txt b/documentation/build/html/_sources/graphicsItems/infiniteline.txt new file mode 100644 index 00000000..e95987bc --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/infiniteline.txt @@ -0,0 +1,8 @@ +InfiniteLine +============ + +.. autoclass:: pyqtgraph.InfiniteLine + :members: + + .. automethod:: pyqtgraph.InfiniteLine.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/labelitem.txt b/documentation/build/html/_sources/graphicsItems/labelitem.txt new file mode 100644 index 00000000..ca420d76 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/labelitem.txt @@ -0,0 +1,8 @@ +LabelItem +========= + +.. autoclass:: pyqtgraph.LabelItem + :members: + + .. automethod:: pyqtgraph.LabelItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/linearregionitem.txt b/documentation/build/html/_sources/graphicsItems/linearregionitem.txt new file mode 100644 index 00000000..9bcb534c --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/linearregionitem.txt @@ -0,0 +1,8 @@ +LinearRegionItem +================ + +.. autoclass:: pyqtgraph.LinearRegionItem + :members: + + .. automethod:: pyqtgraph.LinearRegionItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/plotcurveitem.txt b/documentation/build/html/_sources/graphicsItems/plotcurveitem.txt new file mode 100644 index 00000000..f0b2171d --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/plotcurveitem.txt @@ -0,0 +1,8 @@ +PlotCurveItem +============= + +.. autoclass:: pyqtgraph.PlotCurveItem + :members: + + .. automethod:: pyqtgraph.PlotCurveItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/plotdataitem.txt b/documentation/build/html/_sources/graphicsItems/plotdataitem.txt new file mode 100644 index 00000000..275084e9 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/plotdataitem.txt @@ -0,0 +1,8 @@ +PlotDataItem +============ + +.. autoclass:: pyqtgraph.PlotDataItem + :members: + + .. automethod:: pyqtgraph.PlotDataItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/plotitem.txt b/documentation/build/html/_sources/graphicsItems/plotitem.txt new file mode 100644 index 00000000..cbf5f9f4 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/plotitem.txt @@ -0,0 +1,7 @@ +PlotItem +======== + +.. autoclass:: pyqtgraph.PlotItem + :members: + + .. automethod:: pyqtgraph.PlotItem.__init__ diff --git a/documentation/build/html/_sources/graphicsItems/roi.txt b/documentation/build/html/_sources/graphicsItems/roi.txt new file mode 100644 index 00000000..22945ade --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/roi.txt @@ -0,0 +1,8 @@ +ROI +=== + +.. autoclass:: pyqtgraph.ROI + :members: + + .. automethod:: pyqtgraph.ROI.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/scalebar.txt b/documentation/build/html/_sources/graphicsItems/scalebar.txt new file mode 100644 index 00000000..2ab33967 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/scalebar.txt @@ -0,0 +1,8 @@ +ScaleBar +======== + +.. autoclass:: pyqtgraph.ScaleBar + :members: + + .. automethod:: pyqtgraph.ScaleBar.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/scatterplotitem.txt b/documentation/build/html/_sources/graphicsItems/scatterplotitem.txt new file mode 100644 index 00000000..be2c874b --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/scatterplotitem.txt @@ -0,0 +1,8 @@ +ScatterPlotItem +=============== + +.. autoclass:: pyqtgraph.ScatterPlotItem + :members: + + .. automethod:: pyqtgraph.ScatterPlotItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/uigraphicsitem.txt b/documentation/build/html/_sources/graphicsItems/uigraphicsitem.txt new file mode 100644 index 00000000..4f0b9933 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/uigraphicsitem.txt @@ -0,0 +1,8 @@ +UIGraphicsItem +============== + +.. autoclass:: pyqtgraph.UIGraphicsItem + :members: + + .. automethod:: pyqtgraph.UIGraphicsItem.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/viewbox.txt b/documentation/build/html/_sources/graphicsItems/viewbox.txt new file mode 100644 index 00000000..3593d295 --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/viewbox.txt @@ -0,0 +1,8 @@ +ViewBox +======= + +.. autoclass:: pyqtgraph.ViewBox + :members: + + .. automethod:: pyqtgraph.ViewBox.__init__ + diff --git a/documentation/build/html/_sources/graphicsItems/vtickgroup.txt b/documentation/build/html/_sources/graphicsItems/vtickgroup.txt new file mode 100644 index 00000000..342705de --- /dev/null +++ b/documentation/build/html/_sources/graphicsItems/vtickgroup.txt @@ -0,0 +1,8 @@ +VTickGroup +========== + +.. autoclass:: pyqtgraph.VTickGroup + :members: + + .. automethod:: pyqtgraph.VTickGroup.__init__ + diff --git a/documentation/build/html/_sources/graphicswindow.txt b/documentation/build/html/_sources/graphicswindow.txt new file mode 100644 index 00000000..3d5641c3 --- /dev/null +++ b/documentation/build/html/_sources/graphicswindow.txt @@ -0,0 +1,8 @@ +Basic display widgets +===================== + + - GraphicsWindow + - GraphicsView + - GraphicsLayoutItem + - ViewBox + diff --git a/documentation/build/html/_sources/how_to_use.txt b/documentation/build/html/_sources/how_to_use.txt new file mode 100644 index 00000000..74e901d0 --- /dev/null +++ b/documentation/build/html/_sources/how_to_use.txt @@ -0,0 +1,47 @@ +How to use pyqtgraph +==================== + +There are a few suggested ways to use pyqtgraph: + +* From the interactive shell (python -i, ipython, etc) +* Displaying pop-up windows from an application +* Embedding widgets in a PyQt application + + + +Command-line use +---------------- + +Pyqtgraph makes it very easy to visualize data from the command line. Observe:: + + import pyqtgraph as pg + pg.plot(data) # data can be a list of values or a numpy array + +The example above would open a window displaying a line plot of the data given. I don't think it could reasonably be any simpler than that. The call to pg.plot returns a handle to the plot widget that is created, allowing more data to be added to the same window. + +Further examples:: + + pw = pg.plot(xVals, yVals, pen='r') # plot x vs y in red + pw.plot(xVals, yVals2, pen='b') + + win = pg.GraphicsWindow() # Automatically generates grids with multiple items + win.addPlot(data1, row=0, col=0) + win.addPlot(data2, row=0, col=1) + win.addPlot(data3, row=1, col=0, colspan=2) + + pg.show(imageData) # imageData must be a numpy array with 2 to 4 dimensions + +We're only scratching the surface here--these functions accept many different data formats and options for customizing the appearance of your data. + + +Displaying windows from within an application +--------------------------------------------- + +While I consider this approach somewhat lazy, it is often the case that 'lazy' is indistinguishable from 'highly efficient'. The approach here is simply to use the very same functions that would be used on the command line, but from within an existing application. I often use this when I simply want to get a immediate feedback about the state of data in my application without taking the time to build a user interface for it. + + +Embedding widgets inside PyQt applications +------------------------------------------ + +For the serious application developer, all of the functionality in pyqtgraph is available via widgets that can be embedded just like any other Qt widgets. Most importantly, see: PlotWidget, ImageView, GraphicsView, GraphicsLayoutWidget. Pyqtgraph's widgets can be included in Designer's ui files via the "Promote To..." functionality. + diff --git a/documentation/build/html/_sources/images.txt b/documentation/build/html/_sources/images.txt new file mode 100644 index 00000000..461a9cb7 --- /dev/null +++ b/documentation/build/html/_sources/images.txt @@ -0,0 +1,26 @@ +Displaying images and video +=========================== + +Pyqtgraph displays 2D numpy arrays as images and provides tools for determining how to translate between the numpy data type and RGB values on the screen. If you want to display data from common image and video file formats, you will need to load the data first using another library (PIL works well for images and built-in numpy conversion). + +The easiest way to display 2D or 3D data is using the :func:`pyqtgraph.image` function:: + + import pyqtgraph as pg + pg.image(imageData) + +This function will accept any floating-point or integer data types and displays a single :class:`~pyqtgraph.ImageView` widget containing your data. This widget includes controls for determining how the image data will be converted to 32-bit RGBa values. Conversion happens in two steps (both are optional): + +1. Scale and offset the data (by selecting the dark/light levels on the displayed histogram) +2. Convert the data to color using a lookup table (determined by the colors shown in the gradient editor) + +If the data is 3D (time, x, y), then a time axis will be shown with a slider that can set the currently displayed frame. (if the axes in your data are ordered differently, use numpy.transpose to rearrange them) + +There are a few other methods for displaying images as well: + +* The :class:`~pyqtgraph.ImageView` class can also be instantiated directly and embedded in Qt applications. +* Instances of :class:`~pyqtgraph.ImageItem` can be used inside a GraphicsView. +* For higher performance, use :class:`~pyqtgraph.RawImageWidget`. + +Any of these classes are acceptable for displaying video by calling setImage() to display a new frame. To increase performance, the image processing system uses scipy.weave to produce compiled libraries. If your computer has a compiler available, weave will automatically attempt to build the libraries it needs on demand. If this fails, then the slower pure-python methods will be used instead. + +For more information, see the classes listed above and the 'VideoSpeedTest', 'ImageItem', 'ImageView', and 'HistogramLUT' :ref:`examples`. \ No newline at end of file diff --git a/documentation/build/html/_sources/index.txt b/documentation/build/html/_sources/index.txt new file mode 100644 index 00000000..aa6753ef --- /dev/null +++ b/documentation/build/html/_sources/index.txt @@ -0,0 +1,32 @@ +.. pyqtgraph documentation master file, created by + sphinx-quickstart on Fri Nov 18 19:33:12 2011. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to the documentation for pyqtgraph 1.8 +============================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + introduction + how_to_use + plotting + images + style + region_of_interest + graphicswindow + parametertree + internals + apireference + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/documentation/build/html/_sources/introduction.txt b/documentation/build/html/_sources/introduction.txt new file mode 100644 index 00000000..c5c1dfab --- /dev/null +++ b/documentation/build/html/_sources/introduction.txt @@ -0,0 +1,51 @@ +Introduction +============ + + + +What is pyqtgraph? +------------------ + +Pyqtgraph is a graphics and user interface library for Python that provides functionality commonly required in engineering and science applications. Its primary goals are 1) to provide fast, interactive graphics for displaying data (plots, video, etc.) and 2) to provide tools to aid in rapid application development (for example, property trees such as used in Qt Designer). + +Pyqtgraph makes heavy use of the Qt GUI platform (via PyQt or PySide) for its high-performance graphics and numpy for heavy number crunching. In particular, pyqtgraph uses Qt's GraphicsView framework which is a highly capable graphics system on its own; we bring optimized and simplified primitives to this framework to allow data visualization with minimal effort. + +It is known to run on Linux, Windows, and OSX + + +What can it do? +--------------- + +Amongst the core features of pyqtgraph are: + +* Basic data visualization primitives: Images, line and scatter plots +* Fast enough for realtime update of video/plot data +* Interactive scaling/panning, averaging, FFTs, SVG/PNG export +* Widgets for marking/selecting plot regions +* Widgets for marking/selecting image region-of-interest and automatically slicing multi-dimensional image data +* Framework for building customized image region-of-interest widgets +* Docking system that replaces/complements Qt's dock system to allow more complex (and more predictable) docking arrangements +* ParameterTree widget for rapid prototyping of dynamic interfaces (Similar to the property trees in Qt Designer and many other applications) + + +.. _examples: + +Examples +-------- + +Pyqtgraph includes an extensive set of examples that can be accessed by running:: + + import pyqtgraph.examples + pyqtgraph.examples.run() + +This will start a launcher with a list of available examples. Select an item from the list to view its source code and double-click an item to run the example. + + +How does it compare to... +------------------------- + +* matplotlib: For plotting and making publication-quality graphics, matplotlib is far more mature than pyqtgraph. However, matplotlib is also much slower and not suitable for applications requiring realtime update of plots/video or rapid interactivity. It also does not provide any of the GUI tools and image interaction/slicing functionality in pyqtgraph. + +* pyqwt5: pyqwt is generally more mature than pyqtgraph for plotting and is about as fast. The major differences are 1) pyqtgraph is written in pure python, so it is somewhat more portable than pyqwt, which often lags behind pyqt in development (and can be a pain to install on some platforms) and 2) like matplotlib, pyqwt does not provide any of the GUI tools and image interaction/slicing functionality in pyqtgraph. + +(My experience with these libraries is somewhat outdated; please correct me if I am wrong here) diff --git a/documentation/build/html/_sources/parametertree.txt b/documentation/build/html/_sources/parametertree.txt new file mode 100644 index 00000000..de699492 --- /dev/null +++ b/documentation/build/html/_sources/parametertree.txt @@ -0,0 +1,7 @@ +Rapid GUI prototyping +===================== + + - parametertree + - dockarea + - flowchart + - canvas diff --git a/documentation/build/html/_sources/plotting.txt b/documentation/build/html/_sources/plotting.txt new file mode 100644 index 00000000..ee9ed6dc --- /dev/null +++ b/documentation/build/html/_sources/plotting.txt @@ -0,0 +1,73 @@ +Plotting in pyqtgraph +===================== + +There are a few basic ways to plot data in pyqtgraph: + +================================================================ ================================================== +:func:`pyqtgraph.plot` Create a new plot window showing your data +:func:`PlotWidget.plot() <pyqtgraph.PlotWidget.plot>` Add a new set of data to an existing plot widget +:func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>` Add a new set of data to an existing plot widget +:func:`GraphicsWindow.addPlot() <pyqtgraph.GraphicsWindow.plot>` Add a new plot to a grid of plots +================================================================ ================================================== + +All of these will accept the same basic arguments which control how the plot data is interpreted and displayed: + +* x - Optional X data; if not specified, then a range of integers will be generated automatically. +* y - Y data. +* pen - The pen to use when drawing plot lines, or None to disable lines. +* symbol - A string describing the shape of symbols to use for each point. Optionally, this may also be a sequence of strings with a different symbol for each point. +* symbolPen - The pen (or sequence of pens) to use when drawing the symbol outline. +* symbolBrush - The brush (or sequence of brushes) to use when filling the symbol. +* fillLevel - Fills the area under the plot curve to this Y-value. +* brush - The brush to use when filling under the curve. + +See the 'plotting' :ref:`example <examples>` for a demonstration of these arguments. + +All of the above functions also return handles to the objects that are created, allowing the plots and data to be further modified. + +Organization of Plotting Classes +-------------------------------- + +There are several classes invloved in displaying plot data. Most of these classes are instantiated automatically, but it is useful to understand how they are organized and relate to each other. Pyqtgraph is based heavily on Qt's GraphicsView framework--if you are not already familiar with this, it's worth reading about (but not essential). Most importantly: 1) Qt GUIs are composed of QWidgets, 2) A special widget called QGraphicsView is used for displaying complex graphics, and 3) QGraphicsItems define the objects that are displayed within a QGraphicsView. + +* Data Classes (all subclasses of QGraphicsItem) + * PlotCurveItem - Displays a plot line given x,y data + * ScatterPlotItem - Displays points given x,y data + * :class:`PlotDataItem <pyqtgraph.graphicsItems.PlotDataItem.PlotDataItem>` - Combines PlotCurveItem and ScatterPlotItem. The plotting functions discussed above create objects of this type. +* Container Classes (subclasses of QGraphicsItem; contain other QGraphicsItem objects and must be viewed from within a GraphicsView) + * PlotItem - Contains a ViewBox for displaying data as well as AxisItems and labels for displaying the axes and title. This is a QGraphicsItem subclass and thus may only be used from within a GraphicsView + * GraphicsLayoutItem - QGraphicsItem subclass which displays a grid of items. This is used to display multiple PlotItems together. + * ViewBox - A QGraphicsItem subclass for displaying data. The user may scale/pan the contents of a ViewBox using the mouse. Typically all PlotData/PlotCurve/ScatterPlotItems are displayed from within a ViewBox. + * AxisItem - Displays axis values, ticks, and labels. Most commonly used with PlotItem. +* Container Classes (subclasses of QWidget; may be embedded in PyQt GUIs) + * PlotWidget - A subclass of GraphicsView with a single PlotItem displayed. Most of the methods provided by PlotItem are also available through PlotWidget. + * GraphicsLayoutWidget - QWidget subclass displaying a single GraphicsLayoutItem. Most of the methods provided by GraphicsLayoutItem are also available through GraphicsLayoutWidget. + +.. image:: images/plottingClasses.png + + +Examples +-------- + +See the 'plotting' and 'PlotWidget' :ref:`examples included with pyqtgraph <examples>` for more information. + +Show x,y data as scatter plot:: + + import pyqtgraph as pg + import numpy as np + x = np.random.normal(size=1000) + y = np.random.normal(size=1000) + pg.plot(x, y, pen=None, symbol='o') ## setting pen=None disables line drawing + +Create/show a plot widget, display three data curves:: + + import pyqtgraph as pg + import numpy as np + x = np.arange(1000) + y = np.random.normal(size=(3, 1000)) + plotWidget = pg.plot(title="Three plot curves") + for i in range(3): + plotWidget.plot(x, y[i], pen=(i,3)) ## setting pen=(i,3) automaticaly creates three different-colored pens + + + diff --git a/documentation/build/html/_sources/region_of_interest.txt b/documentation/build/html/_sources/region_of_interest.txt new file mode 100644 index 00000000..24799cb7 --- /dev/null +++ b/documentation/build/html/_sources/region_of_interest.txt @@ -0,0 +1,19 @@ +Region-of-interest controls +=========================== + +Slicing Multidimensional Data +----------------------------- + +Linear Selection and Marking +---------------------------- + +2D Selection and Marking +------------------------ + + + + +- translate / rotate / scale +- highly configurable control handles +- automated data slicing +- linearregion, infiniteline diff --git a/documentation/build/html/_sources/style.txt b/documentation/build/html/_sources/style.txt new file mode 100644 index 00000000..fc172420 --- /dev/null +++ b/documentation/build/html/_sources/style.txt @@ -0,0 +1,17 @@ +Line, Fill, and Color +===================== + +Many functions and methods in pyqtgraph accept arguments specifying the line style (pen), fill style (brush), or color. + +For these function arguments, the following values may be used: + +* single-character string representing color (b, g, r, c, m, y, k, w) +* (r, g, b) or (r, g, b, a) tuple +* single greyscale value (0.0 - 1.0) +* (index, maximum) tuple for automatically iterating through colors (see functions.intColor) +* QColor +* QPen / QBrush where appropriate + +Notably, more complex pens and brushes can be easily built using the mkPen() / mkBrush() functions or with Qt's QPen and QBrush classes. + +Colors can also be built using mkColor(), intColor(), hsvColor(), or Qt's QColor class diff --git a/documentation/build/html/_sources/widgets/checktable.txt b/documentation/build/html/_sources/widgets/checktable.txt new file mode 100644 index 00000000..5301a4e9 --- /dev/null +++ b/documentation/build/html/_sources/widgets/checktable.txt @@ -0,0 +1,8 @@ +CheckTable +========== + +.. autoclass:: pyqtgraph.CheckTable + :members: + + .. automethod:: pyqtgraph.CheckTable.__init__ + diff --git a/documentation/build/html/_sources/widgets/colorbutton.txt b/documentation/build/html/_sources/widgets/colorbutton.txt new file mode 100644 index 00000000..690239d8 --- /dev/null +++ b/documentation/build/html/_sources/widgets/colorbutton.txt @@ -0,0 +1,8 @@ +ColorButton +=========== + +.. autoclass:: pyqtgraph.ColorButton + :members: + + .. automethod:: pyqtgraph.ColorButton.__init__ + diff --git a/documentation/build/html/_sources/widgets/datatreewidget.txt b/documentation/build/html/_sources/widgets/datatreewidget.txt new file mode 100644 index 00000000..f6bbdbaf --- /dev/null +++ b/documentation/build/html/_sources/widgets/datatreewidget.txt @@ -0,0 +1,8 @@ +DataTreeWidget +============== + +.. autoclass:: pyqtgraph.DataTreeWidget + :members: + + .. automethod:: pyqtgraph.DataTreeWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/dockarea.txt b/documentation/build/html/_sources/widgets/dockarea.txt new file mode 100644 index 00000000..09a6acca --- /dev/null +++ b/documentation/build/html/_sources/widgets/dockarea.txt @@ -0,0 +1,5 @@ +dockarea module +=============== + +.. automodule:: pyqtgraph.dockarea + :members: diff --git a/documentation/build/html/_sources/widgets/filedialog.txt b/documentation/build/html/_sources/widgets/filedialog.txt new file mode 100644 index 00000000..bf2f9c07 --- /dev/null +++ b/documentation/build/html/_sources/widgets/filedialog.txt @@ -0,0 +1,8 @@ +FileDialog +========== + +.. autoclass:: pyqtgraph.FileDialog + :members: + + .. automethod:: pyqtgraph.FileDialog.__init__ + diff --git a/documentation/build/html/_sources/widgets/gradientwidget.txt b/documentation/build/html/_sources/widgets/gradientwidget.txt new file mode 100644 index 00000000..a2587503 --- /dev/null +++ b/documentation/build/html/_sources/widgets/gradientwidget.txt @@ -0,0 +1,8 @@ +GradientWidget +============== + +.. autoclass:: pyqtgraph.GradientWidget + :members: + + .. automethod:: pyqtgraph.GradientWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/graphicslayoutwidget.txt b/documentation/build/html/_sources/widgets/graphicslayoutwidget.txt new file mode 100644 index 00000000..5f885f07 --- /dev/null +++ b/documentation/build/html/_sources/widgets/graphicslayoutwidget.txt @@ -0,0 +1,8 @@ +GraphicsLayoutWidget +==================== + +.. autoclass:: pyqtgraph.GraphicsLayoutWidget + :members: + + .. automethod:: pyqtgraph.GraphicsLayoutWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/graphicsview.txt b/documentation/build/html/_sources/widgets/graphicsview.txt new file mode 100644 index 00000000..ac7ae3bf --- /dev/null +++ b/documentation/build/html/_sources/widgets/graphicsview.txt @@ -0,0 +1,8 @@ +GraphicsView +============ + +.. autoclass:: pyqtgraph.GraphicsView + :members: + + .. automethod:: pyqtgraph.GraphicsView.__init__ + diff --git a/documentation/build/html/_sources/widgets/histogramlutwidget.txt b/documentation/build/html/_sources/widgets/histogramlutwidget.txt new file mode 100644 index 00000000..9d8f3b20 --- /dev/null +++ b/documentation/build/html/_sources/widgets/histogramlutwidget.txt @@ -0,0 +1,8 @@ +HistogramLUTWidget +================== + +.. autoclass:: pyqtgraph.HistogramLUTWidget + :members: + + .. automethod:: pyqtgraph.HistogramLUTWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/imageview.txt b/documentation/build/html/_sources/widgets/imageview.txt new file mode 100644 index 00000000..1eadabbf --- /dev/null +++ b/documentation/build/html/_sources/widgets/imageview.txt @@ -0,0 +1,8 @@ +ImageView +========= + +.. autoclass:: pyqtgraph.ImageView + :members: + + .. automethod:: pyqtgraph.ImageView.__init__ + diff --git a/documentation/build/html/_sources/widgets/index.txt b/documentation/build/html/_sources/widgets/index.txt new file mode 100644 index 00000000..bce5b070 --- /dev/null +++ b/documentation/build/html/_sources/widgets/index.txt @@ -0,0 +1,31 @@ +Pyqtgraph's Widgets +=================== + +Pyqtgraph provides several QWidget subclasses which are useful for building user interfaces. These widgets can generally be used in any Qt application and provide functionality that is frequently useful in science and engineering applications. + +Contents: + +.. toctree:: + :maxdepth: 2 + + plotwidget + imageview + datatreewidget + checktable + tablewidget + gradientwidget + colorbutton + graphicslayoutwidget + dockarea + parametertree + histogramlutwidget + progressdialog + spinbox + filedialog + graphicsview + joystickbutton + multiplotwidget + treewidget + verticallabel + rawimagewidget + diff --git a/documentation/build/html/_sources/widgets/joystickbutton.txt b/documentation/build/html/_sources/widgets/joystickbutton.txt new file mode 100644 index 00000000..4d21e16f --- /dev/null +++ b/documentation/build/html/_sources/widgets/joystickbutton.txt @@ -0,0 +1,8 @@ +JoystickButton +============== + +.. autoclass:: pyqtgraph.JoystickButton + :members: + + .. automethod:: pyqtgraph.JoystickButton.__init__ + diff --git a/documentation/build/html/_sources/widgets/multiplotwidget.txt b/documentation/build/html/_sources/widgets/multiplotwidget.txt new file mode 100644 index 00000000..46986db0 --- /dev/null +++ b/documentation/build/html/_sources/widgets/multiplotwidget.txt @@ -0,0 +1,8 @@ +MultiPlotWidget +=============== + +.. autoclass:: pyqtgraph.MultiPlotWidget + :members: + + .. automethod:: pyqtgraph.MultiPlotWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/parametertree.txt b/documentation/build/html/_sources/widgets/parametertree.txt new file mode 100644 index 00000000..565b930b --- /dev/null +++ b/documentation/build/html/_sources/widgets/parametertree.txt @@ -0,0 +1,5 @@ +parametertree module +==================== + +.. automodule:: pyqtgraph.parametertree + :members: diff --git a/documentation/build/html/_sources/widgets/plotwidget.txt b/documentation/build/html/_sources/widgets/plotwidget.txt new file mode 100644 index 00000000..cbded80d --- /dev/null +++ b/documentation/build/html/_sources/widgets/plotwidget.txt @@ -0,0 +1,8 @@ +PlotWidget +========== + +.. autoclass:: pyqtgraph.PlotWidget + :members: + + .. automethod:: pyqtgraph.PlotWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/progressdialog.txt b/documentation/build/html/_sources/widgets/progressdialog.txt new file mode 100644 index 00000000..fff04cb3 --- /dev/null +++ b/documentation/build/html/_sources/widgets/progressdialog.txt @@ -0,0 +1,8 @@ +ProgressDialog +============== + +.. autoclass:: pyqtgraph.ProgressDialog + :members: + + .. automethod:: pyqtgraph.ProgressDialog.__init__ + diff --git a/documentation/build/html/_sources/widgets/rawimagewidget.txt b/documentation/build/html/_sources/widgets/rawimagewidget.txt new file mode 100644 index 00000000..29fda791 --- /dev/null +++ b/documentation/build/html/_sources/widgets/rawimagewidget.txt @@ -0,0 +1,8 @@ +RawImageWidget +============== + +.. autoclass:: pyqtgraph.RawImageWidget + :members: + + .. automethod:: pyqtgraph.RawImageWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/spinbox.txt b/documentation/build/html/_sources/widgets/spinbox.txt new file mode 100644 index 00000000..33da1f4c --- /dev/null +++ b/documentation/build/html/_sources/widgets/spinbox.txt @@ -0,0 +1,8 @@ +SpinBox +======= + +.. autoclass:: pyqtgraph.SpinBox + :members: + + .. automethod:: pyqtgraph.SpinBox.__init__ + diff --git a/documentation/build/html/_sources/widgets/tablewidget.txt b/documentation/build/html/_sources/widgets/tablewidget.txt new file mode 100644 index 00000000..283b540b --- /dev/null +++ b/documentation/build/html/_sources/widgets/tablewidget.txt @@ -0,0 +1,8 @@ +TableWidget +=========== + +.. autoclass:: pyqtgraph.TableWidget + :members: + + .. automethod:: pyqtgraph.TableWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/treewidget.txt b/documentation/build/html/_sources/widgets/treewidget.txt new file mode 100644 index 00000000..00f9fa28 --- /dev/null +++ b/documentation/build/html/_sources/widgets/treewidget.txt @@ -0,0 +1,8 @@ +TreeWidget +========== + +.. autoclass:: pyqtgraph.TreeWidget + :members: + + .. automethod:: pyqtgraph.TreeWidget.__init__ + diff --git a/documentation/build/html/_sources/widgets/verticallabel.txt b/documentation/build/html/_sources/widgets/verticallabel.txt new file mode 100644 index 00000000..4f627437 --- /dev/null +++ b/documentation/build/html/_sources/widgets/verticallabel.txt @@ -0,0 +1,8 @@ +VerticalLabel +============= + +.. autoclass:: pyqtgraph.VerticalLabel + :members: + + .. automethod:: pyqtgraph.VerticalLabel.__init__ + diff --git a/documentation/build/html/_static/basic.css b/documentation/build/html/_static/basic.css new file mode 100644 index 00000000..69f30d4f --- /dev/null +++ b/documentation/build/html/_static/basic.css @@ -0,0 +1,509 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +.align-left { + text-align: left; +} + +.align-center { + clear: both; + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} diff --git a/documentation/build/html/_static/default.css b/documentation/build/html/_static/default.css new file mode 100644 index 00000000..b30cb790 --- /dev/null +++ b/documentation/build/html/_static/default.css @@ -0,0 +1,255 @@ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} \ No newline at end of file diff --git a/documentation/build/html/_static/doctools.js b/documentation/build/html/_static/doctools.js new file mode 100644 index 00000000..eeea95ea --- /dev/null +++ b/documentation/build/html/_static/doctools.js @@ -0,0 +1,247 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for all documentation. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +} + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * small function to check if an array contains + * a given item. + */ +jQuery.contains = function(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == item) + return true; + } + return false; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('<a class="headerlink">\u00B6</a>'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('<a class="headerlink">\u00B6</a>'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('<li class="highlight-link"><a href="javascript:Documentation.' + + 'hideSearchWords()">' + _('Hide Search Matches') + '</a></li>') + .appendTo($('.sidebar .this-page-menu')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('.sidebar .this-page-menu li.highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/documentation/build/html/_static/file.png b/documentation/build/html/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..d18082e397e7e54f20721af768c4c2983258f1b4 GIT binary patch literal 392 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP$Hy<xMqvo~{83qPMQBN1g5R21mCvNmQ;vmqr-?K<V zU4kQ;TS-l(;>OL$D9)yc9|lc|nKf<9@eUiWd>3GuTC!a5vdfWYEazjncPj5ZQX%+1 zt8B*4=d)!cdDz4wr^#OMYfqGz$1LDFF>|#>*O?<HXR=hzTfCLc!DZQEwT&GKx6RhQ zo))l-Eh>AGil(WEs?wLLy{Gj2J_@opDm%`dlax3yA*@*N$G&*ukFv>P8+2CBWO(qz zD0k1@kN>hhb1_6`&wrCswzINE(evt-5C1B^STi2@P<G<wTm_|``|B7th)+*kX+Pib z*M}qKJqcfCd_2yP9QQKsOSb)vO-|{xyF)I_-07lx@Zvo#rJ$WIhTj)AKCk@a+86Xn ktMBn$GheTj#{azILsys7Te#I72ZkAgr>mdKI;Vst0PQB6!2kdN literal 0 HcmV?d00001 diff --git a/documentation/build/html/_static/jquery.js b/documentation/build/html/_static/jquery.js new file mode 100644 index 00000000..5c99a8d4 --- /dev/null +++ b/documentation/build/html/_static/jquery.js @@ -0,0 +1,8176 @@ +/*! + * jQuery JavaScript Library v1.5 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Jan 31 08:31:29 2011 -0500 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The deferred used on DOM ready + readyList, + + // Promise methods + promiseMethods = "then done fail isResolved isRejected promise".split( " " ), + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.5", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.done( fn ); + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + // A third-party is pushing the ready event forwards + if ( wait === true ) { + jQuery.readyWait--; + } + + // Make sure that the DOM is not already loaded + if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test(data.replace(rvalidescape, "@") + .replace(rvalidtokens, "]") + .replace(rvalidbraces, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function( data , xml , tmp ) { + + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + + tmp = xml.documentElement; + + if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { + jQuery.error( "Invalid XML: " + data ); + } + + return xml; + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval() ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type(array); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Create a simple deferred (one callbacks list) + _Deferred: function() { + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function() { + if ( !cancelled ) { + var args = arguments, + i, + length, + elem, + type, + _fired; + if ( fired ) { + _fired = fired; + fired = 0; + } + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.done.apply( deferred, elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + if ( _fired ) { + deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); + } + } + return this; + }, + + // resolve with given context and args + resolveWith: function( context, args ) { + if ( !cancelled && !fired && !firing ) { + firing = 1; + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context, args ); + } + } + finally { + fired = [ context, args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + Deferred: function( func ) { + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + // Add errorDeferred methods, then and promise + jQuery.extend( deferred, { + then: function( doneCallbacks, failCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ); + return this; + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj , i /* internal */ ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ]; + } + return obj; + } + } ); + // Make sure only one callback list will be used + deferred.then( failDeferred.cancel, deferred.cancel ); + // Unexpose cancel + delete deferred.cancel; + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + return deferred; + }, + + // Deferred helper + when: function( object ) { + var args = arguments, + length = args.length, + deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ? + object : + jQuery.Deferred(), + promise = deferred.promise(), + resolveArray; + + if ( length > 1 ) { + resolveArray = new Array( length ); + jQuery.each( args, function( index, element ) { + jQuery.when( element ).then( function( value ) { + resolveArray[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; + if( ! --length ) { + deferred.resolveWith( promise, resolveArray ); + } + }, deferred.reject ); + } ); + } else if ( deferred !== object ) { + deferred.resolve( object ); + } + return promise; + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySubclass( selector, context ) { + return new jQuerySubclass.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySubclass, this ); + jQuerySubclass.superclass = this; + jQuerySubclass.fn = jQuerySubclass.prototype = this(); + jQuerySubclass.fn.constructor = jQuerySubclass; + jQuerySubclass.subclass = this.subclass; + jQuerySubclass.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) { + context = jQuerySubclass(context); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); + }; + jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; + var rootjQuerySubclass = jQuerySubclass(document); + return jQuerySubclass; + }, + + browser: {} +}); + +// Create readyList deferred +readyList = jQuery._Deferred(); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +// Expose jQuery to the global object +return (window.jQuery = window.$ = jQuery); + +})(); + + +(function() { + + jQuery.support = {}; + + var div = document.createElement("div"); + + div.style.display = "none"; + div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0], + select = document.createElement("select"), + opt = select.appendChild( document.createElement("option") ); + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Will be defined later + deleteExpando: true, + optDisabled: false, + checkClone: false, + _scriptEval: null, + noCloneEvent: true, + boxModel: null, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableHiddenOffsets: true + }; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as diabled) + select.disabled = true; + jQuery.support.optDisabled = !opt.disabled; + + jQuery.support.scriptEval = function() { + if ( jQuery.support._scriptEval === null ) { + var root = document.documentElement, + script = document.createElement("script"), + id = "script" + jQuery.now(); + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support._scriptEval = true; + delete window[ id ]; + } else { + jQuery.support._scriptEval = false; + } + + root.removeChild( script ); + // release memory in IE + root = script = id = null; + } + + return jQuery.support._scriptEval; + }; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"), + body = document.getElementsByTagName("body")[0]; + + // Frameset documents with no body should not run this code + if ( !body ) { + return; + } + + div.style.width = div.style.paddingLeft = "1px"; + body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "<div style='width:4px;'></div>"; + jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; + } + + div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; + var tds = div.getElementsByTagName("td"); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; + + tds[0].style.display = ""; + tds[1].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; + div.innerHTML = ""; + + body.removeChild( div ).style.display = "none"; + div = tds = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( !el.attachEvent ) { + return true; + } + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + div = all = a = null; +})(); + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + + return !!elem && !jQuery.isEmptyObject(elem); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } else { + id = jQuery.expando; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" ) { + if ( pvt ) { + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); + } else { + cache[ id ] = jQuery.extend(cache[ id ], name); + } + } + + thisCache = cache[ id ]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; + } + + thisCache = thisCache[ internalKey ]; + } + + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should + // not attempt to inspect the internal events object using jQuery.data, as this + // internal data object is undocumented and subject to change. + if ( name === "events" && !thisCache[name] ) { + return thisCache[ internalKey ] && thisCache[ internalKey ].events; + } + + return getByName ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var internalKey = jQuery.expando, isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + + if ( thisCache ) { + delete thisCache[ name ]; + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( pvt ) { + delete cache[ id ][ internalKey ]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !jQuery.isEmptyObject(cache[ id ]) ) { + return; + } + } + + var internalCache = cache[ id ][ internalKey ]; + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + if ( jQuery.support.deleteExpando || cache != window ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if ( internalCache ) { + cache[ id ] = {}; + cache[ id ][ internalKey ] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } else { + elem[ jQuery.expando ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 ) { + var attr = this[0].attributes, name; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = name.substr( 5 ); + dataAttr( this[0], name, data[ name ] ); + } + } + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var $this = jQuery( this ), + args = [ parts[0], value ]; + + $this.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + data = elem.getAttribute( "data-" + key ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + + + + +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue", true ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspaces = /\s+/, + rreturn = /\r/g, + rspecialurl = /^(?:href|src|style)$/, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rradiocheck = /^(?:radio|checkbox)$/i; + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", + setClass = elem.className; + + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspaces ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( !arguments.length ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray(val) ) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + // 'in' checks fail in Blackberry 4.7 #6931 + if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + if ( value === null ) { + if ( elem.nodeType === 1 ) { + elem.removeAttribute( name ); + } + + } else { + elem[ name ] = value; + } + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + // Ensure that missing attributes return undefined + // Blackberry 4.7 returns "" from getAttribute #6938 + if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { + return undefined; + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + // Handle everything which isn't a DOM element node + if ( set ) { + elem[ name ] = value; + } + return elem[ name ]; + } +}); + + + + +var rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspace = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); + }, + eventKey = "events"; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery._data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData[ eventKey ], + eventHandle = elemData.handle; + + if ( typeof events === "function" ) { + // On plain objects events is a fn that holds the the data + // which prevents this data from being JSON serialized + // the function does not need to be called, it just contains the data + eventHandle = events.handle; + events = events.events; + + } else if ( !events ) { + if ( !elem.nodeType ) { + // On plain objects, create a fn that acts as the holder + // of the values to avoid JSON serialization of event data + elemData[ eventKey ] = elemData = function(){}; + } + + elemData.events = events = {}; + } + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + events = elemData && elemData[ eventKey ]; + + if ( !elemData || !events ) { + return; + } + + if ( typeof events === "function" ) { + elemData = events; + events = events.events; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( typeof elemData === "function" ) { + jQuery.removeData( elem, eventKey, true ); + + } else if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem, undefined, true ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + // XXX This code smells terrible. event.js should not be directly + // inspecting the data cache + jQuery.each( jQuery.cache, function() { + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[ internalKey ]; + if ( internalCache && internalCache.events && internalCache.events[type] ) { + jQuery.event.trigger( event, data, internalCache.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = elem.nodeType ? + jQuery._data( elem, "handle" ) : + (jQuery._data( elem, eventKey ) || {}).handle; + + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + event.preventDefault(); + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (inlineError) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var old, + target = event.target, + targetType = type.replace( rnamespaces, "" ), + isClick = jQuery.nodeName( target, "a" ) && targetType === "click", + special = jQuery.event.special[ targetType ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ targetType ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + targetType ]; + + if ( old ) { + target[ "on" + targetType ] = null; + } + + jQuery.event.triggered = true; + target[ targetType ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (triggerError) {} + + if ( old ) { + target[ "on" + targetType ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace_re, events, + namespace_sort = [], + args = jQuery.makeArray( arguments ); + + event = args[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace_sort = namespaces.slice(0).sort(); + namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.namespace = event.namespace || namespace_sort.join("."); + + events = jQuery._data(this, eventKey); + + if ( typeof events === "function" ) { + events = events.events; + } + + handlers = (events || {})[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, + body = document.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + e.liveFired = undefined; + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + e.liveFired = undefined; + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery._data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery._data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + e.liveFired = undefined; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function( e ) { + var elem = e.target; + jQuery._data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return rformElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return rformElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) || data === false ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( typeof types === "object" && !types.preventDefault ) { + for ( var key in types ) { + context[ name ]( key, data, types[key], selector ); + } + + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery._data( this, eventKey ); + + if ( typeof events === "function" ) { + events = events.events; + } + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !/\W/.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace(/\\/g, ""); + }, + + TAG: function( match, curLoop ) { + return match[1].toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace(/\\/g, ""); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + return "text" === elem.type; + }, + radio: function( elem ) { + return "radio" === elem.type; + }, + + checkbox: function( elem ) { + return "checkbox" === elem.type; + }, + + file: function( elem ) { + return "file" === elem.type; + }, + password: function( elem ) { + return "password" === elem.type; + }, + + submit: function( elem ) { + return "submit" === elem.type; + }, + + image: function( elem ) { + return "image" === elem.type; + }, + + reset: function( elem ) { + return "reset" === elem.type; + }, + + button: function( elem ) { + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // If the nodes are siblings (or identical) we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = "<a name='" + id + "'/>"; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = "<a href='#'></a>"; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "<p class='TEST'></p>"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + context.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + if ( matches ) { + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + return matches.call( node, expr ); + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "<div class='test e'></div><div class='test'></div>"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), + length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + if ( jQuery.isArray( selectors ) ) { + var match, selector, + matches = {}, + level = 1; + + if ( cur && selectors.length ) { + for ( i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + var pos = POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique(ret) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ), + // The variable 'args' was introduced in + // https://github.com/jquery/jquery/commit/52a0238 + // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. + // http://code.google.com/p/v8/issues/detail?id=1050 + args = slice.call(arguments); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, args.join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} + + + + +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /<tbody/i, + rhtml = /<|&#?\w+;/, + rnocache = /<(?:script|object|embed|option|style)/i, + // checked="checked" or checked (html5) + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + wrapMap = { + option: [ 1, "<select multiple='multiple'>", "</select>" ], + legend: [ 1, "<fieldset>", "</fieldset>" ], + thead: [ 1, "<table>", "</table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], + area: [ 1, "<map>", "</map>" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize <link> and <script> tags normally +if ( !jQuery.support.htmlSerialize ) { + wrapMap._default = [ 1, "div<div>", "</div>" ]; +} + +jQuery.fn.extend({ + text: function( text ) { + if ( jQuery.isFunction(text) ) { + return this.each(function(i) { + var self = jQuery( this ); + + self.text( text.call(this, i, self.text()) ); + }); + } + + if ( typeof text !== "object" && text !== undefined ) { + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + } + + return jQuery.text( this ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + return this.each(function() { + jQuery( this ).wrapAll( html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this ); + }); + } else if ( arguments.length ) { + var set = jQuery(arguments[0]); + set.push.apply( set, this.toArray() ); + return this.pushStack( set, "before", arguments ); + } + }, + + after: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + } else if ( arguments.length ) { + var set = this.pushStack( this, "after", arguments ); + set.push.apply( set, jQuery(arguments[0]).toArray() ); + return set; + } + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( [ elem ] ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? true : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + if ( value === undefined ) { + return this[0] && this[0].nodeType === 1 ? + this[0].innerHTML.replace(rinlinejQuery, "") : + null; + + // See if we can take a shortcut and just use innerHTML + } else if ( typeof value === "string" && !rnocache.test( value ) && + (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && + !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { + + value = value.replace(rxhtmlTag, "<$1></$2>"); + + try { + for ( var i = 0, l = this.length; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + if ( this[i].nodeType === 1 ) { + jQuery.cleanData( this[i].getElementsByTagName("*") ); + this[i].innerHTML = value; + } + } + + // If using innerHTML throws an exception, use the fallback method + } catch(e) { + this.empty().append( value ); + } + + } else if ( jQuery.isFunction( value ) ) { + this.each(function(i){ + var self = jQuery( this ); + + self.html( value.call(this, i, self.html()) ); + }); + + } else { + this.empty().append( value ); + } + + return this; + }, + + replaceWith: function( value ) { + if ( this[0] && this[0].parentNode ) { + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this), old = self.html(); + self.replaceWith( value.call( this, i, old ) ); + }); + } + + if ( typeof value !== "string" ) { + value = jQuery( value ).detach(); + } + + return this.each(function() { + var next = this.nextSibling, + parent = this.parentNode; + + jQuery( this ).remove(); + + if ( next ) { + jQuery(next).before( value ); + } else { + jQuery(parent).append( value ); + } + }); + } else { + return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); + } + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + var results, first, fragment, parent, + value = args[0], + scripts = []; + + // We can't cloneNode fragments that contain checked, in WebKit + if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { + return this.each(function() { + jQuery(this).domManip( args, table, callback, true ); + }); + } + + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + args[0] = value.call(this, i, table ? self.html() : undefined); + self.domManip( args, table, callback ); + }); + } + + if ( this[0] ) { + parent = value && value.parentNode; + + // If we're in a fragment, just use that instead of building a new one + if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { + results = { fragment: parent }; + + } else { + results = jQuery.buildFragment( args, this, scripts ); + } + + fragment = results.fragment; + + if ( fragment.childNodes.length === 1 ) { + first = fragment = fragment.firstChild; + } else { + first = fragment.firstChild; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + + for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { + callback.call( + table ? + root(this[i], first) : + this[i], + // Make sure that we do not leak memory by inadvertently discarding + // the original fragment (which might have attached data) instead of + // using it; in addition, use the original fragment object for the last + // item instead of first because it can end up being emptied incorrectly + // in certain situations (Bug #8070). + // Fragments from the fragment cache must always be cloned and never used + // in place. + results.cacheable || (l > 1 && i < lastIndex) ? + jQuery.clone( fragment, true, true ) : + fragment + ); + } + } + + if ( scripts.length ) { + jQuery.each( scripts, evalScript ); + } + } + + return this; + } +}); + +function root( elem, cur ) { + return jQuery.nodeName(elem, "table") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var internalKey = jQuery.expando, + oldData = jQuery.data( src ), + curData = jQuery.data( dest, oldData ); + + // Switch to use the internal data object, if it exists, for the next + // stage of data copying + if ( (oldData = oldData[ internalKey ]) ) { + var events = oldData.events; + curData = curData[ internalKey ] = jQuery.extend({}, oldData); + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( var type in events ) { + for ( var i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ], events[ type ][ i ].data ); + } + } + } + } +} + +function cloneFixAttributes(src, dest) { + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + var nodeName = dest.nodeName.toLowerCase(); + + // clearAttributes removes the attributes, which we don't want, + // but also removes the attachEvent events, which we *do* want + dest.clearAttributes(); + + // mergeAttributes, in contrast, only merges back on the + // original attributes, not the events + dest.mergeAttributes(src); + + // IE6-8 fail to clone children inside object elements that use + // the proprietary classid attribute value (rather than the type + // attribute) to identify the type of content to display + if ( nodeName === "object" ) { + dest.outerHTML = src.outerHTML; + + } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + if ( src.checked ) { + dest.defaultChecked = dest.checked = src.checked; + } + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } + + // Event data gets referenced instead of copied if the expando + // gets copied too + dest.removeAttribute( jQuery.expando ); +} + +jQuery.buildFragment = function( args, nodes, scripts ) { + var fragment, cacheable, cacheresults, + doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); + + // Only cache "small" (1/2 KB) HTML strings that are associated with the main document + // Cloning options loses the selected state, so don't cache them + // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment + // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache + if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && + args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { + + cacheable = true; + cacheresults = jQuery.fragments[ args[0] ]; + if ( cacheresults ) { + if ( cacheresults !== 1 ) { + fragment = cacheresults; + } + } + } + + if ( !fragment ) { + fragment = doc.createDocumentFragment(); + jQuery.clean( args, doc, fragment, scripts ); + } + + if ( cacheable ) { + jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; + } + + return { fragment: fragment, cacheable: cacheable }; +}; + +jQuery.fragments = {}; + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var ret = [], + insert = jQuery( selector ), + parent = this.length === 1 && this[0].parentNode; + + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + insert[ original ]( this[0] ); + return this; + + } else { + for ( var i = 0, l = insert.length; i < l; i++ ) { + var elems = (i > 0 ? this.clone(true) : this).get(); + jQuery( insert[i] )[ original ]( elems ); + ret = ret.concat( elems ); + } + + return this.pushStack( ret, name, insert.selector ); + } + }; +}); + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var clone = elem.cloneNode(true), + srcElements, + destElements, + i; + + if ( !jQuery.support.noCloneEvent && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + // IE copies events bound via attachEvent when using cloneNode. + // Calling detachEvent on the clone will also remove the events + // from the original. In order to get around this, we use some + // proprietary methods to clear the events. Thanks to MooTools + // guys for this hotness. + + // Using Sizzle here is crazy slow, so we use getElementsByTagName + // instead + srcElements = elem.getElementsByTagName("*"); + destElements = clone.getElementsByTagName("*"); + + // Weird iteration because IE will replace the length property + // with an element if you are cloning the body and one of the + // elements on the page has a name or id of "length" + for ( i = 0; srcElements[i]; ++i ) { + cloneFixAttributes( srcElements[i], destElements[i] ); + } + + cloneFixAttributes( elem, clone ); + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + + cloneCopyEvent( elem, clone ); + + if ( deepDataAndEvents && "getElementsByTagName" in elem ) { + + srcElements = elem.getElementsByTagName("*"); + destElements = clone.getElementsByTagName("*"); + + if ( srcElements.length ) { + for ( i = 0; srcElements[i]; ++i ) { + cloneCopyEvent( srcElements[i], destElements[i] ); + } + } + } + } + // Return the cloned set + return clone; + }, + clean: function( elems, context, fragment, scripts ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) { + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + } + + var ret = []; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( typeof elem === "number" ) { + elem += ""; + } + + if ( !elem ) { + continue; + } + + // Convert html string into DOM nodes + if ( typeof elem === "string" && !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + + } else if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1></$2>"); + + // Trim whitespace, otherwise indexOf won't work as expected + var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), + wrap = wrapMap[ tag ] || wrapMap._default, + depth = wrap[0], + div = context.createElement("div"); + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } + + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { + + // String was a <table>, *may* have spurious <tbody> + var hasBody = rtbody.test(elem), + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !hasBody ? + div.childNodes : + []; + + for ( var j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } + } + + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } + + elem = div.childNodes; + } + + if ( elem.nodeType ) { + ret.push( elem ); + } else { + ret = jQuery.merge( ret, elem ); + } + } + + if ( fragment ) { + for ( i = 0; ret[i]; i++ ) { + if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { + scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); + + } else { + if ( ret[i].nodeType === 1 ) { + ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); + } + fragment.appendChild( ret[i] ); + } + } + } + + return ret; + }, + + cleanData: function( elems ) { + var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, + deleteExpando = jQuery.support.deleteExpando; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + continue; + } + + id = elem[ jQuery.expando ]; + + if ( id ) { + data = cache[ id ] && cache[ id ][ internalKey ]; + + if ( data && data.events ) { + for ( var type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + + // Null the DOM reference to avoid IE6/7/8 leak (#7054) + if ( data.handle ) { + data.handle.elem = null; + } + } + + if ( deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + delete cache[ id ]; + } + } + } +}); + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + + + + +var ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity=([^)]*)/, + rdashAlpha = /-([a-z])/ig, + rupper = /([A-Z])/g, + rnumpx = /^-?\d+(?:px)?$/i, + rnum = /^-?\d/, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssWidth = [ "Left", "Right" ], + cssHeight = [ "Top", "Bottom" ], + curCSS, + + getComputedStyle, + currentStyle, + + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn.css = function( name, value ) { + // Setting 'undefined' is a no-op + if ( arguments.length === 2 && value === undefined ) { + return this; + } + + return jQuery.access( this, name, value, true, function( elem, name, value ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }); +}; + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity", "opacity" ); + return ret === "" ? "1" : ret; + + } else { + return elem.style.opacity; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "zIndex": true, + "fontWeight": true, + "opacity": true, + "zoom": true, + "lineHeight": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, origName = jQuery.camelCase( name ), + style = elem.style, hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // Check if we're setting a value + if ( value !== undefined ) { + // Make sure that NaN and null values aren't set. See: #7116 + if ( typeof value === "number" && isNaN( value ) || value == null ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra ) { + // Make sure that we're working with the right name + var ret, origName = jQuery.camelCase( name ), + hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { + return ret; + + // Otherwise, if a way to get the computed value exists, use that + } else if ( curCSS ) { + return curCSS( elem, name, origName ); + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + }, + + camelCase: function( string ) { + return string.replace( rdashAlpha, fcamelCase ); + } +}); + +// DEPRECATED, Use jQuery.css() instead +jQuery.curCSS = jQuery.css; + +jQuery.each(["height", "width"], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + var val; + + if ( computed ) { + if ( elem.offsetWidth !== 0 ) { + val = getWH( elem, name, extra ); + + } else { + jQuery.swap( elem, cssShow, function() { + val = getWH( elem, name, extra ); + }); + } + + if ( val <= 0 ) { + val = curCSS( elem, name, name ); + + if ( val === "0px" && currentStyle ) { + val = currentStyle( elem, name, name ); + } + + if ( val != null ) { + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + } + + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + + return typeof val === "string" ? val : val + "px"; + } + }, + + set: function( elem, value ) { + if ( rnumpx.test( value ) ) { + // ignore negative width and height values #1599 + value = parseFloat(value); + + if ( value >= 0 ) { + return value + "px"; + } + + } else { + return value; + } + } + }; +}); + +if ( !jQuery.support.opacity ) { + jQuery.cssHooks.opacity = { + get: function( elem, computed ) { + // IE uses filters for opacity + return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ? + (parseFloat(RegExp.$1) / 100) + "" : + computed ? "1" : ""; + }, + + set: function( elem, value ) { + var style = elem.style; + + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // Set the alpha filter to set the opacity + var opacity = jQuery.isNaN(value) ? + "" : + "alpha(opacity=" + value * 100 + ")", + filter = style.filter || ""; + + style.filter = ralpha.test(filter) ? + filter.replace(ralpha, opacity) : + style.filter + ' ' + opacity; + } + }; +} + +if ( document.defaultView && document.defaultView.getComputedStyle ) { + getComputedStyle = function( elem, newName, name ) { + var ret, defaultView, computedStyle; + + name = name.replace( rupper, "-$1" ).toLowerCase(); + + if ( !(defaultView = elem.ownerDocument.defaultView) ) { + return undefined; + } + + if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { + ret = computedStyle.getPropertyValue( name ); + if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { + ret = jQuery.style( elem, name ); + } + } + + return ret; + }; +} + +if ( document.documentElement.currentStyle ) { + currentStyle = function( elem, name ) { + var left, + ret = elem.currentStyle && elem.currentStyle[ name ], + rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], + style = elem.style; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { + // Remember the original values + left = style.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : (ret || 0); + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +curCSS = getComputedStyle || currentStyle; + +function getWH( elem, name, extra ) { + var which = name === "width" ? cssWidth : cssHeight, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) { + return val; + } + + jQuery.each( which, function() { + if ( !extra ) { + val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0; + } + + if ( extra === "margin" ) { + val += parseFloat(jQuery.css( elem, "margin" + this )) || 0; + + } else { + val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0; + } + }); + + return val; +} + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + var width = elem.offsetWidth, + height = elem.offsetHeight; + + return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} + + + + +var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rhash = /#.*$/, + rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL + rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + rquery = /\?/, + rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, + rspacesAjax = /\s+/, + rts = /([?&])_=[^&]*/, + rurl = /^(\w+:)\/\/([^\/?#:]+)(?::(\d+))?/, + + // Keep a copy of the old load method + _load = jQuery.fn.load, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + if ( jQuery.isFunction( func ) ) { + var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), + i = 0, + length = dataTypes.length, + dataType, + list, + placeBefore; + + // For each dataType in the dataTypeExpression + for(; i < length; i++ ) { + dataType = dataTypes[ i ]; + // We control if we're asked to add before + // any existing element + placeBefore = /^\+/.test( dataType ); + if ( placeBefore ) { + dataType = dataType.substr( 1 ) || "*"; + } + list = structure[ dataType ] = structure[ dataType ] || []; + // then we add to the structure accordingly + list[ placeBefore ? "unshift" : "push" ]( func ); + } + } + }; +} + +//Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR, + dataType /* internal */, inspected /* internal */ ) { + + dataType = dataType || options.dataTypes[ 0 ]; + inspected = inspected || {}; + + inspected[ dataType ] = true; + + var list = structure[ dataType ], + i = 0, + length = list ? list.length : 0, + executeOnly = ( structure === prefilters ), + selection; + + for(; i < length && ( executeOnly || !selection ); i++ ) { + selection = list[ i ]( options, originalOptions, jXHR ); + // If we got redirected to another dataType + // we try there if not done already + if ( typeof selection === "string" ) { + if ( inspected[ selection ] ) { + selection = undefined; + } else { + options.dataTypes.unshift( selection ); + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jXHR, selection, inspected ); + } + } + } + // If we're only executing or nothing was selected + // we try the catchall dataType if not done already + if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jXHR, "*", inspected ); + } + // unnecessary when only executing (prefilters) + // but it'll be ignored by the caller in that case + return selection; +} + +jQuery.fn.extend({ + load: function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + + // Don't do a request if no elements are being requested + } else if ( !this.length ) { + return this; + } + + var off = url.indexOf( " " ); + if ( off >= 0 ) { + var selector = url.slice( off, url.length ); + url = url.slice( 0, off ); + } + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) { + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = null; + + // Otherwise, build a param string + } else if ( typeof params === "object" ) { + params = jQuery.param( params, jQuery.ajaxSettings.traditional ); + type = "POST"; + } + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + // Complete callback (responseText is used internally) + complete: function( jXHR, status, responseText ) { + // Store the response as specified by the jXHR object + responseText = jXHR.responseText; + // If successful, inject the HTML into all the matched elements + if ( jXHR.isResolved() ) { + // #4825: Get the actual response in case + // a dataFilter is present in ajaxSettings + jXHR.done(function( r ) { + responseText = r; + }); + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("<div>") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(responseText.replace(rscript, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + responseText ); + } + + if ( callback ) { + self.each( callback, [ responseText, status, jXHR ] ); + } + } + }); + + return this; + }, + + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + + serializeArray: function() { + return this.map(function(){ + return this.elements ? jQuery.makeArray( this.elements ) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + ( this.checked || rselectTextarea.test( this.nodeName ) || + rinput.test( this.type ) ); + }) + .map(function( i, elem ){ + var val = jQuery( this ).val(); + + return val == null ? + null : + jQuery.isArray( val ) ? + jQuery.map( val, function( val, i ){ + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }) : + { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ + jQuery.fn[ o ] = function( f ){ + return this.bind( o, f ); + }; +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = null; + } + + return jQuery.ajax({ + type: method, + url: url, + data: data, + success: callback, + dataType: type + }); + }; +} ); + +jQuery.extend({ + + getScript: function( url, callback ) { + return jQuery.get( url, null, callback, "script" ); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + ajaxSetup: function( settings ) { + jQuery.extend( true, jQuery.ajaxSettings, settings ); + if ( settings.context ) { + jQuery.ajaxSettings.context = settings.context; + } + }, + + ajaxSettings: { + url: location.href, + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + traditional: false, + headers: {}, + crossDomain: null, + */ + + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + text: "text/plain", + json: "application/json, text/javascript", + "*": "*/*" + }, + + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText" + }, + + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + } + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If options is not an object, + // we simulate pre-1.5 signature + if ( typeof options !== "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var // Create the final options object + s = jQuery.extend( true, {}, jQuery.ajaxSettings, options ), + // Callbacks contexts + // We force the original context if it exists + // or take it from jQuery.ajaxSettings otherwise + // (plain objects used as context get extended) + callbackContext = + ( s.context = ( "context" in options ? options : jQuery.ajaxSettings ).context ) || s, + globalEventContext = callbackContext === s ? jQuery.event : jQuery( callbackContext ), + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery._Deferred(), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // Headers (they are sent all at once) + requestHeaders = {}, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + loc = document.location, + protocol = loc.protocol || "http:", + parts, + // The jXHR state + state = 0, + // Loop variable + i, + // Fake xhr + jXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( state === 0 ) { + requestHeaders[ name.toLowerCase() ] = value; + } + return this; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( state === 2 ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match || null; + }, + + // Cancel the request + abort: function( statusText ) { + statusText = statusText || "abort"; + if ( transport ) { + transport.abort( statusText ); + } + done( 0, statusText ); + return this; + } + }; + + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status, statusText, responses, headers) { + + // Called once + if ( state === 2 ) { + return; + } + + // State is "done" now + state = 2; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jXHR.readyState = status ? 4 : 0; + + var isSuccess, + success, + error, + response = responses ? ajaxHandleResponses( s, jXHR, responses ) : undefined, + lastModified, + etag; + + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + + if ( ( lastModified = jXHR.getResponseHeader( "Last-Modified" ) ) ) { + jQuery.lastModified[ s.url ] = lastModified; + } + if ( ( etag = jXHR.getResponseHeader( "Etag" ) ) ) { + jQuery.etag[ s.url ] = etag; + } + } + + // If not modified + if ( status === 304 ) { + + statusText = "notmodified"; + isSuccess = true; + + // If we have data + } else { + + try { + success = ajaxConvert( s, response ); + statusText = "success"; + isSuccess = true; + } catch(e) { + // We have a parsererror + statusText = "parsererror"; + error = e; + } + } + } else { + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if( status ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jXHR.status = status; + jXHR.statusText = statusText; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( s.global ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), + [ jXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.resolveWith( callbackContext, [ jXHR, statusText ] ); + + if ( s.global ) { + globalEventContext.trigger( "ajaxComplete", [ jXHR, s] ); + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + // Attach deferreds + deferred.promise( jXHR ); + jXHR.success = jXHR.done; + jXHR.error = jXHR.fail; + jXHR.complete = completeDeferred.done; + + // Status-dependent callbacks + jXHR.statusCode = function( map ) { + if ( map ) { + var tmp; + if ( state < 2 ) { + for( tmp in map ) { + statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; + } + } else { + tmp = map[ jXHR.status ]; + jXHR.then( tmp, tmp ); + } + } + return this; + }; + + // Remove hash character (#7531: and string promotion) + // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) + // We also use the url parameter if available + s.url = ( "" + ( url || s.url ) ).replace( rhash, "" ).replace( rprotocol, protocol + "//" ); + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); + + // Determine if a cross-domain request is in order + if ( !s.crossDomain ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( parts && + ( parts[ 1 ] != protocol || parts[ 2 ] != loc.hostname || + ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != + ( loc.port || ( protocol === "http:" ? 80 : 443 ) ) ) + ); + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jXHR ); + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Watch for a new set of requests + if ( s.global && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + } + + // Add anti-cache in url if needed + if ( s.cache === false ) { + + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts, "$1_=" + ts ); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + requestHeaders[ "content-type" ] = s.contentType; + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ s.url ] ) { + requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ s.url ]; + } + if ( jQuery.etag[ s.url ] ) { + requestHeaders[ "if-none-match" ] = jQuery.etag[ s.url ]; + } + } + + // Set the Accepts header for the server, depending on the dataType + requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? + s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : + s.accepts[ "*" ]; + + // Check for headers option + for ( i in s.headers ) { + requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jXHR, s ) === false || state === 2 ) ) { + // Abort if not done already + done( 0, "abort" ); + // Return false + jXHR = false; + + } else { + + // Install callbacks on deferreds + for ( i in { success: 1, error: 1, complete: 1 } ) { + jXHR[ i ]( s[ i ] ); + } + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + // Set state as sending + state = jXHR.readyState = 1; + // Send global event + if ( s.global ) { + globalEventContext.trigger( "ajaxSend", [ jXHR, s ] ); + } + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout( function(){ + jXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + transport.send( requestHeaders, done ); + } catch (e) { + // Propagate exception as error if not done + if ( status < 2 ) { + done( -1, e ); + // Simply rethrow otherwise + } else { + jQuery.error( e ); + } + } + } + } + return jXHR; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a, traditional ) { + var s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction( value ) ? value() : value; + s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || a.jquery ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( var prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ).replace( r20, "+" ); + } +}); + +function buildParams( prefix, obj, traditional, add ) { + if ( jQuery.isArray( obj ) && obj.length ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && obj != null && typeof obj === "object" ) { + // If we see an array here, it is empty and should be treated as an empty + // object + if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) { + add( prefix, "" ); + + // Serialize object item. + } else { + jQuery.each( obj, function( k, v ) { + buildParams( prefix + "[" + k + "]", v, traditional, add ); + }); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} + +// This is still on the jQuery object... for now +// Want to move this to jQuery.ajax some day +jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {} + +}); + +/* Handles responses to an ajax request: + * - sets all responseXXX fields accordingly + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jXHR, responses ) { + + var contents = s.contents, + dataTypes = s.dataTypes, + responseFields = s.responseFields, + ct, + type, + finalDataType, + firstDataType; + + // Fill responseXXX fields + for( type in responseFields ) { + if ( type in responses ) { + jXHR[ responseFields[type] ] = responses[ type ]; + } + } + + // Remove auto dataType and get content-type in the process + while( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = jXHR.getResponseHeader( "content-type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +// Chain conversions given the request and the original response +function ajaxConvert( s, response ) { + + // Apply the dataFilter if provided + if ( s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + var dataTypes = s.dataTypes, + converters = s.converters, + i, + length = dataTypes.length, + tmp, + // Current and previous dataTypes + current = dataTypes[ 0 ], + prev, + // Conversion expression + conversion, + // Conversion function + conv, + // Conversion functions (transitive conversion) + conv1, + conv2; + + // For each dataType in the chain + for( i = 1; i < length; i++ ) { + + // Get the dataTypes + prev = current; + current = dataTypes[ i ]; + + // If current is auto dataType, update it to prev + if( current === "*" ) { + current = prev; + // If no auto and dataTypes are actually different + } else if ( prev !== "*" && prev !== current ) { + + // Get the converter + conversion = prev + " " + current; + conv = converters[ conversion ] || converters[ "* " + current ]; + + // If there is no direct converter, search transitively + if ( !conv ) { + conv2 = undefined; + for( conv1 in converters ) { + tmp = conv1.split( " " ); + if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { + conv2 = converters[ tmp[1] + " " + current ]; + if ( conv2 ) { + conv1 = converters[ conv1 ]; + if ( conv1 === true ) { + conv = conv2; + } else if ( conv2 === true ) { + conv = conv1; + } + break; + } + } + } + } + // If we found no converter, dispatch an error + if ( !( conv || conv2 ) ) { + jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); + } + // If found converter is not an equivalence + if ( conv !== true ) { + // Convert with 1 or 2 converters accordingly + response = conv ? conv( response ) : conv2( conv1(response) ); + } + } + } + return response; +} + + + + +var jsc = jQuery.now(), + jsre = /(\=)\?(&|$)|()\?\?()/i; + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + return jQuery.expando + "_" + ( jsc++ ); + } +}); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, dataIsString /* internal */ ) { + + dataIsString = ( typeof s.data === "string" ); + + if ( s.dataTypes[ 0 ] === "jsonp" || + originalSettings.jsonpCallback || + originalSettings.jsonp != null || + s.jsonp !== false && ( jsre.test( s.url ) || + dataIsString && jsre.test( s.data ) ) ) { + + var responseContainer, + jsonpCallback = s.jsonpCallback = + jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, + previous = window[ jsonpCallback ], + url = s.url, + data = s.data, + replace = "$1" + jsonpCallback + "$2"; + + if ( s.jsonp !== false ) { + url = url.replace( jsre, replace ); + if ( s.url === url ) { + if ( dataIsString ) { + data = data.replace( jsre, replace ); + } + if ( s.data === data ) { + // Add callback manually + url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + } + } + } + + s.url = url; + s.data = data; + + window[ jsonpCallback ] = function( response ) { + responseContainer = [ response ]; + }; + + s.complete = [ function() { + + // Set callback back to previous value + window[ jsonpCallback ] = previous; + + // Call if it was a function and we have a response + if ( previous) { + if ( responseContainer && jQuery.isFunction( previous ) ) { + window[ jsonpCallback ] ( responseContainer[ 0 ] ); + } + } else { + // else, more memory leak avoidance + try{ + delete window[ jsonpCallback ]; + } catch( e ) {} + } + + }, s.complete ]; + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( ! responseContainer ) { + jQuery.error( jsonpCallback + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Delegate to script + return "script"; + } +} ); + + + + +// Install script dataType +jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript" + }, + contents: { + script: /javascript/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +}); + +// Handle cache's special case and global +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.getElementsByTagName( "head" )[ 0 ] || document.documentElement; + + return { + + send: function( _, callback ) { + + script = document.createElement( "script" ); + + script.async = "async"; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _, isAbort ) { + + if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( head && script.parentNode ) { + head.removeChild( script ); + } + + // Dereference the script + script = undefined; + + // Callback if not abort + if ( !isAbort ) { + callback( 200, "success" ); + } + } + }; + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + }, + + abort: function() { + if ( script ) { + script.onload( 0, 1 ); + } + } + }; + } +} ); + + + + +var // Next active xhr id + xhrId = jQuery.now(), + + // active xhrs + xhrs = {}, + + // #5280: see below + xhrUnloadAbortInstalled, + + // XHR used to determine supports properties + testXHR; + +// Create the request object +// (This is still attached to ajaxSettings for backward compatibility) +jQuery.ajaxSettings.xhr = window.ActiveXObject ? + /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function() { + if ( window.location.protocol !== "file:" ) { + try { + return new window.XMLHttpRequest(); + } catch( xhrError ) {} + } + + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch( activeError ) {} + } : + // For all other browsers, use the standard XMLHttpRequest object + function() { + return new window.XMLHttpRequest(); + }; + +// Test if we can create an xhr object +try { + testXHR = jQuery.ajaxSettings.xhr(); +} catch( xhrCreationException ) {} + +//Does this browser support XHR requests? +jQuery.support.ajax = !!testXHR; + +// Does this browser support crossDomain XHR requests +jQuery.support.cors = testXHR && ( "withCredentials" in testXHR ); + +// No need for the temporary xhr anymore +testXHR = undefined; + +// Create transport if the browser can provide an xhr +if ( jQuery.support.ajax ) { + + jQuery.ajaxTransport(function( s ) { + // Cross domain only allowed if supported through XMLHttpRequest + if ( !s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + send: function( headers, complete ) { + + // #5280: we need to abort on unload or IE will keep connections alive + if ( !xhrUnloadAbortInstalled ) { + + xhrUnloadAbortInstalled = 1; + + jQuery(window).bind( "unload", function() { + + // Abort all pending requests + jQuery.each( xhrs, function( _, xhr ) { + if ( xhr.onreadystatechange ) { + xhr.onreadystatechange( 1 ); + } + } ); + + } ); + } + + // Get a new xhr + var xhr = s.xhr(), + handle; + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open( s.type, s.url, s.async, s.username, s.password ); + } else { + xhr.open( s.type, s.url, s.async ); + } + + // Requested-With header + // Not set for crossDomain requests with no content + // (see why at http://trac.dojotoolkit.org/ticket/9486) + // Won't change header if already provided + if ( !( s.crossDomain && !s.hasContent ) && !headers["x-requested-with"] ) { + headers[ "x-requested-with" ] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + jQuery.each( headers, function( key, value ) { + xhr.setRequestHeader( key, value ); + } ); + } catch( _ ) {} + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send( ( s.hasContent && s.data ) || null ); + + // Listener + callback = function( _, isAbort ) { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = 0; + + // Do not keep as active anymore + if ( handle ) { + xhr.onreadystatechange = jQuery.noop; + delete xhrs[ handle ]; + } + + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + // Get info + var status = xhr.status, + statusText, + responseHeaders = xhr.getAllResponseHeaders(), + responses = {}, + xml = xhr.responseXML; + + // Construct response list + if ( xml && xml.documentElement /* #4958 */ ) { + responses.xml = xml; + } + responses.text = xhr.responseText; + + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch( e ) { + // We normalize with Webkit giving an empty statusText + statusText = ""; + } + + // Filter status for non standard behaviours + status = + // Opera returns 0 when it should be 304 + // Webkit returns 0 for failing cross-domain no matter the real status + status === 0 ? + ( + // Webkit, Firefox: filter out faulty cross-domain requests + !s.crossDomain || statusText ? + ( + // Opera: filter out real aborts #6060 + responseHeaders ? + 304 : + 0 + ) : + // We assume 302 but could be anything cross-domain related + 302 + ) : + ( + // IE sometimes returns 1223 when it should be 204 (see #1450) + status == 1223 ? + 204 : + status + ); + + // Call complete + complete( status, statusText, responses, responseHeaders ); + } + } + }; + + // if we're in sync mode or it's in cache + // and has been retrieved directly (IE6 & IE7) + // we need to manually fire the callback + if ( !s.async || xhr.readyState === 4 ) { + callback(); + } else { + // Add to list of active xhrs + handle = xhrId++; + xhrs[ handle ] = xhr; + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback(0,1); + } + } + }; + } + }); +} + + + + +var elemdisplay = {}, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, + timerId, + fxAttrs = [ + // height animations + [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], + // width animations + [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], + // opacity animations + [ "opacity" ] + ]; + +jQuery.fn.extend({ + show: function( speed, easing, callback ) { + var elem, display; + + if ( speed || speed === 0 ) { + return this.animate( genFx("show", 3), speed, easing, callback); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery._data(elem, "olddisplay") || ""; + } + } + + return this; + } + }, + + hide: function( speed, easing, callback ) { + if ( speed || speed === 0 ) { + return this.animate( genFx("hide", 3), speed, easing, callback); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + var display = jQuery.css( this[i], "display" ); + + if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { + jQuery._data( this[i], "olddisplay", display ); + } + } + + // Set the display of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + this[i].style.display = "none"; + } + + return this; + } + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + + toggle: function( fn, fn2, callback ) { + var bool = typeof fn === "boolean"; + + if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { + this._toggle.apply( this, arguments ); + + } else if ( fn == null || bool ) { + this.each(function() { + var state = bool ? fn : jQuery(this).is(":hidden"); + jQuery(this)[ state ? "show" : "hide" ](); + }); + + } else { + this.animate(genFx("toggle", 3), fn, fn2, callback); + } + + return this; + }, + + fadeTo: function( speed, to, easing, callback ) { + return this.filter(":hidden").css("opacity", 0).show().end() + .animate({opacity: to}, speed, easing, callback); + }, + + animate: function( prop, speed, easing, callback ) { + var optall = jQuery.speed(speed, easing, callback); + + if ( jQuery.isEmptyObject( prop ) ) { + return this.each( optall.complete ); + } + + return this[ optall.queue === false ? "each" : "queue" ](function() { + // XXX 'this' does not always have a nodeName when running the + // test suite + + var opt = jQuery.extend({}, optall), p, + isElement = this.nodeType === 1, + hidden = isElement && jQuery(this).is(":hidden"), + self = this; + + for ( p in prop ) { + var name = jQuery.camelCase( p ); + + if ( p !== name ) { + prop[ name ] = prop[ p ]; + delete prop[ p ]; + p = name; + } + + if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { + return opt.complete.call(this); + } + + if ( isElement && ( p === "height" || p === "width" ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height + // animated + if ( jQuery.css( this, "display" ) === "inline" && + jQuery.css( this, "float" ) === "none" ) { + if ( !jQuery.support.inlineBlockNeedsLayout ) { + this.style.display = "inline-block"; + + } else { + var display = defaultDisplay(this.nodeName); + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( display === "inline" ) { + this.style.display = "inline-block"; + + } else { + this.style.display = "inline"; + this.style.zoom = 1; + } + } + } + } + + if ( jQuery.isArray( prop[p] ) ) { + // Create (if needed) and add to specialEasing + (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; + prop[p] = prop[p][0]; + } + } + + if ( opt.overflow != null ) { + this.style.overflow = "hidden"; + } + + opt.curAnim = jQuery.extend({}, prop); + + jQuery.each( prop, function( name, val ) { + var e = new jQuery.fx( self, opt, name ); + + if ( rfxtypes.test(val) ) { + e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + + } else { + var parts = rfxnum.exec(val), + start = e.cur() || 0; + + if ( parts ) { + var end = parseFloat( parts[2] ), + unit = parts[3] || "px"; + + // We need to compute starting value + if ( unit !== "px" ) { + jQuery.style( self, name, (end || 1) + unit); + start = ((end || 1) / e.cur()) * start; + jQuery.style( self, name, start + unit); + } + + // If a +=/-= token was provided, we're doing a relative animation + if ( parts[1] ) { + end = ((parts[1] === "-=" ? -1 : 1) * end) + start; + } + + e.custom( start, end, unit ); + + } else { + e.custom( start, val, "" ); + } + } + }); + + // For JS strict compliance + return true; + }); + }, + + stop: function( clearQueue, gotoEnd ) { + var timers = jQuery.timers; + + if ( clearQueue ) { + this.queue([]); + } + + this.each(function() { + // go in reverse order so anything added to the queue during the loop is ignored + for ( var i = timers.length - 1; i >= 0; i-- ) { + if ( timers[i].elem === this ) { + if (gotoEnd) { + // force the next step to be the last + timers[i](true); + } + + timers.splice(i, 1); + } + } + }); + + // start the next in the queue if the last step wasn't forced + if ( !gotoEnd ) { + this.dequeue(); + } + + return this; + } + +}); + +function genFx( type, num ) { + var obj = {}; + + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { + obj[ this ] = type; + }); + + return obj; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show", 1), + slideUp: genFx("hide", 1), + slideToggle: genFx("toggle", 1), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.extend({ + speed: function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction(easing) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; + + // Queueing + opt.old = opt.complete; + opt.complete = function() { + if ( opt.queue !== false ) { + jQuery(this).dequeue(); + } + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + } + }, + + timers: [], + + fx: function( elem, options, prop ) { + this.options = options; + this.elem = elem; + this.prop = prop; + + if ( !options.orig ) { + options.orig = {}; + } + } + +}); + +jQuery.fx.prototype = { + // Simple function for setting a style value + update: function() { + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); + }, + + // Get the current size + cur: function() { + if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { + return this.elem[ this.prop ]; + } + + var r = parseFloat( jQuery.css( this.elem, this.prop ) ); + return r || 0; + }, + + // Start an animation from one number to another + custom: function( from, to, unit ) { + var self = this, + fx = jQuery.fx; + + this.startTime = jQuery.now(); + this.start = from; + this.end = to; + this.unit = unit || this.unit || "px"; + this.now = this.start; + this.pos = this.state = 0; + + function t( gotoEnd ) { + return self.step(gotoEnd); + } + + t.elem = this.elem; + + if ( t() && jQuery.timers.push(t) && !timerId ) { + timerId = setInterval(fx.tick, fx.interval); + } + }, + + // Simple 'show' function + show: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.show = true; + + // Begin the animation + // Make sure that we start at a small width/height to avoid any + // flash of content + this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); + + // Start by showing the element + jQuery( this.elem ).show(); + }, + + // Simple 'hide' function + hide: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.hide = true; + + // Begin the animation + this.custom(this.cur(), 0); + }, + + // Each step of an animation + step: function( gotoEnd ) { + var t = jQuery.now(), done = true; + + if ( gotoEnd || t >= this.options.duration + this.startTime ) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[ this.prop ] = true; + + for ( var i in this.options.curAnim ) { + if ( this.options.curAnim[i] !== true ) { + done = false; + } + } + + if ( done ) { + // Reset the overflow + if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { + var elem = this.elem, + options = this.options; + + jQuery.each( [ "", "X", "Y" ], function (index, value) { + elem.style[ "overflow" + value ] = options.overflow[index]; + } ); + } + + // Hide the element if the "hide" operation was done + if ( this.options.hide ) { + jQuery(this.elem).hide(); + } + + // Reset the properties, if the item has been hidden or shown + if ( this.options.hide || this.options.show ) { + for ( var p in this.options.curAnim ) { + jQuery.style( this.elem, p, this.options.orig[p] ); + } + } + + // Execute the complete function + this.options.complete.call( this.elem ); + } + + return false; + + } else { + var n = t - this.startTime; + this.state = n / this.options.duration; + + // Perform the easing function, defaults to swing + var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; + var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); + this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + + // Perform the next step of the animation + this.update(); + } + + return true; + } +}; + +jQuery.extend( jQuery.fx, { + tick: function() { + var timers = jQuery.timers; + + for ( var i = 0; i < timers.length; i++ ) { + if ( !timers[i]() ) { + timers.splice(i--, 1); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + }, + + interval: 13, + + stop: function() { + clearInterval( timerId ); + timerId = null; + }, + + speeds: { + slow: 600, + fast: 200, + // Default speed + _default: 400 + }, + + step: { + opacity: function( fx ) { + jQuery.style( fx.elem, "opacity", fx.now ); + }, + + _default: function( fx ) { + if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { + fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; + } else { + fx.elem[ fx.prop ] = fx.now; + } + } + } +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} + +function defaultDisplay( nodeName ) { + if ( !elemdisplay[ nodeName ] ) { + var elem = jQuery("<" + nodeName + ">").appendTo("body"), + display = elem.css("display"); + + elem.remove(); + + if ( display === "none" || display === "" ) { + display = "block"; + } + + elemdisplay[ nodeName ] = display; + } + + return elemdisplay[ nodeName ]; +} + + + + +var rtable = /^t(?:able|d|h)$/i, + rroot = /^(?:body|html)$/i; + +if ( "getBoundingClientRect" in document.documentElement ) { + jQuery.fn.offset = function( options ) { + var elem = this[0], box; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + try { + box = elem.getBoundingClientRect(); + } catch(e) {} + + var doc = elem.ownerDocument, + docElem = doc.documentElement; + + // Make sure we're not dealing with a disconnected DOM node + if ( !box || !jQuery.contains( docElem, elem ) ) { + return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; + } + + var body = doc.body, + win = getWindow(doc), + clientTop = docElem.clientTop || body.clientTop || 0, + clientLeft = docElem.clientLeft || body.clientLeft || 0, + scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), + scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), + top = box.top + scrollTop - clientTop, + left = box.left + scrollLeft - clientLeft; + + return { top: top, left: left }; + }; + +} else { + jQuery.fn.offset = function( options ) { + var elem = this[0]; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + jQuery.offset.initialize(); + + var computedStyle, + offsetParent = elem.offsetParent, + prevOffsetParent = elem, + doc = elem.ownerDocument, + docElem = doc.documentElement, + body = doc.body, + defaultView = doc.defaultView, + prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, + top = elem.offsetTop, + left = elem.offsetLeft; + + while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + break; + } + + computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; + top -= elem.scrollTop; + left -= elem.scrollLeft; + + if ( elem === offsetParent ) { + top += elem.offsetTop; + left += elem.offsetLeft; + + if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevOffsetParent = offsetParent; + offsetParent = elem.offsetParent; + } + + if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevComputedStyle = computedStyle; + } + + if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { + top += body.offsetTop; + left += body.offsetLeft; + } + + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + top += Math.max( docElem.scrollTop, body.scrollTop ); + left += Math.max( docElem.scrollLeft, body.scrollLeft ); + } + + return { top: top, left: left }; + }; +} + +jQuery.offset = { + initialize: function() { + var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, + html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; + + jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); + + container.innerHTML = html; + body.insertBefore( container, body.firstChild ); + innerDiv = container.firstChild; + checkDiv = innerDiv.firstChild; + td = innerDiv.nextSibling.firstChild.firstChild; + + this.doesNotAddBorder = (checkDiv.offsetTop !== 5); + this.doesAddBorderForTableAndCells = (td.offsetTop === 5); + + checkDiv.style.position = "fixed"; + checkDiv.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); + checkDiv.style.position = checkDiv.style.top = ""; + + innerDiv.style.overflow = "hidden"; + innerDiv.style.position = "relative"; + + this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); + + this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); + + body.removeChild( container ); + body = container = innerDiv = checkDiv = table = td = null; + jQuery.offset.initialize = jQuery.noop; + }, + + bodyOffset: function( body ) { + var top = body.offsetTop, + left = body.offsetLeft; + + jQuery.offset.initialize(); + + if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.css(body, "marginTop") ) || 0; + left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is absolute + if ( calculatePosition ) { + curPosition = curElem.position(); + } + + curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; + curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if (options.top != null) { + props.top = (options.top - curOffset.top) + curTop; + } + if (options.left != null) { + props.left = (options.left - curOffset.left) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + position: function() { + if ( !this[0] ) { + return null; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; + offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; + parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( ["Left", "Top"], function( i, name ) { + var method = "scroll" + name; + + jQuery.fn[ method ] = function(val) { + var elem = this[0], win; + + if ( !elem ) { + return null; + } + + if ( val !== undefined ) { + // Set the scroll offset + return this.each(function() { + win = getWindow( this ); + + if ( win ) { + win.scrollTo( + !i ? val : jQuery(win).scrollLeft(), + i ? val : jQuery(win).scrollTop() + ); + + } else { + this[ method ] = val; + } + }); + } else { + win = getWindow( elem ); + + // Return the scroll offset + return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : + jQuery.support.boxModel && win.document.documentElement[ method ] || + win.document.body[ method ] : + elem[ method ]; + } + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} + + + + +// Create innerHeight, innerWidth, outerHeight and outerWidth methods +jQuery.each([ "Height", "Width" ], function( i, name ) { + + var type = name.toLowerCase(); + + // innerHeight and innerWidth + jQuery.fn["inner" + name] = function() { + return this[0] ? + parseFloat( jQuery.css( this[0], type, "padding" ) ) : + null; + }; + + // outerHeight and outerWidth + jQuery.fn["outer" + name] = function( margin ) { + return this[0] ? + parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) : + null; + }; + + jQuery.fn[ type ] = function( size ) { + // Get window width or height + var elem = this[0]; + if ( !elem ) { + return size == null ? null : this; + } + + if ( jQuery.isFunction( size ) ) { + return this.each(function( i ) { + var self = jQuery( this ); + self[ type ]( size.call( this, i, self[ type ]() ) ); + }); + } + + if ( jQuery.isWindow( elem ) ) { + // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode + // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat + var docElemProp = elem.document.documentElement[ "client" + name ]; + return elem.document.compatMode === "CSS1Compat" && docElemProp || + elem.document.body[ "client" + name ] || docElemProp; + + // Get document width or height + } else if ( elem.nodeType === 9 ) { + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + return Math.max( + elem.documentElement["client" + name], + elem.body["scroll" + name], elem.documentElement["scroll" + name], + elem.body["offset" + name], elem.documentElement["offset" + name] + ); + + // Get or set width or height on the element + } else if ( size === undefined ) { + var orig = jQuery.css( elem, type ), + ret = parseFloat( orig ); + + return jQuery.isNaN( ret ) ? orig : ret; + + // Set the width or height on the element (default to pixels if value is unitless) + } else { + return this.css( type, typeof size === "string" ? size : size + "px" ); + } + }; + +}); + + +})(window); diff --git a/documentation/build/html/_static/minus.png b/documentation/build/html/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..da1c5620d10c047525a467a425abe9ff5269cfc2 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1SHkYJtzcHoCO|{#XvD(5N2eUHAey{$X?>< z>&kweokM_|(Po{+Q=kw>iEBiObAE1aYF-J$w=>iB1I2<oT^vIsE+^X*KjUGJJ8<a0 zfdz{eHHE&rzrX(bySvGUL|lavlN4AuRwpzDOq(`sMv;5Joa+jUx<3|oWPN;mPUJ0` pW__Wi<5+59Lc)&n_i}Q^3>R$WLpMkF=>bh=@O1TaS?83{1OVknK<NMg literal 0 HcmV?d00001 diff --git a/documentation/build/html/_static/plus.png b/documentation/build/html/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..b3cb37425ea68b39ffa7b2e5fb69161275a87541 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1SHkYJtzcHoCO|{#XvD(5N2eUHAey{$X?>< z>&kweokM`jkU7Va11Q8%;u=xnoS&PUnpeW`?aZ|OK(QcC7sn8Z%gHvy&v=;Q4jejg zV8NnAO`-4Z@2~&<?ryS^@YXF`T!a&o6j(S`Cmb}9IcHb(MZ@Xn$H&JXUMl#uzyAM< o7knL=1-mEi3=josIGoJJAh%tCVFz!`HlXPYp00i_>zopr02WF_WB>pF literal 0 HcmV?d00001 diff --git a/documentation/build/html/_static/pygments.css b/documentation/build/html/_static/pygments.css new file mode 100644 index 00000000..1f2d2b61 --- /dev/null +++ b/documentation/build/html/_static/pygments.css @@ -0,0 +1,61 @@ +.hll { background-color: #ffffcc } +.c { color: #408090; font-style: italic } /* Comment */ +.err { border: 1px solid #FF0000 } /* Error */ +.k { color: #007020; font-weight: bold } /* Keyword */ +.o { color: #666666 } /* Operator */ +.cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.cp { color: #007020 } /* Comment.Preproc */ +.c1 { color: #408090; font-style: italic } /* Comment.Single */ +.cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.gd { color: #A00000 } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #FF0000 } /* Generic.Error */ +.gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.gi { color: #00A000 } /* Generic.Inserted */ +.go { color: #303030 } /* Generic.Output */ +.gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.gt { color: #0040D0 } /* Generic.Traceback */ +.kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.kp { color: #007020 } /* Keyword.Pseudo */ +.kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.kt { color: #902000 } /* Keyword.Type */ +.m { color: #208050 } /* Literal.Number */ +.s { color: #4070a0 } /* Literal.String */ +.na { color: #4070a0 } /* Name.Attribute */ +.nb { color: #007020 } /* Name.Builtin */ +.nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.no { color: #60add5 } /* Name.Constant */ +.nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.ne { color: #007020 } /* Name.Exception */ +.nf { color: #06287e } /* Name.Function */ +.nl { color: #002070; font-weight: bold } /* Name.Label */ +.nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.nt { color: #062873; font-weight: bold } /* Name.Tag */ +.nv { color: #bb60d5 } /* Name.Variable */ +.ow { color: #007020; font-weight: bold } /* Operator.Word */ +.w { color: #bbbbbb } /* Text.Whitespace */ +.mf { color: #208050 } /* Literal.Number.Float */ +.mh { color: #208050 } /* Literal.Number.Hex */ +.mi { color: #208050 } /* Literal.Number.Integer */ +.mo { color: #208050 } /* Literal.Number.Oct */ +.sb { color: #4070a0 } /* Literal.String.Backtick */ +.sc { color: #4070a0 } /* Literal.String.Char */ +.sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.s2 { color: #4070a0 } /* Literal.String.Double */ +.se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.sh { color: #4070a0 } /* Literal.String.Heredoc */ +.si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.sx { color: #c65d09 } /* Literal.String.Other */ +.sr { color: #235388 } /* Literal.String.Regex */ +.s1 { color: #4070a0 } /* Literal.String.Single */ +.ss { color: #517918 } /* Literal.String.Symbol */ +.bp { color: #007020 } /* Name.Builtin.Pseudo */ +.vc { color: #bb60d5 } /* Name.Variable.Class */ +.vg { color: #bb60d5 } /* Name.Variable.Global */ +.vi { color: #bb60d5 } /* Name.Variable.Instance */ +.il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/documentation/build/html/_static/searchtools.js b/documentation/build/html/_static/searchtools.js new file mode 100644 index 00000000..5cbfe004 --- /dev/null +++ b/documentation/build/html/_static/searchtools.js @@ -0,0 +1,518 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for the full-text search. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurance, the + * latter for highlighting it. + */ + +jQuery.makeSearchSummary = function(text, keywords, hlwords) { + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('<div class="context"></div>').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; +} + +/** + * Porter Stemmer + */ +var PorterStemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, success: null, + dataType: "script", cache: true}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (var i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out); + this.dots = $('<span></span>').appendTo(this.title); + this.status = $('<p style="display: none"></p>').appendTo(this.out); + this.output = $('<ul class="search"/>').appendTo(this.out); + + $('#search-progress').text(_('Preparing search...')); + this.startPulse(); + + // index already loaded, the browser was quick! + if (this.hasIndex()) + this.query(query); + else + this.deferQuery(query); + }, + + query : function(query) { + var stopwords = ['and', 'then', 'into', 'it', 'as', 'are', 'in', + 'if', 'for', 'no', 'there', 'their', 'was', 'is', + 'be', 'to', 'that', 'but', 'they', 'not', 'such', + 'with', 'by', 'a', 'on', 'these', 'of', 'will', + 'this', 'near', 'the', 'or', 'at']; + + // stem the searchterms and add them to the correct list + var stemmer = new PorterStemmer(); + var searchterms = []; + var excluded = []; + var hlterms = []; + var tmp = query.split(/\s+/); + var object = (tmp.length == 1) ? tmp[0].toLowerCase() : null; + for (var i = 0; i < tmp.length; i++) { + if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/) || + tmp[i] == "") { + // skip this "word" + continue; + } + // stem the word + var word = stemmer.stemWord(tmp[i]).toLowerCase(); + // select the correct list + if (word[0] == '-') { + var toAppend = excluded; + word = word.substr(1); + } + else { + var toAppend = searchterms; + hlterms.push(tmp[i].toLowerCase()); + } + // only add if not already in the list + if (!$.contains(toAppend, word)) + toAppend.push(word); + }; + var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" ")); + + // console.debug('SEARCH: searching for:'); + // console.info('required: ', searchterms); + // console.info('excluded: ', excluded); + + // prepare search + var filenames = this._index.filenames; + var titles = this._index.titles; + var terms = this._index.terms; + var objects = this._index.objects; + var objtypes = this._index.objtypes; + var objnames = this._index.objnames; + var fileMap = {}; + var files = null; + // different result priorities + var importantResults = []; + var objectResults = []; + var regularResults = []; + var unimportantResults = []; + $('#search-progress').empty(); + + // lookup as object + if (object != null) { + for (var prefix in objects) { + for (var name in objects[prefix]) { + var fullname = (prefix ? prefix + '.' : '') + name; + if (fullname.toLowerCase().indexOf(object) > -1) { + match = objects[prefix][name]; + descr = objnames[match[1]] + _(', in ') + titles[match[0]]; + // XXX the generated anchors are not generally correct + // XXX there may be custom prefixes + result = [filenames[match[0]], fullname, '#'+fullname, descr]; + switch (match[2]) { + case 1: objectResults.push(result); break; + case 0: importantResults.push(result); break; + case 2: unimportantResults.push(result); break; + } + } + } + } + } + + // sort results descending + objectResults.sort(function(a, b) { + return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); + }); + + importantResults.sort(function(a, b) { + return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); + }); + + unimportantResults.sort(function(a, b) { + return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); + }); + + + // perform the search on the required terms + for (var i = 0; i < searchterms.length; i++) { + var word = searchterms[i]; + // no match but word was a required one + if ((files = terms[word]) == null) + break; + if (files.length == undefined) { + files = [files]; + } + // create the mapping + for (var j = 0; j < files.length; j++) { + var file = files[j]; + if (file in fileMap) + fileMap[file].push(word); + else + fileMap[file] = [word]; + } + } + + // now check if the files don't contain excluded terms + for (var file in fileMap) { + var valid = true; + + // check if all requirements are matched + if (fileMap[file].length != searchterms.length) + continue; + + // ensure that none of the excluded terms is in the + // search result. + for (var i = 0; i < excluded.length; i++) { + if (terms[excluded[i]] == file || + $.contains(terms[excluded[i]] || [], file)) { + valid = false; + break; + } + } + + // if we have still a valid result we can add it + // to the result list + if (valid) + regularResults.push([filenames[file], titles[file], '', null]); + } + + // delete unused variables in order to not waste + // memory until list is retrieved completely + delete filenames, titles, terms; + + // now sort the regular results descending by title + regularResults.sort(function(a, b) { + var left = a[1].toLowerCase(); + var right = b[1].toLowerCase(); + return (left > right) ? -1 : ((left < right) ? 1 : 0); + }); + + // combine all results + var results = unimportantResults.concat(regularResults) + .concat(objectResults).concat(importantResults); + + // print the results + var resultCount = results.length; + function displayNextItem() { + // results left, load the summary and display it + if (results.length) { + var item = results.pop(); + var listItem = $('<li style="display:none"></li>'); + if (DOCUMENTATION_OPTIONS.FILE_SUFFIX == '') { + // dirhtml builder + var dirname = item[0] + '/'; + if (dirname.match(/\/index\/$/)) { + dirname = dirname.substring(0, dirname.length-6); + } else if (dirname == 'index/') { + dirname = ''; + } + listItem.append($('<a/>').attr('href', + DOCUMENTATION_OPTIONS.URL_ROOT + dirname + + highlightstring + item[2]).html(item[1])); + } else { + // normal html builders + listItem.append($('<a/>').attr('href', + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX + + highlightstring + item[2]).html(item[1])); + } + if (item[3]) { + listItem.append($('<span> (' + item[3] + ')</span>')); + Search.output.append(listItem); + listItem.slideDown(5, function() { + displayNextItem(); + }); + } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) { + $.get(DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + + item[0] + '.txt', function(data) { + if (data != '') { + listItem.append($.makeSearchSummary(data, searchterms, hlterms)); + Search.output.append(listItem); + } + listItem.slideDown(5, function() { + displayNextItem(); + }); + }); + } else { + // no source available, just display title + Search.output.append(listItem); + listItem.slideDown(5, function() { + displayNextItem(); + }); + } + } + // search finished, update title and status message + else { + Search.stopPulse(); + Search.title.text(_('Search Results')); + if (!resultCount) + Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.')); + else + Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount)); + Search.status.fadeIn(500); + } + } + displayNextItem(); + } +} + +$(document).ready(function() { + Search.init(); +}); diff --git a/documentation/build/html/_static/sidebar.js b/documentation/build/html/_static/sidebar.js new file mode 100644 index 00000000..be206ede --- /dev/null +++ b/documentation/build/html/_static/sidebar.js @@ -0,0 +1,147 @@ +/* + * sidebar.js + * ~~~~~~~~~~ + * + * This script makes the Sphinx sidebar collapsible. + * + * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds + * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton + * used to collapse and expand the sidebar. + * + * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden + * and the width of the sidebar and the margin-left of the document + * are decreased. When the sidebar is expanded the opposite happens. + * This script saves a per-browser/per-session cookie used to + * remember the position of the sidebar among the pages. + * Once the browser is closed the cookie is deleted and the position + * reset to the default (expanded). + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +$(function() { + // global elements used by the functions. + // the 'sidebarbutton' element is defined as global after its + // creation, in the add_sidebar_button function + var bodywrapper = $('.bodywrapper'); + var sidebar = $('.sphinxsidebar'); + var sidebarwrapper = $('.sphinxsidebarwrapper'); + + // original margin-left of the bodywrapper and width of the sidebar + // with the sidebar expanded + var bw_margin_expanded = bodywrapper.css('margin-left'); + var ssb_width_expanded = sidebar.width(); + + // margin-left of the bodywrapper and width of the sidebar + // with the sidebar collapsed + var bw_margin_collapsed = '.8em'; + var ssb_width_collapsed = '.8em'; + + // colors used by the current theme + var dark_color = $('.related').css('background-color'); + var light_color = $('.document').css('background-color'); + + function sidebar_is_collapsed() { + return sidebarwrapper.is(':not(:visible)'); + } + + function toggle_sidebar() { + if (sidebar_is_collapsed()) + expand_sidebar(); + else + collapse_sidebar(); + } + + function collapse_sidebar() { + sidebarwrapper.hide(); + sidebar.css('width', ssb_width_collapsed); + bodywrapper.css('margin-left', bw_margin_collapsed); + sidebarbutton.css({ + 'margin-left': '0', + 'height': bodywrapper.height() + }); + sidebarbutton.find('span').text('»'); + sidebarbutton.attr('title', _('Expand sidebar')); + document.cookie = 'sidebar=collapsed'; + } + + function expand_sidebar() { + bodywrapper.css('margin-left', bw_margin_expanded); + sidebar.css('width', ssb_width_expanded); + sidebarwrapper.show(); + sidebarbutton.css({ + 'margin-left': ssb_width_expanded-12, + 'height': bodywrapper.height() + }); + sidebarbutton.find('span').text('«'); + sidebarbutton.attr('title', _('Collapse sidebar')); + document.cookie = 'sidebar=expanded'; + } + + function add_sidebar_button() { + sidebarwrapper.css({ + 'float': 'left', + 'margin-right': '0', + 'width': ssb_width_expanded - 28 + }); + // create the button + sidebar.append( + '<div id="sidebarbutton"><span>«</span></div>' + ); + var sidebarbutton = $('#sidebarbutton'); + // find the height of the viewport to center the '<<' in the page + var viewport_height; + if (window.innerHeight) + viewport_height = window.innerHeight; + else + viewport_height = $(window).height(); + sidebarbutton.find('span').css({ + 'display': 'block', + 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 + }); + + sidebarbutton.click(toggle_sidebar); + sidebarbutton.attr('title', _('Collapse sidebar')); + sidebarbutton.css({ + 'color': '#FFFFFF', + 'border-left': '1px solid ' + dark_color, + 'font-size': '1.2em', + 'cursor': 'pointer', + 'height': bodywrapper.height(), + 'padding-top': '1px', + 'margin-left': ssb_width_expanded - 12 + }); + + sidebarbutton.hover( + function () { + $(this).css('background-color', dark_color); + }, + function () { + $(this).css('background-color', light_color); + } + ); + } + + function set_position_from_cookie() { + if (!document.cookie) + return; + var items = document.cookie.split(';'); + for(var k=0; k<items.length; k++) { + var key_val = items[k].split('='); + var key = key_val[0]; + if (key == 'sidebar') { + var value = key_val[1]; + if ((value == 'collapsed') && (!sidebar_is_collapsed())) + collapse_sidebar(); + else if ((value == 'expanded') && (sidebar_is_collapsed())) + expand_sidebar(); + } + } + } + + add_sidebar_button(); + var sidebarbutton = $('#sidebarbutton'); + set_position_from_cookie(); +}); \ No newline at end of file diff --git a/documentation/build/html/_static/underscore.js b/documentation/build/html/_static/underscore.js new file mode 100644 index 00000000..9146e086 --- /dev/null +++ b/documentation/build/html/_static/underscore.js @@ -0,0 +1,16 @@ +(function(){var j=this,n=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,o=Array.prototype.unshift,p=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e<f;e++)c.call(d, +a[e],e,a);else{var g=b.keys(a);f=g.length;for(e=0;e<f;e++)c.call(d,a[g[e]],g[e],a)}}catch(h){if(h!=m)throw h;}return a};b.map=function(a,c,d){if(a&&b.isFunction(a.map))return a.map(c,d);var e=[];b.each(a,function(f,g,h){e.push(c.call(d,f,g,h))});return e};b.reduce=function(a,c,d,e){if(a&&b.isFunction(a.reduce))return a.reduce(b.bind(d,e),c);b.each(a,function(f,g,h){c=d.call(e,c,f,g,h)});return c};b.reduceRight=function(a,c,d,e){if(a&&b.isFunction(a.reduceRight))return a.reduceRight(b.bind(d,e),c); +var f=b.clone(b.toArray(a)).reverse();b.each(f,function(g,h){c=d.call(e,c,g,h,a)});return c};b.detect=function(a,c,d){var e;b.each(a,function(f,g,h){if(c.call(d,f,g,h)){e=f;b.breakLoop()}});return e};b.select=function(a,c,d){if(a&&b.isFunction(a.filter))return a.filter(c,d);var e=[];b.each(a,function(f,g,h){c.call(d,f,g,h)&&e.push(f)});return e};b.reject=function(a,c,d){var e=[];b.each(a,function(f,g,h){!c.call(d,f,g,h)&&e.push(f)});return e};b.all=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.every))return a.every(c, +d);var e=true;b.each(a,function(f,g,h){(e=e&&c.call(d,f,g,h))||b.breakLoop()});return e};b.any=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.some))return a.some(c,d);var e=false;b.each(a,function(f,g,h){if(e=c.call(d,f,g,h))b.breakLoop()});return e};b.include=function(a,c){if(b.isArray(a))return b.indexOf(a,c)!=-1;var d=false;b.each(a,function(e){if(d=e===c)b.breakLoop()});return d};b.invoke=function(a,c){var d=b.rest(arguments,2);return b.map(a,function(e){return(c?e[c]:e).apply(e,d)})};b.pluck= +function(a,c){return b.map(a,function(d){return d[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g>=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g<e.computed&&(e={value:f,computed:g})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a, +function(e,f,g){return{value:e,criteria:c.call(d,e,f,g)}}).sort(function(e,f){e=e.criteria;f=f.criteria;return e<f?-1:e>f?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?(e=g+1):(f=g)}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return k.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=function(a,c,d){return c&&!d?k.call(a, +0,c):a[0]};b.rest=function(a,c,d){return k.call(a,b.isUndefined(c)||d?1:c)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.select(a,function(c){return!!c})};b.flatten=function(a){return b.reduce(a,[],function(c,d){if(b.isArray(d))return c.concat(b.flatten(d));c.push(d);return c})};b.without=function(a){var c=b.rest(arguments);return b.select(a,function(d){return!b.include(c,d)})};b.uniq=function(a,c){return b.reduce(a,[],function(d,e,f){if(0==f||(c===true?b.last(d)!=e:!b.include(d, +e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return b.select(b.uniq(a),function(d){return b.all(c,function(e){return b.indexOf(e,d)>=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e<c;e++)d[e]=b.pluck(a,String(e));return d};b.indexOf=function(a,c){if(a.indexOf)return a.indexOf(c);for(var d=0,e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,c){if(a.lastIndexOf)return a.lastIndexOf(c);for(var d= +a.length;d--;)if(a[d]===c)return d;return-1};b.range=function(a,c,d){var e=b.toArray(arguments),f=e.length<=1;a=f?0:e[0];c=f?e[0]:e[1];d=e[2]||1;e=Math.ceil((c-a)/d);if(e<=0)return[];e=new Array(e);f=a;for(var g=0;1;f+=d){if((d>0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)}); +return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length); +var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false; +if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length== +0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return p.call(a)==="[object Number]"};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&& +a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g, +" ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');");return c?a(c):a};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){var d=b.toArray(arguments); +o.call(d,this._wrapped);return l(c.apply(b,d),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/documentation/build/html/apireference.html b/documentation/build/html/apireference.html new file mode 100644 index 00000000..81eae398 --- /dev/null +++ b/documentation/build/html/apireference.html @@ -0,0 +1,178 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>API Reference — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="Pyqtgraph’s Helper Functions" href="functions.html" /> + <link rel="prev" title="Rapid GUI prototyping" href="parametertree.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="functions.html" title="Pyqtgraph’s Helper Functions" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="parametertree.html" title="Rapid GUI prototyping" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="api-reference"> +<h1>API Reference<a class="headerlink" href="#api-reference" title="Permalink to this headline">¶</a></h1> +<p>Contents:</p> +<div class="toctree-wrapper compound"> +<ul> +<li class="toctree-l1"><a class="reference internal" href="functions.html">Pyqtgraph’s Helper Functions</a><ul> +<li class="toctree-l2"><a class="reference internal" href="functions.html#simple-data-display-functions">Simple Data Display Functions</a></li> +<li class="toctree-l2"><a class="reference internal" href="functions.html#color-pen-and-brush-functions">Color, Pen, and Brush Functions</a></li> +<li class="toctree-l2"><a class="reference internal" href="functions.html#data-slicing">Data Slicing</a></li> +<li class="toctree-l2"><a class="reference internal" href="functions.html#si-unit-conversion-functions">SI Unit Conversion Functions</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="graphicsItems/index.html">Pyqtgraph’s Graphics Items</a><ul> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/plotdataitem.html">PlotDataItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/plotcurveitem.html">PlotCurveItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/scatterplotitem.html">ScatterPlotItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/plotitem.html">PlotItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/imageitem.html">ImageItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/viewbox.html">ViewBox</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/linearregionitem.html">LinearRegionItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/infiniteline.html">InfiniteLine</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/roi.html">ROI</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/graphicslayout.html">GraphicsLayout</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/axisitem.html">AxisItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/arrowitem.html">ArrowItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/curvepoint.html">CurvePoint</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/curvearrow.html">CurveArrow</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/griditem.html">GridItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/scalebar.html">ScaleBar</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/labelitem.html">LabelItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/vtickgroup.html">VTickGroup</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/gradienteditoritem.html">GradientEditorItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/histogramlutitem.html">HistogramLUTItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/gradientlegend.html">GradientLegend</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/buttonitem.html">ButtonItem</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/graphicsobject.html">GraphicsObject</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/graphicswidget.html">GraphicsWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/uigraphicsitem.html">UIGraphicsItem</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="widgets/index.html">Pyqtgraph’s Widgets</a><ul> +<li class="toctree-l2"><a class="reference internal" href="widgets/plotwidget.html">PlotWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/imageview.html">ImageView</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/datatreewidget.html">DataTreeWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/checktable.html">CheckTable</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/tablewidget.html">TableWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/gradientwidget.html">GradientWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/colorbutton.html">ColorButton</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/graphicslayoutwidget.html">GraphicsLayoutWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/dockarea.html">dockarea module</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/parametertree.html">parametertree module</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/histogramlutwidget.html">HistogramLUTWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/progressdialog.html">ProgressDialog</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/spinbox.html">SpinBox</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/filedialog.html">FileDialog</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/graphicsview.html">GraphicsView</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/joystickbutton.html">JoystickButton</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/multiplotwidget.html">MultiPlotWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/treewidget.html">TreeWidget</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/verticallabel.html">VerticalLabel</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/rawimagewidget.html">RawImageWidget</a></li> +</ul> +</li> +</ul> +</div> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="parametertree.html" + title="previous chapter">Rapid GUI prototyping</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="functions.html" + title="next chapter">Pyqtgraph’s Helper Functions</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/apireference.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="functions.html" title="Pyqtgraph’s Helper Functions" + >next</a> |</li> + <li class="right" > + <a href="parametertree.html" title="Rapid GUI prototyping" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/functions.html b/documentation/build/html/functions.html new file mode 100644 index 00000000..bc11f5d3 --- /dev/null +++ b/documentation/build/html/functions.html @@ -0,0 +1,341 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Pyqtgraph’s Helper Functions — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="up" title="API Reference" href="apireference.html" /> + <link rel="next" title="Pyqtgraph’s Graphics Items" href="graphicsItems/index.html" /> + <link rel="prev" title="API Reference" href="apireference.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicsItems/index.html" title="Pyqtgraph’s Graphics Items" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="apireference.html" title="API Reference" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="apireference.html" accesskey="U">API Reference</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="pyqtgraph-s-helper-functions"> +<h1>Pyqtgraph’s Helper Functions<a class="headerlink" href="#pyqtgraph-s-helper-functions" title="Permalink to this headline">¶</a></h1> +<div class="section" id="simple-data-display-functions"> +<h2>Simple Data Display Functions<a class="headerlink" href="#simple-data-display-functions" title="Permalink to this headline">¶</a></h2> +<dl class="function"> +<dt id="pyqtgraph.plot"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">plot</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="reference internal" href="_modules/pyqtgraph.html#plot"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#pyqtgraph.plot" title="Permalink to this definition">¶</a></dt> +<dd><div class="line-block"> +<div class="line">Create and return a PlotWindow (this is just a window with PlotWidget inside), plot data in it.</div> +<div class="line">Accepts a <em>title</em> argument to set the title of the window.</div> +<div class="line">All other arguments are used to plot data. (see <a class="reference internal" href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.plot" title="pyqtgraph.PlotItem.plot"><tt class="xref py py-func docutils literal"><span class="pre">PlotItem.plot()</span></tt></a>)</div> +</div> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.image"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">image</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="reference internal" href="_modules/pyqtgraph.html#image"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#pyqtgraph.image" title="Permalink to this definition">¶</a></dt> +<dd><div class="line-block"> +<div class="line">Create and return an ImageWindow (this is just a window with ImageView widget inside), show image data inside.</div> +<div class="line">Will show 2D or 3D image data.</div> +<div class="line">Accepts a <em>title</em> argument to set the title of the window.</div> +<div class="line">All other arguments are used to show data. (see <a class="reference internal" href="widgets/imageview.html#pyqtgraph.ImageView.setImage" title="pyqtgraph.ImageView.setImage"><tt class="xref py py-func docutils literal"><span class="pre">ImageView.setImage()</span></tt></a>)</div> +</div> +</dd></dl> + +</div> +<div class="section" id="color-pen-and-brush-functions"> +<h2>Color, Pen, and Brush Functions<a class="headerlink" href="#color-pen-and-brush-functions" title="Permalink to this headline">¶</a></h2> +<p>Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and fill shapes. These classes are highly capable but somewhat awkward to use. Pyqtgraph offers the functions <a class="reference internal" href="#pyqtgraph.mkColor" title="pyqtgraph.mkColor"><tt class="xref py py-func docutils literal"><span class="pre">mkColor()</span></tt></a>, <a class="reference internal" href="#pyqtgraph.mkPen" title="pyqtgraph.mkPen"><tt class="xref py py-func docutils literal"><span class="pre">mkPen()</span></tt></a>, and <a class="reference internal" href="#pyqtgraph.mkBrush" title="pyqtgraph.mkBrush"><tt class="xref py py-func docutils literal"><span class="pre">mkBrush()</span></tt></a> to simplify the process of creating these classes. In most cases, however, it will be unnecessary to call these functions directly–any function or method that accepts <em>pen</em> or <em>brush</em> arguments will make use of these functions for you. For example, the following three lines all have the same effect:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="n">pg</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">xdata</span><span class="p">,</span> <span class="n">ydata</span><span class="p">,</span> <span class="n">pen</span><span class="o">=</span><span class="s">'r'</span><span class="p">)</span> +<span class="n">pg</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">xdata</span><span class="p">,</span> <span class="n">ydata</span><span class="p">,</span> <span class="n">pen</span><span class="o">=</span><span class="n">pg</span><span class="o">.</span><span class="n">mkPen</span><span class="p">(</span><span class="s">'r'</span><span class="p">))</span> +<span class="n">pg</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">xdata</span><span class="p">,</span> <span class="n">ydata</span><span class="p">,</span> <span class="n">pen</span><span class="o">=</span><span class="n">QPen</span><span class="p">(</span><span class="n">QColor</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)))</span> +</pre></div> +</div> +<dl class="function"> +<dt id="pyqtgraph.mkColor"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">mkColor</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.mkColor" title="Permalink to this definition">¶</a></dt> +<dd><p>Convenience function for constructing QColor from a variety of argument types. Accepted arguments are:</p> +<table border="1" class="docutils"> +<colgroup> +<col width="25%" /> +<col width="75%" /> +</colgroup> +<tbody valign="top"> +<tr><td>‘c’</td> +<td>one of: r, g, b, c, m, y, k, w</td> +</tr> +<tr><td>R, G, B, [A]</td> +<td>integers 0-255</td> +</tr> +<tr><td>(R, G, B, [A])</td> +<td>tuple of integers 0-255</td> +</tr> +<tr><td>float</td> +<td>greyscale, 0.0-1.0</td> +</tr> +<tr><td>int</td> +<td>see <a class="reference internal" href="#pyqtgraph.intColor" title="pyqtgraph.intColor"><tt class="xref py py-func docutils literal"><span class="pre">intColor()</span></tt></a></td> +</tr> +<tr><td>(int, hues)</td> +<td>see <a class="reference internal" href="#pyqtgraph.intColor" title="pyqtgraph.intColor"><tt class="xref py py-func docutils literal"><span class="pre">intColor()</span></tt></a></td> +</tr> +<tr><td>“RGB”</td> +<td>hexadecimal strings; may begin with ‘#’</td> +</tr> +<tr><td>“RGBA”</td> +<td> </td> +</tr> +<tr><td>“RRGGBB”</td> +<td> </td> +</tr> +<tr><td>“RRGGBBAA”</td> +<td> </td> +</tr> +<tr><td>QColor</td> +<td>QColor instance; makes a copy.</td> +</tr> +</tbody> +</table> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.mkPen"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">mkPen</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.mkPen" title="Permalink to this definition">¶</a></dt> +<dd><p>Convenience function for constructing QPen.</p> +<p>Examples:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="n">mkPen</span><span class="p">(</span><span class="n">color</span><span class="p">)</span> +<span class="n">mkPen</span><span class="p">(</span><span class="n">color</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span> +<span class="n">mkPen</span><span class="p">(</span><span class="n">cosmetic</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mf">4.5</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'r'</span><span class="p">)</span> +<span class="n">mkPen</span><span class="p">({</span><span class="s">'color'</span><span class="p">:</span> <span class="s">"FF0"</span><span class="p">,</span> <span class="n">width</span><span class="p">:</span> <span class="mi">2</span><span class="p">})</span> +<span class="n">mkPen</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span> <span class="c"># (no pen)</span> +</pre></div> +</div> +<p>In these examples, <em>color</em> may be replaced with any arguments accepted by <a class="reference internal" href="#pyqtgraph.mkColor" title="pyqtgraph.mkColor"><tt class="xref py py-func docutils literal"><span class="pre">mkColor()</span></tt></a></p> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.mkBrush"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">mkBrush</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.mkBrush" title="Permalink to this definition">¶</a></dt> +<dd><div class="line-block"> +<div class="line">Convenience function for constructing Brush.</div> +<div class="line">This function always constructs a solid brush and accepts the same arguments as <a class="reference internal" href="#pyqtgraph.mkColor" title="pyqtgraph.mkColor"><tt class="xref py py-func docutils literal"><span class="pre">mkColor()</span></tt></a></div> +<div class="line">Calling mkBrush(None) returns an invisible brush.</div> +</div> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.hsvColor"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">hsvColor</tt><big>(</big><em>h</em>, <em>s=1.0</em>, <em>v=1.0</em>, <em>a=1.0</em><big>)</big><a class="headerlink" href="#pyqtgraph.hsvColor" title="Permalink to this definition">¶</a></dt> +<dd><p>Generate a QColor from HSVa values.</p> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.intColor"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">intColor</tt><big>(</big><em>index</em>, <em>hues=9</em>, <em>values=1</em>, <em>maxValue=255</em>, <em>minValue=150</em>, <em>maxHue=360</em>, <em>minHue=0</em>, <em>sat=255</em>, <em>alpha=255</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.intColor" title="Permalink to this definition">¶</a></dt> +<dd><p>Creates a QColor from a single index. Useful for stepping through a predefined list of colors.</p> +<p>The argument <em>index</em> determines which color from the set will be returned. All other arguments determine what the set of predefined colors will be</p> +<p>Colors are chosen by cycling across hues while varying the value (brightness). +By default, this selects from a list of 9 hues.</p> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.colorTuple"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">colorTuple</tt><big>(</big><em>c</em><big>)</big><a class="headerlink" href="#pyqtgraph.colorTuple" title="Permalink to this definition">¶</a></dt> +<dd><p>Return a tuple (R,G,B,A) from a QColor</p> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.colorStr"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">colorStr</tt><big>(</big><em>c</em><big>)</big><a class="headerlink" href="#pyqtgraph.colorStr" title="Permalink to this definition">¶</a></dt> +<dd><p>Generate a hex string code from a QColor</p> +</dd></dl> + +</div> +<div class="section" id="data-slicing"> +<h2>Data Slicing<a class="headerlink" href="#data-slicing" title="Permalink to this headline">¶</a></h2> +<dl class="function"> +<dt id="pyqtgraph.affineSlice"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">affineSlice</tt><big>(</big><em>data</em>, <em>shape</em>, <em>origin</em>, <em>vectors</em>, <em>axes</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.affineSlice" title="Permalink to this definition">¶</a></dt> +<dd><p>Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data.</p> +<p>The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets.</p> +<p>For a graphical interface to this function, see <tt class="xref py py-func docutils literal"><span class="pre">ROI.getArrayRegion()</span></tt></p> +<p>Arguments:</p> +<blockquote> +<div class="line-block"> +<div class="line"><em>data</em> (ndarray): the original dataset</div> +<div class="line"><em>shape</em>: the shape of the slice to take (Note the return value may have more dimensions than len(shape))</div> +<div class="line"><em>origin</em>: the location in the original dataset that will become the origin in the sliced data.</div> +<div class="line"><em>vectors</em>: list of unit vectors which point in the direction of the slice axes</div> +</div> +<ul class="simple"> +<li>each vector must have the same length as <em>axes</em></li> +<li>If the vectors are not unit length, the result will be scaled.</li> +<li>If the vectors are not orthogonal, the result will be sheared.</li> +</ul> +<p><em>axes</em>: the axes in the original dataset which correspond to the slice <em>vectors</em></p> +</blockquote> +<p>Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes</p> +<blockquote> +<ul class="simple"> +<li>data = array with dims (time, x, y, z) = (100, 40, 40, 40)</li> +<li>The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1)</li> +<li>The origin of the slice will be at (x,y,z) = (40, 0, 0)</li> +<li>We will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20)</li> +</ul> +</blockquote> +<p>The call for this example would look like:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="n">affineSlice</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">shape</span><span class="o">=</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">),</span> <span class="n">origin</span><span class="o">=</span><span class="p">(</span><span class="mi">40</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="n">vectors</span><span class="o">=</span><span class="p">((</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)),</span> <span class="n">axes</span><span class="o">=</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">))</span> +</pre></div> +</div> +<p>Note the following must be true:</p> +<blockquote> +<div class="line-block"> +<div class="line">len(shape) == len(vectors) </div> +<div class="line">len(origin) == len(axes) == len(vectors[0])</div> +</div> +</blockquote> +</dd></dl> + +</div> +<div class="section" id="si-unit-conversion-functions"> +<h2>SI Unit Conversion Functions<a class="headerlink" href="#si-unit-conversion-functions" title="Permalink to this headline">¶</a></h2> +<dl class="function"> +<dt id="pyqtgraph.siFormat"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">siFormat</tt><big>(</big><em>x</em>, <em>precision=3</em>, <em>suffix=''</em>, <em>space=True</em>, <em>error=None</em>, <em>minVal=1e-25</em>, <em>allowUnicode=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.siFormat" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the number x formatted in engineering notation with SI prefix.</p> +<p>Example:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="n">siFormat</span><span class="p">(</span><span class="mf">0.0001</span><span class="p">,</span> <span class="n">suffix</span><span class="o">=</span><span class="s">'V'</span><span class="p">)</span> <span class="c"># returns "100 μV"</span> +</pre></div> +</div> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.siScale"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">siScale</tt><big>(</big><em>x</em>, <em>minVal=1e-25</em>, <em>allowUnicode=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.siScale" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the recommended scale factor and SI prefix string for x.</p> +<p>Example:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="n">siScale</span><span class="p">(</span><span class="mf">0.0001</span><span class="p">)</span> <span class="c"># returns (1e6, 'μ')</span> +<span class="c"># This indicates that the number 0.0001 is best represented as 0.0001 * 1e6 = 100 μUnits</span> +</pre></div> +</div> +</dd></dl> + +<dl class="function"> +<dt id="pyqtgraph.siEval"> +<tt class="descclassname">pyqtgraph.</tt><tt class="descname">siEval</tt><big>(</big><em>s</em><big>)</big><a class="headerlink" href="#pyqtgraph.siEval" title="Permalink to this definition">¶</a></dt> +<dd><p>Convert a value written in SI notation to its equivalent prefixless value</p> +<p>Example:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="n">siEval</span><span class="p">(</span><span class="s">"100 μV"</span><span class="p">)</span> <span class="c"># returns 0.0001</span> +</pre></div> +</div> +</dd></dl> + +</div> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">Pyqtgraph’s Helper Functions</a><ul> +<li><a class="reference internal" href="#simple-data-display-functions">Simple Data Display Functions</a></li> +<li><a class="reference internal" href="#color-pen-and-brush-functions">Color, Pen, and Brush Functions</a></li> +<li><a class="reference internal" href="#data-slicing">Data Slicing</a></li> +<li><a class="reference internal" href="#si-unit-conversion-functions">SI Unit Conversion Functions</a></li> +</ul> +</li> +</ul> + + <h4>Previous topic</h4> + <p class="topless"><a href="apireference.html" + title="previous chapter">API Reference</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="graphicsItems/index.html" + title="next chapter">Pyqtgraph’s Graphics Items</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/functions.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicsItems/index.html" title="Pyqtgraph’s Graphics Items" + >next</a> |</li> + <li class="right" > + <a href="apireference.html" title="API Reference" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="apireference.html" >API Reference</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/genindex.html b/documentation/build/html/genindex.html new file mode 100644 index 00000000..50d88bf2 --- /dev/null +++ b/documentation/build/html/genindex.html @@ -0,0 +1,457 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Index — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="#" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + + <h1 id="index">Index</h1> + + <div class="genindex-jumpbox"> + <a href="#_"><strong>_</strong></a> | <a href="#A"><strong>A</strong></a> | <a href="#B"><strong>B</strong></a> | <a href="#C"><strong>C</strong></a> | <a href="#D"><strong>D</strong></a> | <a href="#E"><strong>E</strong></a> | <a href="#F"><strong>F</strong></a> | <a href="#G"><strong>G</strong></a> | <a href="#H"><strong>H</strong></a> | <a href="#I"><strong>I</strong></a> | <a href="#J"><strong>J</strong></a> | <a href="#K"><strong>K</strong></a> | <a href="#L"><strong>L</strong></a> | <a href="#M"><strong>M</strong></a> | <a href="#N"><strong>N</strong></a> | <a href="#P"><strong>P</strong></a> | <a href="#R"><strong>R</strong></a> | <a href="#S"><strong>S</strong></a> | <a href="#T"><strong>T</strong></a> | <a href="#U"><strong>U</strong></a> | <a href="#V"><strong>V</strong></a> + </div> +<h2 id="_">_</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/arrowitem.html#pyqtgraph.ArrowItem.__init__">__init__() (pyqtgraph.ArrowItem method)</a></dt> + <dd><dl> + <dt><a href="graphicsItems/axisitem.html#pyqtgraph.AxisItem.__init__">(pyqtgraph.AxisItem method)</a></dt> + <dt><a href="graphicsItems/buttonitem.html#pyqtgraph.ButtonItem.__init__">(pyqtgraph.ButtonItem method)</a></dt> + <dt><a href="widgets/checktable.html#pyqtgraph.CheckTable.__init__">(pyqtgraph.CheckTable method)</a></dt> + <dt><a href="widgets/colorbutton.html#pyqtgraph.ColorButton.__init__">(pyqtgraph.ColorButton method)</a></dt> + <dt><a href="graphicsItems/curvearrow.html#pyqtgraph.CurveArrow.__init__">(pyqtgraph.CurveArrow method)</a></dt> + <dt><a href="graphicsItems/curvepoint.html#pyqtgraph.CurvePoint.__init__">(pyqtgraph.CurvePoint method)</a></dt> + <dt><a href="widgets/datatreewidget.html#pyqtgraph.DataTreeWidget.__init__">(pyqtgraph.DataTreeWidget method)</a></dt> + <dt><a href="widgets/filedialog.html#pyqtgraph.FileDialog.__init__">(pyqtgraph.FileDialog method)</a></dt> + <dt><a href="graphicsItems/gradienteditoritem.html#pyqtgraph.GradientEditorItem.__init__">(pyqtgraph.GradientEditorItem method)</a></dt> + <dt><a href="graphicsItems/gradientlegend.html#pyqtgraph.GradientLegend.__init__">(pyqtgraph.GradientLegend method)</a></dt> + <dt><a href="widgets/gradientwidget.html#pyqtgraph.GradientWidget.__init__">(pyqtgraph.GradientWidget method)</a></dt> + <dt><a href="graphicsItems/graphicslayout.html#pyqtgraph.GraphicsLayout.__init__">(pyqtgraph.GraphicsLayout method)</a></dt> + <dt><a href="widgets/graphicslayoutwidget.html#pyqtgraph.GraphicsLayoutWidget.__init__">(pyqtgraph.GraphicsLayoutWidget method)</a></dt> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.__init__">(pyqtgraph.GraphicsObject method)</a></dt> + <dt><a href="widgets/graphicsview.html#pyqtgraph.GraphicsView.__init__">(pyqtgraph.GraphicsView method)</a></dt> + <dt><a href="graphicsItems/graphicswidget.html#pyqtgraph.GraphicsWidget.__init__">(pyqtgraph.GraphicsWidget method)</a></dt> + <dt><a href="graphicsItems/griditem.html#pyqtgraph.GridItem.__init__">(pyqtgraph.GridItem method)</a></dt> + <dt><a href="graphicsItems/histogramlutitem.html#pyqtgraph.HistogramLUTItem.__init__">(pyqtgraph.HistogramLUTItem method)</a></dt> + <dt><a href="widgets/histogramlutwidget.html#pyqtgraph.HistogramLUTWidget.__init__">(pyqtgraph.HistogramLUTWidget method)</a></dt> + <dt><a href="graphicsItems/imageitem.html#pyqtgraph.ImageItem.__init__">(pyqtgraph.ImageItem method)</a></dt> + <dt><a href="widgets/imageview.html#pyqtgraph.ImageView.__init__">(pyqtgraph.ImageView method)</a></dt> + <dt><a href="graphicsItems/infiniteline.html#pyqtgraph.InfiniteLine.__init__">(pyqtgraph.InfiniteLine method)</a></dt> + <dt><a href="widgets/joystickbutton.html#pyqtgraph.JoystickButton.__init__">(pyqtgraph.JoystickButton method)</a></dt> + <dt><a href="graphicsItems/labelitem.html#pyqtgraph.LabelItem.__init__">(pyqtgraph.LabelItem method)</a></dt> + <dt><a href="graphicsItems/linearregionitem.html#pyqtgraph.LinearRegionItem.__init__">(pyqtgraph.LinearRegionItem method)</a></dt> + <dt><a href="widgets/multiplotwidget.html#pyqtgraph.MultiPlotWidget.__init__">(pyqtgraph.MultiPlotWidget method)</a></dt> + <dt><a href="graphicsItems/plotcurveitem.html#pyqtgraph.PlotCurveItem.__init__">(pyqtgraph.PlotCurveItem method)</a></dt> + <dt><a href="graphicsItems/plotdataitem.html#pyqtgraph.PlotDataItem.__init__">(pyqtgraph.PlotDataItem method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.__init__">(pyqtgraph.PlotItem method)</a></dt> + <dt><a href="widgets/plotwidget.html#pyqtgraph.PlotWidget.__init__">(pyqtgraph.PlotWidget method)</a></dt> + <dt><a href="widgets/progressdialog.html#pyqtgraph.ProgressDialog.__init__">(pyqtgraph.ProgressDialog method)</a></dt> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI.__init__">(pyqtgraph.ROI method)</a></dt> + <dt><a href="widgets/rawimagewidget.html#pyqtgraph.RawImageWidget.__init__">(pyqtgraph.RawImageWidget method)</a></dt> + <dt><a href="graphicsItems/scalebar.html#pyqtgraph.ScaleBar.__init__">(pyqtgraph.ScaleBar method)</a></dt> + <dt><a href="graphicsItems/scatterplotitem.html#pyqtgraph.ScatterPlotItem.__init__">(pyqtgraph.ScatterPlotItem method)</a></dt> + <dt><a href="widgets/spinbox.html#pyqtgraph.SpinBox.__init__">(pyqtgraph.SpinBox method)</a></dt> + <dt><a href="widgets/tablewidget.html#pyqtgraph.TableWidget.__init__">(pyqtgraph.TableWidget method)</a></dt> + <dt><a href="widgets/treewidget.html#pyqtgraph.TreeWidget.__init__">(pyqtgraph.TreeWidget method)</a></dt> + <dt><a href="graphicsItems/uigraphicsitem.html#pyqtgraph.UIGraphicsItem.__init__">(pyqtgraph.UIGraphicsItem method)</a></dt> + <dt><a href="graphicsItems/vtickgroup.html#pyqtgraph.VTickGroup.__init__">(pyqtgraph.VTickGroup method)</a></dt> + <dt><a href="widgets/verticallabel.html#pyqtgraph.VerticalLabel.__init__">(pyqtgraph.VerticalLabel method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.__init__">(pyqtgraph.ViewBox method)</a></dt> + </dl></dd> +</dl></td> +</tr></table> + +<h2 id="A">A</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.addAvgCurve">addAvgCurve() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="functions.html#pyqtgraph.affineSlice">affineSlice() (in module pyqtgraph)</a></dt> + <dt><a href="widgets/tablewidget.html#pyqtgraph.TableWidget.appendData">appendData() (pyqtgraph.TableWidget method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/arrowitem.html#pyqtgraph.ArrowItem">ArrowItem (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/axisitem.html#pyqtgraph.AxisItem">AxisItem (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="B">B</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/buttonitem.html#pyqtgraph.ButtonItem">ButtonItem (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="C">C</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/checktable.html#pyqtgraph.CheckTable">CheckTable (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.childrenBoundingRect">childrenBoundingRect() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.childTransform">childTransform() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="widgets/colorbutton.html#pyqtgraph.ColorButton">ColorButton (class in pyqtgraph)</a></dt> + <dt><a href="functions.html#pyqtgraph.colorStr">colorStr() (in module pyqtgraph)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="functions.html#pyqtgraph.colorTuple">colorTuple() (in module pyqtgraph)</a></dt> + <dt><a href="widgets/tablewidget.html#pyqtgraph.TableWidget.copy">copy() (pyqtgraph.TableWidget method)</a></dt> + <dt><a href="graphicsItems/curvearrow.html#pyqtgraph.CurveArrow">CurveArrow (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/curvepoint.html#pyqtgraph.CurvePoint">CurvePoint (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="D">D</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/datatreewidget.html#pyqtgraph.DataTreeWidget">DataTreeWidget (class in pyqtgraph)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.deviceTransform">deviceTransform() (pyqtgraph.GraphicsObject method)</a></dt> +</dl></td> +</tr></table> + +<h2 id="E">E</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/spinbox.html#pyqtgraph.SpinBox.editingFinishedEvent">editingFinishedEvent() (pyqtgraph.SpinBox method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.enableAutoScale">enableAutoScale() (pyqtgraph.PlotItem method)</a></dt> +</dl></td> +</tr></table> + +<h2 id="F">F</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/filedialog.html#pyqtgraph.FileDialog">FileDialog (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="G">G</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI.getArrayRegion">getArrayRegion() (pyqtgraph.ROI method)</a></dt> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI.getArraySlice">getArraySlice() (pyqtgraph.ROI method)</a></dt> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.getBoundingParents">getBoundingParents() (pyqtgraph.GraphicsObject method)</a></dt> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI.getGlobalTransform">getGlobalTransform() (pyqtgraph.ROI method)</a></dt> + <dt><a href="graphicsItems/imageitem.html#pyqtgraph.ImageItem.getHistogram">getHistogram() (pyqtgraph.ImageItem method)</a></dt> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI.getLocalHandlePositions">getLocalHandlePositions() (pyqtgraph.ROI method)</a></dt> + <dt><a href="graphicsItems/gradienteditoritem.html#pyqtgraph.GradientEditorItem.getLookupTable">getLookupTable() (pyqtgraph.GradientEditorItem method)</a></dt> + <dt><a href="graphicsItems/linearregionitem.html#pyqtgraph.LinearRegionItem.getRegion">getRegion() (pyqtgraph.LinearRegionItem method)</a></dt> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.getViewBox">getViewBox() (pyqtgraph.GraphicsObject method)</a></dt> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.getViewWidget">getViewWidget() (pyqtgraph.GraphicsObject method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/gradienteditoritem.html#pyqtgraph.GradientEditorItem">GradientEditorItem (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/gradientlegend.html#pyqtgraph.GradientLegend">GradientLegend (class in pyqtgraph)</a></dt> + <dt><a href="widgets/gradientwidget.html#pyqtgraph.GradientWidget">GradientWidget (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/graphicslayout.html#pyqtgraph.GraphicsLayout">GraphicsLayout (class in pyqtgraph)</a></dt> + <dt><a href="widgets/graphicslayoutwidget.html#pyqtgraph.GraphicsLayoutWidget">GraphicsLayoutWidget (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject">GraphicsObject (class in pyqtgraph)</a></dt> + <dt><a href="widgets/graphicsview.html#pyqtgraph.GraphicsView">GraphicsView (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/graphicswidget.html#pyqtgraph.GraphicsWidget">GraphicsWidget (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/griditem.html#pyqtgraph.GridItem">GridItem (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="H">H</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI.handleChange">handleChange() (pyqtgraph.ROI method)</a></dt> + <dt><a href="graphicsItems/histogramlutitem.html#pyqtgraph.HistogramLUTItem">HistogramLUTItem (class in pyqtgraph)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/histogramlutwidget.html#pyqtgraph.HistogramLUTWidget">HistogramLUTWidget (class in pyqtgraph)</a></dt> + <dt><a href="functions.html#pyqtgraph.hsvColor">hsvColor() (in module pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="I">I</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="functions.html#pyqtgraph.image">image() (in module pyqtgraph)</a></dt> + <dt><a href="graphicsItems/imageitem.html#pyqtgraph.ImageItem">ImageItem (class in pyqtgraph)</a></dt> + <dt><a href="widgets/imageview.html#pyqtgraph.ImageView">ImageView (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/infiniteline.html#pyqtgraph.InfiniteLine">InfiniteLine (class in pyqtgraph)</a></dt> + <dt><a href="functions.html#pyqtgraph.intColor">intColor() (in module pyqtgraph)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/spinbox.html#pyqtgraph.SpinBox.interpret">interpret() (pyqtgraph.SpinBox method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.itemBoundingRect">itemBoundingRect() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="widgets/treewidget.html#pyqtgraph.TreeWidget.itemMoving">itemMoving() (pyqtgraph.TreeWidget method)</a></dt> + <dt><a href="widgets/tablewidget.html#pyqtgraph.TableWidget.iteratorFn">iteratorFn() (pyqtgraph.TableWidget method)</a></dt> +</dl></td> +</tr></table> + +<h2 id="J">J</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/joystickbutton.html#pyqtgraph.JoystickButton">JoystickButton (class in pyqtgraph)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/imageview.html#pyqtgraph.ImageView.jumpFrames">jumpFrames() (pyqtgraph.ImageView method)</a></dt> +</dl></td> +</tr></table> + +<h2 id="K">K</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.keyPressEvent">keyPressEvent() (pyqtgraph.ViewBox method)</a></dt> +</dl></td> +</tr></table> + +<h2 id="L">L</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/labelitem.html#pyqtgraph.LabelItem">LabelItem (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/linearregionitem.html#pyqtgraph.LinearRegionItem">LinearRegionItem (class in pyqtgraph)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.linkXChanged">linkXChanged() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.linkYChanged">linkYChanged() (pyqtgraph.PlotItem method)</a></dt> +</dl></td> +</tr></table> + +<h2 id="M">M</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.mapFromView">mapFromView() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.mapSceneToView">mapSceneToView() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.mapToView">mapToView() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.mapViewToScene">mapViewToScene() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="functions.html#pyqtgraph.mkBrush">mkBrush() (in module pyqtgraph)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="functions.html#pyqtgraph.mkColor">mkColor() (in module pyqtgraph)</a></dt> + <dt><a href="functions.html#pyqtgraph.mkPen">mkPen() (in module pyqtgraph)</a></dt> + <dt><a href="graphicsItems/uigraphicsitem.html#pyqtgraph.UIGraphicsItem.mouseShape">mouseShape() (pyqtgraph.UIGraphicsItem method)</a></dt> + <dt><a href="widgets/multiplotwidget.html#pyqtgraph.MultiPlotWidget">MultiPlotWidget (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="N">N</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/graphicslayout.html#pyqtgraph.GraphicsLayout.nextCol">nextCol() (pyqtgraph.GraphicsLayout method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/graphicslayout.html#pyqtgraph.GraphicsLayout.nextRow">nextRow() (pyqtgraph.GraphicsLayout method)</a></dt> +</dl></td> +</tr></table> + +<h2 id="P">P</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.pixelLength">pixelLength() (pyqtgraph.GraphicsObject method)</a></dt> + <dt><a href="widgets/graphicsview.html#pyqtgraph.GraphicsView.pixelSize">pixelSize() (pyqtgraph.GraphicsView method)</a></dt> + <dd><dl> + <dt><a href="graphicsItems/imageitem.html#pyqtgraph.ImageItem.pixelSize">(pyqtgraph.ImageItem method)</a></dt> + </dl></dd> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.pixelVectors">pixelVectors() (pyqtgraph.GraphicsObject method)</a></dt> + <dt><a href="functions.html#pyqtgraph.plot">plot() (in module pyqtgraph)</a></dt> + <dd><dl> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.plot">(pyqtgraph.PlotItem method)</a></dt> + </dl></dd> + <dt><a href="graphicsItems/plotcurveitem.html#pyqtgraph.PlotCurveItem">PlotCurveItem (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/plotdataitem.html#pyqtgraph.PlotDataItem">PlotDataItem (class in pyqtgraph)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem">PlotItem (class in pyqtgraph)</a></dt> + <dt><a href="widgets/plotwidget.html#pyqtgraph.PlotWidget">PlotWidget (class in pyqtgraph)</a></dt> + <dt><a href="widgets/progressdialog.html#pyqtgraph.ProgressDialog">ProgressDialog (class in pyqtgraph)</a></dt> + <dt><a href="widgets/dockarea.html#module-pyqtgraph.dockarea">pyqtgraph.dockarea (module)</a></dt> + <dt><a href="widgets/parametertree.html#module-pyqtgraph.parametertree">pyqtgraph.parametertree (module)</a></dt> +</dl></td> +</tr></table> + +<h2 id="R">R</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/rawimagewidget.html#pyqtgraph.RawImageWidget">RawImageWidget (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/uigraphicsitem.html#pyqtgraph.UIGraphicsItem.realBoundingRect">realBoundingRect() (pyqtgraph.UIGraphicsItem method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI">ROI (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="S">S</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI.saveState">saveState() (pyqtgraph.ROI method)</a></dt> + <dt><a href="graphicsItems/scalebar.html#pyqtgraph.ScaleBar">ScaleBar (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.scaleBy">scaleBy() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="widgets/graphicsview.html#pyqtgraph.GraphicsView.scaleToImage">scaleToImage() (pyqtgraph.GraphicsView method)</a></dt> + <dt><a href="graphicsItems/scatterplotitem.html#pyqtgraph.ScatterPlotItem">ScatterPlotItem (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/infiniteline.html#pyqtgraph.InfiniteLine.setAngle">setAngle() (pyqtgraph.InfiniteLine method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.setAspectLocked">setAspectLocked() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="graphicsItems/labelitem.html#pyqtgraph.LabelItem.setAttr">setAttr() (pyqtgraph.LabelItem method)</a></dt> + <dt><a href="graphicsItems/infiniteline.html#pyqtgraph.InfiniteLine.setBounds">setBounds() (pyqtgraph.InfiniteLine method)</a></dt> + <dt><a href="widgets/graphicsview.html#pyqtgraph.GraphicsView.setCentralWidget">setCentralWidget() (pyqtgraph.GraphicsView method)</a></dt> + <dt><a href="widgets/datatreewidget.html#pyqtgraph.DataTreeWidget.setData">setData() (pyqtgraph.DataTreeWidget method)</a></dt> + <dd><dl> + <dt><a href="graphicsItems/plotcurveitem.html#pyqtgraph.PlotCurveItem.setData">(pyqtgraph.PlotCurveItem method)</a></dt> + <dt><a href="graphicsItems/plotdataitem.html#pyqtgraph.PlotDataItem.setData">(pyqtgraph.PlotDataItem method)</a></dt> + </dl></dd> + <dt><a href="graphicsItems/axisitem.html#pyqtgraph.AxisItem.setGrid">setGrid() (pyqtgraph.AxisItem method)</a></dt> + <dt><a href="graphicsItems/imageitem.html#pyqtgraph.ImageItem.setImage">setImage() (pyqtgraph.ImageItem method)</a></dt> + <dd><dl> + <dt><a href="widgets/imageview.html#pyqtgraph.ImageView.setImage">(pyqtgraph.ImageView method)</a></dt> + <dt><a href="widgets/rawimagewidget.html#pyqtgraph.RawImageWidget.setImage">(pyqtgraph.RawImageWidget method)</a></dt> + </dl></dd> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.setLabel">setLabel() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="graphicsItems/gradientlegend.html#pyqtgraph.GradientLegend.setLabels">setLabels() (pyqtgraph.GradientLegend method)</a></dt> + <dt><a href="graphicsItems/imageitem.html#pyqtgraph.ImageItem.setLevels">setLevels() (pyqtgraph.ImageItem method)</a></dt> + <dt><a href="graphicsItems/imageitem.html#pyqtgraph.ImageItem.setLookupTable">setLookupTable() (pyqtgraph.ImageItem method)</a></dt> + <dt><a href="graphicsItems/uigraphicsitem.html#pyqtgraph.UIGraphicsItem.setNewBounds">setNewBounds() (pyqtgraph.UIGraphicsItem method)</a></dt> + <dt><a href="graphicsItems/plotdataitem.html#pyqtgraph.PlotDataItem.setPen">setPen() (pyqtgraph.PlotDataItem method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/scatterplotitem.html#pyqtgraph.ScatterPlotItem.setPoints">setPoints() (pyqtgraph.ScatterPlotItem method)</a></dt> + <dt><a href="widgets/spinbox.html#pyqtgraph.SpinBox.setProperty">setProperty() (pyqtgraph.SpinBox method)</a></dt> + <dt><a href="graphicsItems/imageitem.html#pyqtgraph.ImageItem.setPxMode">setPxMode() (pyqtgraph.ImageItem method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.setRange">setRange() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="graphicsItems/axisitem.html#pyqtgraph.AxisItem.setScale">setScale() (pyqtgraph.AxisItem method)</a></dt> + <dt><a href="graphicsItems/plotdataitem.html#pyqtgraph.PlotDataItem.setShadowPen">setShadowPen() (pyqtgraph.PlotDataItem method)</a></dt> + <dt><a href="graphicsItems/labelitem.html#pyqtgraph.LabelItem.setText">setText() (pyqtgraph.LabelItem method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.setTitle">setTitle() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="widgets/spinbox.html#pyqtgraph.SpinBox.setValue">setValue() (pyqtgraph.SpinBox method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.setXLink">setXLink() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.setYLink">setYLink() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.showAxis">showAxis() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.showLabel">showLabel() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="functions.html#pyqtgraph.siEval">siEval() (in module pyqtgraph)</a></dt> + <dt><a href="functions.html#pyqtgraph.siFormat">siFormat() (in module pyqtgraph)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.sigRangeChanged">sigRangeChanged (pyqtgraph.PlotItem attribute)</a></dt> + <dt><a href="functions.html#pyqtgraph.siScale">siScale() (in module pyqtgraph)</a></dt> + <dt><a href="widgets/spinbox.html#pyqtgraph.SpinBox">SpinBox (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="T">T</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/tablewidget.html#pyqtgraph.TableWidget">TableWidget (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.targetRect">targetRect() (pyqtgraph.ViewBox method)</a></dt> + <dt><a href="widgets/imageview.html#pyqtgraph.ImageView.timeIndex">timeIndex() (pyqtgraph.ImageView method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/roi.html#pyqtgraph.ROI.translate">translate() (pyqtgraph.ROI method)</a></dt> + <dt><a href="widgets/treewidget.html#pyqtgraph.TreeWidget">TreeWidget (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + +<h2 id="U">U</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/uigraphicsitem.html#pyqtgraph.UIGraphicsItem">UIGraphicsItem (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.updatePlotList">updatePlotList() (pyqtgraph.PlotItem method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.updateXScale">updateXScale() (pyqtgraph.PlotItem method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.updateYScale">updateYScale() (pyqtgraph.PlotItem method)</a></dt> +</dl></td> +</tr></table> + +<h2 id="V">V</h2> +<table width="100%" class="indextable genindextable"><tr> + <td width="33%" valign="top"><dl> + <dt><a href="widgets/verticallabel.html#pyqtgraph.VerticalLabel">VerticalLabel (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox">ViewBox (class in pyqtgraph)</a></dt> + <dt><a href="graphicsItems/uigraphicsitem.html#pyqtgraph.UIGraphicsItem.viewChangedEvent">viewChangedEvent() (pyqtgraph.UIGraphicsItem method)</a></dt> + <dt><a href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.viewGeometry">viewGeometry() (pyqtgraph.PlotItem method)</a></dt> +</dl></td> + <td width="33%" valign="top"><dl> + <dt><a href="graphicsItems/uigraphicsitem.html#pyqtgraph.UIGraphicsItem.viewRangeChanged">viewRangeChanged() (pyqtgraph.UIGraphicsItem method)</a></dt> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.viewRect">viewRect() (pyqtgraph.GraphicsObject method)</a></dt> + <dd><dl> + <dt><a href="widgets/graphicsview.html#pyqtgraph.GraphicsView.viewRect">(pyqtgraph.GraphicsView method)</a></dt> + <dt><a href="graphicsItems/viewbox.html#pyqtgraph.ViewBox.viewRect">(pyqtgraph.ViewBox method)</a></dt> + </dl></dd> + <dt><a href="graphicsItems/graphicsobject.html#pyqtgraph.GraphicsObject.viewTransform">viewTransform() (pyqtgraph.GraphicsObject method)</a></dt> + <dt><a href="graphicsItems/vtickgroup.html#pyqtgraph.VTickGroup">VTickGroup (class in pyqtgraph)</a></dt> +</dl></td> +</tr></table> + + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + + + +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="#" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/arrowitem.html b/documentation/build/html/graphicsItems/arrowitem.html new file mode 100644 index 00000000..f6a543e6 --- /dev/null +++ b/documentation/build/html/graphicsItems/arrowitem.html @@ -0,0 +1,132 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ArrowItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="CurvePoint" href="curvepoint.html" /> + <link rel="prev" title="AxisItem" href="axisitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="curvepoint.html" title="CurvePoint" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="axisitem.html" title="AxisItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="arrowitem"> +<h1>ArrowItem<a class="headerlink" href="#arrowitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ArrowItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ArrowItem</tt><big>(</big><em>**opts</em><big>)</big><a class="headerlink" href="#pyqtgraph.ArrowItem" title="Permalink to this definition">¶</a></dt> +<dd><p>For displaying scale-invariant arrows. +For arrows pointing to a location on a curve, see CurveArrow</p> +<dl class="method"> +<dt id="pyqtgraph.ArrowItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>**opts</em><big>)</big><a class="headerlink" href="#pyqtgraph.ArrowItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="axisitem.html" + title="previous chapter">AxisItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="curvepoint.html" + title="next chapter">CurvePoint</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/arrowitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="curvepoint.html" title="CurvePoint" + >next</a> |</li> + <li class="right" > + <a href="axisitem.html" title="AxisItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/axisitem.html b/documentation/build/html/graphicsItems/axisitem.html new file mode 100644 index 00000000..c370aea2 --- /dev/null +++ b/documentation/build/html/graphicsItems/axisitem.html @@ -0,0 +1,154 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>AxisItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="ArrowItem" href="arrowitem.html" /> + <link rel="prev" title="GraphicsLayout" href="graphicslayout.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="arrowitem.html" title="ArrowItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="graphicslayout.html" title="GraphicsLayout" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="axisitem"> +<h1>AxisItem<a class="headerlink" href="#axisitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.AxisItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">AxisItem</tt><big>(</big><em>orientation</em>, <em>pen=None</em>, <em>linkView=None</em>, <em>parent=None</em>, <em>maxTickLength=-5</em><big>)</big><a class="headerlink" href="#pyqtgraph.AxisItem" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.AxisItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>orientation</em>, <em>pen=None</em>, <em>linkView=None</em>, <em>parent=None</em>, <em>maxTickLength=-5</em><big>)</big><a class="headerlink" href="#pyqtgraph.AxisItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd><p>GraphicsItem showing a single plot axis with ticks, values, and label. +Can be configured to fit on any side of a plot, and can automatically synchronize its displayed scale with ViewBox items. +Ticks can be extended to make a grid.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.AxisItem.setGrid"> +<tt class="descname">setGrid</tt><big>(</big><em>grid</em><big>)</big><a class="headerlink" href="#pyqtgraph.AxisItem.setGrid" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the alpha value for the grid, or False to disable.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.AxisItem.setScale"> +<tt class="descname">setScale</tt><big>(</big><em>scale=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.AxisItem.setScale" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the value scaling for this axis. +The scaling value 1) multiplies the values displayed along the axis +and 2) changes the way units are displayed in the label. +For example:</p> +<blockquote> +If the axis spans values from -0.1 to 0.1 and has units set to ‘V’ +then a scale of 1000 would cause the axis to display values -100 to 100 +and the units would appear as ‘mV’</blockquote> +<p>If scale is None, then it will be determined automatically based on the current +range displayed by the axis.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="graphicslayout.html" + title="previous chapter">GraphicsLayout</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="arrowitem.html" + title="next chapter">ArrowItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/axisitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="arrowitem.html" title="ArrowItem" + >next</a> |</li> + <li class="right" > + <a href="graphicslayout.html" title="GraphicsLayout" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/buttonitem.html b/documentation/build/html/graphicsItems/buttonitem.html new file mode 100644 index 00000000..85e06f4e --- /dev/null +++ b/documentation/build/html/graphicsItems/buttonitem.html @@ -0,0 +1,131 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ButtonItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="GraphicsObject" href="graphicsobject.html" /> + <link rel="prev" title="GradientLegend" href="gradientlegend.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicsobject.html" title="GraphicsObject" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="gradientlegend.html" title="GradientLegend" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="buttonitem"> +<h1>ButtonItem<a class="headerlink" href="#buttonitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ButtonItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ButtonItem</tt><big>(</big><em>imageFile</em>, <em>width=None</em>, <em>parentItem=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ButtonItem" title="Permalink to this definition">¶</a></dt> +<dd><p>Button graphicsItem displaying an image.</p> +<dl class="method"> +<dt id="pyqtgraph.ButtonItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>imageFile</em>, <em>width=None</em>, <em>parentItem=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ButtonItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="gradientlegend.html" + title="previous chapter">GradientLegend</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="graphicsobject.html" + title="next chapter">GraphicsObject</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/buttonitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicsobject.html" title="GraphicsObject" + >next</a> |</li> + <li class="right" > + <a href="gradientlegend.html" title="GradientLegend" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/curvearrow.html b/documentation/build/html/graphicsItems/curvearrow.html new file mode 100644 index 00000000..72605c08 --- /dev/null +++ b/documentation/build/html/graphicsItems/curvearrow.html @@ -0,0 +1,132 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>CurveArrow — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="GridItem" href="griditem.html" /> + <link rel="prev" title="CurvePoint" href="curvepoint.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="griditem.html" title="GridItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="curvepoint.html" title="CurvePoint" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="curvearrow"> +<h1>CurveArrow<a class="headerlink" href="#curvearrow" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.CurveArrow"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">CurveArrow</tt><big>(</big><em>curve</em>, <em>index=0</em>, <em>pos=None</em>, <em>**opts</em><big>)</big><a class="headerlink" href="#pyqtgraph.CurveArrow" title="Permalink to this definition">¶</a></dt> +<dd><p>Provides an arrow that points to any specific sample on a PlotCurveItem. +Provides properties that can be animated.</p> +<dl class="method"> +<dt id="pyqtgraph.CurveArrow.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>curve</em>, <em>index=0</em>, <em>pos=None</em>, <em>**opts</em><big>)</big><a class="headerlink" href="#pyqtgraph.CurveArrow.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="curvepoint.html" + title="previous chapter">CurvePoint</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="griditem.html" + title="next chapter">GridItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/curvearrow.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="griditem.html" title="GridItem" + >next</a> |</li> + <li class="right" > + <a href="curvepoint.html" title="CurvePoint" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/curvepoint.html b/documentation/build/html/graphicsItems/curvepoint.html new file mode 100644 index 00000000..a1833226 --- /dev/null +++ b/documentation/build/html/graphicsItems/curvepoint.html @@ -0,0 +1,136 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>CurvePoint — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="CurveArrow" href="curvearrow.html" /> + <link rel="prev" title="ArrowItem" href="arrowitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="curvearrow.html" title="CurveArrow" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="arrowitem.html" title="ArrowItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="curvepoint"> +<h1>CurvePoint<a class="headerlink" href="#curvepoint" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.CurvePoint"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">CurvePoint</tt><big>(</big><em>curve</em>, <em>index=0</em>, <em>pos=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.CurvePoint" title="Permalink to this definition">¶</a></dt> +<dd><p>A GraphicsItem that sets its location to a point on a PlotCurveItem. +Also rotates to be tangent to the curve. +The position along the curve is a Qt property, and thus can be easily animated.</p> +<p>Note: This class does not display anything; see CurveArrow for an applied example</p> +<dl class="method"> +<dt id="pyqtgraph.CurvePoint.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>curve</em>, <em>index=0</em>, <em>pos=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.CurvePoint.__init__" title="Permalink to this definition">¶</a></dt> +<dd><p>Position can be set either as an index referring to the sample number or +the position 0.0 - 1.0</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="arrowitem.html" + title="previous chapter">ArrowItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="curvearrow.html" + title="next chapter">CurveArrow</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/curvepoint.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="curvearrow.html" title="CurveArrow" + >next</a> |</li> + <li class="right" > + <a href="arrowitem.html" title="ArrowItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/gradienteditoritem.html b/documentation/build/html/graphicsItems/gradienteditoritem.html new file mode 100644 index 00000000..c3061263 --- /dev/null +++ b/documentation/build/html/graphicsItems/gradienteditoritem.html @@ -0,0 +1,136 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GradientEditorItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="HistogramLUTItem" href="histogramlutitem.html" /> + <link rel="prev" title="VTickGroup" href="vtickgroup.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="histogramlutitem.html" title="HistogramLUTItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="vtickgroup.html" title="VTickGroup" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="gradienteditoritem"> +<h1>GradientEditorItem<a class="headerlink" href="#gradienteditoritem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GradientEditorItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GradientEditorItem</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.GradientEditorItem" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.GradientEditorItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.GradientEditorItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GradientEditorItem.getLookupTable"> +<tt class="descname">getLookupTable</tt><big>(</big><em>nPts</em>, <em>alpha=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.GradientEditorItem.getLookupTable" title="Permalink to this definition">¶</a></dt> +<dd><p>Return an RGB/A lookup table.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="vtickgroup.html" + title="previous chapter">VTickGroup</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="histogramlutitem.html" + title="next chapter">HistogramLUTItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/gradienteditoritem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="histogramlutitem.html" title="HistogramLUTItem" + >next</a> |</li> + <li class="right" > + <a href="vtickgroup.html" title="VTickGroup" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/gradientlegend.html b/documentation/build/html/graphicsItems/gradientlegend.html new file mode 100644 index 00000000..0d949f0b --- /dev/null +++ b/documentation/build/html/graphicsItems/gradientlegend.html @@ -0,0 +1,138 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GradientLegend — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="ButtonItem" href="buttonitem.html" /> + <link rel="prev" title="HistogramLUTItem" href="histogramlutitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="buttonitem.html" title="ButtonItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="histogramlutitem.html" title="HistogramLUTItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="gradientlegend"> +<h1>GradientLegend<a class="headerlink" href="#gradientlegend" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GradientLegend"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GradientLegend</tt><big>(</big><em>view</em>, <em>size</em>, <em>offset</em><big>)</big><a class="headerlink" href="#pyqtgraph.GradientLegend" title="Permalink to this definition">¶</a></dt> +<dd><p>Draws a color gradient rectangle along with text labels denoting the value at specific +points along the gradient.</p> +<dl class="method"> +<dt id="pyqtgraph.GradientLegend.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>view</em>, <em>size</em>, <em>offset</em><big>)</big><a class="headerlink" href="#pyqtgraph.GradientLegend.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GradientLegend.setLabels"> +<tt class="descname">setLabels</tt><big>(</big><em>l</em><big>)</big><a class="headerlink" href="#pyqtgraph.GradientLegend.setLabels" title="Permalink to this definition">¶</a></dt> +<dd><p>Defines labels to appear next to the color scale. Accepts a dict of {text: value} pairs</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="histogramlutitem.html" + title="previous chapter">HistogramLUTItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="buttonitem.html" + title="next chapter">ButtonItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/gradientlegend.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="buttonitem.html" title="ButtonItem" + >next</a> |</li> + <li class="right" > + <a href="histogramlutitem.html" title="HistogramLUTItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/graphicslayout.html b/documentation/build/html/graphicsItems/graphicslayout.html new file mode 100644 index 00000000..6ba6ebf8 --- /dev/null +++ b/documentation/build/html/graphicsItems/graphicslayout.html @@ -0,0 +1,144 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GraphicsLayout — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="AxisItem" href="axisitem.html" /> + <link rel="prev" title="ROI" href="roi.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="axisitem.html" title="AxisItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="roi.html" title="ROI" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="graphicslayout"> +<h1>GraphicsLayout<a class="headerlink" href="#graphicslayout" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GraphicsLayout"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GraphicsLayout</tt><big>(</big><em>parent=None</em>, <em>border=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsLayout" title="Permalink to this definition">¶</a></dt> +<dd><p>Used for laying out GraphicsWidgets in a grid.</p> +<dl class="method"> +<dt id="pyqtgraph.GraphicsLayout.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>border=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsLayout.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsLayout.nextCol"> +<tt class="descname">nextCol</tt><big>(</big><em>colspan=1</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsLayout.nextCol" title="Permalink to this definition">¶</a></dt> +<dd><p>Advance to next column, while returning the current column number +(generally only for internal use–called by addItem)</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsLayout.nextRow"> +<tt class="descname">nextRow</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsLayout.nextRow" title="Permalink to this definition">¶</a></dt> +<dd><p>Advance to next row for automatic item placement</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="roi.html" + title="previous chapter">ROI</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="axisitem.html" + title="next chapter">AxisItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/graphicslayout.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="axisitem.html" title="AxisItem" + >next</a> |</li> + <li class="right" > + <a href="roi.html" title="ROI" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/graphicsobject.html b/documentation/build/html/graphicsItems/graphicsobject.html new file mode 100644 index 00000000..ad3dcd5f --- /dev/null +++ b/documentation/build/html/graphicsItems/graphicsobject.html @@ -0,0 +1,189 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GraphicsObject — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="GraphicsWidget" href="graphicswidget.html" /> + <link rel="prev" title="ButtonItem" href="buttonitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicswidget.html" title="GraphicsWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="buttonitem.html" title="ButtonItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="graphicsobject"> +<h1>GraphicsObject<a class="headerlink" href="#graphicsobject" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GraphicsObject"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GraphicsObject</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject" title="Permalink to this definition">¶</a></dt> +<dd><p>Extends QGraphicsObject with a few important functions. +(Most of these assume that the object is in a scene with a single view)</p> +<p>This class also generates a cache of the Qt-internal addresses of each item +so that GraphicsScene.items() can return the correct objects (this is a PyQt bug)</p> +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.deviceTransform"> +<tt class="descname">deviceTransform</tt><big>(</big><em>viewportTransform=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.deviceTransform" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the transform that converts item coordinates to device coordinates (usually pixels). +Extends deviceTransform to automatically determine the viewportTransform.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.getBoundingParents"> +<tt class="descname">getBoundingParents</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.getBoundingParents" title="Permalink to this definition">¶</a></dt> +<dd><p>Return a list of parents to this item that have child clipping enabled.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.getViewBox"> +<tt class="descname">getViewBox</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.getViewBox" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the first ViewBox or GraphicsView which bounds this item’s visible space. +If this item is not contained within a ViewBox, then the GraphicsView is returned. +If the item is contained inside nested ViewBoxes, then the inner-most ViewBox is returned. +The result is cached; clear the cache with forgetViewBox()</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.getViewWidget"> +<tt class="descname">getViewWidget</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.getViewWidget" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the view widget for this item. If the scene has multiple views, only the first view is returned. +The return value is cached; clear the cached value with forgetViewWidget()</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.pixelLength"> +<tt class="descname">pixelLength</tt><big>(</big><em>direction</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.pixelLength" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the length of one pixel in the direction indicated (in local coordinates)</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.pixelVectors"> +<tt class="descname">pixelVectors</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.pixelVectors" title="Permalink to this definition">¶</a></dt> +<dd><p>Return vectors in local coordinates representing the width and height of a view pixel.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.viewRect"> +<tt class="descname">viewRect</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.viewRect" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the bounds (in item coordinates) of this item’s ViewBox or GraphicsWidget</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsObject.viewTransform"> +<tt class="descname">viewTransform</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsObject.viewTransform" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the transform that maps from local coordinates to the item’s ViewBox coordinates +If there is no ViewBox, return the scene transform. +Returns None if the item does not have a view.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="buttonitem.html" + title="previous chapter">ButtonItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="graphicswidget.html" + title="next chapter">GraphicsWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/graphicsobject.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicswidget.html" title="GraphicsWidget" + >next</a> |</li> + <li class="right" > + <a href="buttonitem.html" title="ButtonItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/graphicswidget.html b/documentation/build/html/graphicsItems/graphicswidget.html new file mode 100644 index 00000000..d0283c97 --- /dev/null +++ b/documentation/build/html/graphicsItems/graphicswidget.html @@ -0,0 +1,132 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GraphicsWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="UIGraphicsItem" href="uigraphicsitem.html" /> + <link rel="prev" title="GraphicsObject" href="graphicsobject.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="uigraphicsitem.html" title="UIGraphicsItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="graphicsobject.html" title="GraphicsObject" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="graphicswidget"> +<h1>GraphicsWidget<a class="headerlink" href="#graphicswidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GraphicsWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GraphicsWidget</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsWidget" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.GraphicsWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd><p>Extends QGraphicsWidget with a workaround for a PyQt bug. +This class is otherwise identical to QGraphicsWidget.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="graphicsobject.html" + title="previous chapter">GraphicsObject</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="uigraphicsitem.html" + title="next chapter">UIGraphicsItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/graphicswidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="uigraphicsitem.html" title="UIGraphicsItem" + >next</a> |</li> + <li class="right" > + <a href="graphicsobject.html" title="GraphicsObject" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/griditem.html b/documentation/build/html/graphicsItems/griditem.html new file mode 100644 index 00000000..8abbf11f --- /dev/null +++ b/documentation/build/html/graphicsItems/griditem.html @@ -0,0 +1,132 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GridItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="ScaleBar" href="scalebar.html" /> + <link rel="prev" title="CurveArrow" href="curvearrow.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="scalebar.html" title="ScaleBar" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="curvearrow.html" title="CurveArrow" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="griditem"> +<h1>GridItem<a class="headerlink" href="#griditem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GridItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GridItem</tt><a class="headerlink" href="#pyqtgraph.GridItem" title="Permalink to this definition">¶</a></dt> +<dd><p>Displays a rectangular grid of lines indicating major divisions within a coordinate system. +Automatically determines what divisions to use.</p> +<dl class="method"> +<dt id="pyqtgraph.GridItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GridItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="curvearrow.html" + title="previous chapter">CurveArrow</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="scalebar.html" + title="next chapter">ScaleBar</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/griditem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="scalebar.html" title="ScaleBar" + >next</a> |</li> + <li class="right" > + <a href="curvearrow.html" title="CurveArrow" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/histogramlutitem.html b/documentation/build/html/graphicsItems/histogramlutitem.html new file mode 100644 index 00000000..529876dc --- /dev/null +++ b/documentation/build/html/graphicsItems/histogramlutitem.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>HistogramLUTItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="GradientLegend" href="gradientlegend.html" /> + <link rel="prev" title="GradientEditorItem" href="gradienteditoritem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="gradientlegend.html" title="GradientLegend" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="gradienteditoritem.html" title="GradientEditorItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="histogramlutitem"> +<h1>HistogramLUTItem<a class="headerlink" href="#histogramlutitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.HistogramLUTItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">HistogramLUTItem</tt><big>(</big><em>image=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.HistogramLUTItem" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.HistogramLUTItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>image=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.HistogramLUTItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="gradienteditoritem.html" + title="previous chapter">GradientEditorItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="gradientlegend.html" + title="next chapter">GradientLegend</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/histogramlutitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="gradientlegend.html" title="GradientLegend" + >next</a> |</li> + <li class="right" > + <a href="gradienteditoritem.html" title="GradientEditorItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/imageitem.html b/documentation/build/html/graphicsItems/imageitem.html new file mode 100644 index 00000000..e4a2aff2 --- /dev/null +++ b/documentation/build/html/graphicsItems/imageitem.html @@ -0,0 +1,184 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ImageItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="ViewBox" href="viewbox.html" /> + <link rel="prev" title="PlotItem" href="plotitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="viewbox.html" title="ViewBox" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="plotitem.html" title="PlotItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="imageitem"> +<h1>ImageItem<a class="headerlink" href="#imageitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ImageItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ImageItem</tt><big>(</big><em>image=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageItem" title="Permalink to this definition">¶</a></dt> +<dd><p>GraphicsObject displaying an image. Optimized for rapid update (ie video display)</p> +<dl class="method"> +<dt id="pyqtgraph.ImageItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>image=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd><p>See setImage for all allowed arguments.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageItem.getHistogram"> +<tt class="descname">getHistogram</tt><big>(</big><em>bins=500</em>, <em>step=3</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageItem.getHistogram" title="Permalink to this definition">¶</a></dt> +<dd><p>returns x and y arrays containing the histogram values for the current image. +The step argument causes pixels to be skipped when computing the histogram to save time.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageItem.pixelSize"> +<tt class="descname">pixelSize</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.ImageItem.pixelSize" title="Permalink to this definition">¶</a></dt> +<dd><p>return scene-size of a single pixel in the image</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageItem.setImage"> +<tt class="descname">setImage</tt><big>(</big><em>image=None</em>, <em>autoLevels=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageItem.setImage" title="Permalink to this definition">¶</a></dt> +<dd><p>Update the image displayed by this item. +Arguments:</p> +<blockquote> +image +autoLevels +lut +levels +opacity +compositionMode +border</blockquote> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageItem.setLevels"> +<tt class="descname">setLevels</tt><big>(</big><em>levels</em>, <em>update=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageItem.setLevels" title="Permalink to this definition">¶</a></dt> +<dd><dl class="docutils"> +<dt>Set image scaling levels. Can be one of: </dt> +<dd>[blackLevel, whiteLevel] +[[minRed, maxRed], [minGreen, maxGreen], [minBlue, maxBlue]]</dd> +</dl> +<p>Only the first format is compatible with lookup tables.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageItem.setLookupTable"> +<tt class="descname">setLookupTable</tt><big>(</big><em>lut</em>, <em>update=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageItem.setLookupTable" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the lookup table to use for this image. (see functions.makeARGB for more information on how this is used) +Optionally, lut can be a callable that accepts the current image as an argument and returns the lookup table to use.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageItem.setPxMode"> +<tt class="descname">setPxMode</tt><big>(</big><em>b</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageItem.setPxMode" title="Permalink to this definition">¶</a></dt> +<dd><p>Set whether the item ignores transformations and draws directly to screen pixels.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="plotitem.html" + title="previous chapter">PlotItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="viewbox.html" + title="next chapter">ViewBox</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/imageitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="viewbox.html" title="ViewBox" + >next</a> |</li> + <li class="right" > + <a href="plotitem.html" title="PlotItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/index.html b/documentation/build/html/graphicsItems/index.html new file mode 100644 index 00000000..7d8fbe62 --- /dev/null +++ b/documentation/build/html/graphicsItems/index.html @@ -0,0 +1,149 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Pyqtgraph’s Graphics Items — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="API Reference" href="../apireference.html" /> + <link rel="next" title="PlotDataItem" href="plotdataitem.html" /> + <link rel="prev" title="Pyqtgraph’s Helper Functions" href="../functions.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotdataitem.html" title="PlotDataItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="../functions.html" title="Pyqtgraph’s Helper Functions" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" accesskey="U">API Reference</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="pyqtgraph-s-graphics-items"> +<h1>Pyqtgraph’s Graphics Items<a class="headerlink" href="#pyqtgraph-s-graphics-items" title="Permalink to this headline">¶</a></h1> +<p>Since pyqtgraph relies on Qt’s GraphicsView framework, most of its graphics functionality is implemented as QGraphicsItem subclasses. This has two important consequences: 1) virtually anything you want to draw can be easily accomplished using the functionality provided by Qt. 2) Many of pyqtgraph’s GraphicsItem classes can be used in any normal QGraphicsScene.</p> +<p>Contents:</p> +<div class="toctree-wrapper compound"> +<ul> +<li class="toctree-l1"><a class="reference internal" href="plotdataitem.html">PlotDataItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="plotcurveitem.html">PlotCurveItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="scatterplotitem.html">ScatterPlotItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="plotitem.html">PlotItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="imageitem.html">ImageItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="viewbox.html">ViewBox</a></li> +<li class="toctree-l1"><a class="reference internal" href="linearregionitem.html">LinearRegionItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="infiniteline.html">InfiniteLine</a></li> +<li class="toctree-l1"><a class="reference internal" href="roi.html">ROI</a></li> +<li class="toctree-l1"><a class="reference internal" href="graphicslayout.html">GraphicsLayout</a></li> +<li class="toctree-l1"><a class="reference internal" href="axisitem.html">AxisItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="arrowitem.html">ArrowItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="curvepoint.html">CurvePoint</a></li> +<li class="toctree-l1"><a class="reference internal" href="curvearrow.html">CurveArrow</a></li> +<li class="toctree-l1"><a class="reference internal" href="griditem.html">GridItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="scalebar.html">ScaleBar</a></li> +<li class="toctree-l1"><a class="reference internal" href="labelitem.html">LabelItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="vtickgroup.html">VTickGroup</a></li> +<li class="toctree-l1"><a class="reference internal" href="gradienteditoritem.html">GradientEditorItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="histogramlutitem.html">HistogramLUTItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="gradientlegend.html">GradientLegend</a></li> +<li class="toctree-l1"><a class="reference internal" href="buttonitem.html">ButtonItem</a></li> +<li class="toctree-l1"><a class="reference internal" href="graphicsobject.html">GraphicsObject</a></li> +<li class="toctree-l1"><a class="reference internal" href="graphicswidget.html">GraphicsWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="uigraphicsitem.html">UIGraphicsItem</a></li> +</ul> +</div> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="../functions.html" + title="previous chapter">Pyqtgraph’s Helper Functions</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="plotdataitem.html" + title="next chapter">PlotDataItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/index.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotdataitem.html" title="PlotDataItem" + >next</a> |</li> + <li class="right" > + <a href="../functions.html" title="Pyqtgraph’s Helper Functions" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/infiniteline.html b/documentation/build/html/graphicsItems/infiniteline.html new file mode 100644 index 00000000..0f7b3630 --- /dev/null +++ b/documentation/build/html/graphicsItems/infiniteline.html @@ -0,0 +1,155 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>InfiniteLine — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="ROI" href="roi.html" /> + <link rel="prev" title="LinearRegionItem" href="linearregionitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="roi.html" title="ROI" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="linearregionitem.html" title="LinearRegionItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="infiniteline"> +<h1>InfiniteLine<a class="headerlink" href="#infiniteline" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.InfiniteLine"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">InfiniteLine</tt><big>(</big><em>pos=None</em>, <em>angle=90</em>, <em>pen=None</em>, <em>movable=False</em>, <em>bounds=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.InfiniteLine" title="Permalink to this definition">¶</a></dt> +<dd><p>Displays a line of infinite length. +This line may be dragged to indicate a position in data coordinates.</p> +<dl class="method"> +<dt id="pyqtgraph.InfiniteLine.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>pos=None</em>, <em>angle=90</em>, <em>pen=None</em>, <em>movable=False</em>, <em>bounds=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.InfiniteLine.__init__" title="Permalink to this definition">¶</a></dt> +<dd><dl class="docutils"> +<dt>Initialization options:</dt> +<dd>pos - Position of the line. This can be a QPointF or a single value for vertical/horizontal lines. +angle - Angle of line in degrees. 0 is horizontal, 90 is vertical. +pen - Pen to use when drawing line +movable - If True, the line can be dragged to a new position by the user +bounds - Optional [min, max] bounding values. Bounds are only valid if the line is vertical or horizontal.</dd> +</dl> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.InfiniteLine.setAngle"> +<tt class="descname">setAngle</tt><big>(</big><em>angle</em><big>)</big><a class="headerlink" href="#pyqtgraph.InfiniteLine.setAngle" title="Permalink to this definition">¶</a></dt> +<dd><p>Takes angle argument in degrees. +0 is horizontal; 90 is vertical.</p> +<p>Note that the use of value() and setValue() changes if the line is +not vertical or horizontal.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.InfiniteLine.setBounds"> +<tt class="descname">setBounds</tt><big>(</big><em>bounds</em><big>)</big><a class="headerlink" href="#pyqtgraph.InfiniteLine.setBounds" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the (minimum, maximum) allowable values when dragging.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="linearregionitem.html" + title="previous chapter">LinearRegionItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="roi.html" + title="next chapter">ROI</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/infiniteline.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="roi.html" title="ROI" + >next</a> |</li> + <li class="right" > + <a href="linearregionitem.html" title="LinearRegionItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/labelitem.html b/documentation/build/html/graphicsItems/labelitem.html new file mode 100644 index 00000000..058fd7da --- /dev/null +++ b/documentation/build/html/graphicsItems/labelitem.html @@ -0,0 +1,154 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>LabelItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="VTickGroup" href="vtickgroup.html" /> + <link rel="prev" title="ScaleBar" href="scalebar.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="vtickgroup.html" title="VTickGroup" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="scalebar.html" title="ScaleBar" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="labelitem"> +<h1>LabelItem<a class="headerlink" href="#labelitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.LabelItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">LabelItem</tt><big>(</big><em>text</em>, <em>parent=None</em>, <em>**args</em><big>)</big><a class="headerlink" href="#pyqtgraph.LabelItem" title="Permalink to this definition">¶</a></dt> +<dd><p>GraphicsWidget displaying text. +Used mainly as axis labels, titles, etc.</p> +<dl class="docutils"> +<dt>Note: To display text inside a scaled view (ViewBox, PlotWidget, etc) use QGraphicsTextItem</dt> +<dd>with the flag ItemIgnoresTransformations set.</dd> +</dl> +<dl class="method"> +<dt id="pyqtgraph.LabelItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>text</em>, <em>parent=None</em>, <em>**args</em><big>)</big><a class="headerlink" href="#pyqtgraph.LabelItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.LabelItem.setAttr"> +<tt class="descname">setAttr</tt><big>(</big><em>attr</em>, <em>value</em><big>)</big><a class="headerlink" href="#pyqtgraph.LabelItem.setAttr" title="Permalink to this definition">¶</a></dt> +<dd><p>Set default text properties. See setText() for accepted parameters.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.LabelItem.setText"> +<tt class="descname">setText</tt><big>(</big><em>text</em>, <em>**args</em><big>)</big><a class="headerlink" href="#pyqtgraph.LabelItem.setText" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the text and text properties in the label. Accepts optional arguments for auto-generating +a CSS style string:</p> +<blockquote> +color: string (example: ‘CCFF00’) +size: string (example: ‘8pt’) +bold: boolean +italic: boolean</blockquote> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="scalebar.html" + title="previous chapter">ScaleBar</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="vtickgroup.html" + title="next chapter">VTickGroup</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/labelitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="vtickgroup.html" title="VTickGroup" + >next</a> |</li> + <li class="right" > + <a href="scalebar.html" title="ScaleBar" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/linearregionitem.html b/documentation/build/html/graphicsItems/linearregionitem.html new file mode 100644 index 00000000..70795d12 --- /dev/null +++ b/documentation/build/html/graphicsItems/linearregionitem.html @@ -0,0 +1,138 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>LinearRegionItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="InfiniteLine" href="infiniteline.html" /> + <link rel="prev" title="ViewBox" href="viewbox.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="infiniteline.html" title="InfiniteLine" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="viewbox.html" title="ViewBox" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="linearregionitem"> +<h1>LinearRegionItem<a class="headerlink" href="#linearregionitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.LinearRegionItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">LinearRegionItem</tt><big>(</big><em>values=</em><span class="optional">[</span>, <em>0</em>, <em>1</em><span class="optional">]</span>, <em>orientation=None</em>, <em>brush=None</em>, <em>movable=True</em>, <em>bounds=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.LinearRegionItem" title="Permalink to this definition">¶</a></dt> +<dd><p>Used for marking a horizontal or vertical region in plots. +The region can be dragged and is bounded by lines which can be dragged individually.</p> +<dl class="method"> +<dt id="pyqtgraph.LinearRegionItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>values=</em><span class="optional">[</span>, <em>0</em>, <em>1</em><span class="optional">]</span>, <em>orientation=None</em>, <em>brush=None</em>, <em>movable=True</em>, <em>bounds=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.LinearRegionItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.LinearRegionItem.getRegion"> +<tt class="descname">getRegion</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.LinearRegionItem.getRegion" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the values at the edges of the region.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="viewbox.html" + title="previous chapter">ViewBox</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="infiniteline.html" + title="next chapter">InfiniteLine</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/linearregionitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="infiniteline.html" title="InfiniteLine" + >next</a> |</li> + <li class="right" > + <a href="viewbox.html" title="ViewBox" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/plotcurveitem.html b/documentation/build/html/graphicsItems/plotcurveitem.html new file mode 100644 index 00000000..a2586879 --- /dev/null +++ b/documentation/build/html/graphicsItems/plotcurveitem.html @@ -0,0 +1,141 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>PlotCurveItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="ScatterPlotItem" href="scatterplotitem.html" /> + <link rel="prev" title="PlotDataItem" href="plotdataitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="scatterplotitem.html" title="ScatterPlotItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="plotdataitem.html" title="PlotDataItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="plotcurveitem"> +<h1>PlotCurveItem<a class="headerlink" href="#plotcurveitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.PlotCurveItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">PlotCurveItem</tt><big>(</big><em>y=None</em>, <em>x=None</em>, <em>fillLevel=None</em>, <em>copy=False</em>, <em>pen=None</em>, <em>shadowPen=None</em>, <em>brush=None</em>, <em>parent=None</em>, <em>color=None</em>, <em>clickable=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotCurveItem" title="Permalink to this definition">¶</a></dt> +<dd><p>Class representing a single plot curve. Provides: +- Fast data update +- FFT display mode +- shadow pen +- mouse interaction</p> +<dl class="method"> +<dt id="pyqtgraph.PlotCurveItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>y=None</em>, <em>x=None</em>, <em>fillLevel=None</em>, <em>copy=False</em>, <em>pen=None</em>, <em>shadowPen=None</em>, <em>brush=None</em>, <em>parent=None</em>, <em>color=None</em>, <em>clickable=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotCurveItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotCurveItem.setData"> +<tt class="descname">setData</tt><big>(</big><em>x</em>, <em>y</em>, <em>copy=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotCurveItem.setData" title="Permalink to this definition">¶</a></dt> +<dd><p>For Qwt compatibility</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="plotdataitem.html" + title="previous chapter">PlotDataItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="scatterplotitem.html" + title="next chapter">ScatterPlotItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/plotcurveitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="scatterplotitem.html" title="ScatterPlotItem" + >next</a> |</li> + <li class="right" > + <a href="plotdataitem.html" title="PlotDataItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/plotdataitem.html b/documentation/build/html/graphicsItems/plotdataitem.html new file mode 100644 index 00000000..e0f95a14 --- /dev/null +++ b/documentation/build/html/graphicsItems/plotdataitem.html @@ -0,0 +1,289 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>PlotDataItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="PlotCurveItem" href="plotcurveitem.html" /> + <link rel="prev" title="Pyqtgraph’s Graphics Items" href="index.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotcurveitem.html" title="PlotCurveItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="index.html" title="Pyqtgraph’s Graphics Items" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="plotdataitem"> +<h1>PlotDataItem<a class="headerlink" href="#plotdataitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.PlotDataItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">PlotDataItem</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotDataItem" title="Permalink to this definition">¶</a></dt> +<dd><p>GraphicsItem for displaying plot curves, scatter plots, or both.</p> +<dl class="method"> +<dt id="pyqtgraph.PlotDataItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotDataItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd><p>There are many different ways to create a PlotDataItem:</p> +<p>Data initialization: (x,y data only)</p> +<blockquote> +<table border="1" class="docutils"> +<colgroup> +<col width="33%" /> +<col width="67%" /> +</colgroup> +<tbody valign="top"> +<tr><td>PlotDataItem(xValues, yValues)</td> +<td>x and y values may be any sequence (including ndarray) of real numbers</td> +</tr> +<tr><td>PlotDataItem(yValues)</td> +<td>y values only – x will be automatically set to range(len(y))</td> +</tr> +<tr><td>PlotDataItem(x=xValues, y=yValues)</td> +<td>x and y given by keyword arguments</td> +</tr> +<tr><td>PlotDataItem(ndarray(Nx2))</td> +<td>numpy array with shape (N, 2) where x=data[:,0] and y=data[:,1]</td> +</tr> +</tbody> +</table> +</blockquote> +<p>Data initialization: (x,y data AND may include spot style)</p> +<blockquote> +<table border="1" class="docutils"> +<colgroup> +<col width="32%" /> +<col width="68%" /> +</colgroup> +<tbody valign="top"> +<tr><td>PlotDataItem(recarray)</td> +<td>numpy array with dtype=[(‘x’, float), (‘y’, float), ...]</td> +</tr> +<tr><td>PlotDataItem(list-of-dicts)</td> +<td>[{‘x’: x, ‘y’: y, ...}, ...]</td> +</tr> +<tr><td>PlotDataItem(dict-of-lists)</td> +<td>{‘x’: [...], ‘y’: [...], ...}</td> +</tr> +<tr><td>PlotDataItem(MetaArray)</td> +<td>1D array of Y values with X sepecified as axis values +OR 2D array with a column ‘y’ and extra columns as needed.</td> +</tr> +</tbody> +</table> +</blockquote> +<p>Line style keyword arguments:</p> +<blockquote> +<table border="1" class="docutils"> +<colgroup> +<col width="8%" /> +<col width="92%" /> +</colgroup> +<tbody valign="top"> +<tr><td>pen</td> +<td>pen to use for drawing line between points. Default is solid grey, 1px width. Use None to disable line drawing.</td> +</tr> +<tr><td>shadowPen</td> +<td>pen for secondary line to draw behind the primary line. disabled by default.</td> +</tr> +<tr><td>fillLevel</td> +<td>fill the area between the curve and fillLevel</td> +</tr> +<tr><td>fillBrush</td> +<td>fill to use when fillLevel is specified</td> +</tr> +</tbody> +</table> +</blockquote> +<p>Point style keyword arguments:</p> +<blockquote> +<table border="1" class="docutils"> +<colgroup> +<col width="12%" /> +<col width="88%" /> +</colgroup> +<tbody valign="top"> +<tr><td>symbol</td> +<td>symbol to use for drawing points OR list of symbols, one per point. Default is no symbol. +options are o, s, t, d, +</td> +</tr> +<tr><td>symbolPen</td> +<td>outline pen for drawing points OR list of pens, one per point</td> +</tr> +<tr><td>symbolBrush</td> +<td>brush for filling points OR list of brushes, one per point</td> +</tr> +<tr><td>symbolSize</td> +<td>diameter of symbols OR list of diameters</td> +</tr> +<tr><td>pxMode</td> +<td>(bool) If True, then symbolSize is specified in pixels. If False, then symbolSize is +specified in data coordinates.</td> +</tr> +</tbody> +</table> +</blockquote> +<p>Optimization keyword arguments:</p> +<blockquote> +<table border="1" class="docutils"> +<colgroup> +<col width="10%" /> +<col width="90%" /> +</colgroup> +<tbody valign="top"> +<tr><td>identical</td> +<td>spots are all identical. The spot image will be rendered only once and repeated for every point</td> +</tr> +<tr><td>decimate</td> +<td>(int) decimate data</td> +</tr> +</tbody> +</table> +</blockquote> +<p>Meta-info keyword arguments:</p> +<blockquote> +<table border="1" class="docutils"> +<colgroup> +<col width="17%" /> +<col width="83%" /> +</colgroup> +<tbody valign="top"> +<tr><td>name</td> +<td>name of dataset. This would appear in a legend</td> +</tr> +</tbody> +</table> +</blockquote> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotDataItem.setData"> +<tt class="descname">setData</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotDataItem.setData" title="Permalink to this definition">¶</a></dt> +<dd><p>Clear any data displayed by this item and display new data. +See <a class="reference internal" href="#pyqtgraph.PlotDataItem.__init__" title="pyqtgraph.PlotDataItem.__init__"><tt class="xref py py-func docutils literal"><span class="pre">__init__()</span></tt></a> for details; it accepts the same arguments.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotDataItem.setPen"> +<tt class="descname">setPen</tt><big>(</big><em>pen</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotDataItem.setPen" title="Permalink to this definition">¶</a></dt> +<dd><div class="line-block"> +<div class="line">Sets the pen used to draw lines between points.</div> +<div class="line"><em>pen</em> can be a QPen or any argument accepted by <a class="reference internal" href="../functions.html#pyqtgraph.mkPen" title="pyqtgraph.mkPen"><tt class="xref py py-func docutils literal"><span class="pre">pyqtgraph.mkPen()</span></tt></a></div> +</div> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotDataItem.setShadowPen"> +<tt class="descname">setShadowPen</tt><big>(</big><em>pen</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotDataItem.setShadowPen" title="Permalink to this definition">¶</a></dt> +<dd><div class="line-block"> +<div class="line">Sets the shadow pen used to draw lines between points (this is for enhancing contrast or +emphacizing data). </div> +<div class="line">This line is drawn behind the primary pen (see <a class="reference internal" href="#pyqtgraph.PlotDataItem.setPen" title="pyqtgraph.PlotDataItem.setPen"><tt class="xref py py-func docutils literal"><span class="pre">setPen()</span></tt></a>) +and should generally be assigned greater width than the primary pen.</div> +<div class="line"><em>pen</em> can be a QPen or any argument accepted by <a class="reference internal" href="../functions.html#pyqtgraph.mkPen" title="pyqtgraph.mkPen"><tt class="xref py py-func docutils literal"><span class="pre">pyqtgraph.mkPen()</span></tt></a></div> +</div> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="index.html" + title="previous chapter">Pyqtgraph’s Graphics Items</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="plotcurveitem.html" + title="next chapter">PlotCurveItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/plotdataitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotcurveitem.html" title="PlotCurveItem" + >next</a> |</li> + <li class="right" > + <a href="index.html" title="Pyqtgraph’s Graphics Items" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/plotitem.html b/documentation/build/html/graphicsItems/plotitem.html new file mode 100644 index 00000000..9c07f964 --- /dev/null +++ b/documentation/build/html/graphicsItems/plotitem.html @@ -0,0 +1,245 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>PlotItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="ImageItem" href="imageitem.html" /> + <link rel="prev" title="ScatterPlotItem" href="scatterplotitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="imageitem.html" title="ImageItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="scatterplotitem.html" title="ScatterPlotItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="plotitem"> +<h1>PlotItem<a class="headerlink" href="#plotitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.PlotItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">PlotItem</tt><big>(</big><em>parent=None</em>, <em>name=None</em>, <em>labels=None</em>, <em>title=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.PlotItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>name=None</em>, <em>labels=None</em>, <em>title=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.addAvgCurve"> +<tt class="descname">addAvgCurve</tt><big>(</big><em>curve</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.addAvgCurve" title="Permalink to this definition">¶</a></dt> +<dd><p>Add a single curve into the pool of curves averaged together</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.enableAutoScale"> +<tt class="descname">enableAutoScale</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.enableAutoScale" title="Permalink to this definition">¶</a></dt> +<dd><p>Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.linkXChanged"> +<tt class="descname">linkXChanged</tt><big>(</big><em>plot</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.linkXChanged" title="Permalink to this definition">¶</a></dt> +<dd><p>Called when a linked plot has changed its X scale</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.linkYChanged"> +<tt class="descname">linkYChanged</tt><big>(</big><em>plot</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.linkYChanged" title="Permalink to this definition">¶</a></dt> +<dd><p>Called when a linked plot has changed its Y scale</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.plot"> +<tt class="descname">plot</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.plot" title="Permalink to this definition">¶</a></dt> +<dd><p>Add and return a new plot. +See PlotDataItem.__init__ for data arguments</p> +<dl class="docutils"> +<dt>Extra allowed arguments are:</dt> +<dd>clear - clear all plots before displaying new data +params - meta-parameters to associate with this data</dd> +</dl> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.setLabel"> +<tt class="descname">setLabel</tt><big>(</big><em>axis</em>, <em>text=None</em>, <em>units=None</em>, <em>unitPrefix=None</em>, <em>**args</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.setLabel" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the label for an axis. Basic HTML formatting is allowed. +Arguments:</p> +<blockquote> +<p>axis - must be one of ‘left’, ‘bottom’, ‘right’, or ‘top’ +text - text to display along the axis. HTML allowed. +units - units to display after the title. If units are given,</p> +<blockquote> +then an SI prefix will be automatically appended +and the axis values will be scaled accordingly. +(ie, use ‘V’ instead of ‘mV’; ‘m’ will be added automatically)</blockquote> +</blockquote> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.setTitle"> +<tt class="descname">setTitle</tt><big>(</big><em>title=None</em>, <em>**args</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.setTitle" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the title of the plot. Basic HTML formatting is allowed. +If title is None, then the title will be hidden.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.setXLink"> +<tt class="descname">setXLink</tt><big>(</big><em>plot=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.setXLink" title="Permalink to this definition">¶</a></dt> +<dd><p>Link this plot’s X axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.setYLink"> +<tt class="descname">setYLink</tt><big>(</big><em>plot=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.setYLink" title="Permalink to this definition">¶</a></dt> +<dd><p>Link this plot’s Y axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.showAxis"> +<tt class="descname">showAxis</tt><big>(</big><em>axis</em>, <em>show=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.showAxis" title="Permalink to this definition">¶</a></dt> +<dd><p>Show or hide one of the plot’s axes. +axis must be one of ‘left’, ‘bottom’, ‘right’, or ‘top’</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.showLabel"> +<tt class="descname">showLabel</tt><big>(</big><em>axis</em>, <em>show=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.showLabel" title="Permalink to this definition">¶</a></dt> +<dd><p>Show or hide one of the plot’s axis labels (the axis itself will be unaffected). +axis must be one of ‘left’, ‘bottom’, ‘right’, or ‘top’</p> +</dd></dl> + +<dl class="attribute"> +<dt id="pyqtgraph.PlotItem.sigRangeChanged"> +<tt class="descname">sigRangeChanged</tt><a class="headerlink" href="#pyqtgraph.PlotItem.sigRangeChanged" title="Permalink to this definition">¶</a></dt> +<dd><p>Plot graphics item that can be added to any graphics scene. Implements axis titles, scales, interactive viewbox.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.updatePlotList"> +<tt class="descname">updatePlotList</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.updatePlotList" title="Permalink to this definition">¶</a></dt> +<dd><p>Update the list of all plotWidgets in the “link” combos</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.updateXScale"> +<tt class="descname">updateXScale</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.updateXScale" title="Permalink to this definition">¶</a></dt> +<dd><p>Set plot to autoscale or not depending on state of radio buttons</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.updateYScale"> +<tt class="descname">updateYScale</tt><big>(</big><em>b=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.updateYScale" title="Permalink to this definition">¶</a></dt> +<dd><p>Set plot to autoscale or not depending on state of radio buttons</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.PlotItem.viewGeometry"> +<tt class="descname">viewGeometry</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.PlotItem.viewGeometry" title="Permalink to this definition">¶</a></dt> +<dd><p>return the screen geometry of the viewbox</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="scatterplotitem.html" + title="previous chapter">ScatterPlotItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="imageitem.html" + title="next chapter">ImageItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/plotitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="imageitem.html" title="ImageItem" + >next</a> |</li> + <li class="right" > + <a href="scatterplotitem.html" title="ScatterPlotItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/roi.html b/documentation/build/html/graphicsItems/roi.html new file mode 100644 index 00000000..111304f4 --- /dev/null +++ b/documentation/build/html/graphicsItems/roi.html @@ -0,0 +1,185 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ROI — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="GraphicsLayout" href="graphicslayout.html" /> + <link rel="prev" title="InfiniteLine" href="infiniteline.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicslayout.html" title="GraphicsLayout" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="infiniteline.html" title="InfiniteLine" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="roi"> +<h1>ROI<a class="headerlink" href="#roi" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ROI"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ROI</tt><big>(</big><em>pos</em>, <em>size=Point(1.000000</em>, <em>1.000000)</em>, <em>angle=0.0</em>, <em>invertible=False</em>, <em>maxBounds=None</em>, <em>snapSize=1.0</em>, <em>scaleSnap=False</em>, <em>translateSnap=False</em>, <em>rotateSnap=False</em>, <em>parent=None</em>, <em>pen=None</em>, <em>movable=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.ROI" title="Permalink to this definition">¶</a></dt> +<dd><p>Generic region-of-interest widget. +Can be used for implementing many types of selection box with rotate/translate/scale handles.</p> +<dl class="method"> +<dt id="pyqtgraph.ROI.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>pos</em>, <em>size=Point(1.000000</em>, <em>1.000000)</em>, <em>angle=0.0</em>, <em>invertible=False</em>, <em>maxBounds=None</em>, <em>snapSize=1.0</em>, <em>scaleSnap=False</em>, <em>translateSnap=False</em>, <em>rotateSnap=False</em>, <em>parent=None</em>, <em>pen=None</em>, <em>movable=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.ROI.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ROI.getArrayRegion"> +<tt class="descname">getArrayRegion</tt><big>(</big><em>data</em>, <em>img</em>, <em>axes=(0</em>, <em>1)</em><big>)</big><a class="headerlink" href="#pyqtgraph.ROI.getArrayRegion" title="Permalink to this definition">¶</a></dt> +<dd><p>Use the position of this ROI relative to an imageItem to pull a slice from an array.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ROI.getArraySlice"> +<tt class="descname">getArraySlice</tt><big>(</big><em>data</em>, <em>img</em>, <em>axes=(0</em>, <em>1)</em>, <em>returnSlice=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.ROI.getArraySlice" title="Permalink to this definition">¶</a></dt> +<dd><p>Return a tuple of slice objects that can be used to slice the region from data covered by this ROI. +Also returns the transform which maps the ROI into data coordinates.</p> +<p>If returnSlice is set to False, the function returns a pair of tuples with the values that would have +been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ROI.getGlobalTransform"> +<tt class="descname">getGlobalTransform</tt><big>(</big><em>relativeTo=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ROI.getGlobalTransform" title="Permalink to this definition">¶</a></dt> +<dd><p>Return global transformation (rotation angle+translation) required to move from relative state to current state. If relative state isn’t specified, +then we use the state of the ROI when mouse is pressed.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ROI.getLocalHandlePositions"> +<tt class="descname">getLocalHandlePositions</tt><big>(</big><em>index=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ROI.getLocalHandlePositions" title="Permalink to this definition">¶</a></dt> +<dd><p>Returns the position of a handle in ROI coordinates</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ROI.handleChange"> +<tt class="descname">handleChange</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.ROI.handleChange" title="Permalink to this definition">¶</a></dt> +<dd><p>The state of the ROI has changed; redraw if needed.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ROI.saveState"> +<tt class="descname">saveState</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.ROI.saveState" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the state of the widget in a format suitable for storing to disk.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ROI.translate"> +<tt class="descname">translate</tt><big>(</big><em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.ROI.translate" title="Permalink to this definition">¶</a></dt> +<dd><p>accepts either (x, y, snap) or ([x,y], snap) as arguments</p> +<dl class="docutils"> +<dt>snap can be:</dt> +<dd>None (default): use self.translateSnap and self.snapSize to determine whether/how to snap +False: do no snap +Point(w,h) snap to rectangular grid with spacing (w,h) +True: snap using self.snapSize (and ignoring self.translateSnap)</dd> +</dl> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="infiniteline.html" + title="previous chapter">InfiniteLine</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="graphicslayout.html" + title="next chapter">GraphicsLayout</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/roi.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicslayout.html" title="GraphicsLayout" + >next</a> |</li> + <li class="right" > + <a href="infiniteline.html" title="InfiniteLine" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/scalebar.html b/documentation/build/html/graphicsItems/scalebar.html new file mode 100644 index 00000000..2a198eb2 --- /dev/null +++ b/documentation/build/html/graphicsItems/scalebar.html @@ -0,0 +1,131 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ScaleBar — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="LabelItem" href="labelitem.html" /> + <link rel="prev" title="GridItem" href="griditem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="labelitem.html" title="LabelItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="griditem.html" title="GridItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="scalebar"> +<h1>ScaleBar<a class="headerlink" href="#scalebar" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ScaleBar"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ScaleBar</tt><big>(</big><em>size</em>, <em>width=5</em>, <em>color=(100</em>, <em>100</em>, <em>255)</em><big>)</big><a class="headerlink" href="#pyqtgraph.ScaleBar" title="Permalink to this definition">¶</a></dt> +<dd><p>Displays a rectangular bar with 10 divisions to indicate the relative scale of objects on the view.</p> +<dl class="method"> +<dt id="pyqtgraph.ScaleBar.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>size</em>, <em>width=5</em>, <em>color=(100</em>, <em>100</em>, <em>255)</em><big>)</big><a class="headerlink" href="#pyqtgraph.ScaleBar.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="griditem.html" + title="previous chapter">GridItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="labelitem.html" + title="next chapter">LabelItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/scalebar.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="labelitem.html" title="LabelItem" + >next</a> |</li> + <li class="right" > + <a href="griditem.html" title="GridItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/scatterplotitem.html b/documentation/build/html/graphicsItems/scatterplotitem.html new file mode 100644 index 00000000..4333b0c4 --- /dev/null +++ b/documentation/build/html/graphicsItems/scatterplotitem.html @@ -0,0 +1,171 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ScatterPlotItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="PlotItem" href="plotitem.html" /> + <link rel="prev" title="PlotCurveItem" href="plotcurveitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotitem.html" title="PlotItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="plotcurveitem.html" title="PlotCurveItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="scatterplotitem"> +<h1>ScatterPlotItem<a class="headerlink" href="#scatterplotitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ScatterPlotItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ScatterPlotItem</tt><big>(</big><em>spots=None</em>, <em>x=None</em>, <em>y=None</em>, <em>pxMode=True</em>, <em>pen='default'</em>, <em>brush='default'</em>, <em>size=7</em>, <em>symbol=None</em>, <em>identical=False</em>, <em>data=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ScatterPlotItem" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.ScatterPlotItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>spots=None</em>, <em>x=None</em>, <em>y=None</em>, <em>pxMode=True</em>, <em>pen='default'</em>, <em>brush='default'</em>, <em>size=7</em>, <em>symbol=None</em>, <em>identical=False</em>, <em>data=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ScatterPlotItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd><dl class="docutils"> +<dt>Arguments:</dt> +<dd><dl class="first docutils"> +<dt>spots: list of dicts. Each dict specifies parameters for a single spot:</dt> +<dd>{‘pos’: (x,y), ‘size’, ‘pen’, ‘brush’, ‘symbol’}</dd> +</dl> +<p>x,y: array of x,y values. Alternatively, specify spots[‘pos’] = (x,y) +pxMode: If True, spots are always the same size regardless of scaling, and size is given in px.</p> +<blockquote> +Otherwise, size is in scene coordinates and the spots scale with the view.</blockquote> +<dl class="last docutils"> +<dt>identical: If True, all spots are forced to look identical. </dt> +<dd>This can result in performance enhancement.</dd> +<dt>symbol can be one of:</dt> +<dd>‘o’ circle +‘s’ square +‘t’ triangle +‘d’ diamond +‘+’ plus</dd> +</dl> +</dd> +</dl> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ScatterPlotItem.setPoints"> +<tt class="descname">setPoints</tt><big>(</big><em>spots=None</em>, <em>x=None</em>, <em>y=None</em>, <em>data=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ScatterPlotItem.setPoints" title="Permalink to this definition">¶</a></dt> +<dd><p>Remove all existing points in the scatter plot and add a new set. +Arguments:</p> +<blockquote> +<dl class="docutils"> +<dt>spots - list of dicts specifying parameters for each spot</dt> +<dd>[ {‘pos’: (x,y), ‘pen’: ‘r’, ...}, ...]</dd> +<dt>x, y - arrays specifying location of spots to add. </dt> +<dd>all other parameters (pen, symbol, etc.) will be set to the default +values for this scatter plot. +these arguments are IGNORED if ‘spots’ is specified</dd> +<dt>data - list of arbitrary objects to be assigned to spot.data for each spot</dt> +<dd>(this is useful for identifying spots that are clicked on)</dd> +</dl> +</blockquote> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="plotcurveitem.html" + title="previous chapter">PlotCurveItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="plotitem.html" + title="next chapter">PlotItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/scatterplotitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotitem.html" title="PlotItem" + >next</a> |</li> + <li class="right" > + <a href="plotcurveitem.html" title="PlotCurveItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/uigraphicsitem.html b/documentation/build/html/graphicsItems/uigraphicsitem.html new file mode 100644 index 00000000..36ac517d --- /dev/null +++ b/documentation/build/html/graphicsItems/uigraphicsitem.html @@ -0,0 +1,179 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>UIGraphicsItem — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="Pyqtgraph’s Widgets" href="../widgets/index.html" /> + <link rel="prev" title="GraphicsWidget" href="graphicswidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="../widgets/index.html" title="Pyqtgraph’s Widgets" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="graphicswidget.html" title="GraphicsWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="uigraphicsitem"> +<h1>UIGraphicsItem<a class="headerlink" href="#uigraphicsitem" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.UIGraphicsItem"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">UIGraphicsItem</tt><big>(</big><em>bounds=None</em>, <em>parent=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.UIGraphicsItem" title="Permalink to this definition">¶</a></dt> +<dd><p>Base class for graphics items with boundaries relative to a GraphicsView or ViewBox. +The purpose of this class is to allow the creation of GraphicsItems which live inside +a scalable view, but whose boundaries will always stay fixed relative to the view’s boundaries. +For example: GridItem, InfiniteLine</p> +<p>The view can be specified on initialization or it can be automatically detected when the item is painted.</p> +<p>NOTE: Only the item’s boundingRect is affected; the item is not transformed in any way. Use viewRangeChanged +to respond to changes in the view.</p> +<dl class="method"> +<dt id="pyqtgraph.UIGraphicsItem.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>bounds=None</em>, <em>parent=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.UIGraphicsItem.__init__" title="Permalink to this definition">¶</a></dt> +<dd><dl class="docutils"> +<dt>Initialization Arguments:</dt> +<dd><p class="first">#view: The view box whose bounds will be used as a reference vor this item’s bounds +bounds: QRectF with coordinates relative to view box. The default is QRectF(0,0,1,1),</p> +<blockquote class="last"> +which means the item will have the same bounds as the view.</blockquote> +</dd> +</dl> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.UIGraphicsItem.mouseShape"> +<tt class="descname">mouseShape</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.UIGraphicsItem.mouseShape" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the shape of this item after expanding by 2 pixels</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.UIGraphicsItem.realBoundingRect"> +<tt class="descname">realBoundingRect</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.UIGraphicsItem.realBoundingRect" title="Permalink to this definition">¶</a></dt> +<dd><p>Called by ViewBox for determining the auto-range bounds. +If the height or with of the rect is 0, that dimension will be ignored. +By default, UIGraphicsItems are excluded from autoRange by returning +a zero-size rect.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.UIGraphicsItem.setNewBounds"> +<tt class="descname">setNewBounds</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.UIGraphicsItem.setNewBounds" title="Permalink to this definition">¶</a></dt> +<dd><p>Update the item’s bounding rect to match the viewport</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.UIGraphicsItem.viewChangedEvent"> +<tt class="descname">viewChangedEvent</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.UIGraphicsItem.viewChangedEvent" title="Permalink to this definition">¶</a></dt> +<dd><p>Called whenever the view coordinates have changed. +This is a good method to override if you want to respond to change of coordinates.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.UIGraphicsItem.viewRangeChanged"> +<tt class="descname">viewRangeChanged</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.UIGraphicsItem.viewRangeChanged" title="Permalink to this definition">¶</a></dt> +<dd><p>Called when the view widget/viewbox is resized/rescaled</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="graphicswidget.html" + title="previous chapter">GraphicsWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="../widgets/index.html" + title="next chapter">Pyqtgraph’s Widgets</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/uigraphicsitem.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="../widgets/index.html" title="Pyqtgraph’s Widgets" + >next</a> |</li> + <li class="right" > + <a href="graphicswidget.html" title="GraphicsWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/viewbox.html b/documentation/build/html/graphicsItems/viewbox.html new file mode 100644 index 00000000..044c71cf --- /dev/null +++ b/documentation/build/html/graphicsItems/viewbox.html @@ -0,0 +1,227 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ViewBox — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="LinearRegionItem" href="linearregionitem.html" /> + <link rel="prev" title="ImageItem" href="imageitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="linearregionitem.html" title="LinearRegionItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="imageitem.html" title="ImageItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="viewbox"> +<h1>ViewBox<a class="headerlink" href="#viewbox" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ViewBox"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ViewBox</tt><big>(</big><em>parent=None</em>, <em>border=None</em>, <em>lockAspect=False</em>, <em>enableMouse=True</em>, <em>invertY=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox" title="Permalink to this definition">¶</a></dt> +<dd><p>Box that allows internal scaling/panning of children by mouse drag. +Not really compatible with GraphicsView having the same functionality.</p> +<dl class="method"> +<dt id="pyqtgraph.ViewBox.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>border=None</em>, <em>lockAspect=False</em>, <em>enableMouse=True</em>, <em>invertY=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.childTransform"> +<tt class="descname">childTransform</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.childTransform" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the transform that maps from child(item in the childGroup) coordinates to local coordinates. +(This maps from inside the viewbox to outside)</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.childrenBoundingRect"> +<tt class="descname">childrenBoundingRect</tt><big>(</big><em>item=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.childrenBoundingRect" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the bounding rect of all children. Returns None if there are no bounded children</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.itemBoundingRect"> +<tt class="descname">itemBoundingRect</tt><big>(</big><em>item</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.itemBoundingRect" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the bounding rect of the item in view coordinates</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.keyPressEvent"> +<tt class="descname">keyPressEvent</tt><big>(</big><em>ev</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.keyPressEvent" title="Permalink to this definition">¶</a></dt> +<dd><p>This routine should capture key presses in the current view box. +Key presses are used only when self.useLeftButtonPan is false +The following events are implemented: +ctrl-A : zooms out to the default “full” view of the plot +ctrl-+ : moves forward in the zooming stack (if it exists) +ctrl– : moves backward in the zooming stack (if it exists)</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.mapFromView"> +<tt class="descname">mapFromView</tt><big>(</big><em>obj</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.mapFromView" title="Permalink to this definition">¶</a></dt> +<dd><p>Maps from the coordinate system displayed inside the ViewBox to the local coordinates of the ViewBox</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.mapSceneToView"> +<tt class="descname">mapSceneToView</tt><big>(</big><em>obj</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.mapSceneToView" title="Permalink to this definition">¶</a></dt> +<dd><p>Maps from scene coordinates to the coordinate system displayed inside the ViewBox</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.mapToView"> +<tt class="descname">mapToView</tt><big>(</big><em>obj</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.mapToView" title="Permalink to this definition">¶</a></dt> +<dd><p>Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.mapViewToScene"> +<tt class="descname">mapViewToScene</tt><big>(</big><em>obj</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.mapViewToScene" title="Permalink to this definition">¶</a></dt> +<dd><p>Maps from the coordinate system displayed inside the ViewBox to scene coordinates</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.scaleBy"> +<tt class="descname">scaleBy</tt><big>(</big><em>s</em>, <em>center=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.scaleBy" title="Permalink to this definition">¶</a></dt> +<dd><p>Scale by s around given center point (or center of view)</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.setAspectLocked"> +<tt class="descname">setAspectLocked</tt><big>(</big><em>lock=True</em>, <em>ratio=1</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.setAspectLocked" title="Permalink to this definition">¶</a></dt> +<dd><p>If the aspect ratio is locked, view scaling is always forced to be isotropic. +By default, the ratio is set to 1; x and y both have the same scaling. +This ratio can be overridden (width/height), or use None to lock in the current ratio.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.setRange"> +<tt class="descname">setRange</tt><big>(</big><em>ax</em>, <em>minimum=None</em>, <em>maximum=None</em>, <em>padding=0.02</em>, <em>update=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.setRange" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the visible range of the ViewBox. +Can be called with a QRectF:</p> +<blockquote> +setRange(QRectF(x, y, w, h))</blockquote> +<dl class="docutils"> +<dt>Or with axis, min, max:</dt> +<dd>setRange(0, xMin, xMax) +setRange(1, yMin, yMax)</dd> +</dl> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.targetRect"> +<tt class="descname">targetRect</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.targetRect" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the region which has been requested to be visible. +(this is not necessarily the same as the region that is <em>actually</em> visible)</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ViewBox.viewRect"> +<tt class="descname">viewRect</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.ViewBox.viewRect" title="Permalink to this definition">¶</a></dt> +<dd><p>Return a QRectF bounding the region visible within the ViewBox</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="imageitem.html" + title="previous chapter">ImageItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="linearregionitem.html" + title="next chapter">LinearRegionItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/viewbox.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="linearregionitem.html" title="LinearRegionItem" + >next</a> |</li> + <li class="right" > + <a href="imageitem.html" title="ImageItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicsItems/vtickgroup.html b/documentation/build/html/graphicsItems/vtickgroup.html new file mode 100644 index 00000000..d84e5bdb --- /dev/null +++ b/documentation/build/html/graphicsItems/vtickgroup.html @@ -0,0 +1,132 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>VTickGroup — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Graphics Items" href="index.html" /> + <link rel="next" title="GradientEditorItem" href="gradienteditoritem.html" /> + <link rel="prev" title="LabelItem" href="labelitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="gradienteditoritem.html" title="GradientEditorItem" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="labelitem.html" title="LabelItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="vtickgroup"> +<h1>VTickGroup<a class="headerlink" href="#vtickgroup" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.VTickGroup"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">VTickGroup</tt><big>(</big><em>xvals=None</em>, <em>yrange=None</em>, <em>pen=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.VTickGroup" title="Permalink to this definition">¶</a></dt> +<dd><p>Draws a set of tick marks which always occupy the same vertical range of the view, +but have x coordinates relative to the data within the view.</p> +<dl class="method"> +<dt id="pyqtgraph.VTickGroup.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>xvals=None</em>, <em>yrange=None</em>, <em>pen=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.VTickGroup.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="labelitem.html" + title="previous chapter">LabelItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="gradienteditoritem.html" + title="next chapter">GradientEditorItem</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/graphicsItems/vtickgroup.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="gradienteditoritem.html" title="GradientEditorItem" + >next</a> |</li> + <li class="right" > + <a href="labelitem.html" title="LabelItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Graphics Items</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/graphicswindow.html b/documentation/build/html/graphicswindow.html new file mode 100644 index 00000000..87026901 --- /dev/null +++ b/documentation/build/html/graphicswindow.html @@ -0,0 +1,123 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Basic display widgets — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="Rapid GUI prototyping" href="parametertree.html" /> + <link rel="prev" title="Region-of-interest controls" href="region_of_interest.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="parametertree.html" title="Rapid GUI prototyping" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="region_of_interest.html" title="Region-of-interest controls" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="basic-display-widgets"> +<h1>Basic display widgets<a class="headerlink" href="#basic-display-widgets" title="Permalink to this headline">¶</a></h1> +<blockquote> +<ul class="simple"> +<li>GraphicsWindow</li> +<li>GraphicsView</li> +<li>GraphicsLayoutItem</li> +<li>ViewBox</li> +</ul> +</blockquote> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="region_of_interest.html" + title="previous chapter">Region-of-interest controls</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="parametertree.html" + title="next chapter">Rapid GUI prototyping</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/graphicswindow.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="parametertree.html" title="Rapid GUI prototyping" + >next</a> |</li> + <li class="right" > + <a href="region_of_interest.html" title="Region-of-interest controls" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/how_to_use.html b/documentation/build/html/how_to_use.html new file mode 100644 index 00000000..a95e0688 --- /dev/null +++ b/documentation/build/html/how_to_use.html @@ -0,0 +1,161 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>How to use pyqtgraph — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="Plotting in pyqtgraph" href="plotting.html" /> + <link rel="prev" title="Introduction" href="introduction.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotting.html" title="Plotting in pyqtgraph" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="introduction.html" title="Introduction" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="how-to-use-pyqtgraph"> +<h1>How to use pyqtgraph<a class="headerlink" href="#how-to-use-pyqtgraph" title="Permalink to this headline">¶</a></h1> +<p>There are a few suggested ways to use pyqtgraph:</p> +<ul class="simple"> +<li>From the interactive shell (python -i, ipython, etc)</li> +<li>Displaying pop-up windows from an application</li> +<li>Embedding widgets in a PyQt application</li> +</ul> +<div class="section" id="command-line-use"> +<h2>Command-line use<a class="headerlink" href="#command-line-use" title="Permalink to this headline">¶</a></h2> +<p>Pyqtgraph makes it very easy to visualize data from the command line. Observe:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">pyqtgraph</span> <span class="kn">as</span> <span class="nn">pg</span> +<span class="n">pg</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c"># data can be a list of values or a numpy array</span> +</pre></div> +</div> +<p>The example above would open a window displaying a line plot of the data given. I don’t think it could reasonably be any simpler than that. The call to pg.plot returns a handle to the plot widget that is created, allowing more data to be added to the same window.</p> +<p>Further examples:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="n">pw</span> <span class="o">=</span> <span class="n">pg</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">xVals</span><span class="p">,</span> <span class="n">yVals</span><span class="p">,</span> <span class="n">pen</span><span class="o">=</span><span class="s">'r'</span><span class="p">)</span> <span class="c"># plot x vs y in red</span> +<span class="n">pw</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">xVals</span><span class="p">,</span> <span class="n">yVals2</span><span class="p">,</span> <span class="n">pen</span><span class="o">=</span><span class="s">'b'</span><span class="p">)</span> + +<span class="n">win</span> <span class="o">=</span> <span class="n">pg</span><span class="o">.</span><span class="n">GraphicsWindow</span><span class="p">()</span> <span class="c"># Automatically generates grids with multiple items</span> +<span class="n">win</span><span class="o">.</span><span class="n">addPlot</span><span class="p">(</span><span class="n">data1</span><span class="p">,</span> <span class="n">row</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">col</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span> +<span class="n">win</span><span class="o">.</span><span class="n">addPlot</span><span class="p">(</span><span class="n">data2</span><span class="p">,</span> <span class="n">row</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">col</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> +<span class="n">win</span><span class="o">.</span><span class="n">addPlot</span><span class="p">(</span><span class="n">data3</span><span class="p">,</span> <span class="n">row</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">col</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">colspan</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span> + +<span class="n">pg</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="n">imageData</span><span class="p">)</span> <span class="c"># imageData must be a numpy array with 2 to 4 dimensions</span> +</pre></div> +</div> +<p>We’re only scratching the surface here–these functions accept many different data formats and options for customizing the appearance of your data.</p> +</div> +<div class="section" id="displaying-windows-from-within-an-application"> +<h2>Displaying windows from within an application<a class="headerlink" href="#displaying-windows-from-within-an-application" title="Permalink to this headline">¶</a></h2> +<p>While I consider this approach somewhat lazy, it is often the case that ‘lazy’ is indistinguishable from ‘highly efficient’. The approach here is simply to use the very same functions that would be used on the command line, but from within an existing application. I often use this when I simply want to get a immediate feedback about the state of data in my application without taking the time to build a user interface for it.</p> +</div> +<div class="section" id="embedding-widgets-inside-pyqt-applications"> +<h2>Embedding widgets inside PyQt applications<a class="headerlink" href="#embedding-widgets-inside-pyqt-applications" title="Permalink to this headline">¶</a></h2> +<p>For the serious application developer, all of the functionality in pyqtgraph is available via widgets that can be embedded just like any other Qt widgets. Most importantly, see: PlotWidget, ImageView, GraphicsView, GraphicsLayoutWidget. Pyqtgraph’s widgets can be included in Designer’s ui files via the “Promote To...” functionality.</p> +</div> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">How to use pyqtgraph</a><ul> +<li><a class="reference internal" href="#command-line-use">Command-line use</a></li> +<li><a class="reference internal" href="#displaying-windows-from-within-an-application">Displaying windows from within an application</a></li> +<li><a class="reference internal" href="#embedding-widgets-inside-pyqt-applications">Embedding widgets inside PyQt applications</a></li> +</ul> +</li> +</ul> + + <h4>Previous topic</h4> + <p class="topless"><a href="introduction.html" + title="previous chapter">Introduction</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="plotting.html" + title="next chapter">Plotting in pyqtgraph</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/how_to_use.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotting.html" title="Plotting in pyqtgraph" + >next</a> |</li> + <li class="right" > + <a href="introduction.html" title="Introduction" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/images.html b/documentation/build/html/images.html new file mode 100644 index 00000000..81213b28 --- /dev/null +++ b/documentation/build/html/images.html @@ -0,0 +1,135 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Displaying images and video — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="Line, Fill, and Color" href="style.html" /> + <link rel="prev" title="Plotting in pyqtgraph" href="plotting.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="style.html" title="Line, Fill, and Color" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="plotting.html" title="Plotting in pyqtgraph" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="displaying-images-and-video"> +<h1>Displaying images and video<a class="headerlink" href="#displaying-images-and-video" title="Permalink to this headline">¶</a></h1> +<p>Pyqtgraph displays 2D numpy arrays as images and provides tools for determining how to translate between the numpy data type and RGB values on the screen. If you want to display data from common image and video file formats, you will need to load the data first using another library (PIL works well for images and built-in numpy conversion).</p> +<p>The easiest way to display 2D or 3D data is using the <a class="reference internal" href="functions.html#pyqtgraph.image" title="pyqtgraph.image"><tt class="xref py py-func docutils literal"><span class="pre">pyqtgraph.image()</span></tt></a> function:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">pyqtgraph</span> <span class="kn">as</span> <span class="nn">pg</span> +<span class="n">pg</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="n">imageData</span><span class="p">)</span> +</pre></div> +</div> +<p>This function will accept any floating-point or integer data types and displays a single <a class="reference internal" href="widgets/imageview.html#pyqtgraph.ImageView" title="pyqtgraph.ImageView"><tt class="xref py py-class docutils literal"><span class="pre">ImageView</span></tt></a> widget containing your data. This widget includes controls for determining how the image data will be converted to 32-bit RGBa values. Conversion happens in two steps (both are optional):</p> +<ol class="arabic simple"> +<li>Scale and offset the data (by selecting the dark/light levels on the displayed histogram)</li> +<li>Convert the data to color using a lookup table (determined by the colors shown in the gradient editor)</li> +</ol> +<p>If the data is 3D (time, x, y), then a time axis will be shown with a slider that can set the currently displayed frame. (if the axes in your data are ordered differently, use numpy.transpose to rearrange them)</p> +<p>There are a few other methods for displaying images as well:</p> +<ul class="simple"> +<li>The <a class="reference internal" href="widgets/imageview.html#pyqtgraph.ImageView" title="pyqtgraph.ImageView"><tt class="xref py py-class docutils literal"><span class="pre">ImageView</span></tt></a> class can also be instantiated directly and embedded in Qt applications.</li> +<li>Instances of <a class="reference internal" href="graphicsItems/imageitem.html#pyqtgraph.ImageItem" title="pyqtgraph.ImageItem"><tt class="xref py py-class docutils literal"><span class="pre">ImageItem</span></tt></a> can be used inside a GraphicsView.</li> +<li>For higher performance, use <a class="reference internal" href="widgets/rawimagewidget.html#pyqtgraph.RawImageWidget" title="pyqtgraph.RawImageWidget"><tt class="xref py py-class docutils literal"><span class="pre">RawImageWidget</span></tt></a>.</li> +</ul> +<p>Any of these classes are acceptable for displaying video by calling setImage() to display a new frame. To increase performance, the image processing system uses scipy.weave to produce compiled libraries. If your computer has a compiler available, weave will automatically attempt to build the libraries it needs on demand. If this fails, then the slower pure-python methods will be used instead.</p> +<p>For more information, see the classes listed above and the ‘VideoSpeedTest’, ‘ImageItem’, ‘ImageView’, and ‘HistogramLUT’ <a class="reference internal" href="introduction.html#examples"><em>Examples</em></a>.</p> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="plotting.html" + title="previous chapter">Plotting in pyqtgraph</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="style.html" + title="next chapter">Line, Fill, and Color</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/images.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="style.html" title="Line, Fill, and Color" + >next</a> |</li> + <li class="right" > + <a href="plotting.html" title="Plotting in pyqtgraph" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/index.html b/documentation/build/html/index.html new file mode 100644 index 00000000..25db4fd6 --- /dev/null +++ b/documentation/build/html/index.html @@ -0,0 +1,160 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Welcome to the documentation for pyqtgraph 1.8 — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="#" /> + <link rel="next" title="Introduction" href="introduction.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="introduction.html" title="Introduction" + accesskey="N">next</a> |</li> + <li><a href="#">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="welcome-to-the-documentation-for-pyqtgraph-1-8"> +<h1>Welcome to the documentation for pyqtgraph 1.8<a class="headerlink" href="#welcome-to-the-documentation-for-pyqtgraph-1-8" title="Permalink to this headline">¶</a></h1> +<p>Contents:</p> +<div class="toctree-wrapper compound"> +<ul> +<li class="toctree-l1"><a class="reference internal" href="introduction.html">Introduction</a><ul> +<li class="toctree-l2"><a class="reference internal" href="introduction.html#what-is-pyqtgraph">What is pyqtgraph?</a></li> +<li class="toctree-l2"><a class="reference internal" href="introduction.html#what-can-it-do">What can it do?</a></li> +<li class="toctree-l2"><a class="reference internal" href="introduction.html#examples">Examples</a></li> +<li class="toctree-l2"><a class="reference internal" href="introduction.html#how-does-it-compare-to">How does it compare to...</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="how_to_use.html">How to use pyqtgraph</a><ul> +<li class="toctree-l2"><a class="reference internal" href="how_to_use.html#command-line-use">Command-line use</a></li> +<li class="toctree-l2"><a class="reference internal" href="how_to_use.html#displaying-windows-from-within-an-application">Displaying windows from within an application</a></li> +<li class="toctree-l2"><a class="reference internal" href="how_to_use.html#embedding-widgets-inside-pyqt-applications">Embedding widgets inside PyQt applications</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="plotting.html">Plotting in pyqtgraph</a><ul> +<li class="toctree-l2"><a class="reference internal" href="plotting.html#organization-of-plotting-classes">Organization of Plotting Classes</a></li> +<li class="toctree-l2"><a class="reference internal" href="plotting.html#examples">Examples</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="images.html">Displaying images and video</a></li> +<li class="toctree-l1"><a class="reference internal" href="style.html">Line, Fill, and Color</a></li> +<li class="toctree-l1"><a class="reference internal" href="region_of_interest.html">Region-of-interest controls</a><ul> +<li class="toctree-l2"><a class="reference internal" href="region_of_interest.html#slicing-multidimensional-data">Slicing Multidimensional Data</a></li> +<li class="toctree-l2"><a class="reference internal" href="region_of_interest.html#linear-selection-and-marking">Linear Selection and Marking</a></li> +<li class="toctree-l2"><a class="reference internal" href="region_of_interest.html#d-selection-and-marking">2D Selection and Marking</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="graphicswindow.html">Basic display widgets</a></li> +<li class="toctree-l1"><a class="reference internal" href="parametertree.html">Rapid GUI prototyping</a></li> +<li class="toctree-l1"><a class="reference internal" href="apireference.html">API Reference</a><ul> +<li class="toctree-l2"><a class="reference internal" href="functions.html">Pyqtgraph’s Helper Functions</a></li> +<li class="toctree-l2"><a class="reference internal" href="graphicsItems/index.html">Pyqtgraph’s Graphics Items</a></li> +<li class="toctree-l2"><a class="reference internal" href="widgets/index.html">Pyqtgraph’s Widgets</a></li> +</ul> +</li> +</ul> +</div> +</div> +<div class="section" id="indices-and-tables"> +<h1>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Permalink to this headline">¶</a></h1> +<ul class="simple"> +<li><a class="reference internal" href="genindex.html"><em>Index</em></a></li> +<li><a class="reference internal" href="py-modindex.html"><em>Module Index</em></a></li> +<li><a class="reference internal" href="search.html"><em>Search Page</em></a></li> +</ul> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h3><a href="#">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">Welcome to the documentation for pyqtgraph 1.8</a><ul> +</ul> +</li> +<li><a class="reference internal" href="#indices-and-tables">Indices and tables</a></li> +</ul> + + <h4>Next topic</h4> + <p class="topless"><a href="introduction.html" + title="next chapter">Introduction</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/index.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="introduction.html" title="Introduction" + >next</a> |</li> + <li><a href="#">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/introduction.html b/documentation/build/html/introduction.html new file mode 100644 index 00000000..fc156976 --- /dev/null +++ b/documentation/build/html/introduction.html @@ -0,0 +1,163 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Introduction — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="How to use pyqtgraph" href="how_to_use.html" /> + <link rel="prev" title="Welcome to the documentation for pyqtgraph 1.8" href="index.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="how_to_use.html" title="How to use pyqtgraph" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="index.html" title="Welcome to the documentation for pyqtgraph 1.8" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="introduction"> +<h1>Introduction<a class="headerlink" href="#introduction" title="Permalink to this headline">¶</a></h1> +<div class="section" id="what-is-pyqtgraph"> +<h2>What is pyqtgraph?<a class="headerlink" href="#what-is-pyqtgraph" title="Permalink to this headline">¶</a></h2> +<p>Pyqtgraph is a graphics and user interface library for Python that provides functionality commonly required in engineering and science applications. Its primary goals are 1) to provide fast, interactive graphics for displaying data (plots, video, etc.) and 2) to provide tools to aid in rapid application development (for example, property trees such as used in Qt Designer).</p> +<p>Pyqtgraph makes heavy use of the Qt GUI platform (via PyQt or PySide) for its high-performance graphics and numpy for heavy number crunching. In particular, pyqtgraph uses Qt’s GraphicsView framework which is a highly capable graphics system on its own; we bring optimized and simplified primitives to this framework to allow data visualization with minimal effort.</p> +<p>It is known to run on Linux, Windows, and OSX</p> +</div> +<div class="section" id="what-can-it-do"> +<h2>What can it do?<a class="headerlink" href="#what-can-it-do" title="Permalink to this headline">¶</a></h2> +<p>Amongst the core features of pyqtgraph are:</p> +<ul class="simple"> +<li>Basic data visualization primitives: Images, line and scatter plots</li> +<li>Fast enough for realtime update of video/plot data</li> +<li>Interactive scaling/panning, averaging, FFTs, SVG/PNG export</li> +<li>Widgets for marking/selecting plot regions</li> +<li>Widgets for marking/selecting image region-of-interest and automatically slicing multi-dimensional image data</li> +<li>Framework for building customized image region-of-interest widgets</li> +<li>Docking system that replaces/complements Qt’s dock system to allow more complex (and more predictable) docking arrangements</li> +<li>ParameterTree widget for rapid prototyping of dynamic interfaces (Similar to the property trees in Qt Designer and many other applications)</li> +</ul> +</div> +<div class="section" id="examples"> +<span id="id1"></span><h2>Examples<a class="headerlink" href="#examples" title="Permalink to this headline">¶</a></h2> +<p>Pyqtgraph includes an extensive set of examples that can be accessed by running:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">pyqtgraph.examples</span> +<span class="n">pyqtgraph</span><span class="o">.</span><span class="n">examples</span><span class="o">.</span><span class="n">run</span><span class="p">()</span> +</pre></div> +</div> +<p>This will start a launcher with a list of available examples. Select an item from the list to view its source code and double-click an item to run the example.</p> +</div> +<div class="section" id="how-does-it-compare-to"> +<h2>How does it compare to...<a class="headerlink" href="#how-does-it-compare-to" title="Permalink to this headline">¶</a></h2> +<ul class="simple"> +<li>matplotlib: For plotting and making publication-quality graphics, matplotlib is far more mature than pyqtgraph. However, matplotlib is also much slower and not suitable for applications requiring realtime update of plots/video or rapid interactivity. It also does not provide any of the GUI tools and image interaction/slicing functionality in pyqtgraph.</li> +<li>pyqwt5: pyqwt is generally more mature than pyqtgraph for plotting and is about as fast. The major differences are 1) pyqtgraph is written in pure python, so it is somewhat more portable than pyqwt, which often lags behind pyqt in development (and can be a pain to install on some platforms) and 2) like matplotlib, pyqwt does not provide any of the GUI tools and image interaction/slicing functionality in pyqtgraph.</li> +</ul> +<p>(My experience with these libraries is somewhat outdated; please correct me if I am wrong here)</p> +</div> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">Introduction</a><ul> +<li><a class="reference internal" href="#what-is-pyqtgraph">What is pyqtgraph?</a></li> +<li><a class="reference internal" href="#what-can-it-do">What can it do?</a></li> +<li><a class="reference internal" href="#examples">Examples</a></li> +<li><a class="reference internal" href="#how-does-it-compare-to">How does it compare to...</a></li> +</ul> +</li> +</ul> + + <h4>Previous topic</h4> + <p class="topless"><a href="index.html" + title="previous chapter">Welcome to the documentation for pyqtgraph 1.8</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="how_to_use.html" + title="next chapter">How to use pyqtgraph</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/introduction.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="how_to_use.html" title="How to use pyqtgraph" + >next</a> |</li> + <li class="right" > + <a href="index.html" title="Welcome to the documentation for pyqtgraph 1.8" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/objects.inv b/documentation/build/html/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..aa34069b2bd1ed05c16836f5f8c7044c9f2be7df GIT binary patch literal 2225 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 literal 0 HcmV?d00001 diff --git a/documentation/build/html/parametertree.html b/documentation/build/html/parametertree.html new file mode 100644 index 00000000..211df78d --- /dev/null +++ b/documentation/build/html/parametertree.html @@ -0,0 +1,123 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Rapid GUI prototyping — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="API Reference" href="apireference.html" /> + <link rel="prev" title="Basic display widgets" href="graphicswindow.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="apireference.html" title="API Reference" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="graphicswindow.html" title="Basic display widgets" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="rapid-gui-prototyping"> +<h1>Rapid GUI prototyping<a class="headerlink" href="#rapid-gui-prototyping" title="Permalink to this headline">¶</a></h1> +<blockquote> +<ul class="simple"> +<li>parametertree</li> +<li>dockarea</li> +<li>flowchart</li> +<li>canvas</li> +</ul> +</blockquote> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="graphicswindow.html" + title="previous chapter">Basic display widgets</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="apireference.html" + title="next chapter">API Reference</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/parametertree.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="apireference.html" title="API Reference" + >next</a> |</li> + <li class="right" > + <a href="graphicswindow.html" title="Basic display widgets" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/plotting.html b/documentation/build/html/plotting.html new file mode 100644 index 00000000..3855cbec --- /dev/null +++ b/documentation/build/html/plotting.html @@ -0,0 +1,217 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Plotting in pyqtgraph — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="Displaying images and video" href="images.html" /> + <link rel="prev" title="How to use pyqtgraph" href="how_to_use.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="images.html" title="Displaying images and video" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="how_to_use.html" title="How to use pyqtgraph" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="plotting-in-pyqtgraph"> +<h1>Plotting in pyqtgraph<a class="headerlink" href="#plotting-in-pyqtgraph" title="Permalink to this headline">¶</a></h1> +<p>There are a few basic ways to plot data in pyqtgraph:</p> +<table border="1" class="docutils"> +<colgroup> +<col width="56%" /> +<col width="44%" /> +</colgroup> +<tbody valign="top"> +<tr><td><a class="reference internal" href="functions.html#pyqtgraph.plot" title="pyqtgraph.plot"><tt class="xref py py-func docutils literal"><span class="pre">pyqtgraph.plot()</span></tt></a></td> +<td>Create a new plot window showing your data</td> +</tr> +<tr><td><tt class="xref py py-func docutils literal"><span class="pre">PlotWidget.plot()</span></tt></td> +<td>Add a new set of data to an existing plot widget</td> +</tr> +<tr><td><a class="reference internal" href="graphicsItems/plotitem.html#pyqtgraph.PlotItem.plot" title="pyqtgraph.PlotItem.plot"><tt class="xref py py-func docutils literal"><span class="pre">PlotItem.plot()</span></tt></a></td> +<td>Add a new set of data to an existing plot widget</td> +</tr> +<tr><td><tt class="xref py py-func docutils literal"><span class="pre">GraphicsWindow.addPlot()</span></tt></td> +<td>Add a new plot to a grid of plots</td> +</tr> +</tbody> +</table> +<p>All of these will accept the same basic arguments which control how the plot data is interpreted and displayed:</p> +<ul class="simple"> +<li>x - Optional X data; if not specified, then a range of integers will be generated automatically.</li> +<li>y - Y data.</li> +<li>pen - The pen to use when drawing plot lines, or None to disable lines.</li> +<li>symbol - A string describing the shape of symbols to use for each point. Optionally, this may also be a sequence of strings with a different symbol for each point.</li> +<li>symbolPen - The pen (or sequence of pens) to use when drawing the symbol outline.</li> +<li>symbolBrush - The brush (or sequence of brushes) to use when filling the symbol.</li> +<li>fillLevel - Fills the area under the plot curve to this Y-value.</li> +<li>brush - The brush to use when filling under the curve.</li> +</ul> +<p>See the ‘plotting’ <a class="reference internal" href="introduction.html#examples"><em>example</em></a> for a demonstration of these arguments.</p> +<p>All of the above functions also return handles to the objects that are created, allowing the plots and data to be further modified.</p> +<div class="section" id="organization-of-plotting-classes"> +<h2>Organization of Plotting Classes<a class="headerlink" href="#organization-of-plotting-classes" title="Permalink to this headline">¶</a></h2> +<p>There are several classes invloved in displaying plot data. Most of these classes are instantiated automatically, but it is useful to understand how they are organized and relate to each other. Pyqtgraph is based heavily on Qt’s GraphicsView framework–if you are not already familiar with this, it’s worth reading about (but not essential). Most importantly: 1) Qt GUIs are composed of QWidgets, 2) A special widget called QGraphicsView is used for displaying complex graphics, and 3) QGraphicsItems define the objects that are displayed within a QGraphicsView.</p> +<ul> +<li><dl class="first docutils"> +<dt>Data Classes (all subclasses of QGraphicsItem)</dt> +<dd><ul class="first last simple"> +<li>PlotCurveItem - Displays a plot line given x,y data</li> +<li>ScatterPlotItem - Displays points given x,y data</li> +<li><tt class="xref py py-class docutils literal"><span class="pre">PlotDataItem</span></tt> - Combines PlotCurveItem and ScatterPlotItem. The plotting functions discussed above create objects of this type.</li> +</ul> +</dd> +</dl> +</li> +<li><dl class="first docutils"> +<dt>Container Classes (subclasses of QGraphicsItem; contain other QGraphicsItem objects and must be viewed from within a GraphicsView)</dt> +<dd><ul class="first last simple"> +<li>PlotItem - Contains a ViewBox for displaying data as well as AxisItems and labels for displaying the axes and title. This is a QGraphicsItem subclass and thus may only be used from within a GraphicsView</li> +<li>GraphicsLayoutItem - QGraphicsItem subclass which displays a grid of items. This is used to display multiple PlotItems together.</li> +<li>ViewBox - A QGraphicsItem subclass for displaying data. The user may scale/pan the contents of a ViewBox using the mouse. Typically all PlotData/PlotCurve/ScatterPlotItems are displayed from within a ViewBox.</li> +<li>AxisItem - Displays axis values, ticks, and labels. Most commonly used with PlotItem.</li> +</ul> +</dd> +</dl> +</li> +<li><dl class="first docutils"> +<dt>Container Classes (subclasses of QWidget; may be embedded in PyQt GUIs)</dt> +<dd><ul class="first last simple"> +<li>PlotWidget - A subclass of GraphicsView with a single PlotItem displayed. Most of the methods provided by PlotItem are also available through PlotWidget.</li> +<li>GraphicsLayoutWidget - QWidget subclass displaying a single GraphicsLayoutItem. Most of the methods provided by GraphicsLayoutItem are also available through GraphicsLayoutWidget.</li> +</ul> +</dd> +</dl> +</li> +</ul> +<img alt="_images/plottingClasses.png" src="_images/plottingClasses.png" /> +</div> +<div class="section" id="examples"> +<h2>Examples<a class="headerlink" href="#examples" title="Permalink to this headline">¶</a></h2> +<p>See the ‘plotting’ and ‘PlotWidget’ <a class="reference internal" href="introduction.html#examples"><em>examples included with pyqtgraph</em></a> for more information.</p> +<p>Show x,y data as scatter plot:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">pyqtgraph</span> <span class="kn">as</span> <span class="nn">pg</span> +<span class="kn">import</span> <span class="nn">numpy</span> <span class="kn">as</span> <span class="nn">np</span> +<span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">normal</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span> +<span class="n">y</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">normal</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span> +<span class="n">pg</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">pen</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">symbol</span><span class="o">=</span><span class="s">'o'</span><span class="p">)</span> <span class="c">## setting pen=None disables line drawing</span> +</pre></div> +</div> +<p>Create/show a plot widget, display three data curves:</p> +<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">pyqtgraph</span> <span class="kn">as</span> <span class="nn">pg</span> +<span class="kn">import</span> <span class="nn">numpy</span> <span class="kn">as</span> <span class="nn">np</span> +<span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">arange</span><span class="p">(</span><span class="mi">1000</span><span class="p">)</span> +<span class="n">y</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">normal</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1000</span><span class="p">))</span> +<span class="n">plotWidget</span> <span class="o">=</span> <span class="n">pg</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s">"Three plot curves"</span><span class="p">)</span> +<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">):</span> + <span class="n">plotWidget</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">pen</span><span class="o">=</span><span class="p">(</span><span class="n">i</span><span class="p">,</span><span class="mi">3</span><span class="p">))</span> <span class="c">## setting pen=(i,3) automaticaly creates three different-colored pens</span> +</pre></div> +</div> +</div> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">Plotting in pyqtgraph</a><ul> +<li><a class="reference internal" href="#organization-of-plotting-classes">Organization of Plotting Classes</a></li> +<li><a class="reference internal" href="#examples">Examples</a></li> +</ul> +</li> +</ul> + + <h4>Previous topic</h4> + <p class="topless"><a href="how_to_use.html" + title="previous chapter">How to use pyqtgraph</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="images.html" + title="next chapter">Displaying images and video</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/plotting.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="images.html" title="Displaying images and video" + >next</a> |</li> + <li class="right" > + <a href="how_to_use.html" title="How to use pyqtgraph" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/py-modindex.html b/documentation/build/html/py-modindex.html new file mode 100644 index 00000000..50a684ee --- /dev/null +++ b/documentation/build/html/py-modindex.html @@ -0,0 +1,118 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Python Module Index — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + + + + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="#" title="Python Module Index" + >modules</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + + <h1>Python Module Index</h1> + + <div class="modindex-jumpbox"> + <a href="#cap-p"><strong>p</strong></a> + </div> + + <table class="indextable modindextable" cellspacing="0" cellpadding="2"> + <tr class="pcap"><td></td><td> </td><td></td></tr> + <tr class="cap"><td></td><td><a name="cap-p"> + <strong>p</strong></a></td><td></td></tr> + <tr> + <td><img src="_static/minus.png" id="toggle-1" + class="toggler" style="display: none" alt="-" /></td> + <td> + <tt class="xref">pyqtgraph</tt></td><td> + <em></em></td></tr> + <tr class="cg-1"> + <td></td> + <td> + <a href="widgets/dockarea.html#module-pyqtgraph.dockarea"><tt class="xref">pyqtgraph.dockarea</tt></a></td><td> + <em></em></td></tr> + <tr class="cg-1"> + <td></td> + <td> + <a href="widgets/parametertree.html#module-pyqtgraph.parametertree"><tt class="xref">pyqtgraph.parametertree</tt></a></td><td> + <em></em></td></tr> + </table> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="#" title="Python Module Index" + >modules</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/region_of_interest.html b/documentation/build/html/region_of_interest.html new file mode 100644 index 00000000..85b39832 --- /dev/null +++ b/documentation/build/html/region_of_interest.html @@ -0,0 +1,140 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Region-of-interest controls — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="Basic display widgets" href="graphicswindow.html" /> + <link rel="prev" title="Line, Fill, and Color" href="style.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicswindow.html" title="Basic display widgets" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="style.html" title="Line, Fill, and Color" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="region-of-interest-controls"> +<h1>Region-of-interest controls<a class="headerlink" href="#region-of-interest-controls" title="Permalink to this headline">¶</a></h1> +<div class="section" id="slicing-multidimensional-data"> +<h2>Slicing Multidimensional Data<a class="headerlink" href="#slicing-multidimensional-data" title="Permalink to this headline">¶</a></h2> +</div> +<div class="section" id="linear-selection-and-marking"> +<h2>Linear Selection and Marking<a class="headerlink" href="#linear-selection-and-marking" title="Permalink to this headline">¶</a></h2> +</div> +<div class="section" id="d-selection-and-marking"> +<h2>2D Selection and Marking<a class="headerlink" href="#d-selection-and-marking" title="Permalink to this headline">¶</a></h2> +<ul class="simple"> +<li>translate / rotate / scale</li> +<li>highly configurable control handles</li> +<li>automated data slicing</li> +<li>linearregion, infiniteline</li> +</ul> +</div> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">Region-of-interest controls</a><ul> +<li><a class="reference internal" href="#slicing-multidimensional-data">Slicing Multidimensional Data</a></li> +<li><a class="reference internal" href="#linear-selection-and-marking">Linear Selection and Marking</a></li> +<li><a class="reference internal" href="#d-selection-and-marking">2D Selection and Marking</a></li> +</ul> +</li> +</ul> + + <h4>Previous topic</h4> + <p class="topless"><a href="style.html" + title="previous chapter">Line, Fill, and Color</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="graphicswindow.html" + title="next chapter">Basic display widgets</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/region_of_interest.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicswindow.html" title="Basic display widgets" + >next</a> |</li> + <li class="right" > + <a href="style.html" title="Line, Fill, and Color" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/search.html b/documentation/build/html/search.html new file mode 100644 index 00000000..e734bc16 --- /dev/null +++ b/documentation/build/html/search.html @@ -0,0 +1,102 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Search — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <script type="text/javascript" src="_static/searchtools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <script type="text/javascript"> + jQuery(function() { Search.loadIndex("searchindex.js"); }); + </script> + + + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <h1 id="search-documentation">Search</h1> + <div id="fallback" class="admonition warning"> + <script type="text/javascript">$('#fallback').hide();</script> + <p> + Please activate JavaScript to enable the search + functionality. + </p> + </div> + <p> + From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. + </p> + <form action="" method="get"> + <input type="text" name="q" value="" /> + <input type="submit" value="search" /> + <span id="search-progress" style="padding-left: 10px"></span> + </form> + + <div id="search-results"> + + </div> + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/searchindex.js b/documentation/build/html/searchindex.js new file mode 100644 index 00000000..5c07321b --- /dev/null +++ b/documentation/build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({objects:{"pyqtgraph.UIGraphicsItem":{setNewBounds:[10,2,1],viewRangeChanged:[10,2,1],viewChangedEvent:[10,2,1],"__init__":[10,2,1],mouseShape:[10,2,1],realBoundingRect:[10,2,1]},"pyqtgraph.PlotCurveItem":{"__init__":[15,2,1],setData:[15,2,1]},"pyqtgraph.PlotItem":{setXLink:[14,2,1],plot:[14,2,1],setLabel:[14,2,1],enableAutoScale:[14,2,1],linkYChanged:[14,2,1],linkXChanged:[14,2,1],showLabel:[14,2,1],setTitle:[14,2,1],setYLink:[14,2,1],updateXScale:[14,2,1],updateYScale:[14,2,1],viewGeometry:[14,2,1],showAxis:[14,2,1],addAvgCurve:[14,2,1],updatePlotList:[14,2,1],sigRangeChanged:[14,4,1],"__init__":[14,2,1]},"pyqtgraph.ScatterPlotItem":{setPoints:[9,2,1],"__init__":[9,2,1]},"pyqtgraph.ScaleBar":{"__init__":[57,2,1]},"pyqtgraph.HistogramLUTWidget":{"__init__":[40,2,1]},"pyqtgraph.HistogramLUTItem":{"__init__":[41,2,1]},"pyqtgraph.GradientLegend":{setLabels:[32,2,1],"__init__":[32,2,1]},"pyqtgraph.DataTreeWidget":{"__init__":[53,2,1],setData:[53,2,1]},"pyqtgraph.CurveArrow":{"__init__":[33,2,1]},"pyqtgraph.GraphicsView":{scaleToImage:[23,2,1],viewRect:[23,2,1],pixelSize:[23,2,1],setCentralWidget:[23,2,1],"__init__":[23,2,1]},"pyqtgraph.GridItem":{"__init__":[19,2,1]},"pyqtgraph.PlotWidget":{"__init__":[7,2,1]},"pyqtgraph.AxisItem":{"__init__":[26,2,1],setScale:[26,2,1],setGrid:[26,2,1]},"pyqtgraph.ROI":{saveState:[24,2,1],getGlobalTransform:[24,2,1],getLocalHandlePositions:[24,2,1],getArrayRegion:[24,2,1],handleChange:[24,2,1],translate:[24,2,1],getArraySlice:[24,2,1],"__init__":[24,2,1]},"pyqtgraph.CheckTable":{"__init__":[56,2,1]},"pyqtgraph.LinearRegionItem":{getRegion:[45,2,1],"__init__":[45,2,1]},"pyqtgraph.PlotDataItem":{setShadowPen:[1,2,1],setData:[1,2,1],"__init__":[1,2,1],setPen:[1,2,1]},"pyqtgraph.GraphicsWidget":{"__init__":[29,2,1]},"pyqtgraph.InfiniteLine":{setAngle:[47,2,1],setBounds:[47,2,1],"__init__":[47,2,1]},"pyqtgraph.JoystickButton":{"__init__":[44,2,1]},"pyqtgraph.GradientWidget":{"__init__":[22,2,1]},"pyqtgraph.TableWidget":{iteratorFn:[0,2,1],appendData:[0,2,1],copy:[0,2,1],"__init__":[0,2,1]},"pyqtgraph.ImageView":{jumpFrames:[42,2,1],timeIndex:[42,2,1],"__init__":[42,2,1],setImage:[42,2,1]},"pyqtgraph.ArrowItem":{"__init__":[11,2,1]},"pyqtgraph.CurvePoint":{"__init__":[34,2,1]},"pyqtgraph.ColorButton":{"__init__":[51,2,1]},"pyqtgraph.ImageItem":{setPxMode:[3,2,1],setImage:[3,2,1],getHistogram:[3,2,1],setLookupTable:[3,2,1],pixelSize:[3,2,1],setLevels:[3,2,1],"__init__":[3,2,1]},"pyqtgraph.ViewBox":{targetRect:[4,2,1],mapFromView:[4,2,1],mapToView:[4,2,1],itemBoundingRect:[4,2,1],mapViewToScene:[4,2,1],viewRect:[4,2,1],keyPressEvent:[4,2,1],scaleBy:[4,2,1],childrenBoundingRect:[4,2,1],childTransform:[4,2,1],mapSceneToView:[4,2,1],setAspectLocked:[4,2,1],"__init__":[4,2,1],setRange:[4,2,1]},"pyqtgraph.VTickGroup":{"__init__":[28,2,1]},"pyqtgraph.RawImageWidget":{"__init__":[37,2,1],setImage:[37,2,1]},"pyqtgraph.VerticalLabel":{"__init__":[13,2,1]},"pyqtgraph.TreeWidget":{itemMoving:[6,2,1],"__init__":[6,2,1]},"pyqtgraph.GradientEditorItem":{getLookupTable:[49,2,1],"__init__":[49,2,1]},"pyqtgraph.ProgressDialog":{"__init__":[5,2,1]},"pyqtgraph.GraphicsObject":{viewTransform:[8,2,1],getBoundingParents:[8,2,1],pixelVectors:[8,2,1],viewRect:[8,2,1],getViewWidget:[8,2,1],getViewBox:[8,2,1],pixelLength:[8,2,1],deviceTransform:[8,2,1],"__init__":[8,2,1]},pyqtgraph:{VTickGroup:[28,3,1],GraphicsWidget:[29,3,1],affineSlice:[18,1,1],ScaleBar:[57,3,1],image:[18,1,1],mkBrush:[18,1,1],PlotDataItem:[1,3,1],GraphicsObject:[8,3,1],ImageItem:[3,3,1],LinearRegionItem:[45,3,1],ImageView:[42,3,1],FileDialog:[48,3,1],HistogramLUTWidget:[40,3,1],CheckTable:[56,3,1],MultiPlotWidget:[27,3,1],mkPen:[18,1,1],plot:[18,1,1],InfiniteLine:[47,3,1],HistogramLUTItem:[41,3,1],PlotWidget:[7,3,1],GradientWidget:[22,3,1],GridItem:[19,3,1],GradientEditorItem:[49,3,1],GradientLegend:[32,3,1],AxisItem:[26,3,1],ViewBox:[4,3,1],dockarea:[43,0,0],ArrowItem:[11,3,1],hsvColor:[18,1,1],PlotItem:[14,3,1],colorStr:[18,1,1],GraphicsLayout:[46,3,1],siEval:[18,1,1],LabelItem:[16,3,1],ROI:[24,3,1],JoystickButton:[44,3,1],CurveArrow:[33,3,1],CurvePoint:[34,3,1],SpinBox:[31,3,1],mkColor:[18,1,1],GraphicsLayoutWidget:[54,3,1],PlotCurveItem:[15,3,1],ButtonItem:[21,3,1],TreeWidget:[6,3,1],siFormat:[18,1,1],parametertree:[35,0,0],VerticalLabel:[13,3,1],intColor:[18,1,1],ColorButton:[51,3,1],RawImageWidget:[37,3,1],DataTreeWidget:[53,3,1],GraphicsView:[23,3,1],UIGraphicsItem:[10,3,1],siScale:[18,1,1],TableWidget:[0,3,1],ScatterPlotItem:[9,3,1],ProgressDialog:[5,3,1],colorTuple:[18,1,1]},"pyqtgraph.SpinBox":{setProperty:[31,2,1],setValue:[31,2,1],editingFinishedEvent:[31,2,1],"__init__":[31,2,1],interpret:[31,2,1]},"pyqtgraph.GraphicsLayoutWidget":{"__init__":[54,2,1]},"pyqtgraph.LabelItem":{setText:[16,2,1],"__init__":[16,2,1],setAttr:[16,2,1]},"pyqtgraph.ButtonItem":{"__init__":[21,2,1]},"pyqtgraph.MultiPlotWidget":{"__init__":[27,2,1]},"pyqtgraph.FileDialog":{"__init__":[48,2,1]},"pyqtgraph.GraphicsLayout":{nextCol:[46,2,1],nextRow:[46,2,1],"__init__":[46,2,1]}},terms:{roi:[18,24,52,50],all:[1,9,3,4,18,55,14,25],code:[20,18],gradienteditoritem:[52,49,50],edg:45,orthogon:18,osx:20,skip:3,global:24,makeargb:[37,3],rapid:[20,17,39,3,31],prefix:[18,14,31],subclass:[2,25,50],screen:[12,14,3,23],follow:[18,30,4],disk:24,children:4,row:[46,55],hsva:18,whose:10,setlabel:[14,32],middl:23,depend:14,decim:[1,31],intermedi:31,linkxchang:14,mapscenetoview:4,setpen:1,worth:25,sent:37,sourc:[20,18],everi:1,string:[16,0,18,30,25],delaysign:31,fals:[26,1,9,14,4,5,18,31,6,53,23,24,47,15],mous:[24,15,25,23,4],"1px":1,veri:[37,55],affect:10,setcentralwidget:23,exact:23,getarrayregion:[18,24],dim:18,imagedata:[12,55],level:[12,42,3],button:[5,14,23,21],scalabl:10,list:[0,1,9,20,18,8,55,14,12,53],griditem:[19,10,52,50],gethistogram:3,item:[26,17,1,3,4,20,46,50,6,8,52,10,23,55,14,25],vector:[8,18,23],dockarea:[43,2,39,52],refer:[17,52,10,34],arang:25,dimens:[55,18,10],properti:[16,20,33,34],slower:[12,20],direct:[8,18],consequ:50,zero:10,video:[17,3,20,12,37,42],pass:14,further:[55,25],getarrayslic:24,translatesnap:24,click:[20,9],append:14,even:[37,18],index:[17,18,42,30,6,34,33,24],what:[20,17,18,19],hide:14,appear:[26,1,55,32],compar:[20,17],section:18,current:[26,3,4,46,31,12,24],clipboard:0,rgba:[12,18],"new":[1,9,14,12,47,25],"public":20,contrast:1,qgraphicsscen:[23,50],widget:[17,18,2,53,20,42,52,31,6,7,8,27,10,23,36,12,37,24,25,55],full:[23,4],gener:[16,1,2,20,46,18,8,55,37,24,25],whitelevel:3,len:[1,18],tangent:34,uigraphicsitem:[52,10,50],address:8,locat:[34,18,9,11],along:[26,34,14,32],becom:18,modifi:25,legend:1,valu:[16,26,1,9,3,45,42,18,31,32,8,30,55,14,12,24,47,25],wait:5,invis:18,solid:[1,18],convert:[12,8,18],purpos:10,convers:[12,18,52],ahead:42,across:18,larger:18,step:[12,18,3,31],precis:18,within:[17,18,4,28,8,55,19,25],chang:[26,31,10,14,24,47],commonli:[20,25],portabl:20,overrid:[42,10],diamet:1,configur:[26,38],regardless:9,parallelepip:18,labelitem:[16,52,50],extra:[37,1,14,31],appli:34,modul:[43,17,2,52,35],getlookupt:49,setaspectlock:4,api:[17,52],visibl:[8,23,4],ax1stop:24,colortupl:18,ymin:4,select:[0,18,17,20,31,38,12,24],highli:[20,18,55,38],plot:[26,17,1,9,4,20,18,45,55,14,15,25],hexadecim:18,from:[26,17,18,4,20,8,55,12,10,24,25],describ:25,would:[26,1,24,55,18],minval:[5,18],mkbrush:[18,30],regist:14,two:[12,50],next:[46,32],few:[12,8,55,25],live:10,call:[18,4,46,6,10,14,12,55,25],graphicsitem:[26,1,21,50,34,10],recommend:18,dict:[0,1,9,53,32],type:[0,18,31,12,24,25],useopengl:23,more:[18,3,20,30,55,12,37,25],mkpen:[1,30,18],graphicslayout:[46,52,50],intcolor:[18,30],qtreewidget:6,pyqwt:20,relat:25,ital:16,enhanc:[1,9],flag:16,accept:[16,1,3,18,32,6,30,55,12,24,25],particular:20,known:20,central:23,effort:20,cach:8,must:[18,6,55,14,37,25],none:[1,3,4,5,6,7,8,9,10,16,18,21,22,23,24,25,26,27,28,44,31,33,34,14,37,47,40,41,42,45,46,51,53,54,15],graphic:[17,18,20,50,52,10,14,25],xvalu:1,getviewwidget:8,outlin:[1,25],invlov:25,column:[46,1,56],kwarg:31,can:[0,1,2,3,4,8,10,12,17,30,20,23,24,26,9,33,34,14,47,45,50,55],graphicswindow:[36,55,25],progressdialog:[5,2,52],scatter:[20,1,9,25],setbound:47,nearest:31,linkview:26,give:18,process:[12,5,18],imagewindow:18,itemignorestransform:16,indic:[17,18,19,8,42,47,57],plotwindow:18,high:20,xmin:4,minimum:[5,47,4],maxgreen:3,want:[12,55,10,50],graphicsobject:[8,52,3,50],itemmov:6,setxlink:14,alwai:[18,9,10,28,4],surfac:55,multipl:[8,55,25,31],goal:20,awkward:18,anoth:[12,14],pyqt:[17,20,29,8,55,25],divis:[19,57],how:[17,18,3,20,55,12,24,25],sever:[2,25],pure:[12,20],reject:6,opt:[33,11],instead:[12,14],simpl:[18,52],css:16,updat:[3,4,20,15,31,10,23,42,14],qwt:15,map:[8,24,4],lai:46,overridden:[23,4],max:[42,47,4],after:[10,14,31],spot:[1,9],befor:[5,14],showlabel:14,plane:18,scratch:55,aribtrari:18,compat:[15,3,31,4],mai:[1,30,23,47,25,18],npt:49,secondari:1,data:[0,1,9,17,20,15,28,18,38,52,55,14,12,53,24,47,25],averag:[20,14],attempt:12,setproperti:31,seriou:55,gradientwidget:[2,22,52],minim:20,correspond:18,exclud:10,caus:[26,3],inform:[12,3,25],maintain:6,combin:25,allow:[0,3,4,20,47,31,6,10,23,55,14,25],callabl:3,first:[12,8,3],order:12,iteratorfn:0,qgraphicswidget:[29,23],rotat:[34,24,38],fft:[20,15],rang:[26,1,4,28,10,23,25],symbols:1,through:[18,30,25],treewidget:[6,2,52],scatterplotitem:[52,9,25,50],vari:18,ax0stop:24,paramet:[16,9,14],qcolor:[18,30],style:[16,1,30],movabl:[45,24,47],directli:[12,18,3],img:[37,42,24],chosen:18,settitl:14,symbolpen:[1,25],clickabl:15,parametertre:[20,2,39,52,35],platform:20,window:[20,17,18,55,25],enablemous:[23,4],hidden:14,unitprefix:14,pixel:[1,10,3,8,23],shear:18,arrowitem:[11,52,50],them:12,good:10,"return":[0,18,42,3,4,45,46,31,6,8,10,49,23,55,24,14,25],greater:1,thei:[6,18,25],handl:[0,24,55,25,38],auto:[16,10,14],linkychang:14,timeindex:42,rectangl:32,ff0:18,framework:[20,25,50],filedialog:[48,2,52],qgraphicsitem:[25,50],dataset:[1,18],videospeedtest:12,setvalu:[5,47,31],introduct:[20,17],plotitem:[18,27,50,7,52,14,25],updateyscal:14,recarrai:1,anyth:[34,50],edit:31,drop:6,easili:[34,30,50],tablewidget:[0,2,52],mode:15,arrow:[33,11],each:[42,8,9,18,25],returnslic:24,redraw:24,side:26,mean:10,compil:12,imageitem:[3,50,52,12,37,24],maxvalu:18,replac:[20,18],individu:45,continu:14,realli:4,heavi:20,meta:[1,14],greyscal:[18,30],iter:[0,30],siscal:18,xval:[28,42,55],happen:12,lockaspect:4,extract:18,orient:[26,45,18,13,22],special:25,out:[46,18,31,4],shown:12,maptoview:4,unbound:31,space:[8,24,18],gradient:[12,32],weav:12,predefin:18,content:[17,52,2,25,50],suitabl:[20,24],rel:[28,24,10,57],correct:[20,8],red:55,yvals2:55,lag:20,linear:[17,18,31,38],insid:[16,17,18,27,4,7,8,10,12,37,55],advanc:46,given:[1,9,4,55,14,25],delai:31,reason:55,base:[26,10,25],timepoint:18,dictionari:[42,53],usual:8,region:[17,4,20,38,45,24],rect:[10,4],extend:[26,0,5,29,6,8],childrenboundingrect:4,wai:[26,1,55,12,10,25],minvalu:18,angl:[24,47],could:[5,55],synchron:26,forcewidth:13,length:[18,5,6,8,23,47],addplot:[55,25],isn:24,outsid:4,geometri:[14,23],assign:[1,9],frequent:2,histogramlut:12,origin:18,pleas:20,major:[20,19],suffix:18,render:1,symbolbrush:[1,25],onc:1,arrai:[0,1,9,3,18,55,12,53,24],scalesnap:24,qualiti:20,number:[20,34,1,18,46],alreadi:25,cosmet:18,"1e6":18,indistinguish:55,open:55,primari:[20,1],size:[16,9,3,32,10,23,24,25,57],fmri:18,guess:42,workaround:29,width:[1,4,21,18,8,23,57],associ:14,top:14,compositionmod:3,system:[12,20,19,23,4],construct:18,paint:10,necessarili:4,demonstr:[6,25],axisitem:[26,52,25,50],exampl:[16,17,18,20,5,34,10,12,55,26,25],white:42,"\u03bcunit":18,"final":18,store:24,mingreen:3,hue:18,shell:55,option:[16,1,3,31,55,12,42,47,25],tool:[12,20],copi:[0,18,15],specifi:[30,9,1,10,24,25],slider:[12,42],ydata:18,somewhat:[20,18,55],essenti:25,than:[20,1,55,18],png:20,mapfromview:4,conveni:18,setattr:16,whenev:10,provid:[0,2,20,50,33,12,15,25],remov:[9,23],pyqtgraph:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,14,37,44,47,40,41,42,45,46,48,49,50,51,52,53,54,55,56,57],tree:[6,20],structur:53,charact:30,sigrangechang:14,light:12,posit:[34,24,47],arrang:20,appenddata:0,other:[18,9,20,55,12,25],initi:[47,1,10],grei:1,graphicslayoutitem:[36,25],comput:[12,3],clip:8,rawimagewidget:[12,37,2,52],checktabl:[2,56,52],datatreewidget:[2,53,52],curvepoint:[34,52,50],ani:[26,1,2,20,18,50,33,55,14,12,10],infinitelin:[47,50,10,52,38],karg:[40,1,3,29,22,18,49,7,14,54,37,24],have:[18,4,28,31,8,10,24],tabl:[12,17,3,49],need:[1,42,18,12,37,24],minr:3,border:[46,3,4],sat:18,getboundingpar:8,engin:[20,18,2],multiplotwidget:[2,52,27],equival:18,min:[42,47,4],maxr:3,self:[24,4],plotdataitem:[1,52,25,14,50],isotrop:4,note:[16,34,18,10,47],also:[30,20,5,6,34,8,23,12,24,25],qgraphicstextitem:16,take:[47,18,55],which:[18,2,4,20,28,45,8,10,24,25],histogramlutwidget:[40,2,52],data1:55,noth:37,data3:55,data2:55,simplifi:[20,18],begin:18,pain:20,normal:[25,50],multipli:26,object:[57,8,24,25,9],rrggbbaa:18,linearregionitem:[45,52,50],most:[8,50,55,18,25],graphicsscen:8,childtransform:4,pair:[24,32],alpha:[26,18,49],"class":[0,1,3,4,5,6,7,8,9,10,11,12,13,15,16,17,18,19,21,22,23,24,25,26,27,28,29,30,31,32,33,34,14,37,44,47,40,41,42,45,46,48,49,50,51,53,54,56,57],ax0start:24,placement:46,hideroot:53,clear:[1,8,14],differ:[12,20,1,55,25],doe:[20,17,8,34],mri:18,determin:[26,18,19,8,10,12,24],axi:[16,26,1,4,14,12,42,25],minhu:18,viewtransform:8,think:55,viewchangedev:10,show:[26,18,5,55,14,25],forgetviewwidget:8,getregion:45,filllevel:[1,15,25],random:25,bring:20,bright:18,radio:14,feedback:55,vtickgroup:[28,52,50],minblu:3,onli:[1,3,4,46,31,8,55,10,42,47,25],coerc:31,ratio:4,colorbutton:[2,51,52],"true":[1,24,3,4,45,5,18,31,6,9,49,14,37,42,13,47],metaarrai:[0,1],behind:[20,1],should:[1,53,4],graphicslayoutwidget:[52,2,55,25,54],busi:5,black:42,viewport:10,qwidget:[2,25],combo:14,qprogressdialog:5,local:[8,4],control:[12,17,25,23,38],realboundingrect:10,keypressev:4,qgraphicsview:[25,23],info:1,predict:20,move:[6,42,24,31,4],get:[37,55],familiar:25,autom:38,unscal:37,state:[6,24,55,14],"import":[20,50,8,55,12,25],increas:[12,23],maxblu:3,requir:[20,5,24],setdata:[15,1,53],scale:[16,26,18,9,3,4,20,42,32,38,23,11,12,37,24,14,25,57],child:[8,4],bar:57,keyword:1,organ:[17,25],diamond:9,mkcolor:[18,30],method:[12,18,30,10,25],setlookupt:3,stuff:5,common:12,xmax:4,contain:[12,8,3,25],qgraphicsobject:8,where:[1,30],valid:47,view:[16,18,9,4,20,28,32,8,10,23,42,25,57],respond:10,set:[16,26,1,9,3,4,20,47,28,18,31,34,23,12,42,24,14,25],qpointf:47,setscal:26,crunch:20,frame:[12,42],displai:[0,1,3,4,5,55,37,12,19,16,17,18,20,21,42,25,26,34,14,11,47,52,53,36,15,57],see:[16,1,3,18,34,30,55,14,12,11,25],subtre:6,result:[8,9,18,23],arg:[16,0,1,42,48,40,29,22,18,49,8,14,37,24],fail:12,horizont:[45,47],yvalu:1,best:18,plotdata:25,infinit:47,detect:10,lut:3,getglobaltransform:24,mainli:16,boundari:[14,10,31,23],exist:[9,55,25,4],label:[16,26,25,14,32],enough:[20,37],dynam:20,between:[12,1],updateplotlist:14,drawn:1,experi:20,approach:55,qbrush:[18,30],parentitem:21,altern:9,autolevel:[42,3],kei:4,numer:31,inverti:4,complement:20,extens:20,maxhu:18,steroid:31,here:[20,55],pixelvector:8,rotatesnap:24,ipython:55,ax1start:24,pixels:[3,23],notat:[18,31],both:[12,1,4],last:18,fit:[26,42,14],cycl:18,howev:[20,18],lazi:55,showaxi:14,viewbox:[16,26,4,50,8,52,10,14,36,25],etc:[16,20,9,55,31],plotcurveitem:[50,33,34,52,15,25],instanc:[12,18],"__init__":[0,1,3,4,5,6,7,8,9,10,11,13,15,16,44,19,21,22,23,24,26,27,28,29,31,32,33,34,14,37,47,40,41,42,45,46,48,49,51,53,54,56,57],ccff00:16,mani:[1,20,30,50,55,24],fix:10,load:12,simpli:55,point:[1,9,4,18,32,33,34,12,11,24,25],instanti:[12,25],colspan:[46,55],pop:55,height:[8,10,4],header:0,written:[20,18],linux:20,cancel:5,typic:25,mouseshap:10,assum:8,viewporttransform:8,scaletoimag:23,save:3,vertic:[45,28,13,47],rgb:[12,18,49],invert:24,devic:8,compos:25,been:[6,24,4],sinc:50,much:20,interpret:[42,25,31],easiest:12,basic:[20,17,25,14,36],unambigu:23,blacklevel:3,box:[24,10,4],pxmode:[1,9],setimag:[12,37,18,42,3],imag:[17,1,3,20,21,18,41,23,12,37,42],numpi:[0,1,20,55,12,25],search:17,argument:[16,1,9,3,5,18,30,10,14,37,24,47,25],coordin:[1,24,4,28,8,9,10,23,19,47],understand:25,togeth:[25,14],demand:12,emphac:1,spin:31,opac:3,"case":[18,55],canceltext:5,multi:[20,18],ident:[1,9,29],sieval:18,look:[18,9],launcher:20,setylink:14,additem:46,graphicsview:[2,4,20,52,50,7,8,27,10,23,36,12,37,25,55],rectangular:[24,18,19,57],cursor:5,defin:[25,32],"while":[46,18,55],abov:[12,55,25],error:18,wascancel:5,aid:20,scene:[9,3,4,8,14,23],shadowpen:[1,15],observ:55,bin:3,planar:18,helper:[17,18,52],ctrl:4,pool:14,itself:14,qrectf:[10,4],vor:10,pixellength:8,histogramlutitem:[41,52,50],primit:20,verticallabel:[2,13,52],pyqwt5:20,scienc:[20,2],parent:[4,5,6,7,8,10,16,44,22,23,42,26,27,31,14,37,51,40,24,46,53,54,15],colorstr:18,enableautoscal:14,develop:[20,55],welcom:17,design:[20,55],perform:[12,37,9,23,20],suggest:55,make:[6,20,18,26,55],format:[0,18,3,55,14,12,24],fillbrush:1,same:[1,9,4,28,18,55,23,10,25],instal:20,nextcol:46,python:[12,20,55,53],pysid:20,complex:[20,30,25],pad:4,gui:[20,17,39,25],scalebar:[57,52,50],document:17,yval:55,pan:[20,42,25,23,4],higher:12,finish:[5,31],optim:[20,37,1,3],viewrect:[8,23,4],nest:[8,53],effect:18,qpen:[1,30,18],plotcurv:25,capabl:[20,18],lookup:[12,3,49],rais:[5,31],user:[2,20,5,55,47,25],canva:39,addavgcurv:14,stack:4,expand:[6,10],built:[12,30],appropri:30,center:4,labeltext:5,relativeto:24,thu:[34,25],nx2:1,well:[12,25],col:55,matplotlib:20,anim:[33,34],without:55,command:[17,55],thi:[1,3,4,6,8,10,12,18,20,23,42,25,26,9,29,31,34,14,37,47,24,50,55],dimension:[20,18],left:14,graphicswidget:[16,46,29,50,8,52],identifi:9,just:[18,55,31],hierarch:53,curvearrow:[33,34,50,52,11],getviewbox:8,shape:[37,1,10,18,25],via:[20,55,23],virtual:50,aspect:4,heavili:25,dock:20,jumpfram:42,shadow:[1,15],viewgeometri:14,signific:23,easi:55,except:[5,31],param:14,discuss:25,color:[16,17,18,15,30,32,52,12,51,25,57],add:[9,25,14],autorang:[42,10],inner:8,win:55,snap:24,els:37,busycursor:5,match:[10,23],build:[12,20,2,55],real:1,applic:[12,17,2,55,20],around:4,itemboundingrect:4,read:25,mapviewtoscen:4,dark:12,buttonitem:[21,52,50],grid:[26,19,46,55,24,25],xdata:18,background:23,press:[24,4],bit:12,tick:[26,28,25],rescal:10,name:[1,42,14],maxval:5,ignor:[9,24,10,3],like:[20,18,55],specif:[33,32],plotwidget:[16,18,2,7,52,55,14,25],qspinbox:31,childgroup:4,signal:31,arbitrari:[18,9],html:14,integ:[12,18,25,31],forgetviewbox:8,"boolean":16,singl:[26,18,27,3,15,30,7,8,9,14,12,47,25],realtim:20,setshadowpen:1,resiz:[10,23],imagefil:21,unnecessari:18,setrang:[23,4],right:[14,23],often:[20,55],captur:4,settext:16,interact:[20,15,55,14],some:[20,0],draw:[1,3,28,18,32,50,47,25],zoom:4,intern:[6,46,8,4],sampl:[33,34],setnewbound:10,importantli:[55,25],useleftbuttonpan:4,librari:[12,20],slice:[17,18,20,38,52,24],bottom:[22,14],maxticklength:26,per:1,prop:31,wrong:20,"20x20":18,pen:[26,1,9,15,28,52,18,30,55,24,47,25],scrollbar:23,unit:[26,18,52,14],allowunicod:18,notabl:30,either:[34,24,14],core:20,plu:9,run:20,bold:16,spinbox:[2,52,31],perpendicular:18,yrang:28,promot:55,offset:[12,32],rrggbb:18,joystickbutton:[44,2,52],simpler:55,lock:4,about:[20,37,55,25],actual:[31,4],transpos:12,setangl:47,page:17,degre:47,statement:5,includ:[12,20,1,55,25],dialog:5,span:26,getlocalhandleposit:24,disabl:[26,5,1,25],produc:12,"8pt":16,routin:4,own:20,effici:55,snapsiz:24,"float":[12,1,18,31],bound:[4,45,31,8,10,47],automat:[26,0,1,14,20,46,30,8,10,23,12,55,19,25],three:[18,25],diagon:18,targetrect:4,brush:[1,9,45,30,52,15,25,18],devicetransform:8,factor:18,mark:[20,17,28,45,38],your:[12,55,25],gradientlegend:[50,52,32],occupi:28,accordingli:14,dlg:5,triangl:9,ymax:4,val:31,area:[1,25],enabl:[8,14,23],hex:18,transform:[8,24,10,3,4],fast:[20,37,15],custom:[20,55],avail:[12,20,55,25],start:[20,18],reli:50,interfac:[20,18,2,55],editor:12,under:25,forward:4,setpoint:9,entir:23,"function":[0,18,2,3,4,20,17,30,50,8,52,55,12,37,24,25],creation:10,interest:[20,17,24,38],offer:18,forc:[9,4],tupl:[18,24,30],linearregion:38,hsvcolor:[18,30],amongst:20,histogram:[12,3],nextrow:46,link:14,translat:[12,24,38],setgrid:26,don:55,line:[17,1,20,18,45,30,55,19,47,25],dtype:1,bug:[8,29],scalebi:4,reset:31,pull:[18,24],immedi:55,flowchart:39,boundingrect:10,possibl:18,whether:[42,24,3],maxbound:24,access:20,maximum:[5,30,47,4],sepecifi:1,until:5,record:0,scipi:12,otherwis:[9,29],handlechang:24,similar:20,setlevel:3,curv:[1,33,34,14,11,15,25],affineslic:18,featur:[20,31],pil:12,creat:[1,55,18,23,25],"int":[1,18,31],cover:24,repres:[8,30,15,18],autoscal:14,editingfinishedev:31,implement:[27,23,4,50,7,14,24],file:[12,55],imageview:[18,2,52,55,12,42],request:4,attr:16,work:[6,12],rearrang:12,fill:[17,1,30,23,25,18],denot:32,automaticali:25,titl:[16,18,25,14],when:[1,3,4,47,6,55,23,10,24,14,25],detail:1,invalid:31,event:4,"default":[16,1,9,4,42,18,10,23,24],circl:9,bool:[1,42],varieti:18,squar:9,you:[18,50,10,12,37,25],autopixelrang:23,absurd:6,matur:20,repeat:1,"_only_":37,sequenc:[1,25],symbol:[1,9,25],qtablewidget:0,ndarrai:[37,1,42,18],multidimension:[17,38],elsewher:6,drag:[6,45,47,4],accomplish:50,embed:[12,17,55,25],consid:55,savest:24,doubl:20,setpxmod:3,prefixless:18,unaffect:14,stai:10,outdat:20,invari:11,viewrangechang:10,svg:20,updatexscal:14,visual:[20,55],tradeoff:37,text:[16,5,31,32,14,13],obj:4,time:[18,3,5,55,12,42],far:20,siformat:18,"export":20,backward:4,prototyp:[20,17,39]},objtypes:{"0":"py:module","1":"py:function","2":"py:method","3":"py:class","4":"py:attribute"},titles:["TableWidget","PlotDataItem","Pyqtgraph’s Widgets","ImageItem","ViewBox","ProgressDialog","TreeWidget","PlotWidget","GraphicsObject","ScatterPlotItem","UIGraphicsItem","ArrowItem","Displaying images and video","VerticalLabel","PlotItem","PlotCurveItem","LabelItem","Welcome to the documentation for pyqtgraph 1.8","Pyqtgraph’s Helper Functions","GridItem","Introduction","ButtonItem","GradientWidget","GraphicsView","ROI","Plotting in pyqtgraph","AxisItem","MultiPlotWidget","VTickGroup","GraphicsWidget","Line, Fill, and Color","SpinBox","GradientLegend","CurveArrow","CurvePoint","parametertree module","Basic display widgets","RawImageWidget","Region-of-interest controls","Rapid GUI prototyping","HistogramLUTWidget","HistogramLUTItem","ImageView","dockarea module","JoystickButton","LinearRegionItem","GraphicsLayout","InfiniteLine","FileDialog","GradientEditorItem","Pyqtgraph’s Graphics Items","ColorButton","API Reference","DataTreeWidget","GraphicsLayoutWidget","How to use pyqtgraph","CheckTable","ScaleBar"],objnames:{"0":"Python module","1":"Python function","2":"Python method","3":"Python class","4":"Python attribute"},filenames:["widgets/tablewidget","graphicsItems/plotdataitem","widgets/index","graphicsItems/imageitem","graphicsItems/viewbox","widgets/progressdialog","widgets/treewidget","widgets/plotwidget","graphicsItems/graphicsobject","graphicsItems/scatterplotitem","graphicsItems/uigraphicsitem","graphicsItems/arrowitem","images","widgets/verticallabel","graphicsItems/plotitem","graphicsItems/plotcurveitem","graphicsItems/labelitem","index","functions","graphicsItems/griditem","introduction","graphicsItems/buttonitem","widgets/gradientwidget","widgets/graphicsview","graphicsItems/roi","plotting","graphicsItems/axisitem","widgets/multiplotwidget","graphicsItems/vtickgroup","graphicsItems/graphicswidget","style","widgets/spinbox","graphicsItems/gradientlegend","graphicsItems/curvearrow","graphicsItems/curvepoint","widgets/parametertree","graphicswindow","widgets/rawimagewidget","region_of_interest","parametertree","widgets/histogramlutwidget","graphicsItems/histogramlutitem","widgets/imageview","widgets/dockarea","widgets/joystickbutton","graphicsItems/linearregionitem","graphicsItems/graphicslayout","graphicsItems/infiniteline","widgets/filedialog","graphicsItems/gradienteditoritem","graphicsItems/index","widgets/colorbutton","apireference","widgets/datatreewidget","widgets/graphicslayoutwidget","how_to_use","widgets/checktable","graphicsItems/scalebar"]}) \ No newline at end of file diff --git a/documentation/build/html/style.html b/documentation/build/html/style.html new file mode 100644 index 00000000..3cdea05f --- /dev/null +++ b/documentation/build/html/style.html @@ -0,0 +1,127 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Line, Fill, and Color — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="_static/default.css" type="text/css" /> + <link rel="stylesheet" href="_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="_static/jquery.js"></script> + <script type="text/javascript" src="_static/underscore.js"></script> + <script type="text/javascript" src="_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="index.html" /> + <link rel="next" title="Region-of-interest controls" href="region_of_interest.html" /> + <link rel="prev" title="Displaying images and video" href="images.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="region_of_interest.html" title="Region-of-interest controls" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="images.html" title="Displaying images and video" + accesskey="P">previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="line-fill-and-color"> +<h1>Line, Fill, and Color<a class="headerlink" href="#line-fill-and-color" title="Permalink to this headline">¶</a></h1> +<p>Many functions and methods in pyqtgraph accept arguments specifying the line style (pen), fill style (brush), or color.</p> +<p>For these function arguments, the following values may be used:</p> +<ul class="simple"> +<li>single-character string representing color (b, g, r, c, m, y, k, w)</li> +<li>(r, g, b) or (r, g, b, a) tuple</li> +<li>single greyscale value (0.0 - 1.0)</li> +<li>(index, maximum) tuple for automatically iterating through colors (see functions.intColor)</li> +<li>QColor</li> +<li>QPen / QBrush where appropriate</li> +</ul> +<p>Notably, more complex pens and brushes can be easily built using the mkPen() / mkBrush() functions or with Qt’s QPen and QBrush classes.</p> +<p>Colors can also be built using mkColor(), intColor(), hsvColor(), or Qt’s QColor class</p> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="images.html" + title="previous chapter">Displaying images and video</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="region_of_interest.html" + title="next chapter">Region-of-interest controls</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="_sources/style.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="region_of_interest.html" title="Region-of-interest controls" + >next</a> |</li> + <li class="right" > + <a href="images.html" title="Displaying images and video" + >previous</a> |</li> + <li><a href="index.html">pyqtgraph v1.8 documentation</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/checktable.html b/documentation/build/html/widgets/checktable.html new file mode 100644 index 00000000..22f15e17 --- /dev/null +++ b/documentation/build/html/widgets/checktable.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>CheckTable — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="TableWidget" href="tablewidget.html" /> + <link rel="prev" title="DataTreeWidget" href="datatreewidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="tablewidget.html" title="TableWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="datatreewidget.html" title="DataTreeWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="checktable"> +<h1>CheckTable<a class="headerlink" href="#checktable" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.CheckTable"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">CheckTable</tt><big>(</big><em>columns</em><big>)</big><a class="headerlink" href="#pyqtgraph.CheckTable" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.CheckTable.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>columns</em><big>)</big><a class="headerlink" href="#pyqtgraph.CheckTable.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="datatreewidget.html" + title="previous chapter">DataTreeWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="tablewidget.html" + title="next chapter">TableWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/checktable.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="tablewidget.html" title="TableWidget" + >next</a> |</li> + <li class="right" > + <a href="datatreewidget.html" title="DataTreeWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/colorbutton.html b/documentation/build/html/widgets/colorbutton.html new file mode 100644 index 00000000..6865391e --- /dev/null +++ b/documentation/build/html/widgets/colorbutton.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ColorButton — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="GraphicsLayoutWidget" href="graphicslayoutwidget.html" /> + <link rel="prev" title="GradientWidget" href="gradientwidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicslayoutwidget.html" title="GraphicsLayoutWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="gradientwidget.html" title="GradientWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="colorbutton"> +<h1>ColorButton<a class="headerlink" href="#colorbutton" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ColorButton"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ColorButton</tt><big>(</big><em>parent=None</em>, <em>color=(128</em>, <em>128</em>, <em>128)</em><big>)</big><a class="headerlink" href="#pyqtgraph.ColorButton" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.ColorButton.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>color=(128</em>, <em>128</em>, <em>128)</em><big>)</big><a class="headerlink" href="#pyqtgraph.ColorButton.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="gradientwidget.html" + title="previous chapter">GradientWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="graphicslayoutwidget.html" + title="next chapter">GraphicsLayoutWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/colorbutton.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicslayoutwidget.html" title="GraphicsLayoutWidget" + >next</a> |</li> + <li class="right" > + <a href="gradientwidget.html" title="GradientWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/datatreewidget.html b/documentation/build/html/widgets/datatreewidget.html new file mode 100644 index 00000000..449b0b48 --- /dev/null +++ b/documentation/build/html/widgets/datatreewidget.html @@ -0,0 +1,138 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>DataTreeWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="CheckTable" href="checktable.html" /> + <link rel="prev" title="ImageView" href="imageview.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="checktable.html" title="CheckTable" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="imageview.html" title="ImageView" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="datatreewidget"> +<h1>DataTreeWidget<a class="headerlink" href="#datatreewidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.DataTreeWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">DataTreeWidget</tt><big>(</big><em>parent=None</em>, <em>data=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.DataTreeWidget" title="Permalink to this definition">¶</a></dt> +<dd><p>Widget for displaying hierarchical python data structures +(eg, nested dicts, lists, and arrays)</p> +<dl class="method"> +<dt id="pyqtgraph.DataTreeWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>data=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.DataTreeWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.DataTreeWidget.setData"> +<tt class="descname">setData</tt><big>(</big><em>data</em>, <em>hideRoot=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.DataTreeWidget.setData" title="Permalink to this definition">¶</a></dt> +<dd><p>data should be a dictionary.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="imageview.html" + title="previous chapter">ImageView</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="checktable.html" + title="next chapter">CheckTable</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/datatreewidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="checktable.html" title="CheckTable" + >next</a> |</li> + <li class="right" > + <a href="imageview.html" title="ImageView" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/dockarea.html b/documentation/build/html/widgets/dockarea.html new file mode 100644 index 00000000..8473421d --- /dev/null +++ b/documentation/build/html/widgets/dockarea.html @@ -0,0 +1,120 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>dockarea module — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="parametertree module" href="parametertree.html" /> + <link rel="prev" title="GraphicsLayoutWidget" href="graphicslayoutwidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="parametertree.html" title="parametertree module" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="graphicslayoutwidget.html" title="GraphicsLayoutWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="module-pyqtgraph.dockarea"> +<span id="dockarea-module"></span><h1>dockarea module<a class="headerlink" href="#module-pyqtgraph.dockarea" title="Permalink to this headline">¶</a></h1> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="graphicslayoutwidget.html" + title="previous chapter">GraphicsLayoutWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="parametertree.html" + title="next chapter">parametertree module</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/dockarea.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="parametertree.html" title="parametertree module" + >next</a> |</li> + <li class="right" > + <a href="graphicslayoutwidget.html" title="GraphicsLayoutWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/filedialog.html b/documentation/build/html/widgets/filedialog.html new file mode 100644 index 00000000..45fa4ab1 --- /dev/null +++ b/documentation/build/html/widgets/filedialog.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>FileDialog — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="GraphicsView" href="graphicsview.html" /> + <link rel="prev" title="SpinBox" href="spinbox.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicsview.html" title="GraphicsView" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="spinbox.html" title="SpinBox" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="filedialog"> +<h1>FileDialog<a class="headerlink" href="#filedialog" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.FileDialog"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">FileDialog</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.FileDialog" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.FileDialog.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.FileDialog.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="spinbox.html" + title="previous chapter">SpinBox</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="graphicsview.html" + title="next chapter">GraphicsView</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/filedialog.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="graphicsview.html" title="GraphicsView" + >next</a> |</li> + <li class="right" > + <a href="spinbox.html" title="SpinBox" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/gradientwidget.html b/documentation/build/html/widgets/gradientwidget.html new file mode 100644 index 00000000..3a11e659 --- /dev/null +++ b/documentation/build/html/widgets/gradientwidget.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GradientWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="ColorButton" href="colorbutton.html" /> + <link rel="prev" title="TableWidget" href="tablewidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="colorbutton.html" title="ColorButton" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="tablewidget.html" title="TableWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="gradientwidget"> +<h1>GradientWidget<a class="headerlink" href="#gradientwidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GradientWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GradientWidget</tt><big>(</big><em>parent=None</em>, <em>orientation='bottom'</em>, <em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.GradientWidget" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.GradientWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>orientation='bottom'</em>, <em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.GradientWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="tablewidget.html" + title="previous chapter">TableWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="colorbutton.html" + title="next chapter">ColorButton</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/gradientwidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="colorbutton.html" title="ColorButton" + >next</a> |</li> + <li class="right" > + <a href="tablewidget.html" title="TableWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/graphicslayoutwidget.html b/documentation/build/html/widgets/graphicslayoutwidget.html new file mode 100644 index 00000000..3cbc7e00 --- /dev/null +++ b/documentation/build/html/widgets/graphicslayoutwidget.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GraphicsLayoutWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="dockarea module" href="dockarea.html" /> + <link rel="prev" title="ColorButton" href="colorbutton.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="dockarea.html" title="dockarea module" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="colorbutton.html" title="ColorButton" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="graphicslayoutwidget"> +<h1>GraphicsLayoutWidget<a class="headerlink" href="#graphicslayoutwidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GraphicsLayoutWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GraphicsLayoutWidget</tt><big>(</big><em>parent=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsLayoutWidget" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.GraphicsLayoutWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsLayoutWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="colorbutton.html" + title="previous chapter">ColorButton</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="dockarea.html" + title="next chapter">dockarea module</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/graphicslayoutwidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="dockarea.html" title="dockarea module" + >next</a> |</li> + <li class="right" > + <a href="colorbutton.html" title="ColorButton" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/graphicsview.html b/documentation/build/html/widgets/graphicsview.html new file mode 100644 index 00000000..4474a07f --- /dev/null +++ b/documentation/build/html/widgets/graphicsview.html @@ -0,0 +1,162 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>GraphicsView — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="JoystickButton" href="joystickbutton.html" /> + <link rel="prev" title="FileDialog" href="filedialog.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="joystickbutton.html" title="JoystickButton" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="filedialog.html" title="FileDialog" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="graphicsview"> +<h1>GraphicsView<a class="headerlink" href="#graphicsview" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.GraphicsView"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">GraphicsView</tt><big>(</big><em>parent=None</em>, <em>useOpenGL=None</em>, <em>background='k'</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsView" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.GraphicsView.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>useOpenGL=None</em>, <em>background='k'</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsView.__init__" title="Permalink to this definition">¶</a></dt> +<dd><p>Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the +viewed coordinate range. Also automatically creates a QGraphicsScene and a central QGraphicsWidget +that is automatically scaled to the full view geometry.</p> +<p>By default, the view coordinate system matches the widget’s pixel coordinates and +automatically updates when the view is resized. This can be overridden by setting +autoPixelRange=False. The exact visible range can be set with setRange().</p> +<p>The view can be panned using the middle mouse button and scaled using the right mouse button if +enabled via enableMouse().</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsView.pixelSize"> +<tt class="descname">pixelSize</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsView.pixelSize" title="Permalink to this definition">¶</a></dt> +<dd><p>Return vector with the length and width of one view pixel in scene coordinates</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsView.scaleToImage"> +<tt class="descname">scaleToImage</tt><big>(</big><em>image</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsView.scaleToImage" title="Permalink to this definition">¶</a></dt> +<dd><p>Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsView.setCentralWidget"> +<tt class="descname">setCentralWidget</tt><big>(</big><em>item</em><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsView.setCentralWidget" title="Permalink to this definition">¶</a></dt> +<dd><p>Sets a QGraphicsWidget to automatically fill the entire view.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.GraphicsView.viewRect"> +<tt class="descname">viewRect</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.GraphicsView.viewRect" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the boundaries of the view in scene coordinates</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="filedialog.html" + title="previous chapter">FileDialog</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="joystickbutton.html" + title="next chapter">JoystickButton</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/graphicsview.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="joystickbutton.html" title="JoystickButton" + >next</a> |</li> + <li class="right" > + <a href="filedialog.html" title="FileDialog" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/histogramlutwidget.html b/documentation/build/html/widgets/histogramlutwidget.html new file mode 100644 index 00000000..5866ae01 --- /dev/null +++ b/documentation/build/html/widgets/histogramlutwidget.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>HistogramLUTWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="ProgressDialog" href="progressdialog.html" /> + <link rel="prev" title="parametertree module" href="parametertree.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="progressdialog.html" title="ProgressDialog" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="parametertree.html" title="parametertree module" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="histogramlutwidget"> +<h1>HistogramLUTWidget<a class="headerlink" href="#histogramlutwidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.HistogramLUTWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">HistogramLUTWidget</tt><big>(</big><em>parent=None</em>, <em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.HistogramLUTWidget" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.HistogramLUTWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.HistogramLUTWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="parametertree.html" + title="previous chapter">parametertree module</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="progressdialog.html" + title="next chapter">ProgressDialog</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/histogramlutwidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="progressdialog.html" title="ProgressDialog" + >next</a> |</li> + <li class="right" > + <a href="parametertree.html" title="parametertree module" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/imageview.html b/documentation/build/html/widgets/imageview.html new file mode 100644 index 00000000..85680aa6 --- /dev/null +++ b/documentation/build/html/widgets/imageview.html @@ -0,0 +1,158 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ImageView — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="DataTreeWidget" href="datatreewidget.html" /> + <link rel="prev" title="PlotWidget" href="plotwidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="datatreewidget.html" title="DataTreeWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="plotwidget.html" title="PlotWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="imageview"> +<h1>ImageView<a class="headerlink" href="#imageview" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ImageView"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ImageView</tt><big>(</big><em>parent=None</em>, <em>name='ImageView'</em>, <em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageView" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.ImageView.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>name='ImageView'</em>, <em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageView.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageView.jumpFrames"> +<tt class="descname">jumpFrames</tt><big>(</big><em>n</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageView.jumpFrames" title="Permalink to this definition">¶</a></dt> +<dd><p>If this is a video, move ahead n frames</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageView.setImage"> +<tt class="descname">setImage</tt><big>(</big><em>img</em>, <em>autoRange=True</em>, <em>autoLevels=True</em>, <em>levels=None</em>, <em>axes=None</em>, <em>xvals=None</em>, <em>pos=None</em>, <em>scale=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageView.setImage" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the image to be displayed in the widget. +Options are:</p> +<blockquote> +<p>img: ndarray; the image to be displayed. +autoRange: bool; whether to scale/pan the view to fit the image. +autoLevels: bool; whether to update the white/black levels to fit the image. +levels: (min, max); the white and black level values to use. +axes: {‘t’:0, ‘x’:1, ‘y’:2, ‘c’:3}; Dictionary indicating the interpretation for each axis.</p> +<blockquote> +This is only needed to override the default guess.</blockquote> +</blockquote> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.ImageView.timeIndex"> +<tt class="descname">timeIndex</tt><big>(</big><em>slider</em><big>)</big><a class="headerlink" href="#pyqtgraph.ImageView.timeIndex" title="Permalink to this definition">¶</a></dt> +<dd><p>Return the time and frame index indicated by a slider</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="plotwidget.html" + title="previous chapter">PlotWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="datatreewidget.html" + title="next chapter">DataTreeWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/imageview.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="datatreewidget.html" title="DataTreeWidget" + >next</a> |</li> + <li class="right" > + <a href="plotwidget.html" title="PlotWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/index.html b/documentation/build/html/widgets/index.html new file mode 100644 index 00000000..8e84cb40 --- /dev/null +++ b/documentation/build/html/widgets/index.html @@ -0,0 +1,144 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>Pyqtgraph’s Widgets — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="API Reference" href="../apireference.html" /> + <link rel="next" title="PlotWidget" href="plotwidget.html" /> + <link rel="prev" title="UIGraphicsItem" href="../graphicsItems/uigraphicsitem.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotwidget.html" title="PlotWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="../graphicsItems/uigraphicsitem.html" title="UIGraphicsItem" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" accesskey="U">API Reference</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="pyqtgraph-s-widgets"> +<h1>Pyqtgraph’s Widgets<a class="headerlink" href="#pyqtgraph-s-widgets" title="Permalink to this headline">¶</a></h1> +<p>Pyqtgraph provides several QWidget subclasses which are useful for building user interfaces. These widgets can generally be used in any Qt application and provide functionality that is frequently useful in science and engineering applications.</p> +<p>Contents:</p> +<div class="toctree-wrapper compound"> +<ul> +<li class="toctree-l1"><a class="reference internal" href="plotwidget.html">PlotWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="imageview.html">ImageView</a></li> +<li class="toctree-l1"><a class="reference internal" href="datatreewidget.html">DataTreeWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="checktable.html">CheckTable</a></li> +<li class="toctree-l1"><a class="reference internal" href="tablewidget.html">TableWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="gradientwidget.html">GradientWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="colorbutton.html">ColorButton</a></li> +<li class="toctree-l1"><a class="reference internal" href="graphicslayoutwidget.html">GraphicsLayoutWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="dockarea.html">dockarea module</a></li> +<li class="toctree-l1"><a class="reference internal" href="parametertree.html">parametertree module</a></li> +<li class="toctree-l1"><a class="reference internal" href="histogramlutwidget.html">HistogramLUTWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="progressdialog.html">ProgressDialog</a></li> +<li class="toctree-l1"><a class="reference internal" href="spinbox.html">SpinBox</a></li> +<li class="toctree-l1"><a class="reference internal" href="filedialog.html">FileDialog</a></li> +<li class="toctree-l1"><a class="reference internal" href="graphicsview.html">GraphicsView</a></li> +<li class="toctree-l1"><a class="reference internal" href="joystickbutton.html">JoystickButton</a></li> +<li class="toctree-l1"><a class="reference internal" href="multiplotwidget.html">MultiPlotWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="treewidget.html">TreeWidget</a></li> +<li class="toctree-l1"><a class="reference internal" href="verticallabel.html">VerticalLabel</a></li> +<li class="toctree-l1"><a class="reference internal" href="rawimagewidget.html">RawImageWidget</a></li> +</ul> +</div> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="../graphicsItems/uigraphicsitem.html" + title="previous chapter">UIGraphicsItem</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="plotwidget.html" + title="next chapter">PlotWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/index.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="plotwidget.html" title="PlotWidget" + >next</a> |</li> + <li class="right" > + <a href="../graphicsItems/uigraphicsitem.html" title="UIGraphicsItem" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/joystickbutton.html b/documentation/build/html/widgets/joystickbutton.html new file mode 100644 index 00000000..60e8eee3 --- /dev/null +++ b/documentation/build/html/widgets/joystickbutton.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>JoystickButton — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="MultiPlotWidget" href="multiplotwidget.html" /> + <link rel="prev" title="GraphicsView" href="graphicsview.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="multiplotwidget.html" title="MultiPlotWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="graphicsview.html" title="GraphicsView" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="joystickbutton"> +<h1>JoystickButton<a class="headerlink" href="#joystickbutton" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.JoystickButton"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">JoystickButton</tt><big>(</big><em>parent=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.JoystickButton" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.JoystickButton.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.JoystickButton.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="graphicsview.html" + title="previous chapter">GraphicsView</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="multiplotwidget.html" + title="next chapter">MultiPlotWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/joystickbutton.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="multiplotwidget.html" title="MultiPlotWidget" + >next</a> |</li> + <li class="right" > + <a href="graphicsview.html" title="GraphicsView" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/multiplotwidget.html b/documentation/build/html/widgets/multiplotwidget.html new file mode 100644 index 00000000..8624e2e1 --- /dev/null +++ b/documentation/build/html/widgets/multiplotwidget.html @@ -0,0 +1,131 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>MultiPlotWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="TreeWidget" href="treewidget.html" /> + <link rel="prev" title="JoystickButton" href="joystickbutton.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="treewidget.html" title="TreeWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="joystickbutton.html" title="JoystickButton" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="multiplotwidget"> +<h1>MultiPlotWidget<a class="headerlink" href="#multiplotwidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.MultiPlotWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">MultiPlotWidget</tt><big>(</big><em>parent=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.MultiPlotWidget" title="Permalink to this definition">¶</a></dt> +<dd><p>Widget implementing a graphicsView with a single PlotItem inside.</p> +<dl class="method"> +<dt id="pyqtgraph.MultiPlotWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.MultiPlotWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="joystickbutton.html" + title="previous chapter">JoystickButton</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="treewidget.html" + title="next chapter">TreeWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/multiplotwidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="treewidget.html" title="TreeWidget" + >next</a> |</li> + <li class="right" > + <a href="joystickbutton.html" title="JoystickButton" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/parametertree.html b/documentation/build/html/widgets/parametertree.html new file mode 100644 index 00000000..cb2ff84f --- /dev/null +++ b/documentation/build/html/widgets/parametertree.html @@ -0,0 +1,120 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>parametertree module — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="HistogramLUTWidget" href="histogramlutwidget.html" /> + <link rel="prev" title="dockarea module" href="dockarea.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="histogramlutwidget.html" title="HistogramLUTWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="dockarea.html" title="dockarea module" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="module-pyqtgraph.parametertree"> +<span id="parametertree-module"></span><h1>parametertree module<a class="headerlink" href="#module-pyqtgraph.parametertree" title="Permalink to this headline">¶</a></h1> +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="dockarea.html" + title="previous chapter">dockarea module</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="histogramlutwidget.html" + title="next chapter">HistogramLUTWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/parametertree.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="histogramlutwidget.html" title="HistogramLUTWidget" + >next</a> |</li> + <li class="right" > + <a href="dockarea.html" title="dockarea module" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/plotwidget.html b/documentation/build/html/widgets/plotwidget.html new file mode 100644 index 00000000..c8b060f2 --- /dev/null +++ b/documentation/build/html/widgets/plotwidget.html @@ -0,0 +1,131 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>PlotWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="ImageView" href="imageview.html" /> + <link rel="prev" title="Pyqtgraph’s Widgets" href="index.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="imageview.html" title="ImageView" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="index.html" title="Pyqtgraph’s Widgets" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="plotwidget"> +<h1>PlotWidget<a class="headerlink" href="#plotwidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.PlotWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">PlotWidget</tt><big>(</big><em>parent=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotWidget" title="Permalink to this definition">¶</a></dt> +<dd><p>Widget implementing a graphicsView with a single PlotItem inside.</p> +<dl class="method"> +<dt id="pyqtgraph.PlotWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.PlotWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="index.html" + title="previous chapter">Pyqtgraph’s Widgets</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="imageview.html" + title="next chapter">ImageView</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/plotwidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="imageview.html" title="ImageView" + >next</a> |</li> + <li class="right" > + <a href="index.html" title="Pyqtgraph’s Widgets" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/progressdialog.html b/documentation/build/html/widgets/progressdialog.html new file mode 100644 index 00000000..969b09af --- /dev/null +++ b/documentation/build/html/widgets/progressdialog.html @@ -0,0 +1,153 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>ProgressDialog — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="SpinBox" href="spinbox.html" /> + <link rel="prev" title="HistogramLUTWidget" href="histogramlutwidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="spinbox.html" title="SpinBox" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="histogramlutwidget.html" title="HistogramLUTWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="progressdialog"> +<h1>ProgressDialog<a class="headerlink" href="#progressdialog" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.ProgressDialog"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">ProgressDialog</tt><big>(</big><em>labelText</em>, <em>minimum=0</em>, <em>maximum=100</em>, <em>cancelText='Cancel'</em>, <em>parent=None</em>, <em>wait=250</em>, <em>busyCursor=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.ProgressDialog" title="Permalink to this definition">¶</a></dt> +<dd><p>Extends QProgressDialog for use in ‘with’ statements. +Arguments:</p> +<blockquote> +labelText (required) +cancelText Text to display on cancel button, or None to disable it. +minimum +maximum +parent +wait Length of time (im ms) to wait before displaying dialog +busyCursor If True, show busy cursor until dialog finishes</blockquote> +<dl class="docutils"> +<dt>Example:</dt> +<dd><dl class="first last docutils"> +<dt>with ProgressDialog(“Processing..”, minVal, maxVal) as dlg:</dt> +<dd><p class="first"># do stuff +dlg.setValue(i) ## could also use dlg += 1 +if dlg.wasCanceled():</p> +<blockquote class="last"> +raise Exception(“Processing canceled by user”)</blockquote> +</dd> +</dl> +</dd> +</dl> +<dl class="method"> +<dt id="pyqtgraph.ProgressDialog.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>labelText</em>, <em>minimum=0</em>, <em>maximum=100</em>, <em>cancelText='Cancel'</em>, <em>parent=None</em>, <em>wait=250</em>, <em>busyCursor=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.ProgressDialog.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="histogramlutwidget.html" + title="previous chapter">HistogramLUTWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="spinbox.html" + title="next chapter">SpinBox</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/progressdialog.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="spinbox.html" title="SpinBox" + >next</a> |</li> + <li class="right" > + <a href="histogramlutwidget.html" title="HistogramLUTWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/rawimagewidget.html b/documentation/build/html/widgets/rawimagewidget.html new file mode 100644 index 00000000..88a89584 --- /dev/null +++ b/documentation/build/html/widgets/rawimagewidget.html @@ -0,0 +1,132 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>RawImageWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="prev" title="VerticalLabel" href="verticallabel.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="verticallabel.html" title="VerticalLabel" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="rawimagewidget"> +<h1>RawImageWidget<a class="headerlink" href="#rawimagewidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.RawImageWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">RawImageWidget</tt><big>(</big><em>parent=None</em>, <em>scaled=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.RawImageWidget" title="Permalink to this definition">¶</a></dt> +<dd><p>Widget optimized for very fast video display. +Generally using an ImageItem inside GraphicsView is fast enough, +but if you need even more performance, this widget is about as fast as it gets.</p> +<p>The tradeoff is that this widget will _only_ display the unscaled image +and nothing else.</p> +<dl class="method"> +<dt id="pyqtgraph.RawImageWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>scaled=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.RawImageWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.RawImageWidget.setImage"> +<tt class="descname">setImage</tt><big>(</big><em>img</em>, <em>*args</em>, <em>**kargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.RawImageWidget.setImage" title="Permalink to this definition">¶</a></dt> +<dd><p>img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). +Extra arguments are sent to functions.makeARGB</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="verticallabel.html" + title="previous chapter">VerticalLabel</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/rawimagewidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="verticallabel.html" title="VerticalLabel" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/spinbox.html b/documentation/build/html/widgets/spinbox.html new file mode 100644 index 00000000..5eaf1b51 --- /dev/null +++ b/documentation/build/html/widgets/spinbox.html @@ -0,0 +1,164 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>SpinBox — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="FileDialog" href="filedialog.html" /> + <link rel="prev" title="ProgressDialog" href="progressdialog.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="filedialog.html" title="FileDialog" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="progressdialog.html" title="ProgressDialog" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="spinbox"> +<h1>SpinBox<a class="headerlink" href="#spinbox" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.SpinBox"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">SpinBox</tt><big>(</big><em>parent=None</em>, <em>value=0.0</em>, <em>**kwargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.SpinBox" title="Permalink to this definition">¶</a></dt> +<dd><p>QSpinBox widget on steroids. Allows selection of numerical value, with extra features: +- SI prefix notation +- Float values with linear and decimal stepping (1-9, 10-90, 100-900, etc.) +- Option for unbounded values +- Delayed signals (allows multiple rapid changes with only one change signal)</p> +<dl class="method"> +<dt id="pyqtgraph.SpinBox.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em>, <em>value=0.0</em>, <em>**kwargs</em><big>)</big><a class="headerlink" href="#pyqtgraph.SpinBox.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.SpinBox.editingFinishedEvent"> +<tt class="descname">editingFinishedEvent</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.SpinBox.editingFinishedEvent" title="Permalink to this definition">¶</a></dt> +<dd><p>Edit has finished; set value.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.SpinBox.interpret"> +<tt class="descname">interpret</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.SpinBox.interpret" title="Permalink to this definition">¶</a></dt> +<dd><p>Return value of text. Return False if text is invalid, raise exception if text is intermediate</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.SpinBox.setProperty"> +<tt class="descname">setProperty</tt><big>(</big><em>prop</em>, <em>val</em><big>)</big><a class="headerlink" href="#pyqtgraph.SpinBox.setProperty" title="Permalink to this definition">¶</a></dt> +<dd><p>setProperty is just for compatibility with QSpinBox</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.SpinBox.setValue"> +<tt class="descname">setValue</tt><big>(</big><em>value=None</em>, <em>update=True</em>, <em>delaySignal=False</em><big>)</big><a class="headerlink" href="#pyqtgraph.SpinBox.setValue" title="Permalink to this definition">¶</a></dt> +<dd><p>Set the value of this spin. +If the value is out of bounds, it will be moved to the nearest boundary +If the spin is integer type, the value will be coerced to int +Returns the actual value set.</p> +<p>If value is None, then the current value is used (this is for resetting +the value after bounds, etc. have changed)</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="progressdialog.html" + title="previous chapter">ProgressDialog</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="filedialog.html" + title="next chapter">FileDialog</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/spinbox.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="filedialog.html" title="FileDialog" + >next</a> |</li> + <li class="right" > + <a href="progressdialog.html" title="ProgressDialog" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/tablewidget.html b/documentation/build/html/widgets/tablewidget.html new file mode 100644 index 00000000..5e6febc4 --- /dev/null +++ b/documentation/build/html/widgets/tablewidget.html @@ -0,0 +1,168 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>TableWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="GradientWidget" href="gradientwidget.html" /> + <link rel="prev" title="CheckTable" href="checktable.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="gradientwidget.html" title="GradientWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="checktable.html" title="CheckTable" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="tablewidget"> +<h1>TableWidget<a class="headerlink" href="#tablewidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.TableWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">TableWidget</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.TableWidget" title="Permalink to this definition">¶</a></dt> +<dd><p>Extends QTableWidget with some useful functions for automatic data handling. +Can automatically format and display:</p> +<blockquote> +<p>numpy arrays +numpy record arrays +metaarrays +list-of-lists [[1,2,3], [4,5,6]] +dict-of-lists {‘x’: [1,2,3], ‘y’: [4,5,6]} +list-of-dicts [</p> +<blockquote> +<blockquote> +{‘x’: 1, ‘y’: 4}, +{‘x’: 2, ‘y’: 5}, +{‘x’: 3, ‘y’: 6}</blockquote> +<p>]</p> +</blockquote> +</blockquote> +<dl class="method"> +<dt id="pyqtgraph.TableWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#pyqtgraph.TableWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.TableWidget.appendData"> +<tt class="descname">appendData</tt><big>(</big><em>data</em><big>)</big><a class="headerlink" href="#pyqtgraph.TableWidget.appendData" title="Permalink to this definition">¶</a></dt> +<dd><p>Types allowed: +1 or 2D numpy array or metaArray +1D numpy record array +list-of-lists, list-of-dicts or dict-of-lists</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.TableWidget.copy"> +<tt class="descname">copy</tt><big>(</big><big>)</big><a class="headerlink" href="#pyqtgraph.TableWidget.copy" title="Permalink to this definition">¶</a></dt> +<dd><p>Copy selected data to clipboard.</p> +</dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.TableWidget.iteratorFn"> +<tt class="descname">iteratorFn</tt><big>(</big><em>data</em><big>)</big><a class="headerlink" href="#pyqtgraph.TableWidget.iteratorFn" title="Permalink to this definition">¶</a></dt> +<dd><p>Return 1) a function that will provide an iterator for data and 2) a list of header strings</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="checktable.html" + title="previous chapter">CheckTable</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="gradientwidget.html" + title="next chapter">GradientWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/tablewidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="gradientwidget.html" title="GradientWidget" + >next</a> |</li> + <li class="right" > + <a href="checktable.html" title="CheckTable" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/treewidget.html b/documentation/build/html/widgets/treewidget.html new file mode 100644 index 00000000..d20bcbc4 --- /dev/null +++ b/documentation/build/html/widgets/treewidget.html @@ -0,0 +1,140 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>TreeWidget — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="VerticalLabel" href="verticallabel.html" /> + <link rel="prev" title="MultiPlotWidget" href="multiplotwidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="verticallabel.html" title="VerticalLabel" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="multiplotwidget.html" title="MultiPlotWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="treewidget"> +<h1>TreeWidget<a class="headerlink" href="#treewidget" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.TreeWidget"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">TreeWidget</tt><big>(</big><em>parent=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.TreeWidget" title="Permalink to this definition">¶</a></dt> +<dd><p>Extends QTreeWidget to allow internal drag/drop with widgets in the tree. +Also maintains the expanded state of subtrees as they are moved. +This class demonstrates the absurd lengths one must go to to make drag/drop work.</p> +<dl class="method"> +<dt id="pyqtgraph.TreeWidget.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>parent=None</em><big>)</big><a class="headerlink" href="#pyqtgraph.TreeWidget.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +<dl class="method"> +<dt id="pyqtgraph.TreeWidget.itemMoving"> +<tt class="descname">itemMoving</tt><big>(</big><em>item</em>, <em>parent</em>, <em>index</em><big>)</big><a class="headerlink" href="#pyqtgraph.TreeWidget.itemMoving" title="Permalink to this definition">¶</a></dt> +<dd><p>Called when item has been dropped elsewhere in the tree. +Return True to accept the move, False to reject.</p> +</dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="multiplotwidget.html" + title="previous chapter">MultiPlotWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="verticallabel.html" + title="next chapter">VerticalLabel</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/treewidget.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="verticallabel.html" title="VerticalLabel" + >next</a> |</li> + <li class="right" > + <a href="multiplotwidget.html" title="MultiPlotWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/build/html/widgets/verticallabel.html b/documentation/build/html/widgets/verticallabel.html new file mode 100644 index 00000000..335a9c1e --- /dev/null +++ b/documentation/build/html/widgets/verticallabel.html @@ -0,0 +1,130 @@ + + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + + <title>VerticalLabel — pyqtgraph v1.8 documentation</title> + <link rel="stylesheet" href="../_static/default.css" type="text/css" /> + <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> + <script type="text/javascript"> + var DOCUMENTATION_OPTIONS = { + URL_ROOT: '../', + VERSION: '1.8', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true + }; + </script> + <script type="text/javascript" src="../_static/jquery.js"></script> + <script type="text/javascript" src="../_static/underscore.js"></script> + <script type="text/javascript" src="../_static/doctools.js"></script> + <link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" /> + <link rel="up" title="Pyqtgraph’s Widgets" href="index.html" /> + <link rel="next" title="RawImageWidget" href="rawimagewidget.html" /> + <link rel="prev" title="TreeWidget" href="treewidget.html" /> + </head> + <body> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + accesskey="I">index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="rawimagewidget.html" title="RawImageWidget" + accesskey="N">next</a> |</li> + <li class="right" > + <a href="treewidget.html" title="TreeWidget" + accesskey="P">previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" accesskey="U">Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + + <div class="document"> + <div class="documentwrapper"> + <div class="bodywrapper"> + <div class="body"> + + <div class="section" id="verticallabel"> +<h1>VerticalLabel<a class="headerlink" href="#verticallabel" title="Permalink to this headline">¶</a></h1> +<dl class="class"> +<dt id="pyqtgraph.VerticalLabel"> +<em class="property">class </em><tt class="descclassname">pyqtgraph.</tt><tt class="descname">VerticalLabel</tt><big>(</big><em>text</em>, <em>orientation='vertical'</em>, <em>forceWidth=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.VerticalLabel" title="Permalink to this definition">¶</a></dt> +<dd><dl class="method"> +<dt id="pyqtgraph.VerticalLabel.__init__"> +<tt class="descname">__init__</tt><big>(</big><em>text</em>, <em>orientation='vertical'</em>, <em>forceWidth=True</em><big>)</big><a class="headerlink" href="#pyqtgraph.VerticalLabel.__init__" title="Permalink to this definition">¶</a></dt> +<dd></dd></dl> + +</dd></dl> + +</div> + + + </div> + </div> + </div> + <div class="sphinxsidebar"> + <div class="sphinxsidebarwrapper"> + <h4>Previous topic</h4> + <p class="topless"><a href="treewidget.html" + title="previous chapter">TreeWidget</a></p> + <h4>Next topic</h4> + <p class="topless"><a href="rawimagewidget.html" + title="next chapter">RawImageWidget</a></p> + <h3>This Page</h3> + <ul class="this-page-menu"> + <li><a href="../_sources/widgets/verticallabel.txt" + rel="nofollow">Show Source</a></li> + </ul> +<div id="searchbox" style="display: none"> + <h3>Quick search</h3> + <form class="search" action="../search.html" method="get"> + <input type="text" name="q" size="18" /> + <input type="submit" value="Go" /> + <input type="hidden" name="check_keywords" value="yes" /> + <input type="hidden" name="area" value="default" /> + </form> + <p class="searchtip" style="font-size: 90%"> + Enter search terms or a module, class or function name. + </p> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> + </div> + </div> + <div class="clearer"></div> + </div> + <div class="related"> + <h3>Navigation</h3> + <ul> + <li class="right" style="margin-right: 10px"> + <a href="../genindex.html" title="General Index" + >index</a></li> + <li class="right" > + <a href="../py-modindex.html" title="Python Module Index" + >modules</a> |</li> + <li class="right" > + <a href="rawimagewidget.html" title="RawImageWidget" + >next</a> |</li> + <li class="right" > + <a href="treewidget.html" title="TreeWidget" + >previous</a> |</li> + <li><a href="../index.html">pyqtgraph v1.8 documentation</a> »</li> + <li><a href="../apireference.html" >API Reference</a> »</li> + <li><a href="index.html" >Pyqtgraph’s Widgets</a> »</li> + </ul> + </div> + <div class="footer"> + © Copyright 2011, Luke Campagnola. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1. + </div> + </body> +</html> \ No newline at end of file diff --git a/documentation/make.bat b/documentation/make.bat new file mode 100644 index 00000000..1d76823d --- /dev/null +++ b/documentation/make.bat @@ -0,0 +1,155 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^<target^>` where ^<target^> is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyqtgraph.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyqtgraph.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/documentation/source/apireference.rst b/documentation/source/apireference.rst new file mode 100644 index 00000000..ab4ec666 --- /dev/null +++ b/documentation/source/apireference.rst @@ -0,0 +1,11 @@ +API Reference +============= + +Contents: + +.. toctree:: + :maxdepth: 2 + + functions + graphicsItems/index + widgets/index diff --git a/documentation/source/conf.py b/documentation/source/conf.py new file mode 100644 index 00000000..6df8d7b9 --- /dev/null +++ b/documentation/source/conf.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- +# +# pyqtgraph documentation build configuration file, created by +# sphinx-quickstart on Fri Nov 18 19:33:12 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(path, '..', '..', '..')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'pyqtgraph' +copyright = u'2011, Luke Campagnola' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.8' +# The full version, including alpha/beta/rc tags. +release = '1.8' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pyqtgraphdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'pyqtgraph.tex', u'pyqtgraph Documentation', + u'Luke Campagnola', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'pyqtgraph', u'pyqtgraph Documentation', + [u'Luke Campagnola'], 1) +] diff --git a/documentation/source/functions.rst b/documentation/source/functions.rst new file mode 100644 index 00000000..3d56a4d9 --- /dev/null +++ b/documentation/source/functions.rst @@ -0,0 +1,53 @@ +Pyqtgraph's Helper Functions +============================ + +Simple Data Display Functions +----------------------------- + +.. autofunction:: pyqtgraph.plot + +.. autofunction:: pyqtgraph.image + + + +Color, Pen, and Brush Functions +------------------------------- + +Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and fill shapes. These classes are highly capable but somewhat awkward to use. Pyqtgraph offers the functions :func:`~pyqtgraph.mkColor`, :func:`~pyqtgraph.mkPen`, and :func:`~pyqtgraph.mkBrush` to simplify the process of creating these classes. In most cases, however, it will be unnecessary to call these functions directly--any function or method that accepts *pen* or *brush* arguments will make use of these functions for you. For example, the following three lines all have the same effect:: + + pg.plot(xdata, ydata, pen='r') + pg.plot(xdata, ydata, pen=pg.mkPen('r')) + pg.plot(xdata, ydata, pen=QPen(QColor(255, 0, 0))) + + +.. autofunction:: pyqtgraph.mkColor + +.. autofunction:: pyqtgraph.mkPen + +.. autofunction:: pyqtgraph.mkBrush + +.. autofunction:: pyqtgraph.hsvColor + +.. autofunction:: pyqtgraph.intColor + +.. autofunction:: pyqtgraph.colorTuple + +.. autofunction:: pyqtgraph.colorStr + + +Data Slicing +------------ + +.. autofunction:: pyqtgraph.affineSlice + + + +SI Unit Conversion Functions +---------------------------- + +.. autofunction:: pyqtgraph.siFormat + +.. autofunction:: pyqtgraph.siScale + +.. autofunction:: pyqtgraph.siEval + diff --git a/documentation/source/graphicsItems/arrowitem.rst b/documentation/source/graphicsItems/arrowitem.rst new file mode 100644 index 00000000..250957a5 --- /dev/null +++ b/documentation/source/graphicsItems/arrowitem.rst @@ -0,0 +1,8 @@ +ArrowItem +========= + +.. autoclass:: pyqtgraph.ArrowItem + :members: + + .. automethod:: pyqtgraph.ArrowItem.__init__ + diff --git a/documentation/source/graphicsItems/axisitem.rst b/documentation/source/graphicsItems/axisitem.rst new file mode 100644 index 00000000..8f76d130 --- /dev/null +++ b/documentation/source/graphicsItems/axisitem.rst @@ -0,0 +1,8 @@ +AxisItem +======== + +.. autoclass:: pyqtgraph.AxisItem + :members: + + .. automethod:: pyqtgraph.AxisItem.__init__ + diff --git a/documentation/source/graphicsItems/buttonitem.rst b/documentation/source/graphicsItems/buttonitem.rst new file mode 100644 index 00000000..44469db6 --- /dev/null +++ b/documentation/source/graphicsItems/buttonitem.rst @@ -0,0 +1,8 @@ +ButtonItem +========== + +.. autoclass:: pyqtgraph.ButtonItem + :members: + + .. automethod:: pyqtgraph.ButtonItem.__init__ + diff --git a/documentation/source/graphicsItems/curvearrow.rst b/documentation/source/graphicsItems/curvearrow.rst new file mode 100644 index 00000000..4c7f11ab --- /dev/null +++ b/documentation/source/graphicsItems/curvearrow.rst @@ -0,0 +1,8 @@ +CurveArrow +========== + +.. autoclass:: pyqtgraph.CurveArrow + :members: + + .. automethod:: pyqtgraph.CurveArrow.__init__ + diff --git a/documentation/source/graphicsItems/curvepoint.rst b/documentation/source/graphicsItems/curvepoint.rst new file mode 100644 index 00000000..f19791f7 --- /dev/null +++ b/documentation/source/graphicsItems/curvepoint.rst @@ -0,0 +1,8 @@ +CurvePoint +========== + +.. autoclass:: pyqtgraph.CurvePoint + :members: + + .. automethod:: pyqtgraph.CurvePoint.__init__ + diff --git a/documentation/source/graphicsItems/gradienteditoritem.rst b/documentation/source/graphicsItems/gradienteditoritem.rst new file mode 100644 index 00000000..02d40956 --- /dev/null +++ b/documentation/source/graphicsItems/gradienteditoritem.rst @@ -0,0 +1,8 @@ +GradientEditorItem +================== + +.. autoclass:: pyqtgraph.GradientEditorItem + :members: + + .. automethod:: pyqtgraph.GradientEditorItem.__init__ + diff --git a/documentation/source/graphicsItems/gradientlegend.rst b/documentation/source/graphicsItems/gradientlegend.rst new file mode 100644 index 00000000..f47031c0 --- /dev/null +++ b/documentation/source/graphicsItems/gradientlegend.rst @@ -0,0 +1,8 @@ +GradientLegend +============== + +.. autoclass:: pyqtgraph.GradientLegend + :members: + + .. automethod:: pyqtgraph.GradientLegend.__init__ + diff --git a/documentation/source/graphicsItems/graphicslayout.rst b/documentation/source/graphicsItems/graphicslayout.rst new file mode 100644 index 00000000..f45dfd87 --- /dev/null +++ b/documentation/source/graphicsItems/graphicslayout.rst @@ -0,0 +1,8 @@ +GraphicsLayout +============== + +.. autoclass:: pyqtgraph.GraphicsLayout + :members: + + .. automethod:: pyqtgraph.GraphicsLayout.__init__ + diff --git a/documentation/source/graphicsItems/graphicsobject.rst b/documentation/source/graphicsItems/graphicsobject.rst new file mode 100644 index 00000000..736d941e --- /dev/null +++ b/documentation/source/graphicsItems/graphicsobject.rst @@ -0,0 +1,8 @@ +GraphicsObject +============== + +.. autoclass:: pyqtgraph.GraphicsObject + :members: + + .. automethod:: pyqtgraph.GraphicsObject.__init__ + diff --git a/documentation/source/graphicsItems/graphicswidget.rst b/documentation/source/graphicsItems/graphicswidget.rst new file mode 100644 index 00000000..7cf23bbe --- /dev/null +++ b/documentation/source/graphicsItems/graphicswidget.rst @@ -0,0 +1,8 @@ +GraphicsWidget +============== + +.. autoclass:: pyqtgraph.GraphicsWidget + :members: + + .. automethod:: pyqtgraph.GraphicsWidget.__init__ + diff --git a/documentation/source/graphicsItems/griditem.rst b/documentation/source/graphicsItems/griditem.rst new file mode 100644 index 00000000..aa932766 --- /dev/null +++ b/documentation/source/graphicsItems/griditem.rst @@ -0,0 +1,8 @@ +GridItem +======== + +.. autoclass:: pyqtgraph.GridItem + :members: + + .. automethod:: pyqtgraph.GridItem.__init__ + diff --git a/documentation/source/graphicsItems/histogramlutitem.rst b/documentation/source/graphicsItems/histogramlutitem.rst new file mode 100644 index 00000000..db0e18cb --- /dev/null +++ b/documentation/source/graphicsItems/histogramlutitem.rst @@ -0,0 +1,8 @@ +HistogramLUTItem +================ + +.. autoclass:: pyqtgraph.HistogramLUTItem + :members: + + .. automethod:: pyqtgraph.HistogramLUTItem.__init__ + diff --git a/documentation/source/graphicsItems/imageitem.rst b/documentation/source/graphicsItems/imageitem.rst new file mode 100644 index 00000000..49a981dc --- /dev/null +++ b/documentation/source/graphicsItems/imageitem.rst @@ -0,0 +1,8 @@ +ImageItem +========= + +.. autoclass:: pyqtgraph.ImageItem + :members: + + .. automethod:: pyqtgraph.ImageItem.__init__ + diff --git a/documentation/source/graphicsItems/index.rst b/documentation/source/graphicsItems/index.rst new file mode 100644 index 00000000..46f5a938 --- /dev/null +++ b/documentation/source/graphicsItems/index.rst @@ -0,0 +1,37 @@ +Pyqtgraph's Graphics Items +========================== + +Since pyqtgraph relies on Qt's GraphicsView framework, most of its graphics functionality is implemented as QGraphicsItem subclasses. This has two important consequences: 1) virtually anything you want to draw can be easily accomplished using the functionality provided by Qt. 2) Many of pyqtgraph's GraphicsItem classes can be used in any normal QGraphicsScene. + + +Contents: + +.. toctree:: + :maxdepth: 2 + + plotdataitem + plotcurveitem + scatterplotitem + plotitem + imageitem + viewbox + linearregionitem + infiniteline + roi + graphicslayout + axisitem + arrowitem + curvepoint + curvearrow + griditem + scalebar + labelitem + vtickgroup + gradienteditoritem + histogramlutitem + gradientlegend + buttonitem + graphicsobject + graphicswidget + uigraphicsitem + diff --git a/documentation/source/graphicsItems/infiniteline.rst b/documentation/source/graphicsItems/infiniteline.rst new file mode 100644 index 00000000..e95987bc --- /dev/null +++ b/documentation/source/graphicsItems/infiniteline.rst @@ -0,0 +1,8 @@ +InfiniteLine +============ + +.. autoclass:: pyqtgraph.InfiniteLine + :members: + + .. automethod:: pyqtgraph.InfiniteLine.__init__ + diff --git a/documentation/source/graphicsItems/labelitem.rst b/documentation/source/graphicsItems/labelitem.rst new file mode 100644 index 00000000..ca420d76 --- /dev/null +++ b/documentation/source/graphicsItems/labelitem.rst @@ -0,0 +1,8 @@ +LabelItem +========= + +.. autoclass:: pyqtgraph.LabelItem + :members: + + .. automethod:: pyqtgraph.LabelItem.__init__ + diff --git a/documentation/source/graphicsItems/linearregionitem.rst b/documentation/source/graphicsItems/linearregionitem.rst new file mode 100644 index 00000000..9bcb534c --- /dev/null +++ b/documentation/source/graphicsItems/linearregionitem.rst @@ -0,0 +1,8 @@ +LinearRegionItem +================ + +.. autoclass:: pyqtgraph.LinearRegionItem + :members: + + .. automethod:: pyqtgraph.LinearRegionItem.__init__ + diff --git a/documentation/source/graphicsItems/make b/documentation/source/graphicsItems/make new file mode 100644 index 00000000..2a990405 --- /dev/null +++ b/documentation/source/graphicsItems/make @@ -0,0 +1,37 @@ +files = """ArrowItem +AxisItem +ButtonItem +CurvePoint +GradientEditorItem +GradientLegend +GraphicsLayout +GraphicsObject +GraphicsWidget +GridItem +HistogramLUTItem +ImageItem +InfiniteLine +LabelItem +LinearRegionItem +PlotCurveItem +PlotDataItem +ROI +ScaleBar +ScatterPlotItem +UIGraphicsItem +ViewBox +VTickGroup""".split('\n') + +for f in files: + print f + fh = open(f.lower()+'.rst', 'w') + fh.write( +"""%s +%s + +.. autoclass:: pyqtgraph.%s + :members: + + .. automethod:: pyqtgraph.%s.__init__ + +""" % (f, '='*len(f), f, f)) diff --git a/documentation/source/graphicsItems/plotcurveitem.rst b/documentation/source/graphicsItems/plotcurveitem.rst new file mode 100644 index 00000000..f0b2171d --- /dev/null +++ b/documentation/source/graphicsItems/plotcurveitem.rst @@ -0,0 +1,8 @@ +PlotCurveItem +============= + +.. autoclass:: pyqtgraph.PlotCurveItem + :members: + + .. automethod:: pyqtgraph.PlotCurveItem.__init__ + diff --git a/documentation/source/graphicsItems/plotdataitem.rst b/documentation/source/graphicsItems/plotdataitem.rst new file mode 100644 index 00000000..275084e9 --- /dev/null +++ b/documentation/source/graphicsItems/plotdataitem.rst @@ -0,0 +1,8 @@ +PlotDataItem +============ + +.. autoclass:: pyqtgraph.PlotDataItem + :members: + + .. automethod:: pyqtgraph.PlotDataItem.__init__ + diff --git a/documentation/source/graphicsItems/plotitem.rst b/documentation/source/graphicsItems/plotitem.rst new file mode 100644 index 00000000..cbf5f9f4 --- /dev/null +++ b/documentation/source/graphicsItems/plotitem.rst @@ -0,0 +1,7 @@ +PlotItem +======== + +.. autoclass:: pyqtgraph.PlotItem + :members: + + .. automethod:: pyqtgraph.PlotItem.__init__ diff --git a/documentation/source/graphicsItems/roi.rst b/documentation/source/graphicsItems/roi.rst new file mode 100644 index 00000000..22945ade --- /dev/null +++ b/documentation/source/graphicsItems/roi.rst @@ -0,0 +1,8 @@ +ROI +=== + +.. autoclass:: pyqtgraph.ROI + :members: + + .. automethod:: pyqtgraph.ROI.__init__ + diff --git a/documentation/source/graphicsItems/scalebar.rst b/documentation/source/graphicsItems/scalebar.rst new file mode 100644 index 00000000..2ab33967 --- /dev/null +++ b/documentation/source/graphicsItems/scalebar.rst @@ -0,0 +1,8 @@ +ScaleBar +======== + +.. autoclass:: pyqtgraph.ScaleBar + :members: + + .. automethod:: pyqtgraph.ScaleBar.__init__ + diff --git a/documentation/source/graphicsItems/scatterplotitem.rst b/documentation/source/graphicsItems/scatterplotitem.rst new file mode 100644 index 00000000..be2c874b --- /dev/null +++ b/documentation/source/graphicsItems/scatterplotitem.rst @@ -0,0 +1,8 @@ +ScatterPlotItem +=============== + +.. autoclass:: pyqtgraph.ScatterPlotItem + :members: + + .. automethod:: pyqtgraph.ScatterPlotItem.__init__ + diff --git a/documentation/source/graphicsItems/uigraphicsitem.rst b/documentation/source/graphicsItems/uigraphicsitem.rst new file mode 100644 index 00000000..4f0b9933 --- /dev/null +++ b/documentation/source/graphicsItems/uigraphicsitem.rst @@ -0,0 +1,8 @@ +UIGraphicsItem +============== + +.. autoclass:: pyqtgraph.UIGraphicsItem + :members: + + .. automethod:: pyqtgraph.UIGraphicsItem.__init__ + diff --git a/documentation/source/graphicsItems/viewbox.rst b/documentation/source/graphicsItems/viewbox.rst new file mode 100644 index 00000000..3593d295 --- /dev/null +++ b/documentation/source/graphicsItems/viewbox.rst @@ -0,0 +1,8 @@ +ViewBox +======= + +.. autoclass:: pyqtgraph.ViewBox + :members: + + .. automethod:: pyqtgraph.ViewBox.__init__ + diff --git a/documentation/source/graphicsItems/vtickgroup.rst b/documentation/source/graphicsItems/vtickgroup.rst new file mode 100644 index 00000000..342705de --- /dev/null +++ b/documentation/source/graphicsItems/vtickgroup.rst @@ -0,0 +1,8 @@ +VTickGroup +========== + +.. autoclass:: pyqtgraph.VTickGroup + :members: + + .. automethod:: pyqtgraph.VTickGroup.__init__ + diff --git a/documentation/source/graphicswindow.rst b/documentation/source/graphicswindow.rst new file mode 100644 index 00000000..3d5641c3 --- /dev/null +++ b/documentation/source/graphicswindow.rst @@ -0,0 +1,8 @@ +Basic display widgets +===================== + + - GraphicsWindow + - GraphicsView + - GraphicsLayoutItem + - ViewBox + diff --git a/documentation/source/how_to_use.rst b/documentation/source/how_to_use.rst new file mode 100644 index 00000000..74e901d0 --- /dev/null +++ b/documentation/source/how_to_use.rst @@ -0,0 +1,47 @@ +How to use pyqtgraph +==================== + +There are a few suggested ways to use pyqtgraph: + +* From the interactive shell (python -i, ipython, etc) +* Displaying pop-up windows from an application +* Embedding widgets in a PyQt application + + + +Command-line use +---------------- + +Pyqtgraph makes it very easy to visualize data from the command line. Observe:: + + import pyqtgraph as pg + pg.plot(data) # data can be a list of values or a numpy array + +The example above would open a window displaying a line plot of the data given. I don't think it could reasonably be any simpler than that. The call to pg.plot returns a handle to the plot widget that is created, allowing more data to be added to the same window. + +Further examples:: + + pw = pg.plot(xVals, yVals, pen='r') # plot x vs y in red + pw.plot(xVals, yVals2, pen='b') + + win = pg.GraphicsWindow() # Automatically generates grids with multiple items + win.addPlot(data1, row=0, col=0) + win.addPlot(data2, row=0, col=1) + win.addPlot(data3, row=1, col=0, colspan=2) + + pg.show(imageData) # imageData must be a numpy array with 2 to 4 dimensions + +We're only scratching the surface here--these functions accept many different data formats and options for customizing the appearance of your data. + + +Displaying windows from within an application +--------------------------------------------- + +While I consider this approach somewhat lazy, it is often the case that 'lazy' is indistinguishable from 'highly efficient'. The approach here is simply to use the very same functions that would be used on the command line, but from within an existing application. I often use this when I simply want to get a immediate feedback about the state of data in my application without taking the time to build a user interface for it. + + +Embedding widgets inside PyQt applications +------------------------------------------ + +For the serious application developer, all of the functionality in pyqtgraph is available via widgets that can be embedded just like any other Qt widgets. Most importantly, see: PlotWidget, ImageView, GraphicsView, GraphicsLayoutWidget. Pyqtgraph's widgets can be included in Designer's ui files via the "Promote To..." functionality. + diff --git a/documentation/source/images.rst b/documentation/source/images.rst new file mode 100644 index 00000000..461a9cb7 --- /dev/null +++ b/documentation/source/images.rst @@ -0,0 +1,26 @@ +Displaying images and video +=========================== + +Pyqtgraph displays 2D numpy arrays as images and provides tools for determining how to translate between the numpy data type and RGB values on the screen. If you want to display data from common image and video file formats, you will need to load the data first using another library (PIL works well for images and built-in numpy conversion). + +The easiest way to display 2D or 3D data is using the :func:`pyqtgraph.image` function:: + + import pyqtgraph as pg + pg.image(imageData) + +This function will accept any floating-point or integer data types and displays a single :class:`~pyqtgraph.ImageView` widget containing your data. This widget includes controls for determining how the image data will be converted to 32-bit RGBa values. Conversion happens in two steps (both are optional): + +1. Scale and offset the data (by selecting the dark/light levels on the displayed histogram) +2. Convert the data to color using a lookup table (determined by the colors shown in the gradient editor) + +If the data is 3D (time, x, y), then a time axis will be shown with a slider that can set the currently displayed frame. (if the axes in your data are ordered differently, use numpy.transpose to rearrange them) + +There are a few other methods for displaying images as well: + +* The :class:`~pyqtgraph.ImageView` class can also be instantiated directly and embedded in Qt applications. +* Instances of :class:`~pyqtgraph.ImageItem` can be used inside a GraphicsView. +* For higher performance, use :class:`~pyqtgraph.RawImageWidget`. + +Any of these classes are acceptable for displaying video by calling setImage() to display a new frame. To increase performance, the image processing system uses scipy.weave to produce compiled libraries. If your computer has a compiler available, weave will automatically attempt to build the libraries it needs on demand. If this fails, then the slower pure-python methods will be used instead. + +For more information, see the classes listed above and the 'VideoSpeedTest', 'ImageItem', 'ImageView', and 'HistogramLUT' :ref:`examples`. \ No newline at end of file diff --git a/documentation/source/images/plottingClasses.png b/documentation/source/images/plottingClasses.png new file mode 100644 index 0000000000000000000000000000000000000000..7c8325a5bbe0809aa3b40ac3a8f76972b4d802a9 GIT binary patch literal 68667 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 literal 0 HcmV?d00001 diff --git a/documentation/source/images/plottingClasses.svg b/documentation/source/images/plottingClasses.svg new file mode 100644 index 00000000..393d16d7 --- /dev/null +++ b/documentation/source/images/plottingClasses.svg @@ -0,0 +1,580 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="648.03278" + height="268.51233" + id="svg2" + version="1.1" + inkscape:version="0.48.1 r9760" + sodipodi:docname="plottingClasses.svg" + inkscape:export-filename="/home/luke/work/manis_lab/code/pyqtgraph/documentation/source/images/plottingClasses.png" + inkscape:export-xdpi="124.99" + inkscape:export-ydpi="124.99"> + <defs + id="defs4"> + <filter + inkscape:collect="always" + id="filter4029" + color-interpolation-filters="sRGB"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="3.3898323" + id="feGaussianBlur4031" /> + </filter> + <filter + color-interpolation-filters="sRGB" + inkscape:collect="always" + id="filter4029-0"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="3.3898323" + id="feGaussianBlur4031-5" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="383.64946" + inkscape:cy="21.059243" + inkscape:document-units="px" + inkscape:current-layer="g3978" + showgrid="false" + inkscape:window-width="1400" + inkscape:window-height="1030" + inkscape:window-x="-3" + inkscape:window-y="-3" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-9.6542608,-141.60496)"> + <g + id="g3941" + transform="matrix(0.62675963,0,0,0.62675963,-43.966218,98.521874)"> + <g + style="stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none" + transform="translate(0,-258)" + id="g3765"> + <rect + y="336.48514" + x="95.275459" + height="407.9704" + width="495.98489" + id="rect4003" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:3.19101596;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter4029)" /> + <rect + style="fill:#cecae4;fill-opacity:1;stroke:#000000;stroke-width:3.19101596;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect2985" + width="495.98489" + height="407.9704" + x="88.893425" + y="330.10312" /> + <text + xml:space="preserve" + style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + x="91.923874" + y="348.28586" + id="text3755" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3757" + x="91.923874" + y="348.28586" + style="font-size:20px">PlotWidget(GraphicsView)</tspan></text> + </g> + <g + transform="translate(3.0304576,-251.93908)" + id="g3770"> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3759" + width="450.52805" + height="361.871" + x="111.11678" + y="355.35693" /> + <text + sodipodi:linespacing="125%" + id="text3761" + y="376.57013" + x="124.24876" + style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + xml:space="preserve"><tspan + style="font-size:18px" + y="376.57013" + x="124.24876" + id="tspan3763" + sodipodi:role="line">PlotItem(GraphicsItem)</tspan></text> + </g> + <g + id="g3777" + transform="translate(70.710678,-158.4213)"> + <rect + y="355.35693" + x="111.11678" + height="226.27419" + width="314.15744" + id="rect3779" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <text + xml:space="preserve" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + x="125.25891" + y="378.59042" + id="text3781" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3783" + x="125.25891" + y="378.59042" + style="font-size:16px">ViewBox(GraphicsItem)</tspan></text> + <path + transform="translate(-39.395949,-44.446712)" + inkscape:connector-curvature="0" + id="path3789" + d="m 171.72593,536.17423 29.29443,-30.30457 28.28427,54.54823 22.22335,-34.34518 23.23351,24.24366 31.31473,-10.10153 27.27412,-34.34518 23.23351,-40.40611 23.23351,31.31473 36.36549,1.01016 19.1929,40.4061" + style="fill:none;stroke:#000000;stroke-width:1.59550798px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + <g + id="g3801" + transform="matrix(0,-1,1,0,-207.00664,541.37735)"> + <rect + y="355.35693" + x="119.19801" + height="29.294403" + width="225.26402" + id="rect3803" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <text + xml:space="preserve" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + x="150.59395" + y="374.5498" + id="text3805" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3807" + x="150.59395" + y="374.5498" + style="font-size:16px">AxisItem(GraphicsItem)</tspan></text> + </g> + <g + id="g3809" + transform="translate(104.06602,-189.05576)"> + <rect + y="355.35693" + x="77.781746" + height="26.85568" + width="313.26996" + id="rect3811" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <text + xml:space="preserve" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + x="154.59395" + y="374.5498" + id="text3813" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3815" + x="154.59395" + y="374.5498" + style="font-size:16px">AxisItem(GraphicsItem)</tspan></text> + </g> + <g + transform="translate(104.06602,72.94424)" + id="g3817"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3819" + width="313.26996" + height="26.85568" + x="77.781746" + y="355.35693" /> + <text + sodipodi:linespacing="125%" + id="text3821" + y="374.5498" + x="154.59395" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + xml:space="preserve"><tspan + style="font-size:16px" + y="374.5498" + x="154.59395" + id="tspan3823" + sodipodi:role="line">AxisItem(GraphicsItem)</tspan></text> + </g> + <g + transform="matrix(0,-1,1,0,144.99336,541.37735)" + id="g3825"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3827" + width="225.26402" + height="29.294403" + x="119.19801" + y="355.35693" /> + <text + sodipodi:linespacing="125%" + id="text3829" + y="374.5498" + x="150.59395" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + xml:space="preserve"><tspan + style="font-size:16px" + y="374.5498" + x="150.59395" + id="tspan3831" + sodipodi:role="line">AxisItem(GraphicsItem)</tspan></text> + </g> + <g + transform="translate(104.06602,-219.05576)" + id="g3833"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3835" + width="313.26996" + height="26.85568" + x="77.781746" + y="355.35693" /> + <text + sodipodi:linespacing="125%" + id="text3837" + y="374.5498" + x="118.59395" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + xml:space="preserve"><tspan + style="font-size:16px" + y="374.5498" + x="118.59395" + id="tspan3839" + sodipodi:role="line">Title - LabelItem(GraphicsItem)</tspan></text> + </g> + <text + sodipodi:linespacing="125%" + id="text3879" + y="363.02704" + x="301.12698" + style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + xml:space="preserve"><tspan + y="363.02704" + x="301.12698" + id="tspan3881" + sodipodi:role="line">PlotDataItem(GraphicsItem)</tspan></text> + </g> + <g + id="g3978" + transform="matrix(0.62675963,0,0,0.62675963,280.29275,-190.12921)"> + <rect + y="540.00317" + x="96.42733" + height="407.9704" + width="495.98489" + id="rect4003-9" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:3.19101596;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter4029-0)" /> + <g + transform="translate(0,202)" + id="g3883"> + <rect + y="330.10312" + x="88.893425" + height="407.9704" + width="495.98489" + id="rect3885" + style="fill:#cecae4;fill-opacity:1;stroke:#000000;stroke-width:3.19101596;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <text + sodipodi:linespacing="125%" + id="text3887" + y="348.28586" + x="91.923874" + style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + xml:space="preserve"><tspan + style="font-size:20px" + y="348.28586" + x="91.923874" + id="tspan3889" + sodipodi:role="line">GraphicsLayoutWidget(GraphicsView)</tspan></text> + </g> + <g + id="g3891" + transform="translate(3.0304576,214.06092)"> + <rect + y="355.35693" + x="111.11678" + height="361.871" + width="450.52805" + id="rect3893" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <text + xml:space="preserve" + style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + x="124.24876" + y="376.57013" + id="text3895" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3897" + x="124.24876" + y="376.57013" + style="font-size:18px">GraphicsLayoutItem(GraphicsItem)</tspan></text> + </g> + <g + transform="translate(17.172593,259.49748)" + id="g3909"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3911" + width="199.00005" + height="144.45183" + x="111.11678" + y="355.35693" /> + <text + sodipodi:linespacing="125%" + id="text3913" + y="378.59042" + x="125.25891" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + xml:space="preserve"><tspan + style="font-size:16px" + y="378.59042" + x="125.25891" + id="tspan3915" + sodipodi:role="line">PlotItem</tspan></text> + <g + transform="matrix(0.73495341,0,0,0.52372233,28.190975,147.93852)" + id="g4155" + style="opacity:0.21238936"> + <g + transform="matrix(0.62675963,0,0,0.62675963,80.420081,271.29053)" + id="g3777-4"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:4.10315084;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3779-6" + width="314.15744" + height="226.27419" + x="111.11678" + y="355.35693" /> + <path + style="fill:none;stroke:#000000;stroke-width:4.10315084px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 171.72593,536.17423 29.29443,-30.30457 28.28427,54.54823 22.22335,-34.34518 23.23351,24.24366 31.31473,-10.10153 27.27412,-34.34518 23.23351,-40.40611 23.23351,31.31473 36.36549,1.01016 19.1929,40.4061" + id="path3789-2" + inkscape:connector-curvature="0" + transform="translate(-39.395949,-44.446712)" /> + </g> + <g + transform="matrix(0,-0.62675963,0.62675963,0,-93.641919,709.89607)" + id="g3801-4"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:4.10315084;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3803-7" + width="225.26402" + height="29.294403" + x="119.19801" + y="355.35693" /> + </g> + <g + transform="matrix(0.62675963,0,0,0.62675963,101.32586,252.09009)" + id="g3809-4"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:4.10315084;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3811-8" + width="313.26996" + height="26.85568" + x="77.781746" + y="355.35693" /> + </g> + <g + id="g3817-8" + transform="matrix(0.62675963,0,0,0.62675963,101.32586,416.30111)"> + <rect + y="355.35693" + x="77.781746" + height="26.85568" + width="313.26996" + id="rect3819-9" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:4.10315084;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + </g> + <g + id="g3825-8" + transform="matrix(0,-0.62675963,0.62675963,0,126.97747,709.89607)"> + <rect + y="355.35693" + x="119.19801" + height="29.294403" + width="225.26402" + id="rect3827-0" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:4.10315084;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + </g> + <g + id="g3833-0" + transform="matrix(0.62675963,0,0,0.62675963,101.32586,233.2873)"> + <rect + y="355.35693" + x="77.781746" + height="26.85568" + width="313.26996" + id="rect3835-5" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:4.10315084;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + </g> + </g> + </g> + <g + id="g3919" + transform="translate(237.17259,259.49748)"> + <rect + y="355.35693" + x="111.11678" + height="144.45183" + width="199.00005" + id="rect3925" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <text + xml:space="preserve" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + x="125.25891" + y="378.59042" + id="text3921" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3923" + x="125.25891" + y="378.59042" + style="font-size:16px">ViewBox</tspan></text> + <path + inkscape:connector-curvature="0" + id="path3927" + d="m 132.32998,422.03916 15.82573,16.2779 15.28001,-29.30022 12.00571,18.44828 12.55144,-13.02232 16.91715,5.42597 14.7343,18.44829 12.55143,21.70388 12.55144,-16.8205 19.64573,-0.54261 10.36857,-21.70387" + style="fill:none;stroke:#000000;stroke-width:1.59550798px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + <g + id="g3931" + transform="translate(17.172593,417.49748)"> + <rect + y="355.35693" + x="111.11678" + height="144.45183" + width="419.21329" + id="rect3937" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:1.59550798;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <text + xml:space="preserve" + style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + x="125.25891" + y="378.59042" + id="text3933" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3935" + x="125.25891" + y="378.59042" + style="font-size:16px">PlotItem</tspan></text> + <g + transform="matrix(1.675408,0,0,0.51883759,-93.913147,152.77473)" + id="g4155-5" + style="opacity:0.18141593"> + <g + transform="matrix(0.62675963,0,0,0.62675963,80.420081,271.29053)" + id="g3777-4-0"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:2.73037291;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3779-6-6" + width="314.15744" + height="226.27419" + x="111.11678" + y="355.35693" /> + <path + style="fill:none;stroke:#000000;stroke-width:2.73037291px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 171.72593,536.17423 29.29443,-30.30457 28.28427,54.54823 22.22335,-34.34518 23.23351,24.24366 31.31473,-10.10153 27.27412,-34.34518 23.23351,-40.40611 23.23351,31.31473 36.36549,1.01016 19.1929,40.4061" + id="path3789-2-4" + inkscape:connector-curvature="0" + transform="translate(-39.395949,-44.446712)" /> + </g> + <g + transform="matrix(0,-0.62675963,0.26439647,0,45.741554,709.89607)" + id="g3801-4-6"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:4.203825;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3803-7-2" + width="225.26402" + height="29.294403" + x="119.19801" + y="355.35693" /> + </g> + <g + transform="matrix(0.62675963,0,0,0.62675963,101.32586,252.09009)" + id="g3809-4-5"> + <rect + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:2.73037291;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3811-8-8" + width="313.26996" + height="26.85568" + x="77.781746" + y="355.35693" /> + </g> + <g + id="g3817-8-6" + transform="matrix(0.62675963,0,0,0.62675963,101.32586,416.30111)"> + <rect + y="355.35693" + x="77.781746" + height="26.85568" + width="313.26996" + id="rect3819-9-2" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:2.73037291;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + </g> + <g + id="g3825-8-8" + transform="matrix(0,-0.62675963,0.24174877,0,263.79375,709.89607)"> + <rect + y="355.35693" + x="119.19801" + height="29.294403" + width="225.26402" + id="rect3827-0-4" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:4.39633036;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + </g> + <g + id="g3833-0-7" + transform="matrix(0.62675963,0,0,0.62675963,101.32586,233.2873)"> + <rect + y="355.35693" + x="77.781746" + height="26.85568" + width="313.26996" + id="rect3835-5-2" + style="fill:#d7d7d7;fill-opacity:1;stroke:#000000;stroke-width:2.73037291;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + </g> + </g> + </g> + </g> + </g> +</svg> diff --git a/documentation/source/index.rst b/documentation/source/index.rst new file mode 100644 index 00000000..aa6753ef --- /dev/null +++ b/documentation/source/index.rst @@ -0,0 +1,32 @@ +.. pyqtgraph documentation master file, created by + sphinx-quickstart on Fri Nov 18 19:33:12 2011. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to the documentation for pyqtgraph 1.8 +============================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + introduction + how_to_use + plotting + images + style + region_of_interest + graphicswindow + parametertree + internals + apireference + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/documentation/source/internals.rst b/documentation/source/internals.rst new file mode 100644 index 00000000..3f25376d --- /dev/null +++ b/documentation/source/internals.rst @@ -0,0 +1,9 @@ +Internals - Extensions to Qt's GraphicsView +================================ + +* GraphicsView +* GraphicsScene (mouse events) +* GraphicsObject +* GraphicsWidget +* ViewBox + diff --git a/documentation/source/introduction.rst b/documentation/source/introduction.rst new file mode 100644 index 00000000..c5c1dfab --- /dev/null +++ b/documentation/source/introduction.rst @@ -0,0 +1,51 @@ +Introduction +============ + + + +What is pyqtgraph? +------------------ + +Pyqtgraph is a graphics and user interface library for Python that provides functionality commonly required in engineering and science applications. Its primary goals are 1) to provide fast, interactive graphics for displaying data (plots, video, etc.) and 2) to provide tools to aid in rapid application development (for example, property trees such as used in Qt Designer). + +Pyqtgraph makes heavy use of the Qt GUI platform (via PyQt or PySide) for its high-performance graphics and numpy for heavy number crunching. In particular, pyqtgraph uses Qt's GraphicsView framework which is a highly capable graphics system on its own; we bring optimized and simplified primitives to this framework to allow data visualization with minimal effort. + +It is known to run on Linux, Windows, and OSX + + +What can it do? +--------------- + +Amongst the core features of pyqtgraph are: + +* Basic data visualization primitives: Images, line and scatter plots +* Fast enough for realtime update of video/plot data +* Interactive scaling/panning, averaging, FFTs, SVG/PNG export +* Widgets for marking/selecting plot regions +* Widgets for marking/selecting image region-of-interest and automatically slicing multi-dimensional image data +* Framework for building customized image region-of-interest widgets +* Docking system that replaces/complements Qt's dock system to allow more complex (and more predictable) docking arrangements +* ParameterTree widget for rapid prototyping of dynamic interfaces (Similar to the property trees in Qt Designer and many other applications) + + +.. _examples: + +Examples +-------- + +Pyqtgraph includes an extensive set of examples that can be accessed by running:: + + import pyqtgraph.examples + pyqtgraph.examples.run() + +This will start a launcher with a list of available examples. Select an item from the list to view its source code and double-click an item to run the example. + + +How does it compare to... +------------------------- + +* matplotlib: For plotting and making publication-quality graphics, matplotlib is far more mature than pyqtgraph. However, matplotlib is also much slower and not suitable for applications requiring realtime update of plots/video or rapid interactivity. It also does not provide any of the GUI tools and image interaction/slicing functionality in pyqtgraph. + +* pyqwt5: pyqwt is generally more mature than pyqtgraph for plotting and is about as fast. The major differences are 1) pyqtgraph is written in pure python, so it is somewhat more portable than pyqwt, which often lags behind pyqt in development (and can be a pain to install on some platforms) and 2) like matplotlib, pyqwt does not provide any of the GUI tools and image interaction/slicing functionality in pyqtgraph. + +(My experience with these libraries is somewhat outdated; please correct me if I am wrong here) diff --git a/documentation/source/parametertree.rst b/documentation/source/parametertree.rst new file mode 100644 index 00000000..de699492 --- /dev/null +++ b/documentation/source/parametertree.rst @@ -0,0 +1,7 @@ +Rapid GUI prototyping +===================== + + - parametertree + - dockarea + - flowchart + - canvas diff --git a/documentation/source/plotting.rst b/documentation/source/plotting.rst new file mode 100644 index 00000000..ee9ed6dc --- /dev/null +++ b/documentation/source/plotting.rst @@ -0,0 +1,73 @@ +Plotting in pyqtgraph +===================== + +There are a few basic ways to plot data in pyqtgraph: + +================================================================ ================================================== +:func:`pyqtgraph.plot` Create a new plot window showing your data +:func:`PlotWidget.plot() <pyqtgraph.PlotWidget.plot>` Add a new set of data to an existing plot widget +:func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>` Add a new set of data to an existing plot widget +:func:`GraphicsWindow.addPlot() <pyqtgraph.GraphicsWindow.plot>` Add a new plot to a grid of plots +================================================================ ================================================== + +All of these will accept the same basic arguments which control how the plot data is interpreted and displayed: + +* x - Optional X data; if not specified, then a range of integers will be generated automatically. +* y - Y data. +* pen - The pen to use when drawing plot lines, or None to disable lines. +* symbol - A string describing the shape of symbols to use for each point. Optionally, this may also be a sequence of strings with a different symbol for each point. +* symbolPen - The pen (or sequence of pens) to use when drawing the symbol outline. +* symbolBrush - The brush (or sequence of brushes) to use when filling the symbol. +* fillLevel - Fills the area under the plot curve to this Y-value. +* brush - The brush to use when filling under the curve. + +See the 'plotting' :ref:`example <examples>` for a demonstration of these arguments. + +All of the above functions also return handles to the objects that are created, allowing the plots and data to be further modified. + +Organization of Plotting Classes +-------------------------------- + +There are several classes invloved in displaying plot data. Most of these classes are instantiated automatically, but it is useful to understand how they are organized and relate to each other. Pyqtgraph is based heavily on Qt's GraphicsView framework--if you are not already familiar with this, it's worth reading about (but not essential). Most importantly: 1) Qt GUIs are composed of QWidgets, 2) A special widget called QGraphicsView is used for displaying complex graphics, and 3) QGraphicsItems define the objects that are displayed within a QGraphicsView. + +* Data Classes (all subclasses of QGraphicsItem) + * PlotCurveItem - Displays a plot line given x,y data + * ScatterPlotItem - Displays points given x,y data + * :class:`PlotDataItem <pyqtgraph.graphicsItems.PlotDataItem.PlotDataItem>` - Combines PlotCurveItem and ScatterPlotItem. The plotting functions discussed above create objects of this type. +* Container Classes (subclasses of QGraphicsItem; contain other QGraphicsItem objects and must be viewed from within a GraphicsView) + * PlotItem - Contains a ViewBox for displaying data as well as AxisItems and labels for displaying the axes and title. This is a QGraphicsItem subclass and thus may only be used from within a GraphicsView + * GraphicsLayoutItem - QGraphicsItem subclass which displays a grid of items. This is used to display multiple PlotItems together. + * ViewBox - A QGraphicsItem subclass for displaying data. The user may scale/pan the contents of a ViewBox using the mouse. Typically all PlotData/PlotCurve/ScatterPlotItems are displayed from within a ViewBox. + * AxisItem - Displays axis values, ticks, and labels. Most commonly used with PlotItem. +* Container Classes (subclasses of QWidget; may be embedded in PyQt GUIs) + * PlotWidget - A subclass of GraphicsView with a single PlotItem displayed. Most of the methods provided by PlotItem are also available through PlotWidget. + * GraphicsLayoutWidget - QWidget subclass displaying a single GraphicsLayoutItem. Most of the methods provided by GraphicsLayoutItem are also available through GraphicsLayoutWidget. + +.. image:: images/plottingClasses.png + + +Examples +-------- + +See the 'plotting' and 'PlotWidget' :ref:`examples included with pyqtgraph <examples>` for more information. + +Show x,y data as scatter plot:: + + import pyqtgraph as pg + import numpy as np + x = np.random.normal(size=1000) + y = np.random.normal(size=1000) + pg.plot(x, y, pen=None, symbol='o') ## setting pen=None disables line drawing + +Create/show a plot widget, display three data curves:: + + import pyqtgraph as pg + import numpy as np + x = np.arange(1000) + y = np.random.normal(size=(3, 1000)) + plotWidget = pg.plot(title="Three plot curves") + for i in range(3): + plotWidget.plot(x, y[i], pen=(i,3)) ## setting pen=(i,3) automaticaly creates three different-colored pens + + + diff --git a/documentation/source/region_of_interest.rst b/documentation/source/region_of_interest.rst new file mode 100644 index 00000000..24799cb7 --- /dev/null +++ b/documentation/source/region_of_interest.rst @@ -0,0 +1,19 @@ +Region-of-interest controls +=========================== + +Slicing Multidimensional Data +----------------------------- + +Linear Selection and Marking +---------------------------- + +2D Selection and Marking +------------------------ + + + + +- translate / rotate / scale +- highly configurable control handles +- automated data slicing +- linearregion, infiniteline diff --git a/documentation/source/style.rst b/documentation/source/style.rst new file mode 100644 index 00000000..fc172420 --- /dev/null +++ b/documentation/source/style.rst @@ -0,0 +1,17 @@ +Line, Fill, and Color +===================== + +Many functions and methods in pyqtgraph accept arguments specifying the line style (pen), fill style (brush), or color. + +For these function arguments, the following values may be used: + +* single-character string representing color (b, g, r, c, m, y, k, w) +* (r, g, b) or (r, g, b, a) tuple +* single greyscale value (0.0 - 1.0) +* (index, maximum) tuple for automatically iterating through colors (see functions.intColor) +* QColor +* QPen / QBrush where appropriate + +Notably, more complex pens and brushes can be easily built using the mkPen() / mkBrush() functions or with Qt's QPen and QBrush classes. + +Colors can also be built using mkColor(), intColor(), hsvColor(), or Qt's QColor class diff --git a/documentation/source/widgets/checktable.rst b/documentation/source/widgets/checktable.rst new file mode 100644 index 00000000..5301a4e9 --- /dev/null +++ b/documentation/source/widgets/checktable.rst @@ -0,0 +1,8 @@ +CheckTable +========== + +.. autoclass:: pyqtgraph.CheckTable + :members: + + .. automethod:: pyqtgraph.CheckTable.__init__ + diff --git a/documentation/source/widgets/colorbutton.rst b/documentation/source/widgets/colorbutton.rst new file mode 100644 index 00000000..690239d8 --- /dev/null +++ b/documentation/source/widgets/colorbutton.rst @@ -0,0 +1,8 @@ +ColorButton +=========== + +.. autoclass:: pyqtgraph.ColorButton + :members: + + .. automethod:: pyqtgraph.ColorButton.__init__ + diff --git a/documentation/source/widgets/datatreewidget.rst b/documentation/source/widgets/datatreewidget.rst new file mode 100644 index 00000000..f6bbdbaf --- /dev/null +++ b/documentation/source/widgets/datatreewidget.rst @@ -0,0 +1,8 @@ +DataTreeWidget +============== + +.. autoclass:: pyqtgraph.DataTreeWidget + :members: + + .. automethod:: pyqtgraph.DataTreeWidget.__init__ + diff --git a/documentation/source/widgets/dockarea.rst b/documentation/source/widgets/dockarea.rst new file mode 100644 index 00000000..09a6acca --- /dev/null +++ b/documentation/source/widgets/dockarea.rst @@ -0,0 +1,5 @@ +dockarea module +=============== + +.. automodule:: pyqtgraph.dockarea + :members: diff --git a/documentation/source/widgets/filedialog.rst b/documentation/source/widgets/filedialog.rst new file mode 100644 index 00000000..bf2f9c07 --- /dev/null +++ b/documentation/source/widgets/filedialog.rst @@ -0,0 +1,8 @@ +FileDialog +========== + +.. autoclass:: pyqtgraph.FileDialog + :members: + + .. automethod:: pyqtgraph.FileDialog.__init__ + diff --git a/documentation/source/widgets/gradientwidget.rst b/documentation/source/widgets/gradientwidget.rst new file mode 100644 index 00000000..a2587503 --- /dev/null +++ b/documentation/source/widgets/gradientwidget.rst @@ -0,0 +1,8 @@ +GradientWidget +============== + +.. autoclass:: pyqtgraph.GradientWidget + :members: + + .. automethod:: pyqtgraph.GradientWidget.__init__ + diff --git a/documentation/source/widgets/graphicslayoutwidget.rst b/documentation/source/widgets/graphicslayoutwidget.rst new file mode 100644 index 00000000..5f885f07 --- /dev/null +++ b/documentation/source/widgets/graphicslayoutwidget.rst @@ -0,0 +1,8 @@ +GraphicsLayoutWidget +==================== + +.. autoclass:: pyqtgraph.GraphicsLayoutWidget + :members: + + .. automethod:: pyqtgraph.GraphicsLayoutWidget.__init__ + diff --git a/documentation/source/widgets/graphicsview.rst b/documentation/source/widgets/graphicsview.rst new file mode 100644 index 00000000..ac7ae3bf --- /dev/null +++ b/documentation/source/widgets/graphicsview.rst @@ -0,0 +1,8 @@ +GraphicsView +============ + +.. autoclass:: pyqtgraph.GraphicsView + :members: + + .. automethod:: pyqtgraph.GraphicsView.__init__ + diff --git a/documentation/source/widgets/histogramlutwidget.rst b/documentation/source/widgets/histogramlutwidget.rst new file mode 100644 index 00000000..9d8f3b20 --- /dev/null +++ b/documentation/source/widgets/histogramlutwidget.rst @@ -0,0 +1,8 @@ +HistogramLUTWidget +================== + +.. autoclass:: pyqtgraph.HistogramLUTWidget + :members: + + .. automethod:: pyqtgraph.HistogramLUTWidget.__init__ + diff --git a/documentation/source/widgets/imageview.rst b/documentation/source/widgets/imageview.rst new file mode 100644 index 00000000..1eadabbf --- /dev/null +++ b/documentation/source/widgets/imageview.rst @@ -0,0 +1,8 @@ +ImageView +========= + +.. autoclass:: pyqtgraph.ImageView + :members: + + .. automethod:: pyqtgraph.ImageView.__init__ + diff --git a/documentation/source/widgets/index.rst b/documentation/source/widgets/index.rst new file mode 100644 index 00000000..1beaf1ec --- /dev/null +++ b/documentation/source/widgets/index.rst @@ -0,0 +1,31 @@ +Pyqtgraph's Widgets +=================== + +Pyqtgraph provides several QWidget subclasses which are useful for building user interfaces. These widgets can generally be used in any Qt application and provide functionality that is frequently useful in science and engineering applications. + +Contents: + +.. toctree:: + :maxdepth: 2 + + plotwidget + imageview + dockarea + spinbox + gradientwidget + histogramlutwidget + parametertree + graphicsview + rawimagewidget + datatreewidget + tablewidget + treewidget + checktable + colorbutton + graphicslayoutwidget + progressdialog + filedialog + joystickbutton + multiplotwidget + verticallabel + diff --git a/documentation/source/widgets/joystickbutton.rst b/documentation/source/widgets/joystickbutton.rst new file mode 100644 index 00000000..4d21e16f --- /dev/null +++ b/documentation/source/widgets/joystickbutton.rst @@ -0,0 +1,8 @@ +JoystickButton +============== + +.. autoclass:: pyqtgraph.JoystickButton + :members: + + .. automethod:: pyqtgraph.JoystickButton.__init__ + diff --git a/documentation/source/widgets/make b/documentation/source/widgets/make new file mode 100644 index 00000000..40d0e126 --- /dev/null +++ b/documentation/source/widgets/make @@ -0,0 +1,31 @@ +files = """CheckTable +ColorButton +DataTreeWidget +FileDialog +GradientWidget +GraphicsLayoutWidget +GraphicsView +HistogramLUTWidget +JoystickButton +MultiPlotWidget +PlotWidget +ProgressDialog +RawImageWidget +SpinBox +TableWidget +TreeWidget +VerticalLabel""".split('\n') + +for f in files: + print f + fh = open(f.lower()+'.rst', 'w') + fh.write( +"""%s +%s + +.. autoclass:: pyqtgraph.%s + :members: + + .. automethod:: pyqtgraph.%s.__init__ + +""" % (f, '='*len(f), f, f)) diff --git a/documentation/source/widgets/multiplotwidget.rst b/documentation/source/widgets/multiplotwidget.rst new file mode 100644 index 00000000..46986db0 --- /dev/null +++ b/documentation/source/widgets/multiplotwidget.rst @@ -0,0 +1,8 @@ +MultiPlotWidget +=============== + +.. autoclass:: pyqtgraph.MultiPlotWidget + :members: + + .. automethod:: pyqtgraph.MultiPlotWidget.__init__ + diff --git a/documentation/source/widgets/parametertree.rst b/documentation/source/widgets/parametertree.rst new file mode 100644 index 00000000..565b930b --- /dev/null +++ b/documentation/source/widgets/parametertree.rst @@ -0,0 +1,5 @@ +parametertree module +==================== + +.. automodule:: pyqtgraph.parametertree + :members: diff --git a/documentation/source/widgets/plotwidget.rst b/documentation/source/widgets/plotwidget.rst new file mode 100644 index 00000000..cbded80d --- /dev/null +++ b/documentation/source/widgets/plotwidget.rst @@ -0,0 +1,8 @@ +PlotWidget +========== + +.. autoclass:: pyqtgraph.PlotWidget + :members: + + .. automethod:: pyqtgraph.PlotWidget.__init__ + diff --git a/documentation/source/widgets/progressdialog.rst b/documentation/source/widgets/progressdialog.rst new file mode 100644 index 00000000..fff04cb3 --- /dev/null +++ b/documentation/source/widgets/progressdialog.rst @@ -0,0 +1,8 @@ +ProgressDialog +============== + +.. autoclass:: pyqtgraph.ProgressDialog + :members: + + .. automethod:: pyqtgraph.ProgressDialog.__init__ + diff --git a/documentation/source/widgets/rawimagewidget.rst b/documentation/source/widgets/rawimagewidget.rst new file mode 100644 index 00000000..29fda791 --- /dev/null +++ b/documentation/source/widgets/rawimagewidget.rst @@ -0,0 +1,8 @@ +RawImageWidget +============== + +.. autoclass:: pyqtgraph.RawImageWidget + :members: + + .. automethod:: pyqtgraph.RawImageWidget.__init__ + diff --git a/documentation/source/widgets/spinbox.rst b/documentation/source/widgets/spinbox.rst new file mode 100644 index 00000000..33da1f4c --- /dev/null +++ b/documentation/source/widgets/spinbox.rst @@ -0,0 +1,8 @@ +SpinBox +======= + +.. autoclass:: pyqtgraph.SpinBox + :members: + + .. automethod:: pyqtgraph.SpinBox.__init__ + diff --git a/documentation/source/widgets/tablewidget.rst b/documentation/source/widgets/tablewidget.rst new file mode 100644 index 00000000..283b540b --- /dev/null +++ b/documentation/source/widgets/tablewidget.rst @@ -0,0 +1,8 @@ +TableWidget +=========== + +.. autoclass:: pyqtgraph.TableWidget + :members: + + .. automethod:: pyqtgraph.TableWidget.__init__ + diff --git a/documentation/source/widgets/treewidget.rst b/documentation/source/widgets/treewidget.rst new file mode 100644 index 00000000..00f9fa28 --- /dev/null +++ b/documentation/source/widgets/treewidget.rst @@ -0,0 +1,8 @@ +TreeWidget +========== + +.. autoclass:: pyqtgraph.TreeWidget + :members: + + .. automethod:: pyqtgraph.TreeWidget.__init__ + diff --git a/documentation/source/widgets/verticallabel.rst b/documentation/source/widgets/verticallabel.rst new file mode 100644 index 00000000..4f627437 --- /dev/null +++ b/documentation/source/widgets/verticallabel.rst @@ -0,0 +1,8 @@ +VerticalLabel +============= + +.. autoclass:: pyqtgraph.VerticalLabel + :members: + + .. automethod:: pyqtgraph.VerticalLabel.__init__ + diff --git a/examples/test_Arrow.py b/examples/Arrow.py similarity index 77% rename from examples/test_Arrow.py rename to examples/Arrow.py index 7d0c4aad..f9384008 100755 --- a/examples/test_Arrow.py +++ b/examples/Arrow.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -## Add path to library (just for examples; you do not need this) +## Add path to library (just for examples; you do not need this) import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) - import numpy as np from PyQt4 import QtGui, QtCore import pyqtgraph as pg @@ -15,7 +14,7 @@ mw.resize(800,800) p = pg.PlotWidget() mw.setCentralWidget(p) -c = p.plot(x=np.sin(np.linspace(0, 2*np.pi, 100)), y=np.cos(np.linspace(0, 2*np.pi, 100))) +c = p.plot(x=np.sin(np.linspace(0, 2*np.pi, 1000)), y=np.cos(np.linspace(0, 6*np.pi, 1000))) a = pg.CurveArrow(c) p.addItem(a) diff --git a/examples/CLIexample.py b/examples/CLIexample.py new file mode 100644 index 00000000..f2def91a --- /dev/null +++ b/examples/CLIexample.py @@ -0,0 +1,22 @@ +## Add path to library (just for examples; you do not need this) +import sys, os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + + +from PyQt4 import QtGui, QtCore +import numpy as np +import pyqtgraph as pg + +app = QtGui.QApplication([]) + + +data = np.random.normal(size=1000) +pg.plot(data, title="Simplest possible plotting example") + +data = np.random.normal(size=(500,500)) +pg.show(data, title="Simplest possible image example") + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/DataSlicing.py b/examples/DataSlicing.py new file mode 100644 index 00000000..32b9c584 --- /dev/null +++ b/examples/DataSlicing.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +## Add path to library (just for examples; you do not need this) +import sys, os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + + +import numpy as np +import scipy +from PyQt4 import QtCore, QtGui +import pyqtgraph as pg + +app = QtGui.QApplication([]) + +## Create window with two ImageView widgets +win = QtGui.QMainWindow() +win.resize(800,800) +cw = QtGui.QWidget() +win.setCentralWidget(cw) +l = QtGui.QGridLayout() +cw.setLayout(l) +imv1 = pg.ImageView() +imv2 = pg.ImageView() +l.addWidget(imv1, 0, 0) +l.addWidget(imv2, 1, 0) +win.show() + +roi = pg.LineSegmentROI([[10, 64], [120,64]], pen='r') +imv1.addItem(roi) + +x1 = np.linspace(-30, 10, 128)[:, np.newaxis, np.newaxis] +x2 = np.linspace(-20, 20, 128)[:, np.newaxis, np.newaxis] +y = np.linspace(-30, 10, 128)[np.newaxis, :, np.newaxis] +z = np.linspace(-20, 20, 128)[np.newaxis, np.newaxis, :] +d1 = np.sqrt(x1**2 + y**2 + z**2) +d2 = 2*np.sqrt(x1[::-1]**2 + y**2 + z**2) +d3 = 4*np.sqrt(x2**2 + y[:,::-1]**2 + z**2) +data = (np.sin(d1) / d1**2) + (np.sin(d2) / d2**2) + (np.sin(d3) / d3**2) + +def update(): + global data, imv1, imv2 + d2 = roi.getArrayRegion(data, imv1.imageItem, axes=(1,2)) + imv2.setImage(d2) + +roi.sigRegionChanged.connect(update) + + +## Display the data +imv1.setImage(data) +imv1.setHistogramRange(data.min(), data.max()) + +update() + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/test_draw.py b/examples/Draw.py old mode 100755 new mode 100644 similarity index 80% rename from examples/test_draw.py rename to examples/Draw.py index b40932ba..83736cc4 --- a/examples/test_draw.py +++ b/examples/Draw.py @@ -20,7 +20,6 @@ win.show() ## Allow mouse scale/pan view.enableMouse() - ## ..But lock the aspect ratio view.setAspectLocked(True) @@ -31,8 +30,14 @@ view.scene().addItem(img) ## Set initial view bounds view.setRange(QtCore.QRectF(0, 0, 200, 200)) -img.setDrawKernel(1) -img.setLevels(10,0) +## start drawing with 3x3 brush +kern = np.array([ + [0.0, 0.5, 0.0], + [0.5, 1.0, 0.5], + [0.0, 0.5, 0.0] +]) +img.setDrawKernel(kern, mask=kern, center=(1,1), mode='add') +img.setLevels([0, 10]) ## Start Qt event loop unless running in interactive mode. if sys.flags.interactive != 1: diff --git a/examples/Flowchart.py b/examples/Flowchart.py new file mode 100644 index 00000000..749fd3b6 --- /dev/null +++ b/examples/Flowchart.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +import sys, os + +## Make sure pyqtgraph is importable +p = os.path.dirname(os.path.abspath(__file__)) +p = os.path.join(p, '..', '..') +sys.path.insert(0, p) + + +from pyqtgraph.flowchart import Flowchart +from pyqtgraph.Qt import QtGui + +#import pyqtgraph.flowchart as f + +app = QtGui.QApplication([]) + +#TETRACYCLINE = True + +fc = Flowchart(terminals={ + 'dataIn': {'io': 'in'}, + 'dataOut': {'io': 'out'} +}) +w = fc.widget() +w.resize(400,200) +w.show() + +n1 = fc.createNode('Add') +n2 = fc.createNode('Subtract') +n3 = fc.createNode('Abs') +n4 = fc.createNode('Add') + +fc.connectTerminals(fc.dataIn, n1.A) +fc.connectTerminals(fc.dataIn, n1.B) +fc.connectTerminals(fc.dataIn, n2.A) +fc.connectTerminals(n1.Out, n4.A) +fc.connectTerminals(n1.Out, n2.B) +fc.connectTerminals(n2.Out, n3.In) +fc.connectTerminals(n3.Out, n4.B) +fc.connectTerminals(n4.Out, fc.dataOut) + + +def process(**kargs): + return fc.process(**kargs) + + +print process(dataIn=7) + +fc.setInput(dataIn=3) + +s = fc.saveState() +fc.clear() + +fc.restoreState(s) + +fc.setInput(dataIn=3) + +#f.NodeMod.TETRACYCLINE = False + +if sys.flags.interactive == 0: + app.exec_() + diff --git a/examples/GradientEditor.py b/examples/GradientEditor.py new file mode 100644 index 00000000..f22479db --- /dev/null +++ b/examples/GradientEditor.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +## Add path to library (just for examples; you do not need this) +import sys, os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +import numpy as np +from PyQt4 import QtGui, QtCore +import pyqtgraph as pg + + +app = QtGui.QApplication([]) +mw = pg.GraphicsView() +mw.resize(800,800) +mw.show() + +#ts = pg.TickSliderItem() +#mw.setCentralItem(ts) +#ts.addTick(0.5, 'r') +#ts.addTick(0.9, 'b') + +ge = pg.GradientEditorItem() +mw.setCentralItem(ge) + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/GraphicsLayout.py b/examples/GraphicsLayout.py new file mode 100755 index 00000000..940d450f --- /dev/null +++ b/examples/GraphicsLayout.py @@ -0,0 +1,46 @@ +## Add path to library (just for examples; you do not need this) +import sys, os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +from PyQt4 import QtGui, QtCore +import pyqtgraph as pg +import user + +app = QtGui.QApplication([]) +view = pg.GraphicsView() +l = pg.GraphicsLayout(border=pg.mkPen(0, 0, 255)) +view.setCentralItem(l) +view.show() + +## Add 3 plots into the first row (automatic position) +p1 = l.addPlot() +p2 = l.addPlot() +p3 = l.addPlot() + +## Add a viewbox into the second row (automatic position) +l.nextRow() +vb = l.addViewBox(colspan=3) + +## Add 2 more plots into the third row (manual position) +p4 = l.addPlot(row=2, col=0) +p5 = l.addPlot(row=2, col=1, colspan=2) + + + +## show some content +p1.plot([1,3,2,4,3,5]) +p2.plot([1,3,2,4,3,5]) +p3.plot([1,3,2,4,3,5]) +p4.plot([1,3,2,4,3,5]) +p5.plot([1,3,2,4,3,5]) + +b = QtGui.QGraphicsRectItem(0, 0, 1, 1) +b.setPen(pg.mkPen(255,255,0)) +vb.addItem(b) +vb.setRange(QtCore.QRectF(-1, -1, 3, 3)) + + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/GraphicsScene.py b/examples/GraphicsScene.py new file mode 100644 index 00000000..9720f65b --- /dev/null +++ b/examples/GraphicsScene.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +## Add path to library (just for examples; you do not need this) +import sys, os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +from PyQt4 import QtCore, QtGui +import pyqtgraph as pg +from pyqtgraph.GraphicsScene import GraphicsScene + +app = QtGui.QApplication([]) +win = pg.GraphicsView() +win.show() + + +class Obj(QtGui.QGraphicsObject): + def __init__(self): + QtGui.QGraphicsObject.__init__(self) + GraphicsScene.registerObject(self) + + def paint(self, p, *args): + p.setPen(pg.mkPen(200,200,200)) + p.drawRect(self.boundingRect()) + + def boundingRect(self): + return QtCore.QRectF(0, 0, 20, 20) + + def mouseClickEvent(self, ev): + if ev.double(): + print "double click" + else: + print "click" + ev.accept() + + #def mouseDragEvent(self, ev): + #print "drag" + #ev.accept() + #self.setPos(self.pos() + ev.pos()-ev.lastPos()) + + + +vb = pg.ViewBox() +win.setCentralItem(vb) + +obj = Obj() +vb.addItem(obj) + +obj2 = Obj() +win.addItem(obj2) + +def clicked(): + print "button click" +btn = QtGui.QPushButton("BTN") +btn.clicked.connect(clicked) +prox = QtGui.QGraphicsProxyWidget() +prox.setWidget(btn) +prox.setPos(100,0) +vb.addItem(prox) + +g = pg.GridItem() +vb.addItem(g) + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/HistogramLUT.py b/examples/HistogramLUT.py new file mode 100644 index 00000000..114da050 --- /dev/null +++ b/examples/HistogramLUT.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +## Add path to library (just for examples; you do not need this) +import sys, os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +import numpy as np +import scipy.ndimage as ndi +from PyQt4 import QtGui, QtCore +import pyqtgraph as pg + + +app = QtGui.QApplication([]) +win = QtGui.QMainWindow() +win.resize(800,600) +win.show() + +cw = QtGui.QWidget() +win.setCentralWidget(cw) + +l = QtGui.QGridLayout() +cw.setLayout(l) +l.setSpacing(0) + +v = pg.GraphicsView() +vb = pg.ViewBox() +vb.setAspectLocked() +v.setCentralItem(vb) +l.addWidget(v, 0, 0) + +w = pg.HistogramLUTWidget() +l.addWidget(w, 0, 1) + +data = ndi.gaussian_filter(np.random.normal(size=(256, 256)), (20, 20)) +for i in range(32): + for j in range(32): + data[i*8, j*8] += .1 +img = pg.ImageItem(data) +#data2 = np.zeros((2,) + data.shape + (2,)) +#data2[0,:,:,0] = data ## make non-contiguous array for testing purposes +#img = pg.ImageItem(data2[0,:,:,0]) +vb.addItem(img) +vb.autoRange() + +w.setImageItem(img) + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/test_ImageItem.py b/examples/ImageItem.py old mode 100755 new mode 100644 similarity index 53% rename from examples/test_ImageItem.py rename to examples/ImageItem.py index f48f0f51..6697e93c --- a/examples/test_ImageItem.py +++ b/examples/ImageItem.py @@ -2,7 +2,7 @@ ## Add path to library (just for examples; you do not need this) import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) - +import ptime from PyQt4 import QtCore, QtGui import numpy as np @@ -14,55 +14,48 @@ app = QtGui.QApplication([]) win = QtGui.QMainWindow() win.resize(800,800) view = pg.GraphicsView() -#view.useOpenGL(True) win.setCentralWidget(view) win.show() -## Allow mouse scale/pan +## Allow mouse scale/pan. Normally we use a ViewBox for this, but +## for simple examples this is easier. view.enableMouse() -## ..But lock the aspect ratio +## lock the aspect ratio so pixels are always square view.setAspectLocked(True) ## Create image item -img = pg.ImageItem() +img = pg.ImageItem(border='w') view.scene().addItem(img) ## Set initial view bounds -view.setRange(QtCore.QRectF(0, 0, 200, 200)) +view.setRange(QtCore.QRectF(0, 0, 600, 600)) ## Create random image -data = np.random.normal(size=(50, 200, 200)) +data = np.random.normal(size=(15, 600, 600), loc=1024, scale=64).astype(np.uint16) i = 0 +updateTime = ptime.time() +fps = 0 + def updateData(): - global img, data, i + global img, data, i, updateTime, fps ## Display the data - img.updateImage(data[i]) + img.setImage(data[i]) i = (i+1) % data.shape[0] - QtCore.QTimer.singleShot(20, updateData) + QtCore.QTimer.singleShot(1, updateData) + now = ptime.time() + fps2 = 1.0 / (now-updateTime) + updateTime = now + fps = fps * 0.9 + fps2 * 0.1 + + #print "%0.1f fps" % fps -# update image data every 20ms (or so) -#t = QtCore.QTimer() -#t.timeout.connect(updateData) -#t.start(20) updateData() - -def doWork(): - while True: - x = '.'.join(['%f'%i for i in range(100)]) ## some work for the thread to do - if time is None: ## main thread has started cleaning up, bail out now - break - time.sleep(1e-3) - -import thread -thread.start_new_thread(doWork, ()) - - ## Start Qt event loop unless running in interactive mode. if sys.flags.interactive != 1: app.exec_() diff --git a/examples/test_ImageView.py b/examples/ImageView.py old mode 100755 new mode 100644 similarity index 100% rename from examples/test_ImageView.py rename to examples/ImageView.py diff --git a/examples/test_MultiPlotWidget.py b/examples/MultiPlotWidget.py old mode 100755 new mode 100644 similarity index 94% rename from examples/test_MultiPlotWidget.py rename to examples/MultiPlotWidget.py index 4c72275b..9e5878a2 --- a/examples/test_MultiPlotWidget.py +++ b/examples/MultiPlotWidget.py @@ -9,7 +9,7 @@ from scipy import random from numpy import linspace from PyQt4 import QtGui, QtCore import pyqtgraph as pg -from pyqtgraph.MultiPlotWidget import MultiPlotWidget +from pyqtgraph import MultiPlotWidget try: from metaarray import * except: diff --git a/examples/PlotSpeedTest.py b/examples/PlotSpeedTest.py new file mode 100644 index 00000000..3010270b --- /dev/null +++ b/examples/PlotSpeedTest.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +## Add path to library (just for examples; you do not need this) +import sys, os, time +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + + +from PyQt4 import QtGui, QtCore +import numpy as np +import pyqtgraph as pg + +#QtGui.QApplication.setGraphicsSystem('raster') +app = QtGui.QApplication([]) +#mw = QtGui.QMainWindow() +#mw.resize(800,800) + +p = pg.plot() + +curve = p.plot() +data = np.random.normal(size=(10,50000)) +ptr = 0 +lastTime = time.time() +fps = None +def update(): + global curve, data, ptr, p, lastTime, fps + curve.setData(data[ptr%10]) + ptr += 1 + now = time.time() + dt = now - lastTime + lastTime = now + if fps is None: + fps = 1.0/dt + else: + s = np.clip(dt*3., 0, 1) + fps = fps * (1-s) + (1.0/dt) * s + p.setTitle('%0.2f fps' % fps) + app.processEvents() ## force complete redraw for every plot +timer = QtCore.QTimer() +timer.timeout.connect(update) +timer.start(0) + + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/test_PlotWidget.py b/examples/PlotWidget.py old mode 100755 new mode 100644 similarity index 76% rename from examples/test_PlotWidget.py rename to examples/PlotWidget.py index 2b2ef496..cecbb58e --- a/examples/test_PlotWidget.py +++ b/examples/PlotWidget.py @@ -32,10 +32,14 @@ p1 = pw.plot() p1.setPen((200,200,100)) ## Add in some extra graphics -rect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 1)) +rect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 5e-11)) rect.setPen(QtGui.QPen(QtGui.QColor(100, 200, 100))) pw.addItem(rect) +pw.setLabel('left', 'Value', units='V') +pw.setLabel('bottom', 'Time', units='s') +pw.setXRange(0, 2) +pw.setYRange(0, 1e-10) def rand(n): data = np.random.random(n) @@ -49,12 +53,13 @@ def rand(n): def updateData(): yd, xd = rand(10000) - p1.updateData(yd, x=xd) + p1.setData(y=yd, x=xd) ## Start a timer to rapidly update the plot in pw t = QtCore.QTimer() t.timeout.connect(updateData) t.start(50) +#updateData() ## Multiple parameterized plots--we can autogenerate averages for these. for i in range(0, 5): @@ -63,10 +68,19 @@ for i in range(0, 5): pw2.plot(y=yd*(j+1), x=xd, params={'iter': i, 'val': j}) ## Test large numbers -curve = pw3.plot(np.random.normal(size=100)*1e6) +curve = pw3.plot(np.random.normal(size=100)*1e0, clickable=True) curve.setPen('w') ## white pen curve.setShadowPen(pg.mkPen((70,70,30), width=6, cosmetic=True)) +def clicked(): + print "curve clicked" +curve.sigClicked.connect(clicked) + +lr = pg.LinearRegionItem([1, 30], bounds=[0,100], movable=True) +pw3.addItem(lr) +line = pg.InfiniteLine(angle=90, movable=True) +pw3.addItem(line) +line.setBounds([0,200]) ## Start Qt event loop unless running in interactive mode. if sys.flags.interactive != 1: diff --git a/examples/Plotting.py b/examples/Plotting.py new file mode 100644 index 00000000..cfeb4786 --- /dev/null +++ b/examples/Plotting.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +## Add path to library (just for examples; you do not need this) +import sys, os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + + +from PyQt4 import QtGui, QtCore +import numpy as np +import pyqtgraph as pg + +#QtGui.QApplication.setGraphicsSystem('raster') +app = QtGui.QApplication([]) +#mw = QtGui.QMainWindow() +#mw.resize(800,800) + +win = pg.GraphicsWindow(title="Basic plotting examples") +win.resize(800,600) + + + +p1 = win.addPlot(title="Basic array plotting", y=np.random.normal(size=100)) + +p2 = win.addPlot(title="Multiple curves") +p2.plot(np.random.normal(size=100), pen=(255,0,0)) +p2.plot(np.random.normal(size=100)+5, pen=(0,255,0)) +p2.plot(np.random.normal(size=100)+10, pen=(0,0,255)) + +p3 = win.addPlot(title="Drawing with points") +p3.plot(np.random.normal(size=100), pen=(200,200,200), symbolBrush=(255,0,0), symbolPen='w') + + +win.nextRow() + +p4 = win.addPlot(title="Parametric") +x = np.cos(np.linspace(0, 2*np.pi, 1000)) +y = np.sin(np.linspace(0, 4*np.pi, 1000)) +p4.plot(x, y) + +p5 = win.addPlot(title="Scatter plot with labels") +x = np.random.normal(size=1000) * 1e-5 +y = x*1000 + 0.005 * np.random.normal(size=1000) +p5.plot(x, y, pen=None, symbol='t', symbolPen=None, symbolSize=10, symbolBrush=(100, 100, 255, 50)) +p5.setLabel('left', "Y Axis", units='A') +p5.setLabel('bottom', "Y Axis", units='s') + + +p6 = win.addPlot(title="Updating plot") +curve = p6.plot(pen='y') +data = np.random.normal(size=(10,1000)) +ptr = 0 +def update(): + global curve, data, ptr, p6 + curve.setData(data[ptr%10]) + if ptr == 0: + p6.enableAutoRange('xy', False) + ptr += 1 +timer = QtCore.QTimer() +timer.timeout.connect(update) +timer.start(50) + + +win.nextRow() + +p7 = win.addPlot(title="Filled plot") +y = np.sin(np.linspace(0, 10, 1000)) + np.random.normal(size=1000, scale=0.1) +p7.plot(y, fillLevel=-0.3, brush=(50,50,200,100)) + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/test_ROItypes.py b/examples/ROItypes.py old mode 100755 new mode 100644 similarity index 58% rename from examples/test_ROItypes.py rename to examples/ROItypes.py index f080e0b4..7649baaa --- a/examples/test_ROItypes.py +++ b/examples/ROItypes.py @@ -11,17 +11,22 @@ import pyqtgraph as pg ## create GUI app = QtGui.QApplication([]) -w = QtGui.QMainWindow() -w.resize(800,800) -v = pg.GraphicsView() -#v.invertY(True) ## Images usually have their Y-axis pointing downward + +w = pg.GraphicsWindow(size=(800,800), border=True) + +v = w.addViewBox(colspan=2) + +#w = QtGui.QMainWindow() +#w.resize(800,800) +#v = pg.GraphicsView() +v.invertY(True) ## Images usually have their Y-axis pointing downward v.setAspectLocked(True) -v.enableMouse(True) -v.autoPixelScale = False -w.setCentralWidget(v) -s = v.scene() -v.setRange(QtCore.QRect(-2, -2, 220, 220)) -w.show() +#v.enableMouse(True) +#v.autoPixelScale = False +#w.setCentralWidget(v) +#s = v.scene() +#v.setRange(QtCore.QRectF(-2, -2, 220, 220)) + ## Create image to display arr = np.ones((100, 100), dtype=float) @@ -36,23 +41,35 @@ arr[:, 50] = 10 ## Create image items, add to scene and set position im1 = pg.ImageItem(arr) im2 = pg.ImageItem(arr) -s.addItem(im1) -s.addItem(im2) +v.addItem(im1) +v.addItem(im2) im2.moveBy(110, 20) +v.setRange(QtCore.QRectF(0, 0, 200, 120)) + im3 = pg.ImageItem() -s.addItem(im3) -im3.moveBy(0, 130) +v2 = w.addViewBox(1,0) +v2.addItem(im3) +v2.setRange(QtCore.QRectF(0, 0, 60, 60)) +v2.invertY(True) +v2.setAspectLocked(True) +#im3.moveBy(0, 130) im3.setZValue(10) + im4 = pg.ImageItem() -s.addItem(im4) -im4.moveBy(110, 130) +v3 = w.addViewBox(1,1) +v3.addItem(im4) +v3.setRange(QtCore.QRectF(0, 0, 60, 60)) +v3.invertY(True) +v3.setAspectLocked(True) +#im4.moveBy(110, 130) im4.setZValue(10) ## create the plot -pi1 = pg.PlotItem() -s.addItem(pi1) -pi1.scale(0.5, 0.5) -pi1.setGeometry(0, 170, 300, 100) +pi1 = w.addPlot(2,0, colspan=2) +#pi1 = pg.PlotItem() +#s.addItem(pi1) +#pi1.scale(0.5, 0.5) +#pi1.setGeometry(0, 170, 300, 100) lastRoi = None @@ -62,31 +79,31 @@ def updateRoi(roi): return lastRoi = roi arr1 = roi.getArrayRegion(im1.image, img=im1) - im3.updateImage(arr1, autoRange=True) + im3.setImage(arr1) arr2 = roi.getArrayRegion(im2.image, img=im2) - im4.updateImage(arr2, autoRange=True) + im4.setImage(arr2) updateRoiPlot(roi, arr1) def updateRoiPlot(roi, data=None): if data is None: data = roi.getArrayRegion(im1.image, img=im1) if data is not None: - roi.curve.updateData(data.mean(axis=1)) + roi.curve.setData(data.mean(axis=1)) ## Create a variety of different ROI types rois = [] -rois.append(pg.widgets.TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=(0,9))) -rois.append(pg.widgets.LineROI([0, 0], [20, 20], width=5, pen=(1,9))) -rois.append(pg.widgets.MultiLineROI([[0, 50], [50, 60], [60, 30]], width=5, pen=(2,9))) -rois.append(pg.widgets.EllipseROI([110, 10], [30, 20], pen=(3,9))) -rois.append(pg.widgets.CircleROI([110, 50], [20, 20], pen=(4,9))) -rois.append(pg.widgets.PolygonROI([[2,0], [2.1,0], [2,.1]], pen=(5,9))) +rois.append(pg.TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=(0,9))) +rois.append(pg.LineROI([0, 0], [20, 20], width=5, pen=(1,9))) +rois.append(pg.MultiLineROI([[0, 50], [50, 60], [60, 30]], width=5, pen=(2,9))) +rois.append(pg.EllipseROI([110, 10], [30, 20], pen=(3,9))) +rois.append(pg.CircleROI([110, 50], [20, 20], pen=(4,9))) +rois.append(pg.PolygonROI([[2,0], [2.1,0], [2,.1]], pen=(5,9))) #rois.append(SpiralROI([20,30], [1,1], pen=mkPen(0))) ## Add each ROI to the scene and link its data to a plot curve with the same color for r in rois: - s.addItem(r) + v.addItem(r) c = pi1.plot(pen=r.pen) r.curve = c r.sigRegionChanged.connect(updateRoi) diff --git a/examples/ScatterPlot.py b/examples/ScatterPlot.py new file mode 100755 index 00000000..da9d4750 --- /dev/null +++ b/examples/ScatterPlot.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +import sys, os +## Add path to library (just for examples; you do not need this) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +from PyQt4 import QtGui, QtCore +import pyqtgraph as pg +import numpy as np + +app = QtGui.QApplication([]) +mw = QtGui.QMainWindow() +mw.resize(800,800) +view = pg.GraphicsLayoutWidget() ## GraphicsView with GraphicsLayout inserted by default +mw.setCentralWidget(view) +mw.show() + +## create four areas to add plots +w1 = view.addPlot() +w2 = view.addViewBox() +w2.setAspectLocked(True) +view.nextRow() +w3 = view.addPlot() +w4 = view.addPlot() +print "Generating data, this takes a few seconds..." + +## There are a few different ways we can draw scatter plots; each is optimized for different types of data: + + +## 1) All spots identical and transform-invariant (top-left plot). +## In this case we can get a huge performance boost by pre-rendering the spot +## image and just drawing that image repeatedly. (use identical=True in the constructor) +## (An even faster approach might be to use QPainter.drawPixmapFragments) + +n = 300 +s1 = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 20), identical=True) +pos = np.random.normal(size=(2,n), scale=1e-5) +spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}] +s1.addPoints(spots) +w1.addItem(s1) + +## This plot is clickable +def clicked(plot, points): + print "clicked points", points +s1.sigClicked.connect(clicked) + + + +## 2) Spots are transform-invariant, but not identical (top-right plot). +## In this case, drawing is as fast as 1), but there is more startup overhead +## and memory usage since each spot generates its own pre-rendered image. + +s2 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True) +pos = np.random.normal(size=(2,n), scale=1e-5) +spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'style': i%5, 'size': 5+i/10.} for i in xrange(n)] +s2.addPoints(spots) +w2.addItem(s2) +w2.setRange(s2.boundingRect()) +s2.sigClicked.connect(clicked) + + +## 3) Spots are not transform-invariant, not identical (bottom-left). +## This is the slowest case, since all spots must be completely re-drawn +## every time because their apparent transformation may have changed. + +s3 = pg.ScatterPlotItem(pxMode=False) ## Set pxMode=False to allow spots to transform with the view +spots3 = [] +for i in range(10): + for j in range(10): + spots3.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'brush':pg.intColor(i*10+j, 100)}) +s3.addPoints(spots3) +w3.addItem(s3) +s3.sigClicked.connect(clicked) + + +## Coming: use qpainter.drawpixmapfragments for scatterplots which do not require mouse interaction + +s4 = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 20), identical=True) +pos = np.random.normal(size=(2,10000), scale=1e-9) +s4.addPoints(x=pos[0], y=pos[1]) +w4.addItem(s4) + + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() + diff --git a/examples/VideoSpeedTest.py b/examples/VideoSpeedTest.py new file mode 100644 index 00000000..4a1d5fb4 --- /dev/null +++ b/examples/VideoSpeedTest.py @@ -0,0 +1,139 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +## Add path to library (just for examples; you do not need this) +import sys, os, time +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + + +from PyQt4 import QtGui, QtCore +import numpy as np +import pyqtgraph as pg +from pyqtgraph import RawImageWidget +import scipy.ndimage as ndi +import pyqtgraph.ptime as ptime +import VideoTemplate + +#QtGui.QApplication.setGraphicsSystem('raster') +app = QtGui.QApplication([]) +#mw = QtGui.QMainWindow() +#mw.resize(800,800) + +win = QtGui.QMainWindow() +ui = VideoTemplate.Ui_MainWindow() +ui.setupUi(win) +win.show() +ui.maxSpin1.setOpts(value=255, step=1) +ui.minSpin1.setOpts(value=0, step=1) + +vb = pg.ViewBox() +ui.graphicsView.setCentralItem(vb) +vb.setAspectLocked() +img = pg.ImageItem() +vb.addItem(img) +vb.setRange(QtCore.QRectF(0, 0, 512, 512)) + +LUT = None +def updateLUT(): + global LUT, ui + dtype = ui.dtypeCombo.currentText() + if dtype == 'uint8': + n = 256 + else: + n = 4096 + LUT = ui.gradient.getLookupTable(n, alpha=ui.alphaCheck.isChecked()) +ui.gradient.sigGradientChanged.connect(updateLUT) +updateLUT() + +ui.alphaCheck.toggled.connect(updateLUT) + +def updateScale(): + global ui + spins = [ui.minSpin1, ui.maxSpin1, ui.minSpin2, ui.maxSpin2, ui.minSpin3, ui.maxSpin3] + if ui.rgbCheck.isChecked(): + for s in spins[2:]: + s.setEnabled(True) + else: + for s in spins[2:]: + s.setEnabled(False) +ui.rgbCheck.toggled.connect(updateScale) + +cache = {} +def mkData(): + global data, cache, ui + dtype = ui.dtypeCombo.currentText() + if dtype not in cache: + if dtype == 'uint8': + dt = np.uint8 + loc = 128 + scale = 64 + mx = 255 + elif dtype == 'uint16': + dt = np.uint16 + loc = 4096 + scale = 1024 + mx = 2**16 + elif dtype == 'float': + dt = np.float + loc = 1.0 + scale = 0.1 + + data = np.random.normal(size=(20,512,512), loc=loc, scale=scale) + data = ndi.gaussian_filter(data, (0, 3, 3)) + if dtype != 'float': + data = np.clip(data, 0, mx) + data = data.astype(dt) + cache[dtype] = data + + data = cache[dtype] + updateLUT() +mkData() +ui.dtypeCombo.currentIndexChanged.connect(mkData) + + +ptr = 0 +lastTime = ptime.time() +fps = None +def update(): + global ui, ptr, lastTime, fps, LUT, img + if ui.lutCheck.isChecked(): + useLut = LUT + else: + useLut = None + + if ui.scaleCheck.isChecked(): + if ui.rgbCheck.isChecked(): + useScale = [ + [ui.minSpin1.value(), ui.maxSpin1.value()], + [ui.minSpin2.value(), ui.maxSpin2.value()], + [ui.minSpin3.value(), ui.maxSpin3.value()]] + else: + useScale = [ui.minSpin1.value(), ui.maxSpin1.value()] + else: + useScale = None + + if ui.rawRadio.isChecked(): + ui.rawImg.setImage(data[ptr%data.shape[0]], lut=useLut, levels=useScale) + else: + img.setImage(data[ptr%data.shape[0]], autoLevels=False, levels=useScale, lut=useLut) + #img.setImage(data[ptr%data.shape[0]], autoRange=False) + + ptr += 1 + now = ptime.time() + dt = now - lastTime + lastTime = now + if fps is None: + fps = 1.0/dt + else: + s = np.clip(dt*3., 0, 1) + fps = fps * (1-s) + (1.0/dt) * s + ui.fpsLabel.setText('%0.2f fps' % fps) + app.processEvents() ## force complete redraw for every plot +timer = QtCore.QTimer() +timer.timeout.connect(update) +timer.start(0) + + + +## Start Qt event loop unless running in interactive mode. +if sys.flags.interactive != 1: + app.exec_() diff --git a/examples/VideoTemplate.py b/examples/VideoTemplate.py new file mode 100644 index 00000000..d4f712d5 --- /dev/null +++ b/examples/VideoTemplate.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'VideoTemplate.ui' +# +# Created: Sun Jan 8 19:22:32 2012 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName(_fromUtf8("MainWindow")) + MainWindow.resize(985, 674) + self.centralwidget = QtGui.QWidget(MainWindow) + self.centralwidget.setObjectName(_fromUtf8("centralwidget")) + self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.gridLayout = QtGui.QGridLayout() + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.rawImg = RawImageWidget(self.centralwidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) + self.rawImg.setSizePolicy(sizePolicy) + self.rawImg.setObjectName(_fromUtf8("rawImg")) + self.gridLayout.addWidget(self.rawImg, 0, 0, 1, 1) + self.graphicsView = GraphicsView(self.centralwidget) + self.graphicsView.setObjectName(_fromUtf8("graphicsView")) + self.gridLayout.addWidget(self.graphicsView, 0, 1, 1, 1) + self.rawRadio = QtGui.QRadioButton(self.centralwidget) + self.rawRadio.setChecked(True) + self.rawRadio.setObjectName(_fromUtf8("rawRadio")) + self.gridLayout.addWidget(self.rawRadio, 1, 0, 1, 1) + self.gfxRadio = QtGui.QRadioButton(self.centralwidget) + self.gfxRadio.setObjectName(_fromUtf8("gfxRadio")) + self.gridLayout.addWidget(self.gfxRadio, 1, 1, 1, 1) + self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) + self.label = QtGui.QLabel(self.centralwidget) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.dtypeCombo = QtGui.QComboBox(self.centralwidget) + self.dtypeCombo.setObjectName(_fromUtf8("dtypeCombo")) + self.dtypeCombo.addItem(_fromUtf8("")) + self.dtypeCombo.addItem(_fromUtf8("")) + self.dtypeCombo.addItem(_fromUtf8("")) + self.gridLayout_2.addWidget(self.dtypeCombo, 2, 2, 1, 1) + self.scaleCheck = QtGui.QCheckBox(self.centralwidget) + self.scaleCheck.setObjectName(_fromUtf8("scaleCheck")) + self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1) + self.rgbCheck = QtGui.QCheckBox(self.centralwidget) + self.rgbCheck.setObjectName(_fromUtf8("rgbCheck")) + self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.minSpin1 = SpinBox(self.centralwidget) + self.minSpin1.setObjectName(_fromUtf8("minSpin1")) + self.horizontalLayout.addWidget(self.minSpin1) + self.label_2 = QtGui.QLabel(self.centralwidget) + self.label_2.setAlignment(QtCore.Qt.AlignCenter) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.horizontalLayout.addWidget(self.label_2) + self.maxSpin1 = SpinBox(self.centralwidget) + self.maxSpin1.setObjectName(_fromUtf8("maxSpin1")) + self.horizontalLayout.addWidget(self.maxSpin1) + self.gridLayout_2.addLayout(self.horizontalLayout, 3, 2, 1, 1) + self.horizontalLayout_2 = QtGui.QHBoxLayout() + self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) + self.minSpin2 = SpinBox(self.centralwidget) + self.minSpin2.setEnabled(False) + self.minSpin2.setObjectName(_fromUtf8("minSpin2")) + self.horizontalLayout_2.addWidget(self.minSpin2) + self.label_3 = QtGui.QLabel(self.centralwidget) + self.label_3.setAlignment(QtCore.Qt.AlignCenter) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.horizontalLayout_2.addWidget(self.label_3) + self.maxSpin2 = SpinBox(self.centralwidget) + self.maxSpin2.setEnabled(False) + self.maxSpin2.setObjectName(_fromUtf8("maxSpin2")) + self.horizontalLayout_2.addWidget(self.maxSpin2) + self.gridLayout_2.addLayout(self.horizontalLayout_2, 4, 2, 1, 1) + self.horizontalLayout_3 = QtGui.QHBoxLayout() + self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3")) + self.minSpin3 = SpinBox(self.centralwidget) + self.minSpin3.setEnabled(False) + self.minSpin3.setObjectName(_fromUtf8("minSpin3")) + self.horizontalLayout_3.addWidget(self.minSpin3) + self.label_4 = QtGui.QLabel(self.centralwidget) + self.label_4.setAlignment(QtCore.Qt.AlignCenter) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.horizontalLayout_3.addWidget(self.label_4) + self.maxSpin3 = SpinBox(self.centralwidget) + self.maxSpin3.setEnabled(False) + self.maxSpin3.setObjectName(_fromUtf8("maxSpin3")) + self.horizontalLayout_3.addWidget(self.maxSpin3) + self.gridLayout_2.addLayout(self.horizontalLayout_3, 5, 2, 1, 1) + self.lutCheck = QtGui.QCheckBox(self.centralwidget) + self.lutCheck.setObjectName(_fromUtf8("lutCheck")) + self.gridLayout_2.addWidget(self.lutCheck, 6, 0, 1, 1) + self.alphaCheck = QtGui.QCheckBox(self.centralwidget) + self.alphaCheck.setObjectName(_fromUtf8("alphaCheck")) + self.gridLayout_2.addWidget(self.alphaCheck, 6, 1, 1, 1) + self.gradient = GradientWidget(self.centralwidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) + self.gradient.setSizePolicy(sizePolicy) + self.gradient.setObjectName(_fromUtf8("gradient")) + self.gridLayout_2.addWidget(self.gradient, 6, 2, 1, 2) + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem, 2, 3, 1, 1) + self.fpsLabel = QtGui.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(12) + self.fpsLabel.setFont(font) + self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fpsLabel.setObjectName(_fromUtf8("fpsLabel")) + self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) + MainWindow.setCentralWidget(self.centralwidget) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) + self.rawRadio.setText(QtGui.QApplication.translate("MainWindow", "RawImageWidget (unscaled; faster)", None, QtGui.QApplication.UnicodeUTF8)) + self.gfxRadio.setText(QtGui.QApplication.translate("MainWindow", "GraphicsView + ImageItem (scaled; slower)", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("MainWindow", "Data type", None, QtGui.QApplication.UnicodeUTF8)) + self.dtypeCombo.setItemText(0, QtGui.QApplication.translate("MainWindow", "uint8", None, QtGui.QApplication.UnicodeUTF8)) + self.dtypeCombo.setItemText(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8)) + self.dtypeCombo.setItemText(2, QtGui.QApplication.translate("MainWindow", "float", None, QtGui.QApplication.UnicodeUTF8)) + self.scaleCheck.setText(QtGui.QApplication.translate("MainWindow", "Scale Data", None, QtGui.QApplication.UnicodeUTF8)) + self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8)) + self.lutCheck.setText(QtGui.QApplication.translate("MainWindow", "Use Lookup Table", None, QtGui.QApplication.UnicodeUTF8)) + self.alphaCheck.setText(QtGui.QApplication.translate("MainWindow", "alpha", None, QtGui.QApplication.UnicodeUTF8)) + self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget diff --git a/examples/VideoTemplate.ui b/examples/VideoTemplate.ui new file mode 100644 index 00000000..078e7ccf --- /dev/null +++ b/examples/VideoTemplate.ui @@ -0,0 +1,250 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>985</width> + <height>674</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0" colspan="4"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="RawImageWidget" name="rawImg" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <zorder>fpsLabel</zorder> + </widget> + </item> + <item row="0" column="1"> + <widget class="GraphicsView" name="graphicsView"/> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="rawRadio"> + <property name="text"> + <string>RawImageWidget (unscaled; faster)</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QRadioButton" name="gfxRadio"> + <property name="text"> + <string>GraphicsView + ImageItem (scaled; slower)</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Data type</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QComboBox" name="dtypeCombo"> + <item> + <property name="text"> + <string>uint8</string> + </property> + </item> + <item> + <property name="text"> + <string>uint16</string> + </property> + </item> + <item> + <property name="text"> + <string>float</string> + </property> + </item> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="scaleCheck"> + <property name="text"> + <string>Scale Data</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="rgbCheck"> + <property name="text"> + <string>RGB</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="SpinBox" name="minSpin1"/> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string><---></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="SpinBox" name="maxSpin1"/> + </item> + </layout> + </item> + <item row="4" column="2"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="SpinBox" name="minSpin2"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string><---></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="SpinBox" name="maxSpin2"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="5" column="2"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="SpinBox" name="minSpin3"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string><---></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="SpinBox" name="maxSpin3"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="6" column="0"> + <widget class="QCheckBox" name="lutCheck"> + <property name="text"> + <string>Use Lookup Table</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QCheckBox" name="alphaCheck"> + <property name="text"> + <string>alpha</string> + </property> + </widget> + </item> + <item row="6" column="2" colspan="2"> + <widget class="GradientWidget" name="gradient" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="2" column="3"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="4"> + <widget class="QLabel" name="fpsLabel"> + <property name="font"> + <font> + <pointsize>12</pointsize> + </font> + </property> + <property name="text"> + <string>FPS</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <customwidgets> + <customwidget> + <class>GraphicsView</class> + <extends>QGraphicsView</extends> + <header>pyqtgraph</header> + </customwidget> + <customwidget> + <class>RawImageWidget</class> + <extends>QWidget</extends> + <header>pyqtgraph</header> + <container>1</container> + </customwidget> + <customwidget> + <class>GradientWidget</class> + <extends>QWidget</extends> + <header>pyqtgraph</header> + <container>1</container> + </customwidget> + <customwidget> + <class>SpinBox</class> + <extends>QDoubleSpinBox</extends> + <header>pyqtgraph</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/examples/test_viewBox.py b/examples/ViewBox.py similarity index 83% rename from examples/test_viewBox.py rename to examples/ViewBox.py index 0b8db232..6e30a7e6 100755 --- a/examples/test_viewBox.py +++ b/examples/ViewBox.py @@ -13,25 +13,28 @@ import pyqtgraph as pg app = QtGui.QApplication([]) mw = QtGui.QMainWindow() -cw = QtGui.QWidget() -vl = QtGui.QVBoxLayout() -cw.setLayout(vl) -mw.setCentralWidget(cw) +#cw = QtGui.QWidget() +#vl = QtGui.QVBoxLayout() +#cw.setLayout(vl) +#mw.setCentralWidget(cw) mw.show() mw.resize(800, 600) - -gv = pg.GraphicsView(cw) -gv.enableMouse(False) ## Mouse interaction will be handled by the ViewBox +gv = pg.GraphicsView() +mw.setCentralWidget(gv) +#gv.enableMouse(False) ## Mouse interaction will be handled by the ViewBox l = QtGui.QGraphicsGridLayout() l.setHorizontalSpacing(0) l.setVerticalSpacing(0) +#vl.addWidget(gv) vb = pg.ViewBox() -p1 = pg.PlotCurveItem() +#grid = pg.GridItem() +#vb.addItem(grid) + +p1 = pg.PlotDataItem() vb.addItem(p1) -vl.addWidget(gv) class movableRect(QtGui.QGraphicsRectItem): def __init__(self, *args): @@ -63,9 +66,9 @@ l.addItem(vb, 0, 1) gv.centralWidget.setLayout(l) -xScale = pg.ScaleItem(orientation='bottom', linkView=vb) +xScale = pg.AxisItem(orientation='bottom', linkView=vb) l.addItem(xScale, 1, 1) -yScale = pg.ScaleItem(orientation='left', linkView=vb) +yScale = pg.AxisItem(orientation='left', linkView=vb) l.addItem(yScale, 0, 0) xScale.setLabel(text=u"<span style='color: #ff0000; font-weight: bold'>X</span> <i>Axis</i>", units="s") @@ -82,7 +85,7 @@ def rand(n): def updateData(): yd, xd = rand(10000) - p1.updateData(yd, x=xd) + p1.setData(y=yd, x=xd) yd, xd = rand(10000) updateData() diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 00000000..23b7cd58 --- /dev/null +++ b/examples/__init__.py @@ -0,0 +1 @@ +from __main__ import run diff --git a/examples/__main__.py b/examples/__main__.py new file mode 100644 index 00000000..f8fe4c76 --- /dev/null +++ b/examples/__main__.py @@ -0,0 +1,101 @@ +from PyQt4 import QtCore, QtGui +from exampleLoaderTemplate import Ui_Form +import os, sys +from collections import OrderedDict + +examples = OrderedDict([ + ('Command-line usage', 'CLIexample.py'), + ('Basic Plotting', 'Plotting.py'), + ('GraphicsItems', OrderedDict([ + ('PlotItem', 'PlotItem.py'), + ('ImageItem - video', 'ImageItem.py'), + ('ImageItem - draw', 'Draw.py'), + ('Region-of-Interest', 'ROItypes.py'), + ('GraphicsLayout', 'GraphicsLayout.py'), + ('Scatter Plot', 'ScatterPlot.py'), + ('ViewBox', 'ViewBox.py'), + ('Arrow', 'Arrow.py'), + ])), + ('Widgets', OrderedDict([ + ('PlotWidget', 'PlotWidget.py'), + ('SpinBox', '../widgets/SpinBox.py'), + ('TreeWidget', '../widgets/TreeWidget.py'), + ('DataTreeWidget', '../widgets/DataTreeWidget.py'), + ('GradientWidget', '../widgets/GradientWidget.py'), + ('TableWidget', '../widgets/TableWidget.py'), + ('ColorButton', '../widgets/ColorButton.py'), + ('CheckTable', '../widgets/CheckTable.py'), + ('VerticalLabel', '../widgets/VerticalLabel.py'), + ('JoystickButton', '../widgets/JoystickButton.py'), + ])), + ('ImageView', 'ImageView.py'), + ('GraphicsScene', 'GraphicsScene.py'), + ('Flowcharts', 'Flowchart.py'), + ('ParameterTree', '../parametertree'), + ('Canvas', '../canvas'), + ('MultiPlotWidget', 'MultiPlotWidget.py'), +]) + +path = os.path.abspath(os.path.dirname(__file__)) + +class ExampleLoader(QtGui.QMainWindow): + def __init__(self): + QtGui.QMainWindow.__init__(self) + self.ui = Ui_Form() + self.cw = QtGui.QWidget() + self.setCentralWidget(self.cw) + self.ui.setupUi(self.cw) + + global examples + self.populateTree(self.ui.exampleTree.invisibleRootItem(), examples) + self.ui.exampleTree.expandAll() + + self.resize(900,500) + self.show() + self.ui.splitter.setSizes([150,750]) + self.ui.loadBtn.clicked.connect(self.loadFile) + self.ui.exampleTree.currentItemChanged.connect(self.showFile) + self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile) + + + def populateTree(self, root, examples): + for key, val in examples.iteritems(): + item = QtGui.QTreeWidgetItem([key]) + if isinstance(val, basestring): + item.file = val + else: + self.populateTree(item, val) + root.addChild(item) + + + def currentFile(self): + item = self.ui.exampleTree.currentItem() + if hasattr(item, 'file'): + global path + return os.path.join(path, item.file) + return None + + def loadFile(self): + fn = self.currentFile() + if fn is None: + return + os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, fn) + + + def showFile(self): + fn = self.currentFile() + if fn is None: + self.ui.codeView.clear() + return + text = open(fn).read() + self.ui.codeView.setPlainText(text) + +def run(): + app = QtGui.QApplication([]) + loader = ExampleLoader() + + if sys.flags.interactive != 1: + app.exec_() + +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/examples/exampleLoaderTemplate.py b/examples/exampleLoaderTemplate.py new file mode 100644 index 00000000..7447e84c --- /dev/null +++ b/examples/exampleLoaderTemplate.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'exampleLoaderTemplate.ui' +# +# Created: Sat Dec 17 23:46:27 2011 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(762, 302) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget) + self.verticalLayout.setMargin(0) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.exampleTree = QtGui.QTreeWidget(self.layoutWidget) + self.exampleTree.setObjectName(_fromUtf8("exampleTree")) + self.exampleTree.headerItem().setText(0, _fromUtf8("1")) + self.exampleTree.header().setVisible(False) + self.verticalLayout.addWidget(self.exampleTree) + self.loadBtn = QtGui.QPushButton(self.layoutWidget) + self.loadBtn.setObjectName(_fromUtf8("loadBtn")) + self.verticalLayout.addWidget(self.loadBtn) + self.codeView = QtGui.QTextBrowser(self.splitter) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Monospace")) + font.setPointSize(10) + self.codeView.setFont(font) + self.codeView.setObjectName(_fromUtf8("codeView")) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load Example", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/examples/exampleLoaderTemplate.ui b/examples/exampleLoaderTemplate.ui new file mode 100644 index 00000000..b1941447 --- /dev/null +++ b/examples/exampleLoaderTemplate.ui @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>762</width> + <height>302</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QWidget" name="layoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTreeWidget" name="exampleTree"> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + </widget> + </item> + <item> + <widget class="QPushButton" name="loadBtn"> + <property name="text"> + <string>Load Example</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QTextBrowser" name="codeView"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/test_scatterPlot.py b/examples/test_scatterPlot.py deleted file mode 100755 index e8d91eea..00000000 --- a/examples/test_scatterPlot.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -import sys, os -## Add path to library (just for examples; you do not need this) -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) - -from PyQt4 import QtGui, QtCore -import pyqtgraph as pg -import numpy as np - -#QtGui.QApplication.setGraphicsSystem('raster') -app = QtGui.QApplication([]) - -mw = QtGui.QMainWindow() -mw.resize(800,800) -cw = QtGui.QWidget() -layout = QtGui.QGridLayout() -cw.setLayout(layout) -mw.setCentralWidget(cw) - -w1 = pg.PlotWidget() -layout.addWidget(w1, 0,0) - -w2 = pg.PlotWidget() -layout.addWidget(w2, 1,0) - -w3 = pg.GraphicsView() -w3.enableMouse() -w3.aspectLocked = True -layout.addWidget(w3, 0,1) - -w4 = pg.PlotWidget() -#vb = pg.ViewBox() -#w4.setCentralItem(vb) -layout.addWidget(w4, 1,1) - -mw.show() - - -n = 3000 -s1 = pg.ScatterPlotItem(size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20))) -pos = np.random.normal(size=(2,n), scale=1e-5) -spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}] -s1.addPoints(spots) -w1.addDataItem(s1) - -def clicked(plot, points): - print "clicked points", points - -s1.sigClicked.connect(clicked) - - -s2 = pg.ScatterPlotItem(pxMode=False) -spots2 = [] -for i in range(10): - for j in range(10): - spots2.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'brush':pg.intColor(i*10+j, 100)}) -s2.addPoints(spots2) -w2.addDataItem(s2) - -s2.sigClicked.connect(clicked) - - -s3 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True) -pos = np.random.normal(size=(2,3000), scale=1e-5) -spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, 3000)} for i in range(3000)] -s3.addPoints(spots) -w3.addItem(s3) -w3.setRange(s3.boundingRect()) -s3.sigClicked.connect(clicked) - - -s4 = pg.ScatterPlotItem(identical=True, size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20))) -#pos = np.random.normal(size=(2,n), scale=1e-5) -#spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}] -s4.addPoints(spots) -w4.addDataItem(s4) - - -## Start Qt event loop unless running in interactive mode. -if sys.flags.interactive != 1: - app.exec_() - diff --git a/flowchart/Flowchart.py b/flowchart/Flowchart.py new file mode 100644 index 00000000..acbdb27e --- /dev/null +++ b/flowchart/Flowchart.py @@ -0,0 +1,920 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +#from PyQt4 import QtCore, QtGui +#from PySide import QtCore, QtGui +#import Node as NodeMod +from Node import * +#import functions +from collections import OrderedDict +from pyqtgraph.widgets.TreeWidget import * +#from .. DataTreeWidget import * +import FlowchartTemplate +import FlowchartCtrlTemplate +from Terminal import Terminal +from numpy import ndarray +import library +from debug import printExc +import configfile +import pyqtgraph.dockarea as dockarea +import pyqtgraph as pg +import FlowchartGraphicsView + +def strDict(d): + return dict([(str(k), v) for k, v in d.iteritems()]) + + +def toposort(deps, nodes=None, seen=None, stack=None, depth=0): + """Topological sort. Arguments are: + deps dictionary describing dependencies where a:[b,c] means "a depends on b and c" + nodes optional, specifies list of starting nodes (these should be the nodes + which are not depended on by any other nodes) + """ + + if nodes is None: + ## run through deps to find nodes that are not depended upon + rem = set() + for dep in deps.itervalues(): + rem |= set(dep) + nodes = set(deps.keys()) - rem + if seen is None: + seen = set() + stack = [] + sorted = [] + #print " "*depth, "Starting from", nodes + for n in nodes: + if n in stack: + raise Exception("Cyclic dependency detected", stack + [n]) + if n in seen: + continue + seen.add(n) + #print " "*depth, " descending into", n, deps[n] + sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1)) + #print " "*depth, " Added", n + sorted.append(n) + #print " "*depth, " ", sorted + return sorted + + +class Flowchart(Node): + + sigFileLoaded = QtCore.Signal(object) + sigFileSaved = QtCore.Signal(object) + + + #sigOutputChanged = QtCore.Signal() ## inherited from Node + sigChartLoaded = QtCore.Signal() + sigStateChanged = QtCore.Signal() + + def __init__(self, terminals=None, name=None, filePath=None): + if name is None: + name = "Flowchart" + if terminals is None: + terminals = {} + self.filePath = filePath + Node.__init__(self, name) ## create node without terminals; we'll add these later + + self.inputWasSet = False ## flag allows detection of changes in the absence of input change. + self._nodes = {} + self.nextZVal = 10 + #self.connects = [] + #self._chartGraphicsItem = FlowchartGraphicsItem(self) + self._widget = None + self._scene = None + self.processing = False ## flag that prevents recursive node updates + + self.widget() + + self.inputNode = Node('Input', allowRemove=False) + self.outputNode = Node('Output', allowRemove=False) + self.addNode(self.inputNode, 'Input', [-150, 0]) + self.addNode(self.outputNode, 'Output', [300, 0]) + + self.outputNode.sigOutputChanged.connect(self.outputChanged) + self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) + self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) + + self.viewBox.autoRange(padding = 0.04) + + for name, opts in terminals.iteritems(): + self.addTerminal(name, **opts) + + def setInput(self, **args): + #print "setInput", args + #Node.setInput(self, **args) + #print " ....." + self.inputWasSet = True + self.inputNode.setOutput(**args) + + def outputChanged(self): + self.widget().outputChanged(self.outputNode.inputValues()) + self.sigOutputChanged.emit(self) + + def output(self): + return self.outputNode.inputValues() + + def nodes(self): + return self._nodes + + def addTerminal(self, name, **opts): + term = Node.addTerminal(self, name, **opts) + name = term.name() + if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node + opts['io'] = 'out' + opts['multi'] = False + term2 = self.inputNode.addTerminal(name, **opts) + else: + opts['io'] = 'in' + #opts['multi'] = False + term2 = self.outputNode.addTerminal(name, **opts) + return term + + def removeTerminal(self, name): + #print "remove:", name + term = self[name] + inTerm = self.internalTerminal(term) + Node.removeTerminal(self, name) + inTerm.node().removeTerminal(inTerm.name()) + + def internalTerminalRenamed(self, term, oldName): + self[oldName].rename(term.name()) + + def terminalRenamed(self, term, oldName): + newName = term.name() + #print "flowchart rename", newName, oldName + #print self.terminals + Node.terminalRenamed(self, self[oldName], oldName) + #print self.terminals + for n in [self.inputNode, self.outputNode]: + if oldName in n.terminals: + n[oldName].rename(newName) + + def createNode(self, nodeType, name=None, pos=None): + if name is None: + n = 0 + while True: + name = "%s.%d" % (nodeType, n) + if name not in self._nodes: + break + n += 1 + + node = library.getNodeType(nodeType)(name) + self.addNode(node, name, pos) + return node + + def addNode(self, node, name, pos=None): + if pos is None: + pos = [0, 0] + if type(pos) in [QtCore.QPoint, QtCore.QPointF]: + pos = [pos.x(), pos.y()] + item = node.graphicsItem() + item.setZValue(self.nextZVal*2) + self.nextZVal += 1 + #item.setParentItem(self.chartGraphicsItem()) + self.viewBox.addItem(item) + #item.setPos(pos2.x(), pos2.y()) + item.moveBy(*pos) + self._nodes[name] = node + self.widget().addNode(node) + #QtCore.QObject.connect(node, QtCore.SIGNAL('closed'), self.nodeClosed) + node.sigClosed.connect(self.nodeClosed) + #QtCore.QObject.connect(node, QtCore.SIGNAL('renamed'), self.nodeRenamed) + node.sigRenamed.connect(self.nodeRenamed) + #QtCore.QObject.connect(node, QtCore.SIGNAL('outputChanged'), self.nodeOutputChanged) + node.sigOutputChanged.connect(self.nodeOutputChanged) + + def removeNode(self, node): + node.close() + + def nodeClosed(self, node): + del self._nodes[node.name()] + self.widget().removeNode(node) + #QtCore.QObject.disconnect(node, QtCore.SIGNAL('closed'), self.nodeClosed) + try: + node.sigClosed.disconnect(self.nodeClosed) + except TypeError: + pass + #QtCore.QObject.disconnect(node, QtCore.SIGNAL('renamed'), self.nodeRenamed) + try: + node.sigRenamed.disconnect(self.nodeRenamed) + except TypeError: + pass + #QtCore.QObject.disconnect(node, QtCore.SIGNAL('outputChanged'), self.nodeOutputChanged) + try: + node.sigOutputChanged.disconnect(self.nodeOutputChanged) + except TypeError: + pass + + def nodeRenamed(self, node, oldName): + del self._nodes[oldName] + self._nodes[node.name()] = node + self.widget().nodeRenamed(node, oldName) + + def arrangeNodes(self): + pass + + def internalTerminal(self, term): + """If the terminal belongs to the external Node, return the corresponding internal terminal""" + if term.node() is self: + if term.isInput(): + return self.inputNode[term.name()] + else: + return self.outputNode[term.name()] + else: + return term + + def connectTerminals(self, term1, term2): + """Connect two terminals together within this flowchart.""" + term1 = self.internalTerminal(term1) + term2 = self.internalTerminal(term2) + term1.connectTo(term2) + + + def process(self, **args): + """ + Process data through the flowchart, returning the output. + Keyword arguments must be the names of input terminals + + """ + data = {} ## Stores terminal:value pairs + + ## determine order of operations + ## order should look like [('p', node1), ('p', node2), ('d', terminal1), ...] + ## Each tuple specifies either (p)rocess this node or (d)elete the result from this terminal + order = self.processOrder() + #print "ORDER:", order + + ## Record inputs given to process() + for n, t in self.inputNode.outputs().iteritems(): + if n not in args: + raise Exception("Parameter %s required to process this chart." % n) + data[t] = args[n] + + ret = {} + + ## process all in order + for c, arg in order: + + if c == 'p': ## Process a single node + #print "===> process:", arg + node = arg + if node is self.inputNode: + continue ## input node has already been processed. + + + ## get input and output terminals for this node + outs = node.outputs().values() + ins = node.inputs().values() + + ## construct input value dictionary + args = {} + for inp in ins: + inputs = inp.inputTerminals() + if len(inputs) == 0: + continue + if inp.isMultiValue(): ## multi-input terminals require a dict of all inputs + args[inp.name()] = dict([(i, data[i]) for i in inputs]) + else: ## single-inputs terminals only need the single input value available + args[inp.name()] = data[inputs[0]] + + if node is self.outputNode: + ret = args ## we now have the return value, but must keep processing in case there are other endpoint nodes in the chart + else: + try: + if node.isBypassed(): + result = node.processBypassed(args) + else: + result = node.process(display=False, **args) + except: + print "Error processing node %s. Args are: %s" % (str(node), str(args)) + raise + for out in outs: + #print " Output:", out, out.name() + #print out.name() + try: + data[out] = result[out.name()] + except: + print out, out.name() + raise + elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) + #print "===> delete", arg + if arg in data: + del data[arg] + + return ret + + def processOrder(self): + """Return the order of operations required to process this chart. + The order returned should look like [('p', node1), ('p', node2), ('d', terminal1), ...] + where each tuple specifies either (p)rocess this node or (d)elete the result from this terminal + """ + + ## first collect list of nodes/terminals and their dependencies + deps = {} + tdeps = {} ## {terminal: [nodes that depend on terminal]} + for name, node in self._nodes.iteritems(): + deps[node] = node.dependentNodes() + for t in node.outputs().itervalues(): + tdeps[t] = t.dependentNodes() + + #print "DEPS:", deps + ## determine correct node-processing order + #deps[self] = [] + order = toposort(deps) + #print "ORDER1:", order + + ## construct list of operations + ops = [('p', n) for n in order] + + ## determine when it is safe to delete terminal values + dels = [] + for t, nodes in tdeps.iteritems(): + lastInd = 0 + lastNode = None + for n in nodes: ## determine which node is the last to be processed according to order + if n is self: + lastInd = None + break + else: + try: + ind = order.index(n) + except ValueError: + continue + if lastNode is None or ind > lastInd: + lastNode = n + lastInd = ind + #tdeps[t] = lastNode + if lastInd is not None: + dels.append((lastInd+1, t)) + dels.sort(lambda a,b: cmp(b[0], a[0])) + for i, t in dels: + ops.insert(i, ('d', t)) + + return ops + + + def nodeOutputChanged(self, startNode): + """Triggered when a node's output values have changed. (NOT called during process()) + Propagates new data forward through network.""" + ## first collect list of nodes/terminals and their dependencies + + if self.processing: + return + self.processing = True + try: + deps = {} + for name, node in self._nodes.iteritems(): + deps[node] = [] + for t in node.outputs().itervalues(): + deps[node].extend(t.dependentNodes()) + + ## determine order of updates + order = toposort(deps, nodes=[startNode]) + order.reverse() + + ## keep track of terminals that have been updated + terms = set(startNode.outputs().values()) + + #print "======= Updating", startNode + #print "Order:", order + for node in order[1:]: + #print "Processing node", node + for term in node.inputs().values(): + #print " checking terminal", term + deps = term.connections().keys() + update = False + for d in deps: + if d in terms: + #print " ..input", d, "changed" + update = True + term.inputChanged(d, process=False) + if update: + #print " processing.." + node.update() + terms |= set(node.outputs().values()) + + finally: + self.processing = False + if self.inputWasSet: + self.inputWasSet = False + else: + self.sigStateChanged.emit() + + + + def chartGraphicsItem(self): + """Return the graphicsItem which displays the internals of this flowchart. + (graphicsItem() still returns the external-view item)""" + #return self._chartGraphicsItem + return self.viewBox + + def widget(self): + if self._widget is None: + self._widget = FlowchartCtrlWidget(self) + self.scene = self._widget.scene() + self.viewBox = self._widget.viewBox() + #self._scene = QtGui.QGraphicsScene() + #self._widget.setScene(self._scene) + #self.scene.addItem(self.chartGraphicsItem()) + + #ci = self.chartGraphicsItem() + #self.viewBox.addItem(ci) + #self.viewBox.autoRange() + return self._widget + + def listConnections(self): + conn = set() + for n in self._nodes.itervalues(): + terms = n.outputs() + for n, t in terms.iteritems(): + for c in t.connections(): + conn.add((t, c)) + return conn + + def saveState(self): + state = Node.saveState(self) + state['nodes'] = [] + state['connects'] = [] + state['terminals'] = self.saveTerminals() + + for name, node in self._nodes.iteritems(): + cls = type(node) + if hasattr(cls, 'nodeName'): + clsName = cls.nodeName + pos = node.graphicsItem().pos() + ns = {'class': clsName, 'name': name, 'pos': (pos.x(), pos.y()), 'state': node.saveState()} + state['nodes'].append(ns) + + conn = self.listConnections() + for a, b in conn: + state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name())) + + state['inputNode'] = self.inputNode.saveState() + state['outputNode'] = self.outputNode.saveState() + + return state + + def restoreState(self, state, clear=False): + self.blockSignals(True) + try: + if clear: + self.clear() + Node.restoreState(self, state) + nodes = state['nodes'] + nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0])) + for n in nodes: + if n['name'] in self._nodes: + self._nodes[n['name']].moveBy(*n['pos']) + continue + try: + node = self.createNode(n['class'], name=n['name']) + node.restoreState(n['state']) + except: + printExc("Error creating node %s: (continuing anyway)" % n['name']) + #node.graphicsItem().moveBy(*n['pos']) + + self.inputNode.restoreState(state.get('inputNode', {})) + self.outputNode.restoreState(state.get('outputNode', {})) + + #self.restoreTerminals(state['terminals']) + for n1, t1, n2, t2 in state['connects']: + try: + self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) + except: + print self._nodes[n1].terminals + print self._nodes[n2].terminals + printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) + + + finally: + self.blockSignals(False) + + self.sigChartLoaded.emit() + self.outputChanged() + #self.sigOutputChanged.emit() + + def loadFile(self, fileName=None, startDir=None): + if fileName is None: + if startDir is None: + startDir = self.filePath + if startDir is None: + startDir = '.' + self.fileDialog = pg.FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") + #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.loadFile) + return + ## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs.. + #fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") + fileName = str(fileName) + state = configfile.readConfigFile(fileName) + self.restoreState(state, clear=True) + self.viewBox.autoRange() + #self.emit(QtCore.SIGNAL('fileLoaded'), fileName) + self.sigFileLoaded.emit(fileName) + + def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): + if fileName is None: + if startDir is None: + startDir = self.filePath + if startDir is None: + startDir = '.' + self.fileDialog = pg.FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") + #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + #self.fileDialog.setDirectory(startDir) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.saveFile) + return + #fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") + configfile.writeConfigFile(self.saveState(), fileName) + self.sigFileSaved.emit(fileName) + + def clear(self): + for n in self._nodes.values(): + if n is self.inputNode or n is self.outputNode: + continue + n.close() ## calls self.nodeClosed(n) by signal + #self.clearTerminals() + self.widget().clear() + + def clearTerminals(self): + Node.clearTerminals(self) + self.inputNode.clearTerminals() + self.outputNode.clearTerminals() + +#class FlowchartGraphicsItem(QtGui.QGraphicsItem): +class FlowchartGraphicsItem(GraphicsObject): + + def __init__(self, chart): + #print "FlowchartGraphicsItem.__init__" + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + self.chart = chart ## chart is an instance of Flowchart() + self.updateTerminals() + + def updateTerminals(self): + #print "FlowchartGraphicsItem.updateTerminals" + self.terminals = {} + bounds = self.boundingRect() + inp = self.chart.inputs() + dy = bounds.height() / (len(inp)+1) + y = dy + for n, t in inp.iteritems(): + item = t.graphicsItem() + self.terminals[n] = item + item.setParentItem(self) + item.setAnchor(bounds.width(), y) + y += dy + out = self.chart.outputs() + dy = bounds.height() / (len(out)+1) + y = dy + for n, t in out.iteritems(): + item = t.graphicsItem() + self.terminals[n] = item + item.setParentItem(self) + item.setAnchor(0, y) + y += dy + + def boundingRect(self): + #print "FlowchartGraphicsItem.boundingRect" + return QtCore.QRectF() + + def paint(self, p, *args): + #print "FlowchartGraphicsItem.paint" + pass + #p.drawRect(self.boundingRect()) + + +class FlowchartCtrlWidget(QtGui.QWidget): + """The widget that contains the list of all the nodes in a flowchart and their controls, as well as buttons for loading/saving flowcharts.""" + + def __init__(self, chart): + self.items = {} + #self.loadDir = loadDir ## where to look initially for chart files + self.currentFileName = None + QtGui.QWidget.__init__(self) + self.chart = chart + self.ui = FlowchartCtrlTemplate.Ui_Form() + self.ui.setupUi(self) + self.ui.ctrlList.setColumnCount(2) + #self.ui.ctrlList.setColumnWidth(0, 200) + self.ui.ctrlList.setColumnWidth(1, 20) + self.ui.ctrlList.setVerticalScrollMode(self.ui.ctrlList.ScrollPerPixel) + self.ui.ctrlList.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + self.chartWidget = FlowchartWidget(chart, self) + #self.chartWidget.viewBox().autoRange() + self.cwWin = QtGui.QMainWindow() + self.cwWin.setWindowTitle('Flowchart') + self.cwWin.setCentralWidget(self.chartWidget) + self.cwWin.resize(1000,800) + + h = self.ui.ctrlList.header() + h.setResizeMode(0, h.Stretch) + + self.ui.ctrlList.itemChanged.connect(self.itemChanged) + self.ui.loadBtn.clicked.connect(self.loadClicked) + self.ui.saveBtn.clicked.connect(self.saveClicked) + self.ui.saveAsBtn.clicked.connect(self.saveAsClicked) + self.ui.showChartBtn.toggled.connect(self.chartToggled) + self.chart.sigFileLoaded.connect(self.setCurrentFile) + self.ui.reloadBtn.clicked.connect(self.reloadClicked) + self.chart.sigFileSaved.connect(self.fileSaved) + + + + #def resizeEvent(self, ev): + #QtGui.QWidget.resizeEvent(self, ev) + #self.ui.ctrlList.setColumnWidth(0, self.ui.ctrlList.viewport().width()-20) + + def chartToggled(self, b): + if b: + self.cwWin.show() + else: + self.cwWin.hide() + + def reloadClicked(self): + try: + self.chartWidget.reloadLibrary() + self.ui.reloadBtn.success("Reloaded.") + except: + self.ui.reloadBtn.success("Error.") + raise + + + def loadClicked(self): + newFile = self.chart.loadFile() + #self.setCurrentFile(newFile) + + def fileSaved(self, fileName): + self.setCurrentFile(fileName) + self.ui.saveBtn.success("Saved.") + + def saveClicked(self): + if self.currentFileName is None: + self.saveAsClicked() + else: + try: + self.chart.saveFile(self.currentFileName) + #self.ui.saveBtn.success("Saved.") + except: + self.ui.saveBtn.failure("Error") + raise + + def saveAsClicked(self): + try: + if self.currentFileName is None: + newFile = self.chart.saveFile() + else: + newFile = self.chart.saveFile(suggestedFileName=self.currentFileName) + #self.ui.saveAsBtn.success("Saved.") + #print "Back to saveAsClicked." + except: + self.ui.saveBtn.failure("Error") + raise + + #self.setCurrentFile(newFile) + + def setCurrentFile(self, fileName): + self.currentFileName = fileName + if fileName is None: + self.ui.fileNameLabel.setText("<b>[ new ]</b>") + else: + self.ui.fileNameLabel.setText("<b>%s</b>" % os.path.split(self.currentFileName)[1]) + self.resizeEvent(None) + + def itemChanged(self, *args): + pass + + def scene(self): + return self.chartWidget.scene() ## returns the GraphicsScene object + + def viewBox(self): + return self.chartWidget.viewBox() + + def nodeRenamed(self, node, oldName): + self.items[node].setText(0, node.name()) + + def addNode(self, node): + ctrl = node.ctrlWidget() + #if ctrl is None: + #return + item = QtGui.QTreeWidgetItem([node.name(), '', '']) + self.ui.ctrlList.addTopLevelItem(item) + byp = QtGui.QPushButton('X') + byp.setCheckable(True) + byp.setFixedWidth(20) + item.bypassBtn = byp + self.ui.ctrlList.setItemWidget(item, 1, byp) + byp.node = node + node.bypassButton = byp + byp.setChecked(node.isBypassed()) + byp.clicked.connect(self.bypassClicked) + + if ctrl is not None: + item2 = QtGui.QTreeWidgetItem() + item.addChild(item2) + self.ui.ctrlList.setItemWidget(item2, 0, ctrl) + + self.items[node] = item + + def removeNode(self, node): + if node in self.items: + item = self.items[node] + #self.disconnect(item.bypassBtn, QtCore.SIGNAL('clicked()'), self.bypassClicked) + try: + item.bypassBtn.clicked.disconnect(self.bypassClicked) + except TypeError: + pass + self.ui.ctrlList.removeTopLevelItem(item) + + def bypassClicked(self): + btn = QtCore.QObject.sender(self) + btn.node.bypass(btn.isChecked()) + + def chartWidget(self): + return self.chartWidget + + def outputChanged(self, data): + pass + #self.ui.outputTree.setData(data, hideRoot=True) + + def clear(self): + self.chartWidget.clear() + + def select(self, node): + item = self.items[node] + self.ui.ctrlList.setCurrentItem(item) + +class FlowchartWidget(dockarea.DockArea): + """Includes the actual graphical flowchart and debugging interface""" + def __init__(self, chart, ctrl): + #QtGui.QWidget.__init__(self) + dockarea.DockArea.__init__(self) + self.chart = chart + self.ctrl = ctrl + self.hoverItem = None + #self.setMinimumWidth(250) + #self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)) + + #self.ui = FlowchartTemplate.Ui_Form() + #self.ui.setupUi(self) + + ## build user interface (it was easier to do it here than via developer) + self.view = FlowchartGraphicsView.FlowchartGraphicsView(self) + self.viewDock = dockarea.Dock('view', size=(1000,600)) + self.viewDock.addWidget(self.view) + self.viewDock.hideTitleBar() + self.addDock(self.viewDock) + + + self.hoverText = QtGui.QTextEdit() + self.hoverText.setReadOnly(True) + self.hoverDock = dockarea.Dock('Hover Info', size=(1000,20)) + self.hoverDock.addWidget(self.hoverText) + self.addDock(self.hoverDock, 'bottom') + + self.selInfo = QtGui.QWidget() + self.selInfoLayout = QtGui.QGridLayout() + self.selInfo.setLayout(self.selInfoLayout) + self.selDescLabel = QtGui.QLabel() + self.selNameLabel = QtGui.QLabel() + self.selDescLabel.setWordWrap(True) + self.selectedTree = pg.DataTreeWidget() + #self.selectedTree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + #self.selInfoLayout.addWidget(self.selNameLabel) + self.selInfoLayout.addWidget(self.selDescLabel) + self.selInfoLayout.addWidget(self.selectedTree) + self.selDock = dockarea.Dock('Selected Node', size=(1000,200)) + self.selDock.addWidget(self.selInfo) + self.addDock(self.selDock, 'bottom') + + self._scene = self.view.scene() + self._viewBox = self.view.viewBox() + #self._scene = QtGui.QGraphicsScene() + #self._scene = FlowchartGraphicsView.FlowchartGraphicsScene() + #self.view.setScene(self._scene) + + self.buildMenu() + #self.ui.addNodeBtn.mouseReleaseEvent = self.addNodeBtnReleased + + self._scene.selectionChanged.connect(self.selectionChanged) + self._scene.sigMouseHover.connect(self.hoverOver) + #self.view.sigClicked.connect(self.showViewMenu) + #self._scene.sigSceneContextMenu.connect(self.showViewMenu) + #self._viewBox.sigActionPositionChanged.connect(self.menuPosChanged) + + + def reloadLibrary(self): + #QtCore.QObject.disconnect(self.nodeMenu, QtCore.SIGNAL('triggered(QAction*)'), self.nodeMenuTriggered) + self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered) + self.nodeMenu = None + self.subMenus = [] + library.loadLibrary(reloadLibs=True) + self.buildMenu() + + def buildMenu(self, pos=None): + self.nodeMenu = QtGui.QMenu() + self.subMenus = [] + for section, nodes in library.getNodeTree().iteritems(): + menu = QtGui.QMenu(section) + self.nodeMenu.addMenu(menu) + for name in nodes: + act = menu.addAction(name) + act.nodeType = name + act.pos = pos + self.subMenus.append(menu) + self.nodeMenu.triggered.connect(self.nodeMenuTriggered) + return self.nodeMenu + + def menuPosChanged(self, pos): + self.menuPos = pos + + def showViewMenu(self, ev): + #QtGui.QPushButton.mouseReleaseEvent(self.ui.addNodeBtn, ev) + #if ev.button() == QtCore.Qt.RightButton: + #self.menuPos = self.view.mapToScene(ev.pos()) + #self.nodeMenu.popup(ev.globalPos()) + #print "Flowchart.showViewMenu called" + + #self.menuPos = ev.scenePos() + self.buildMenu(ev.scenePos()) + self.nodeMenu.popup(ev.screenPos()) + + def scene(self): + return self._scene ## the GraphicsScene item + + def viewBox(self): + return self._viewBox ## the viewBox that items should be added to + + def nodeMenuTriggered(self, action): + nodeType = action.nodeType + if action.pos is not None: + pos = action.pos + else: + pos = self.menuPos + pos = self.viewBox().mapSceneToView(pos) + + self.chart.createNode(nodeType, pos=pos) + + + def selectionChanged(self): + #print "FlowchartWidget.selectionChanged called." + items = self._scene.selectedItems() + #print " scene.selectedItems: ", items + if len(items) == 0: + data = None + else: + item = items[0] + if hasattr(item, 'node') and isinstance(item.node, Node): + n = item.node + self.ctrl.select(n) + data = {'outputs': n.outputValues(), 'inputs': n.inputValues()} + self.selNameLabel.setText(n.name()) + if hasattr(n, 'nodeName'): + self.selDescLabel.setText("<b>%s</b>: %s" % (n.nodeName, n.__class__.__doc__)) + else: + self.selDescLabel.setText("") + if n.exception is not None: + data['exception'] = n.exception + else: + data = None + self.selectedTree.setData(data, hideRoot=True) + + def hoverOver(self, items): + #print "FlowchartWidget.hoverOver called." + term = None + for item in items: + if item is self.hoverItem: + return + self.hoverItem = item + if hasattr(item, 'term') and isinstance(item.term, Terminal): + term = item.term + break + if term is None: + self.hoverText.setPlainText("") + else: + val = term.value() + if isinstance(val, ndarray): + val = "%s %s %s" % (type(val).__name__, str(val.shape), str(val.dtype)) + else: + val = str(val) + if len(val) > 400: + val = val[:400] + "..." + self.hoverText.setPlainText("%s.%s = %s" % (term.node().name(), term.name(), val)) + #self.hoverLabel.setCursorPosition(0) + + + + def clear(self): + #self.outputTree.setData(None) + self.selectedTree.setData(None) + self.hoverText.setPlainText('') + self.selNameLabel.setText('') + self.selDescLabel.setText('') + + +class FlowchartNode(Node): + pass + diff --git a/flowchart/FlowchartCtrlTemplate.py b/flowchart/FlowchartCtrlTemplate.py new file mode 100644 index 00000000..0f2ec162 --- /dev/null +++ b/flowchart/FlowchartCtrlTemplate.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'FlowchartCtrlTemplate.ui' +# +# Created: Sun Dec 18 20:55:57 2011 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(217, 499) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.loadBtn = QtGui.QPushButton(Form) + self.loadBtn.setObjectName(_fromUtf8("loadBtn")) + self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) + self.saveBtn = FeedbackButton(Form) + self.saveBtn.setObjectName(_fromUtf8("saveBtn")) + self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) + self.saveAsBtn = FeedbackButton(Form) + self.saveAsBtn.setObjectName(_fromUtf8("saveAsBtn")) + self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) + self.reloadBtn = FeedbackButton(Form) + self.reloadBtn.setCheckable(False) + self.reloadBtn.setFlat(False) + self.reloadBtn.setObjectName(_fromUtf8("reloadBtn")) + self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) + self.showChartBtn = QtGui.QPushButton(Form) + self.showChartBtn.setCheckable(True) + self.showChartBtn.setObjectName(_fromUtf8("showChartBtn")) + self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) + self.ctrlList = TreeWidget(Form) + self.ctrlList.setObjectName(_fromUtf8("ctrlList")) + self.ctrlList.headerItem().setText(0, _fromUtf8("1")) + self.ctrlList.header().setVisible(False) + self.ctrlList.header().setStretchLastSection(False) + self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) + self.fileNameLabel = QtGui.QLabel(Form) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.fileNameLabel.setFont(font) + self.fileNameLabel.setText(_fromUtf8("")) + self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fileNameLabel.setObjectName(_fromUtf8("fileNameLabel")) + self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8)) + self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8)) + self.reloadBtn.setText(QtGui.QApplication.translate("Form", "Reload Libs", None, QtGui.QApplication.UnicodeUTF8)) + self.showChartBtn.setText(QtGui.QApplication.translate("Form", "Flowchart", None, QtGui.QApplication.UnicodeUTF8)) + +from FeedbackButton import FeedbackButton +from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/flowchart/FlowchartCtrlTemplate.ui b/flowchart/FlowchartCtrlTemplate.ui new file mode 100644 index 00000000..a931fb3a --- /dev/null +++ b/flowchart/FlowchartCtrlTemplate.ui @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>217</width> + <height>499</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="verticalSpacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item row="1" column="0"> + <widget class="QPushButton" name="loadBtn"> + <property name="text"> + <string>Load..</string> + </property> + </widget> + </item> + <item row="1" column="1" colspan="2"> + <widget class="FeedbackButton" name="saveBtn"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="FeedbackButton" name="saveAsBtn"> + <property name="text"> + <string>As..</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="FeedbackButton" name="reloadBtn"> + <property name="text"> + <string>Reload Libs</string> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="4" column="2" colspan="2"> + <widget class="QPushButton" name="showChartBtn"> + <property name="text"> + <string>Flowchart</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0" colspan="4"> + <widget class="TreeWidget" name="ctrlList"> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="fileNameLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>TreeWidget</class> + <extends>QTreeWidget</extends> + <header>pyqtgraph.widgets.TreeWidget</header> + </customwidget> + <customwidget> + <class>FeedbackButton</class> + <extends>QPushButton</extends> + <header>FeedbackButton</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/flowchart/FlowchartGraphicsView.py b/flowchart/FlowchartGraphicsView.py new file mode 100644 index 00000000..0ec4d5c8 --- /dev/null +++ b/flowchart/FlowchartGraphicsView.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.widgets.GraphicsView import GraphicsView +from pyqtgraph.GraphicsScene import GraphicsScene +from pyqtgraph.graphicsItems.ViewBox import ViewBox + +#class FlowchartGraphicsView(QtGui.QGraphicsView): +class FlowchartGraphicsView(GraphicsView): + + sigHoverOver = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + + def __init__(self, widget, *args): + #QtGui.QGraphicsView.__init__(self, *args) + GraphicsView.__init__(self, *args, useOpenGL=False) + #self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255,255,255))) + self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True) + self.setCentralItem(self._vb) + #self.scene().addItem(self.vb) + #self.setMouseTracking(True) + #self.lastPos = None + #self.setTransformationAnchor(self.AnchorViewCenter) + #self.setRenderHints(QtGui.QPainter.Antialiasing) + self.setRenderHint(QtGui.QPainter.Antialiasing, True) + #self.setDragMode(QtGui.QGraphicsView.RubberBandDrag) + #self.setRubberBandSelectionMode(QtCore.Qt.ContainsItemBoundingRect) + + def viewBox(self): + return self._vb + + + #def mousePressEvent(self, ev): + #self.moved = False + #self.lastPos = ev.pos() + #return QtGui.QGraphicsView.mousePressEvent(self, ev) + + #def mouseMoveEvent(self, ev): + #self.moved = True + #callSuper = False + #if ev.buttons() & QtCore.Qt.RightButton: + #if self.lastPos is not None: + #dif = ev.pos() - self.lastPos + #self.scale(1.01**-dif.y(), 1.01**-dif.y()) + #elif ev.buttons() & QtCore.Qt.MidButton: + #if self.lastPos is not None: + #dif = ev.pos() - self.lastPos + #self.translate(dif.x(), -dif.y()) + #else: + ##self.emit(QtCore.SIGNAL('hoverOver'), self.items(ev.pos())) + #self.sigHoverOver.emit(self.items(ev.pos())) + #callSuper = True + #self.lastPos = ev.pos() + + #if callSuper: + #QtGui.QGraphicsView.mouseMoveEvent(self, ev) + + #def mouseReleaseEvent(self, ev): + #if not self.moved: + ##self.emit(QtCore.SIGNAL('clicked'), ev) + #self.sigClicked.emit(ev) + #return QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + +class FlowchartViewBox(ViewBox): + + def __init__(self, widget, *args, **kwargs): + ViewBox.__init__(self, *args, **kwargs) + self.widget = widget + #self.menu = None + #self._subMenus = None ## need a place to store the menus otherwise they dissappear (even though they've been added to other menus) ((yes, it doesn't make sense)) + + + + + def getMenu(self, ev): + ## called by ViewBox to create a new context menu + self._fc_menu = QtGui.QMenu() + self._subMenus = self.getContextMenus(ev) + for menu in self._subMenus: + self._fc_menu.addMenu(menu) + return self._fc_menu + + def getContextMenus(self, ev): + ## called by scene to add menus on to someone else's context menu + menu = self.widget.buildMenu(ev.scenePos()) + menu.setTitle("Add node") + return [menu, ViewBox.getMenu(self, ev)] + + + + + + + + + + +##class FlowchartGraphicsScene(QtGui.QGraphicsScene): +#class FlowchartGraphicsScene(GraphicsScene): + + #sigContextMenuEvent = QtCore.Signal(object) + + #def __init__(self, *args): + ##QtGui.QGraphicsScene.__init__(self, *args) + #GraphicsScene.__init__(self, *args) + + #def mouseClickEvent(self, ev): + ##QtGui.QGraphicsScene.contextMenuEvent(self, ev) + #if not ev.button() in [QtCore.Qt.RightButton]: + #self.sigContextMenuEvent.emit(ev) \ No newline at end of file diff --git a/flowchart/FlowchartTemplate.py b/flowchart/FlowchartTemplate.py new file mode 100644 index 00000000..ec8823f1 --- /dev/null +++ b/flowchart/FlowchartTemplate.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'FlowchartTemplate.ui' +# +# Created: Sun Dec 18 20:55:57 2011 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(529, 329) + self.selInfoWidget = QtGui.QWidget(Form) + self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) + self.selInfoWidget.setObjectName(_fromUtf8("selInfoWidget")) + self.gridLayout = QtGui.QGridLayout(self.selInfoWidget) + self.gridLayout.setMargin(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.selDescLabel = QtGui.QLabel(self.selInfoWidget) + self.selDescLabel.setText(_fromUtf8("")) + self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.selDescLabel.setWordWrap(True) + self.selDescLabel.setObjectName(_fromUtf8("selDescLabel")) + self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) + self.selNameLabel = QtGui.QLabel(self.selInfoWidget) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.selNameLabel.setFont(font) + self.selNameLabel.setText(_fromUtf8("")) + self.selNameLabel.setObjectName(_fromUtf8("selNameLabel")) + self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) + self.selectedTree = DataTreeWidget(self.selInfoWidget) + self.selectedTree.setObjectName(_fromUtf8("selectedTree")) + self.selectedTree.headerItem().setText(0, _fromUtf8("1")) + self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) + self.hoverText = QtGui.QTextEdit(Form) + self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) + self.hoverText.setObjectName(_fromUtf8("hoverText")) + self.view = FlowchartGraphicsView(Form) + self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) + self.view.setObjectName(_fromUtf8("view")) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget +from FlowchartGraphicsView import FlowchartGraphicsView diff --git a/flowchart/FlowchartTemplate.ui b/flowchart/FlowchartTemplate.ui new file mode 100644 index 00000000..e4530800 --- /dev/null +++ b/flowchart/FlowchartTemplate.ui @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>529</width> + <height>329</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <widget class="QWidget" name="selInfoWidget" native="true"> + <property name="geometry"> + <rect> + <x>260</x> + <y>10</y> + <width>264</width> + <height>222</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="selDescLabel"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="selNameLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="DataTreeWidget" name="selectedTree"> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + </widget> + </item> + </layout> + </widget> + <widget class="QTextEdit" name="hoverText"> + <property name="geometry"> + <rect> + <x>0</x> + <y>240</y> + <width>521</width> + <height>81</height> + </rect> + </property> + </widget> + <widget class="FlowchartGraphicsView" name="view"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>256</width> + <height>192</height> + </rect> + </property> + </widget> + </widget> + <customwidgets> + <customwidget> + <class>DataTreeWidget</class> + <extends>QTreeWidget</extends> + <header>pyqtgraph.widgets.DataTreeWidget</header> + </customwidget> + <customwidget> + <class>FlowchartGraphicsView</class> + <extends>QGraphicsView</extends> + <header>FlowchartGraphicsView</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/flowchart/Node.py b/flowchart/Node.py new file mode 100644 index 00000000..88a6d3b2 --- /dev/null +++ b/flowchart/Node.py @@ -0,0 +1,561 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +#from PySide import QtCore, QtGui +from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject +import pyqtgraph.functions as fn +from Terminal import * +from collections import OrderedDict +from debug import * +import numpy as np +#from pyqtgraph.ObjectWorkaround import QObjectWorkaround +from eq import * + +#TETRACYCLINE = True + +def strDict(d): + return dict([(str(k), v) for k, v in d.iteritems()]) + +class Node(QtCore.QObject): + + sigOutputChanged = QtCore.Signal(object) # self + sigClosed = QtCore.Signal(object) + sigRenamed = QtCore.Signal(object, object) + sigTerminalRenamed = QtCore.Signal(object, object) + + + def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True): + QtCore.QObject.__init__(self) + self._name = name + self._bypass = False + self.bypassButton = None ## this will be set by the flowchart ctrl widget.. + self._graphicsItem = None + self.terminals = OrderedDict() + self._inputs = {} + self._outputs = {} + self._allowAddInput = allowAddInput ## flags to allow the user to add/remove terminals + self._allowAddOutput = allowAddOutput + self._allowRemove = allowRemove + + self.exception = None + if terminals is None: + return + for name, opts in terminals.iteritems(): + self.addTerminal(name, **opts) + + + def nextTerminalName(self, name): + """Return an unused terminal name""" + name2 = name + i = 1 + while name2 in self.terminals: + name2 = "%s.%d" % (name, i) + i += 1 + return name2 + + def addInput(self, name="Input", **args): + #print "Node.addInput called." + return self.addTerminal(name, io='in', **args) + + def addOutput(self, name="Output", **args): + return self.addTerminal(name, io='out', **args) + + def removeTerminal(self, term): + ## term may be a terminal or its name + + if isinstance(term, Terminal): + name = term.name() + else: + name = term + term = self.terminals[name] + + #print "remove", name + #term.disconnectAll() + term.close() + del self.terminals[name] + if name in self._inputs: + del self._inputs[name] + if name in self._outputs: + del self._outputs[name] + self.graphicsItem().updateTerminals() + + def terminalRenamed(self, term, oldName): + """Called after a terminal has been renamed""" + newName = term.name() + #print "node", self, "handling rename..", newName, oldName + for d in [self.terminals, self._inputs, self._outputs]: + if oldName not in d: + continue + #print " got one" + d[newName] = d[oldName] + del d[oldName] + + self.graphicsItem().updateTerminals() + #self.emit(QtCore.SIGNAL('terminalRenamed'), term, oldName) + self.sigTerminalRenamed.emit(term, oldName) + + def addTerminal(self, name, **opts): + #print "Node.addTerminal called. name:", name, "opts:", opts + #global TETRACYCLINE + #print "TETRACYCLINE: ", TETRACYCLINE + #if TETRACYCLINE: + #print "Creating Terminal..." + name = self.nextTerminalName(name) + term = Terminal(self, name, **opts) + self.terminals[name] = term + if term.isInput(): + self._inputs[name] = term + elif term.isOutput(): + self._outputs[name] = term + self.graphicsItem().updateTerminals() + return term + + + def inputs(self): + return self._inputs + + def outputs(self): + return self._outputs + + def process(self, **kargs): + """Process data through this node. Each named argument supplies data to the corresponding terminal.""" + return {} + + def graphicsItem(self): + """Return a (the?) graphicsitem for this node""" + #print "Node.graphicsItem called." + if self._graphicsItem is None: + #print "Creating NodeGraphicsItem..." + self._graphicsItem = NodeGraphicsItem(self) + #print "Node.graphicsItem is returning ", self._graphicsItem + return self._graphicsItem + + def __getattr__(self, attr): + """Return the terminal with the given name""" + if attr not in self.terminals: + raise AttributeError(attr) + else: + return self.terminals[attr] + + def __getitem__(self, item): + return getattr(self, item) + + def name(self): + return self._name + + def rename(self, name): + oldName = self._name + self._name = name + #self.emit(QtCore.SIGNAL('renamed'), self, oldName) + self.sigRenamed.emit(self, oldName) + + def dependentNodes(self): + """Return the list of nodes which provide direct input to this node""" + nodes = set() + for t in self.inputs().itervalues(): + nodes |= set([i.node() for i in t.inputTerminals()]) + return nodes + #return set([t.inputTerminals().node() for t in self.listInputs().itervalues()]) + + def __repr__(self): + return "<Node %s @%x>" % (self.name(), id(self)) + + def ctrlWidget(self): + return None + + def bypass(self, byp): + self._bypass = byp + if self.bypassButton is not None: + self.bypassButton.setChecked(byp) + self.update() + + def isBypassed(self): + return self._bypass + + def setInput(self, **args): + """Set the values on input terminals. For most nodes, this will happen automatically through Terminal.inputChanged. + This is normally only used for nodes with no connected inputs.""" + changed = False + for k, v in args.iteritems(): + term = self._inputs[k] + oldVal = term.value() + if not eq(oldVal, v): + changed = True + term.setValue(v, process=False) + if changed and '_updatesHandled_' not in args: + self.update() + + def inputValues(self): + vals = {} + for n, t in self.inputs().iteritems(): + vals[n] = t.value() + return vals + + def outputValues(self): + vals = {} + for n, t in self.outputs().iteritems(): + vals[n] = t.value() + return vals + + def connected(self, localTerm, remoteTerm): + """Called whenever one of this node's terminals is connected elsewhere.""" + pass + + def disconnected(self, localTerm, remoteTerm): + """Called whenever one of this node's terminals is connected elsewhere.""" + pass + + def update(self, signal=True): + """Collect all input values, attempt to process new output values, and propagate downstream.""" + vals = self.inputValues() + #print " inputs:", vals + try: + if self.isBypassed(): + out = self.processBypassed(vals) + else: + out = self.process(**strDict(vals)) + #print " output:", out + if out is not None: + if signal: + self.setOutput(**out) + else: + self.setOutputNoSignal(**out) + for n,t in self.inputs().iteritems(): + t.setValueAcceptable(True) + self.clearException() + except: + #printExc( "Exception while processing %s:" % self.name()) + for n,t in self.outputs().iteritems(): + t.setValue(None) + self.setException(sys.exc_info()) + + if signal: + #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data + self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data + + def processBypassed(self, args): + result = {} + for term in self.outputs().values(): + byp = term.bypassValue() + if byp is None: + result[term.name()] = None + else: + result[term.name()] = args.get(byp, None) + return result + + def setOutput(self, **vals): + self.setOutputNoSignal(**vals) + #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data + self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data + + def setOutputNoSignal(self, **vals): + for k, v in vals.iteritems(): + term = self.outputs()[k] + term.setValue(v) + #targets = term.connections() + #for t in targets: ## propagate downstream + #if t is term: + #continue + #t.inputChanged(term) + term.setValueAcceptable(True) + + def setException(self, exc): + self.exception = exc + self.recolor() + + def clearException(self): + self.setException(None) + + def recolor(self): + if self.exception is None: + self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(0, 0, 0))) + else: + self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3)) + + def saveState(self): + pos = self.graphicsItem().pos() + return {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()} + + def restoreState(self, state): + pos = state.get('pos', (0,0)) + self.graphicsItem().setPos(*pos) + self.bypass(state.get('bypass', False)) + + def saveTerminals(self): + terms = OrderedDict() + for n, t in self.terminals.iteritems(): + terms[n] = (t.saveState()) + return terms + + def restoreTerminals(self, state): + for name in self.terminals.keys(): + if name not in state: + self.removeTerminal(name) + for name, opts in state.iteritems(): + if name in self.terminals: + continue + try: + opts = strDict(opts) + self.addTerminal(name, **opts) + except: + printExc("Error restoring terminal %s (%s):" % (str(name), str(opts))) + + + def clearTerminals(self): + for t in self.terminals.itervalues(): + t.close() + self.terminals = OrderedDict() + self._inputs = {} + self._outputs = {} + + def close(self): + """Cleans up after the node--removes terminals, graphicsItem, widget""" + self.disconnectAll() + self.clearTerminals() + item = self.graphicsItem() + if item.scene() is not None: + item.scene().removeItem(item) + self._graphicsItem = None + w = self.ctrlWidget() + if w is not None: + w.setParent(None) + #self.emit(QtCore.SIGNAL('closed'), self) + self.sigClosed.emit(self) + + def disconnectAll(self): + for t in self.terminals.values(): + t.disconnectAll() + + +#class NodeGraphicsItem(QtGui.QGraphicsItem): +class NodeGraphicsItem(GraphicsObject): + def __init__(self, node): + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + #QObjectWorkaround.__init__(self) + + #self.shadow = QtGui.QGraphicsDropShadowEffect() + #self.shadow.setOffset(5,5) + #self.shadow.setBlurRadius(10) + #self.setGraphicsEffect(self.shadow) + + self.pen = fn.mkPen(0,0,0) + self.selectPen = fn.mkPen(200,200,200,width=2) + self.brush = fn.mkBrush(200, 200, 200, 150) + self.hoverBrush = fn.mkBrush(200, 200, 200, 200) + self.selectBrush = fn.mkBrush(200, 200, 255, 200) + self.hovered = False + + self.node = node + flags = self.ItemIsMovable | self.ItemIsSelectable | self.ItemIsFocusable |self.ItemSendsGeometryChanges + #flags = self.ItemIsFocusable |self.ItemSendsGeometryChanges + + self.setFlags(flags) + self.bounds = QtCore.QRectF(0, 0, 100, 100) + self.nameItem = QtGui.QGraphicsTextItem(self.node.name(), self) + self.nameItem.setDefaultTextColor(QtGui.QColor(50, 50, 50)) + self.nameItem.moveBy(self.bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) + self.nameItem.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) + self.updateTerminals() + #self.setZValue(10) + + self.nameItem.focusOutEvent = self.labelFocusOut + self.nameItem.keyPressEvent = self.labelKeyPress + + self.menu = None + self.buildMenu() + + #self.node.sigTerminalRenamed.connect(self.updateActionMenu) + + #def setZValue(self, z): + #for t, item in self.terminals.itervalues(): + #item.setZValue(z+1) + #GraphicsObject.setZValue(self, z) + + def labelFocusOut(self, ev): + QtGui.QGraphicsTextItem.focusOutEvent(self.nameItem, ev) + self.labelChanged() + + def labelKeyPress(self, ev): + if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: + self.labelChanged() + else: + QtGui.QGraphicsTextItem.keyPressEvent(self.nameItem, ev) + + def labelChanged(self): + newName = str(self.nameItem.toPlainText()) + if newName != self.node.name(): + self.node.rename(newName) + + ### re-center the label + bounds = self.boundingRect() + self.nameItem.setPos(bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) + + def setPen(self, pen): + self.pen = pen + self.update() + + def setBrush(self, brush): + self.brush = brush + self.update() + + + def updateTerminals(self): + bounds = self.bounds + self.terminals = {} + inp = self.node.inputs() + dy = bounds.height() / (len(inp)+1) + y = dy + for i, t in inp.iteritems(): + item = t.graphicsItem() + item.setParentItem(self) + #item.setZValue(self.zValue()+1) + br = self.bounds + item.setAnchor(0, y) + self.terminals[i] = (t, item) + y += dy + + out = self.node.outputs() + dy = bounds.height() / (len(out)+1) + y = dy + for i, t in out.iteritems(): + item = t.graphicsItem() + item.setParentItem(self) + item.setZValue(self.zValue()) + br = self.bounds + item.setAnchor(bounds.width(), y) + self.terminals[i] = (t, item) + y += dy + + #self.buildMenu() + + + def boundingRect(self): + return self.bounds.adjusted(-5, -5, 5, 5) + + def paint(self, p, *args): + + p.setPen(self.pen) + if self.isSelected(): + p.setPen(self.selectPen) + p.setBrush(self.selectBrush) + else: + p.setPen(self.pen) + if self.hovered: + p.setBrush(self.hoverBrush) + else: + p.setBrush(self.brush) + + p.drawRect(self.bounds) + + + def mousePressEvent(self, ev): + ev.ignore() + + + def mouseClickEvent(self, ev): + #print "Node.mouseClickEvent called." + if int(ev.button()) == int(QtCore.Qt.LeftButton): + ev.accept() + #print " ev.button: left" + sel = self.isSelected() + #ret = QtGui.QGraphicsItem.mousePressEvent(self, ev) + self.setSelected(True) + if not sel and self.isSelected(): + #self.setBrush(QtGui.QBrush(QtGui.QColor(200, 200, 255))) + #self.emit(QtCore.SIGNAL('selected')) + #self.scene().selectionChanged.emit() ## for some reason this doesn't seem to be happening automatically + self.update() + #return ret + + elif int(ev.button()) == int(QtCore.Qt.RightButton): + #print " ev.button: right" + ev.accept() + #pos = ev.screenPos() + self.raiseContextMenu(ev) + #self.menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def mouseDragEvent(self, ev): + #print "Node.mouseDrag" + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + self.setPos(self.pos()+self.mapToParent(ev.pos())-self.mapToParent(ev.lastPos())) + + def hoverEvent(self, ev): + if not ev.isExit() and ev.acceptClicks(QtCore.Qt.LeftButton): + ev.acceptDrags(QtCore.Qt.LeftButton) + self.hovered = True + else: + self.hovered = False + self.update() + + #def mouseReleaseEvent(self, ev): + #ret = QtGui.QGraphicsItem.mouseReleaseEvent(self, ev) + #return ret + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: + ev.accept() + if not self.node._allowRemove: + return + self.node.close() + else: + ev.ignore() + + def itemChange(self, change, val): + if change == self.ItemPositionHasChanged: + for k, t in self.terminals.iteritems(): + t[1].nodeMoved() + return QtGui.QGraphicsItem.itemChange(self, change, val) + + + #def contextMenuEvent(self, ev): + #ev.accept() + #self.menu.popup(ev.screenPos()) + + def getMenu(self): + return self.menu + + + def getContextMenus(self, event): + return [self.menu] + + def raiseContextMenu(self, ev): + menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def buildMenu(self): + self.menu = QtGui.QMenu() + self.menu.setTitle("Node") + a = self.menu.addAction("Add input", self.node.addInput) + if not self.node._allowAddInput: + a.setEnabled(False) + a = self.menu.addAction("Add output", self.node.addOutput) + if not self.node._allowAddOutput: + a.setEnabled(False) + a = self.menu.addAction("Remove node", self.node.close) + if not self.node._allowRemove: + a.setEnabled(False) + + #def menuTriggered(self, action): + ##print "node.menuTriggered called. action:", action + #act = str(action.text()) + #if act == "Add input": + #self.node.addInput() + #self.updateActionMenu() + #elif act == "Add output": + #self.node.addOutput() + #self.updateActionMenu() + #elif act == "Remove node": + #self.node.close() + #else: ## only other option is to remove a terminal + #self.node.removeTerminal(act) + #self.terminalMenu.removeAction(action) + + #def updateActionMenu(self): + #for t in self.node.terminals: + #if t not in [str(a.text()) for a in self.terminalMenu.actions()]: + #self.terminalMenu.addAction(t) + #for a in self.terminalMenu.actions(): + #if str(a.text()) not in self.node.terminals: + #self.terminalMenu.removeAction(a) diff --git a/flowchart/Terminal.py b/flowchart/Terminal.py new file mode 100644 index 00000000..d41f702e --- /dev/null +++ b/flowchart/Terminal.py @@ -0,0 +1,555 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +import weakref +from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject +import pyqtgraph.functions as fn +from pyqtgraph.Point import Point +#from PySide import QtCore, QtGui +from eq import * + +class Terminal: + def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, bypass=None): + """Construct a new terminal. Optiona are: + node - the node to which this terminal belongs + name - string, the name of the terminal + io - 'in' or 'out' + optional - bool, whether the node may process without connection to this terminal + multi - bool, for inputs: whether this terminal may make multiple connections + for outputs: whether this terminal creates a different value for each connection + pos - [x, y], the position of the terminal within its node's boundaries + """ + self._io = io + #self._isOutput = opts[0] in ['out', 'io'] + #self._isInput = opts[0]] in ['in', 'io'] + #self._isIO = opts[0]=='io' + self._optional = optional + self._multi = multi + self._node = weakref.ref(node) + self._name = name + self._renamable = renamable + self._connections = {} + self._graphicsItem = TerminalGraphicsItem(self, parent=self._node().graphicsItem()) + self._bypass = bypass + + if multi: + self._value = {} ## dictionary of terminal:value pairs. + else: + self._value = None + + self.valueOk = None + self.recolor() + + def value(self, term=None): + """Return the value this terminal provides for the connected terminal""" + if term is None: + return self._value + + if self.isMultiValue(): + return self._value.get(term, None) + else: + return self._value + + def bypassValue(self): + return self._bypass + + def setValue(self, val, process=True): + """If this is a single-value terminal, val should be a single value. + If this is a multi-value terminal, val should be a dict of terminal:value pairs""" + if not self.isMultiValue(): + if eq(val, self._value): + return + self._value = val + else: + if val is not None: + self._value.update(val) + + self.setValueAcceptable(None) ## by default, input values are 'unchecked' until Node.update(). + if self.isInput() and process: + self.node().update() + + ## Let the flowchart handle this. + #if self.isOutput(): + #for c in self.connections(): + #if c.isInput(): + #c.inputChanged(self) + self.recolor() + + def connected(self, term): + """Called whenever this terminal has been connected to another. (note--this function is called on both terminals)""" + if self.isInput() and term.isOutput(): + self.inputChanged(term) + if self.isOutput() and self.isMultiValue(): + self.node().update() + self.node().connected(self, term) + + def disconnected(self, term): + """Called whenever this terminal has been disconnected from another. (note--this function is called on both terminals)""" + if self.isMultiValue() and term in self._value: + del self._value[term] + self.node().update() + #self.recolor() + else: + if self.isInput(): + self.setValue(None) + self.node().disconnected(self, term) + #self.node().update() + + def inputChanged(self, term, process=True): + """Called whenever there is a change to the input value to this terminal. + It may often be useful to override this function.""" + if self.isMultiValue(): + self.setValue({term: term.value(self)}, process=process) + else: + self.setValue(term.value(self), process=process) + + def valueIsAcceptable(self): + """Returns True->acceptable None->unknown False->Unacceptable""" + return self.valueOk + + def setValueAcceptable(self, v=True): + self.valueOk = v + self.recolor() + + def connections(self): + return self._connections + + def node(self): + return self._node() + + def isInput(self): + return self._io == 'in' + + def isMultiValue(self): + return self._multi + + def isOutput(self): + return self._io == 'out' + + def isRenamable(self): + return self._renamable + + def name(self): + return self._name + + def graphicsItem(self): + return self._graphicsItem + + def isConnected(self): + return len(self.connections()) > 0 + + def connectedTo(self, term): + return term in self.connections() + + def hasInput(self): + #conn = self.extendedConnections() + for t in self.connections(): + if t.isOutput(): + return True + return False + + def inputTerminals(self): + """Return the terminal(s) that give input to this one.""" + #terms = self.extendedConnections() + #for t in terms: + #if t.isOutput(): + #return t + return [t for t in self.connections() if t.isOutput()] + + + def dependentNodes(self): + """Return the list of nodes which receive input from this terminal.""" + #conn = self.extendedConnections() + #del conn[self] + return set([t.node() for t in self.connections() if t.isInput()]) + + def connectTo(self, term, connectionItem=None): + try: + if self.connectedTo(term): + raise Exception('Already connected') + if term is self: + raise Exception('Not connecting terminal to self') + if term.node() is self.node(): + raise Exception("Can't connect to terminal on same node.") + for t in [self, term]: + if t.isInput() and not t._multi and len(t.connections()) > 0: + raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, t.connections().keys())) + #if self.hasInput() and term.hasInput(): + #raise Exception('Target terminal already has input') + + #if term in self.node().terminals.values(): + #if self.isOutput() or term.isOutput(): + #raise Exception('Can not connect an output back to the same node.') + except: + if connectionItem is not None: + connectionItem.close() + raise + + if connectionItem is None: + connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem()) + #self.graphicsItem().scene().addItem(connectionItem) + self.graphicsItem().getViewBox().addItem(connectionItem) + #connectionItem.setParentItem(self.graphicsItem().parent().parent()) + self._connections[term] = connectionItem + term._connections[self] = connectionItem + + self.recolor() + + #if self.isOutput() and term.isInput(): + #term.inputChanged(self) + #if term.isInput() and term.isOutput(): + #self.inputChanged(term) + self.connected(term) + term.connected(self) + + return connectionItem + + def disconnectFrom(self, term): + if not self.connectedTo(term): + return + item = self._connections[term] + #print "removing connection", item + #item.scene().removeItem(item) + item.close() + del self._connections[term] + del term._connections[self] + self.recolor() + term.recolor() + + self.disconnected(term) + term.disconnected(self) + #if self.isOutput() and term.isInput(): + #term.inputChanged(self) + #if term.isInput() and term.isOutput(): + #self.inputChanged(term) + + + def disconnectAll(self): + for t in self._connections.keys(): + self.disconnectFrom(t) + + def recolor(self, color=None, recurse=True): + if color is None: + if not self.isConnected(): ## disconnected terminals are black + color = QtGui.QColor(0,0,0) + elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals + color = QtGui.QColor(200,200,0) + elif self._value is None or eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error) + color = QtGui.QColor(255,255,255) + elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok + color = QtGui.QColor(200, 200, 0) + elif self.valueIsAcceptable() is True: ## terminal has good input, all ok + color = QtGui.QColor(0, 200, 0) + else: ## terminal has bad input + color = QtGui.QColor(200, 0, 0) + self.graphicsItem().setBrush(QtGui.QBrush(color)) + + if recurse: + for t in self.connections(): + t.recolor(color, recurse=False) + + + def rename(self, name): + oldName = self._name + self._name = name + self.node().terminalRenamed(self, oldName) + self.graphicsItem().termRenamed(name) + + def __repr__(self): + return "<Terminal %s.%s>" % (str(self.node().name()), str(self.name())) + + #def extendedConnections(self, terms=None): + #"""Return list of terminals (including this one) that are directly or indirectly wired to this.""" + #if terms is None: + #terms = {} + #terms[self] = None + #for t in self._connections: + #if t in terms: + #continue + #terms.update(t.extendedConnections(terms)) + #return terms + + def __hash__(self): + return id(self) + + def close(self): + self.disconnectAll() + item = self.graphicsItem() + if item.scene() is not None: + item.scene().removeItem(item) + + def saveState(self): + return {'io': self._io, 'multi': self._multi, 'optional': self._optional} + + +#class TerminalGraphicsItem(QtGui.QGraphicsItem): +class TerminalGraphicsItem(GraphicsObject): + + def __init__(self, term, parent=None): + self.term = term + #QtGui.QGraphicsItem.__init__(self, parent) + GraphicsObject.__init__(self, parent) + self.brush = fn.mkBrush(0,0,0) + self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self) + self.label = QtGui.QGraphicsTextItem(self.term.name(), self) + self.label.scale(0.7, 0.7) + #self.setAcceptHoverEvents(True) + self.newConnection = None + self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem + if self.term.isRenamable(): + self.label.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) + self.label.focusOutEvent = self.labelFocusOut + self.label.keyPressEvent = self.labelKeyPress + self.setZValue(1) + self.menu = None + + + def labelFocusOut(self, ev): + QtGui.QGraphicsTextItem.focusOutEvent(self.label, ev) + self.labelChanged() + + def labelKeyPress(self, ev): + if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: + self.labelChanged() + else: + QtGui.QGraphicsTextItem.keyPressEvent(self.label, ev) + + def labelChanged(self): + newName = str(self.label.toPlainText()) + if newName != self.term.name(): + self.term.rename(newName) + + def termRenamed(self, name): + self.label.setPlainText(name) + + def setBrush(self, brush): + self.brush = brush + self.box.setBrush(brush) + + def disconnect(self, target): + self.term.disconnectFrom(target.term) + + def boundingRect(self): + br = self.box.mapRectToParent(self.box.boundingRect()) + lr = self.label.mapRectToParent(self.label.boundingRect()) + return br | lr + + def paint(self, p, *args): + pass + + def setAnchor(self, x, y): + pos = QtCore.QPointF(x, y) + self.anchorPos = pos + br = self.box.mapRectToParent(self.box.boundingRect()) + lr = self.label.mapRectToParent(self.label.boundingRect()) + + + if self.term.isInput(): + self.box.setPos(pos.x(), pos.y()-br.height()/2.) + self.label.setPos(pos.x() + br.width(), pos.y() - lr.height()/2.) + else: + self.box.setPos(pos.x()-br.width(), pos.y()-br.height()/2.) + self.label.setPos(pos.x()-br.width()-lr.width(), pos.y()-lr.height()/2.) + self.updateConnections() + + def updateConnections(self): + for t, c in self.term.connections().iteritems(): + c.updateLine() + + def mousePressEvent(self, ev): + #ev.accept() + ev.ignore() + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + self.label.setFocus(QtCore.Qt.MouseFocusReason) + if ev.button() == QtCore.Qt.RightButton: + if self.raiseContextMenu(ev): + ev.accept() + + def raiseContextMenu(self, ev): + ## only raise menu if this terminal is removable + menu = self.getMenu() + if menu is None: + return False + menu = self.scene().addParentContextMenus(self, menu, ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + return True + + def getMenu(self): + if self.menu is None: + if self.removable(): + self.menu = QtGui.QMenu() + self.menu.setTitle("Terminal") + self.menu.addAction("Remove terminal", self.removeSelf) + else: + return None + return self.menu + + def removable(self): + return ( + (self.term.isOutput() and self.term.node()._allowAddOutput) or + (self.term.isInput() and self.term.node()._allowAddInput)) + + ## probably never need this + #def getContextMenus(self, ev): + #return [self.getMenu()] + + def removeSelf(self): + self.term.node().removeTerminal(self.term) + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + ev.ignore() + return + + ev.accept() + if ev.isStart(): + if self.newConnection is None: + self.newConnection = ConnectionItem(self) + #self.scene().addItem(self.newConnection) + self.getViewBox().addItem(self.newConnection) + #self.newConnection.setParentItem(self.parent().parent()) + + self.newConnection.setTarget(self.mapToView(ev.pos())) + elif ev.isFinish(): + if self.newConnection is not None: + items = self.scene().items(ev.scenePos()) + gotTarget = False + for i in items: + if isinstance(i, TerminalGraphicsItem): + self.newConnection.setTarget(i) + try: + self.term.connectTo(i.term, self.newConnection) + gotTarget = True + except: + self.scene().removeItem(self.newConnection) + self.newConnection = None + raise + break + + if not gotTarget: + #print "remove unused connection" + #self.scene().removeItem(self.newConnection) + self.newConnection.close() + self.newConnection = None + else: + if self.newConnection is not None: + self.newConnection.setTarget(self.mapToView(ev.pos())) + + def hoverEvent(self, ev): + if not ev.isExit() and ev.acceptDrags(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. + self.box.setBrush(fn.mkBrush('w')) + else: + self.box.setBrush(self.brush) + self.update() + + #def hoverEnterEvent(self, ev): + #self.hover = True + + #def hoverLeaveEvent(self, ev): + #self.hover = False + + def connectPoint(self): + ## return the connect position of this terminal in view coords + return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center())) + + def nodeMoved(self): + for t, item in self.term.connections().iteritems(): + item.updateLine() + + +#class ConnectionItem(QtGui.QGraphicsItem): +class ConnectionItem(GraphicsObject): + + def __init__(self, source, target=None): + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + self.setFlags( + self.ItemIsSelectable | + self.ItemIsFocusable + ) + self.source = source + self.target = target + self.length = 0 + self.hovered = False + #self.line = QtGui.QGraphicsLineItem(self) + self.source.getViewBox().addItem(self) + self.updateLine() + self.setZValue(0) + + def close(self): + if self.scene() is not None: + #self.scene().removeItem(self.line) + self.scene().removeItem(self) + + def setTarget(self, target): + self.target = target + self.updateLine() + + def updateLine(self): + start = Point(self.source.connectPoint()) + if isinstance(self.target, TerminalGraphicsItem): + stop = Point(self.target.connectPoint()) + elif isinstance(self.target, QtCore.QPointF): + stop = Point(self.target) + else: + return + self.prepareGeometryChange() + self.resetTransform() + ang = (stop-start).angle(Point(0, 1)) + if ang is None: + ang = 0 + self.rotate(ang) + self.setPos(start) + self.length = (start-stop).length() + self.update() + #self.line.setLine(start.x(), start.y(), stop.x(), stop.y()) + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: + #if isinstance(self.target, TerminalGraphicsItem): + self.source.disconnect(self.target) + ev.accept() + else: + ev.ignore() + + def mousePressEvent(self, ev): + ev.ignore() + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + sel = self.isSelected() + self.setSelected(True) + if not sel and self.isSelected(): + self.update() + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): + self.hovered = True + else: + self.hovered = False + self.update() + + + def boundingRect(self): + #return self.line.boundingRect() + px = self.pixelWidth() + return QtCore.QRectF(-5*px, 0, 10*px, self.length) + + #def shape(self): + #return self.line.shape() + + def paint(self, p, *args): + if self.isSelected(): + p.setPen(fn.mkPen(200, 200, 0, width=3)) + else: + if self.hovered: + p.setPen(fn.mkPen(150, 150, 250, width=1)) + else: + p.setPen(fn.mkPen(100, 100, 250, width=1)) + + p.drawLine(0, 0, 0, self.length) diff --git a/flowchart/__init__.py b/flowchart/__init__.py new file mode 100644 index 00000000..24f562f4 --- /dev/null +++ b/flowchart/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from Flowchart import * + +from library import getNodeType, registerNodeType, getNodeTree \ No newline at end of file diff --git a/flowchart/eq.py b/flowchart/eq.py new file mode 100644 index 00000000..f2f744e4 --- /dev/null +++ b/flowchart/eq.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from numpy import ndarray, bool_ + +def eq(a, b): + """The great missing equivalence function: Guaranteed evaluation to a single bool value.""" + try: + e = a==b + except ValueError: + return False + except AttributeError: + return False + except: + print "a:", str(type(a)), str(a) + print "b:", str(type(b)), str(b) + raise + t = type(e) + if t is bool: + return e + elif t is bool_: + return bool(e) + elif isinstance(e, ndarray): + try: ## disaster: if a is an empty array and b is not, then e.all() is True + if a.shape != b.shape: + return False + except: + return False + return e.all() + else: + raise Exception("== operator returned type %s" % str(type(e))) diff --git a/flowchart/library/Data.py b/flowchart/library/Data.py new file mode 100644 index 00000000..e8999a3d --- /dev/null +++ b/flowchart/library/Data.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- +from ..Node import Node +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +from common import * +from pyqtgraph.Transform import Transform +from pyqtgraph.Point import Point +from pyqtgraph.widgets.TreeWidget import TreeWidget +from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem + +import functions + +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + +class ColumnSelectNode(Node): + """Select named columns from a record array or MetaArray.""" + nodeName = "ColumnSelect" + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in'}}) + self.columns = set() + self.columnList = QtGui.QListWidget() + self.axis = 0 + self.columnList.itemChanged.connect(self.itemChanged) + + def process(self, In, display=True): + if display: + self.updateList(In) + + out = {} + if HAVE_METAARRAY and isinstance(In, metaarray.MetaArray): + for c in self.columns: + out[c] = In[self.axis:c] + elif isinstance(In, np.ndarray) and In.dtype.fields is not None: + for c in self.columns: + out[c] = In[c] + else: + self.In.setValueAcceptable(False) + raise Exception("Input must be MetaArray or ndarray with named fields") + + return out + + def ctrlWidget(self): + return self.columnList + + def updateList(self, data): + if HAVE_METAARRAY and isinstance(data, metaarray.MetaArray): + cols = data.listColumns() + for ax in cols: ## find first axis with columns + if len(cols[ax]) > 0: + self.axis = ax + cols = set(cols[ax]) + break + else: + cols = data.dtype.fields.keys() + + rem = set() + for c in self.columns: + if c not in cols: + self.removeTerminal(c) + rem.add(c) + self.columns -= rem + + self.columnList.blockSignals(True) + self.columnList.clear() + for c in cols: + item = QtGui.QListWidgetItem(c) + item.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsUserCheckable) + if c in self.columns: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + self.columnList.addItem(item) + self.columnList.blockSignals(False) + + + def itemChanged(self, item): + col = str(item.text()) + if item.checkState() == QtCore.Qt.Checked: + if col not in self.columns: + self.columns.add(col) + self.addOutput(col) + else: + if col in self.columns: + self.columns.remove(col) + self.removeTerminal(col) + self.update() + + def saveState(self): + state = Node.saveState(self) + state['columns'] = list(self.columns) + return state + + def restoreState(self, state): + Node.restoreState(self, state) + self.columns = set(state.get('columns', [])) + for c in self.columns: + self.addOutput(c) + + + +class RegionSelectNode(CtrlNode): + """Returns a slice from a 1-D array. Connect the 'widget' output to a plot to display a region-selection widget.""" + nodeName = "RegionSelect" + uiTemplate = [ + ('start', 'spin', {'value': 0, 'step': 0.1}), + ('stop', 'spin', {'value': 0.1, 'step': 0.1}), + ('display', 'check', {'value': True}), + ('movable', 'check', {'value': True}), + ] + + def __init__(self, name): + self.items = {} + CtrlNode.__init__(self, name, terminals={ + 'data': {'io': 'in'}, + 'selected': {'io': 'out'}, + 'region': {'io': 'out'}, + 'widget': {'io': 'out', 'multi': True} + }) + self.ctrls['display'].toggled.connect(self.displayToggled) + self.ctrls['movable'].toggled.connect(self.movableToggled) + + def displayToggled(self, b): + for item in self.items.itervalues(): + item.setVisible(b) + + def movableToggled(self, b): + for item in self.items.itervalues(): + item.setMovable(b) + + + def process(self, data=None, display=True): + #print "process.." + s = self.stateGroup.state() + region = [s['start'], s['stop']] + + if display: + conn = self['widget'].connections() + for c in conn: + plot = c.node().getPlot() + if plot is None: + continue + if c in self.items: + item = self.items[c] + item.setRegion(region) + #print " set rgn:", c, region + #item.setXVals(events) + else: + item = LinearRegionItem(values=region) + self.items[c] = item + #item.connect(item, QtCore.SIGNAL('regionChanged'), self.rgnChanged) + item.sigRegionChanged.connect(self.rgnChanged) + item.setVisible(s['display']) + item.setMovable(s['movable']) + #print " new rgn:", c, region + #self.items[c].setYRange([0., 0.2], relative=True) + + if self.selected.isConnected(): + if data is None: + sliced = None + elif isinstance(data, MetaArray): + sliced = data[0:s['start']:s['stop']] + else: + mask = (data['time'] >= s['start']) * (data['time'] < s['stop']) + sliced = data[mask] + else: + sliced = None + + return {'selected': sliced, 'widget': self.items, 'region': region} + + + def rgnChanged(self, item): + region = item.getRegion() + self.stateGroup.setState({'start': region[0], 'stop': region[1]}) + self.update() + + +class EvalNode(Node): + """Return the output of a string evaluated/executed by the python interpreter. + The string may be either an expression or a python script, and inputs are accessed as the name of the terminal. + For expressions, a single value may be evaluated for a single output, or a dict for multiple outputs. + For a script, the text will be executed as the body of a function.""" + nodeName = 'PythonEval' + + def __init__(self, name): + Node.__init__(self, name, + terminals = { + 'input': {'io': 'in', 'renamable': True}, + 'output': {'io': 'out', 'renamable': True}, + }, + allowAddInput=True, allowAddOutput=True) + + self.ui = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + self.addInBtn = QtGui.QPushButton('+Input') + self.addOutBtn = QtGui.QPushButton('+Output') + self.text = QtGui.QTextEdit() + self.text.setTabStopWidth(30) + self.layout.addWidget(self.addInBtn, 0, 0) + self.layout.addWidget(self.addOutBtn, 0, 1) + self.layout.addWidget(self.text, 1, 0, 1, 2) + self.ui.setLayout(self.layout) + + #QtCore.QObject.connect(self.addInBtn, QtCore.SIGNAL('clicked()'), self.addInput) + self.addInBtn.clicked.connect(self.addInput) + #QtCore.QObject.connect(self.addOutBtn, QtCore.SIGNAL('clicked()'), self.addOutput) + self.addOutBtn.clicked.connect(self.addOutput) + self.ui.focusOutEvent = lambda ev: self.focusOutEvent(ev) + self.lastText = None + + def ctrlWidget(self): + return self.ui + + def addInput(self): + Node.addInput(self, 'input', renamable=True) + + def addOutput(self): + Node.addOutput(self, 'output', renamable=True) + + def focusOutEvent(self, ev): + text = str(self.text.toPlainText()) + if text != self.lastText: + self.lastText = text + print "eval node update" + self.update() + + def process(self, display=True, **args): + l = locals() + l.update(args) + ## try eval first, then exec + try: + text = str(self.text.toPlainText()).replace('\n', ' ') + output = eval(text, globals(), l) + except SyntaxError: + fn = "def fn(**args):\n" + run = "\noutput=fn(**args)\n" + text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run + exec(text) + return output + + def saveState(self): + state = Node.saveState(self) + state['text'] = str(self.text.toPlainText()) + state['terminals'] = self.saveTerminals() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + self.text.clear() + self.text.insertPlainText(state['text']) + self.restoreTerminals(state['terminals']) + self.update() + +class ColumnJoinNode(Node): + """Concatenates record arrays and/or adds new columns""" + nodeName = 'ColumnJoin' + + def __init__(self, name): + Node.__init__(self, name, terminals = { + 'output': {'io': 'out'}, + }) + + #self.items = [] + + self.ui = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + self.ui.setLayout(self.layout) + + self.tree = TreeWidget() + self.addInBtn = QtGui.QPushButton('+ Input') + self.remInBtn = QtGui.QPushButton('- Input') + + self.layout.addWidget(self.tree, 0, 0, 1, 2) + self.layout.addWidget(self.addInBtn, 1, 0) + self.layout.addWidget(self.remInBtn, 1, 1) + + self.addInBtn.clicked.connect(self.addInput) + self.remInBtn.clicked.connect(self.remInput) + self.tree.sigItemMoved.connect(self.update) + + def ctrlWidget(self): + return self.ui + + def addInput(self): + #print "ColumnJoinNode.addInput called." + term = Node.addInput(self, 'input', renamable=True) + #print "Node.addInput returned. term:", term + item = QtGui.QTreeWidgetItem([term.name()]) + item.term = term + term.joinItem = item + #self.items.append((term, item)) + self.tree.addTopLevelItem(item) + + def remInput(self): + sel = self.tree.currentItem() + term = sel.term + term.joinItem = None + sel.term = None + self.tree.removeTopLevelItem(sel) + self.removeTerminal(term) + self.update() + + def process(self, display=True, **args): + order = self.order() + vals = [] + for name in order: + if name not in args: + continue + val = args[name] + if isinstance(val, np.ndarray) and len(val.dtype) > 0: + vals.append(val) + else: + vals.append((name, None, val)) + return {'output': functions.concatenateColumns(vals)} + + def order(self): + return [str(self.tree.topLevelItem(i).text(0)) for i in range(self.tree.topLevelItemCount())] + + def saveState(self): + state = Node.saveState(self) + state['order'] = self.order() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + inputs = [inp.name() for inp in self.inputs()] + for name in inputs: + if name not in state['order']: + self.removeTerminal(name) + for name in state['order']: + if name not in inputs: + Node.addInput(self, name, renamable=True) + + self.tree.clear() + for name in state['order']: + term = self[name] + item = QtGui.QTreeWidgetItem([name]) + item.term = term + term.joinItem = item + #self.items.append((term, item)) + self.tree.addTopLevelItem(item) + + def terminalRenamed(self, term, oldName): + Node.terminalRenamed(self, term, oldName) + item = term.joinItem + item.setText(0, term.name()) + self.update() + + \ No newline at end of file diff --git a/flowchart/library/Display.py b/flowchart/library/Display.py new file mode 100644 index 00000000..4379858f --- /dev/null +++ b/flowchart/library/Display.py @@ -0,0 +1,245 @@ +# -*- coding: utf-8 -*- +from ..Node import Node +import weakref +#from pyqtgraph import graphicsItems +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem +from pyqtgraph.graphicsItems.PlotCurveItem import PlotCurveItem + +from common import * +import numpy as np + +class PlotWidgetNode(Node): + """Connection to PlotWidget. Will plot arrays, metaarrays, and display event lists.""" + nodeName = 'PlotWidget' + sigPlotChanged = QtCore.Signal(object) + + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) + self.plot = None + self.items = {} + + def disconnected(self, localTerm, remoteTerm): + if localTerm is self.In and remoteTerm in self.items: + self.plot.removeItem(self.items[remoteTerm]) + del self.items[remoteTerm] + + def setPlot(self, plot): + #print "======set plot" + self.plot = plot + self.sigPlotChanged.emit(self) + + def getPlot(self): + return self.plot + + def process(self, In, display=True): + if display: + #self.plot.clearPlots() + items = set() + for name, vals in In.iteritems(): + if vals is None: + continue + if type(vals) is not list: + vals = [vals] + + for val in vals: + vid = id(val) + if vid in self.items: + items.add(vid) + else: + #if isinstance(val, PlotCurveItem): + #self.plot.addItem(val) + #item = val + #if isinstance(val, ScatterPlotItem): + #self.plot.addItem(val) + #item = val + if isinstance(val, QtGui.QGraphicsItem): + self.plot.addItem(val) + item = val + else: + item = self.plot.plot(val) + self.items[vid] = item + items.add(vid) + for vid in self.items.keys(): + if vid not in items: + #print "remove", self.items[vid] + self.plot.removeItem(self.items[vid]) + del self.items[vid] + + #def setInput(self, **args): + #for k in args: + #self.plot.plot(args[k]) + + + +class CanvasNode(Node): + """Connection to a Canvas widget.""" + nodeName = 'CanvasWidget' + + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) + self.canvas = None + self.items = {} + + def disconnected(self, localTerm, remoteTerm): + if localTerm is self.In and remoteTerm in self.items: + self.canvas.removeItem(self.items[remoteTerm]) + del self.items[remoteTerm] + + def setCanvas(self, canvas): + self.canvas = canvas + + def getCanvas(self): + return self.canvas + + def process(self, In, display=True): + if display: + items = set() + for name, vals in In.iteritems(): + if vals is None: + continue + if type(vals) is not list: + vals = [vals] + + for val in vals: + vid = id(val) + if vid in self.items: + items.add(vid) + else: + self.canvas.addItem(val) + item = val + self.items[vid] = item + items.add(vid) + for vid in self.items.keys(): + if vid not in items: + #print "remove", self.items[vid] + self.canvas.removeItem(self.items[vid]) + del self.items[vid] + + + + +class ScatterPlot(CtrlNode): + """Generates a scatter plot from a record array or nested dicts""" + nodeName = 'ScatterPlot' + uiTemplate = [ + ('x', 'combo', {'values': [], 'index': 0}), + ('y', 'combo', {'values': [], 'index': 0}), + ('sizeEnabled', 'check', {'value': False}), + ('size', 'combo', {'values': [], 'index': 0}), + ('absoluteSize', 'check', {'value': False}), + ('colorEnabled', 'check', {'value': False}), + ('color', 'colormap', {}), + ('borderEnabled', 'check', {'value': False}), + ('border', 'colormap', {}), + ] + + def __init__(self, name): + CtrlNode.__init__(self, name, terminals={ + 'input': {'io': 'in'}, + 'plot': {'io': 'out'} + }) + self.item = ScatterPlotItem() + self.keys = [] + + #self.ui = QtGui.QWidget() + #self.layout = QtGui.QGridLayout() + #self.ui.setLayout(self.layout) + + #self.xCombo = QtGui.QComboBox() + #self.yCombo = QtGui.QComboBox() + + + + def process(self, input, display=True): + #print "scatterplot process" + if not display: + return {'plot': None} + + self.updateKeys(input[0]) + + x = str(self.ctrls['x'].currentText()) + y = str(self.ctrls['y'].currentText()) + size = str(self.ctrls['size'].currentText()) + pen = QtGui.QPen(QtGui.QColor(0,0,0,0)) + points = [] + for i in input: + pt = {'pos': (i[x], i[y])} + if self.ctrls['sizeEnabled'].isChecked(): + pt['size'] = i[size] + if self.ctrls['borderEnabled'].isChecked(): + pt['pen'] = QtGui.QPen(self.ctrls['border'].getColor(i)) + else: + pt['pen'] = pen + if self.ctrls['colorEnabled'].isChecked(): + pt['brush'] = QtGui.QBrush(self.ctrls['color'].getColor(i)) + points.append(pt) + self.item.setPxMode(not self.ctrls['absoluteSize'].isChecked()) + + self.item.setPoints(points) + + return {'plot': self.item} + + + + def updateKeys(self, data): + if isinstance(data, dict): + keys = data.keys() + elif isinstance(data, list) or isinstance(data, tuple): + keys = data + elif isinstance(data, np.ndarray) or isinstance(data, np.void): + keys = data.dtype.names + else: + print "Unknown data type:", type(data), data + return + + for c in self.ctrls.itervalues(): + c.blockSignals(True) + for c in [self.ctrls['x'], self.ctrls['y'], self.ctrls['size']]: + cur = str(c.currentText()) + c.clear() + for k in keys: + c.addItem(k) + if k == cur: + c.setCurrentIndex(c.count()-1) + for c in [self.ctrls['color'], self.ctrls['border']]: + c.setArgList(keys) + for c in self.ctrls.itervalues(): + c.blockSignals(False) + + self.keys = keys + + + def saveState(self): + state = CtrlNode.saveState(self) + return {'keys': self.keys, 'ctrls': state} + + def restoreState(self, state): + self.updateKeys(state['keys']) + CtrlNode.restoreState(self, state['ctrls']) + +#class ImageItem(Node): + #"""Creates an ImageItem for display in a canvas from a file handle.""" + #nodeName = 'Image' + + #def __init__(self, name): + #Node.__init__(self, name, terminals={ + #'file': {'io': 'in'}, + #'image': {'io': 'out'} + #}) + #self.imageItem = graphicsItems.ImageItem() + #self.handle = None + + #def process(self, file, display=True): + #if not display: + #return {'image': None} + + #if file != self.handle: + #self.handle = file + #data = file.read() + #self.imageItem.updateImage(data) + + #pos = file. + + + \ No newline at end of file diff --git a/flowchart/library/EventDetection.py b/flowchart/library/EventDetection.py new file mode 100644 index 00000000..eb23b90a --- /dev/null +++ b/flowchart/library/EventDetection.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- + +from ..Node import Node +import functions +from common import * + +class Threshold(CtrlNode): + """Absolute threshold detection filter. Returns indexes where data crosses threshold.""" + nodeName = 'ThresholdDetect' + uiTemplate = [ + ('direction', 'combo', {'values': ['rising', 'falling'], 'index': 0}), + ('threshold', 'spin', {'value': 0, 'step': 1, 'minStep': 1e-12, 'dec': True, 'range': [None, None], 'siPrefix': True}), + ] + + def __init__(self, name, **opts): + CtrlNode.__init__(self, name, self.uiTemplate) + + def processData(self, data): + s = self.stateGroup.state() + if s['direction'] == 'rising': + d = 1 + else: + d = -1 + return functions.threshold(data, s['threshold'], d) + +class StdevThreshold(CtrlNode): + """Relative threshold event detection. Finds regions in data greater than threshold*stdev. + Returns a record array with columns: index, length, sum, peak. + This function is only useful for data with its baseline removed.""" + + nodeName = 'StdevThreshold' + uiTemplate = [ + ('threshold', 'spin', {'value': 0, 'step': 1, 'minStep': 0.1, 'dec': True, 'range': [None, None], 'siPrefix': True}), + ] + + def __init__(self, name, **opts): + CtrlNode.__init__(self, name, self.uiTemplate) + + def processData(self, data): + s = self.stateGroup.state() + return functions.stdevThresholdEvents(data, s['threshold']) + + +class ZeroCrossingEvents(CtrlNode): + """Detects events in a waveform by splitting the data up into chunks separated by zero-crossings, + then keeping only the ones that meet certain criteria.""" + nodeName = 'ZeroCrossing' + uiTemplate = [ + ('minLength', 'intSpin', {'value': 0, 'min': 0, 'max': 100000}), + ('minSum', 'spin', {'value': 0, 'step': 1, 'minStep': 0.1, 'dec': True, 'range': [None, None], 'siPrefix': True}), + ('minPeak', 'spin', {'value': 0, 'step': 1, 'minStep': 0.1, 'dec': True, 'range': [None, None], 'siPrefix': True}), + ('eventLimit', 'intSpin', {'value': 400, 'min': 1, 'max': 1e9}), + ] + + def __init__(self, name, **opts): + CtrlNode.__init__(self, name, self.uiTemplate) + + def processData(self, data): + s = self.stateGroup.state() + events = functions.zeroCrossingEvents(data, minLength=s['minLength'], minPeak=s['minPeak'], minSum=s['minSum']) + events = events[:s['eventLimit']] + return events + +class ThresholdEvents(CtrlNode): + """Detects regions of a waveform that cross a threshold (positive or negative) and returns the time, length, sum, and peak of each event.""" + nodeName = 'ThresholdEvents' + uiTemplate = [ + ('threshold', 'spin', {'value': 1e-12, 'step': 1, 'minStep': 0.1, 'dec': True, 'range': [None, None], 'siPrefix': True, 'tip': 'Events are detected only if they cross this threshold.'}), + ('adjustTimes', 'check', {'value': True, 'tip': 'If False, then event times are reported where the trace crosses threshold. If True, the event time is adjusted to estimate when the trace would have crossed 0.'}), + #('index', 'combo', {'values':['start','peak'], 'index':0}), + ('minLength', 'intSpin', {'value': 0, 'min': 0, 'max': 1e9, 'tip': 'Events must contain this many samples to be detected.'}), + ('minSum', 'spin', {'value': 0, 'step': 1, 'minStep': 0.1, 'dec': True, 'range': [None, None], 'siPrefix': True}), + ('minPeak', 'spin', {'value': 0, 'step': 1, 'minStep': 0.1, 'dec': True, 'range': [None, None], 'siPrefix': True, 'tip': 'Events must reach this threshold to be detected.'}), + ('eventLimit', 'intSpin', {'value': 100, 'min': 1, 'max': 1e9, 'tip': 'Limits the number of events that may be detected in a single trace. This prevents runaway processes due to over-sensitive detection criteria.'}), + ('deadTime', 'spin', {'value': 0, 'step': 1, 'minStep': 1e-4, 'range': [0,None], 'siPrefix': True, 'suffix': 's', 'tip': 'Ignore events that occur too quickly following another event.'}), + ('reverseTime', 'spin', {'value': 0, 'step': 1, 'minStep': 1e-4, 'range': [0,None], 'siPrefix': True, 'suffix': 's', 'tip': 'Ignore events that 1) have the opposite sign of the event immediately prior and 2) occur within the given time window after the prior event. This is useful for ignoring rebound signals.'}), + ] + + def __init__(self, name, **opts): + CtrlNode.__init__(self, name, self.uiTemplate) + #self.addOutput('plot') + #self.remotePlot = None + + #def connected(self, term, remote): + #CtrlNode.connected(self, term, remote) + #if term is not self.plot: + #return + #node = remote.node() + #node.sigPlotChanged.connect(self.connectToPlot) + #self.connectToPlot(node) + + #def connectToPlot(self, node): + #if self.remotePlot is not None: + #self.remotePlot = None + + #if node.plot is None: + #return + #plot = self.plot. + + #def disconnected(self, term, remote): + #CtrlNode.disconnected(self, term, remote) + #if term is not self.plot: + #return + #remote.node().sigPlotChanged.disconnect(self.connectToPlot) + #self.disconnectFromPlot() + + #def disconnectFromPlot(self): + #if self.remotePlot is None: + #return + #for l in self.lines: + #l.scene().removeItem(l) + #self.lines = [] + + def processData(self, data): + s = self.stateGroup.state() + events = functions.thresholdEvents(data, s['threshold'], s['adjustTimes']) + + ## apply first round of filters + mask = events['len'] >= s['minLength'] + mask *= abs(events['sum']) >= s['minSum'] + mask *= abs(events['peak']) >= s['minPeak'] + events = events[mask] + + ## apply deadtime filter + mask = np.ones(len(events), dtype=bool) + last = 0 + dt = s['deadTime'] + rt = s['reverseTime'] + for i in xrange(1, len(events)): + tdiff = events[i]['time'] - events[last]['time'] + if tdiff < dt: ## check dead time + mask[i] = False + elif tdiff < rt and (events[i]['peak'] * events[last]['peak'] < 0): ## check reverse time + mask[i] = False + else: + last = i + #mask[1:] *= (events[1:]['time']-events[:-1]['time']) >= s['deadTime'] + events = events[mask] + + ## limit number of events + events = events[:s['eventLimit']] + return events + + + + + +class SpikeDetector(CtrlNode): + """Very simple spike detector. Returns the indexes of sharp spikes by comparing each sample to its neighbors.""" + nodeName = "SpikeDetect" + uiTemplate = [ + ('radius', 'intSpin', {'value': 1, 'min': 1, 'max': 100000}), + ('minDiff', 'spin', {'value': 0, 'step': 1, 'minStep': 1e-12, 'dec': True, 'siPrefix': True}), + ] + + def __init__(self, name, **opts): + CtrlNode.__init__(self, name, self.uiTemplate) + + def processData(self, data): + s = self.stateGroup.state() + radius = s['radius'] + d1 = data.view(np.ndarray) + d2 = data[radius:] - data[:-radius] #a derivative + mask1 = d2 > s['minDiff'] #where derivative is large and positive + mask2 = d2 < -s['minDiff'] #where derivative is large and negative + maskpos = mask1[:-radius] * mask2[radius:] #both need to be true + maskneg = mask1[radius:] * mask2[:-radius] + mask = maskpos + maskneg ## All regions that match criteria + + ## now reduce consecutive hits to a single hit. + hits = (mask[1:] - mask[:-1]) > 0 + sHits = np.argwhere(hits)[:,0]+(radius+2) + + ## convert to record array with 'index' column + ret = np.empty(len(sHits), dtype=[('index', int), ('time', float)]) + ret['index'] = sHits + ret['time'] = data.xvals('Time')[sHits] + return ret + + def processBypassed(self, args): + return {'Out': np.empty(0, dtype=[('index', int), ('time', float)])} + + + + + + \ No newline at end of file diff --git a/flowchart/library/Filters.py b/flowchart/library/Filters.py new file mode 100644 index 00000000..1819b01e --- /dev/null +++ b/flowchart/library/Filters.py @@ -0,0 +1,245 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +from ..Node import Node +from scipy.signal import detrend +from scipy.ndimage import median_filter, gaussian_filter +#from pyqtgraph.SignalProxy import SignalProxy +import functions +from common import * +import numpy as np + +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + + +class Downsample(CtrlNode): + """Downsample by averaging samples together.""" + nodeName = 'Downsample' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + def processData(self, data): + return functions.downsample(data, self.ctrls['n'].value(), axis=0) + + +class Subsample(CtrlNode): + """Downsample by selecting every Nth sample.""" + nodeName = 'Subsample' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + def processData(self, data): + return data[::self.ctrls['n'].value()] + + +class Bessel(CtrlNode): + """Bessel filter. Input data must have time values.""" + nodeName = 'BesselFilter' + uiTemplate = [ + ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), + ('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}), + ('bidir', 'check', {'checked': True}) + ] + + def processData(self, data): + s = self.stateGroup.state() + if s['band'] == 'lowpass': + mode = 'low' + else: + mode = 'high' + return functions.besselFilter(data, bidir=s['bidir'], btype=mode, cutoff=s['cutoff'], order=s['order']) + + +class Butterworth(CtrlNode): + """Butterworth filter""" + nodeName = 'ButterworthFilter' + uiTemplate = [ + ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), + ('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('bidir', 'check', {'checked': True}) + ] + + def processData(self, data): + s = self.stateGroup.state() + if s['band'] == 'lowpass': + mode = 'low' + else: + mode = 'high' + ret = functions.butterworthFilter(data, bidir=s['bidir'], btype=mode, wPass=s['wPass'], wStop=s['wStop'], gPass=s['gPass'], gStop=s['gStop']) + return ret + + +class Mean(CtrlNode): + """Filters data by taking the mean of a sliding window""" + nodeName = 'MeanFilter' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + n = self.ctrls['n'].value() + return functions.rollingSum(data, n) / n + + +class Median(CtrlNode): + """Filters data by taking the median of a sliding window""" + nodeName = 'MedianFilter' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + return median_filter(data, self.ctrls['n'].value()) + +class Mode(CtrlNode): + """Filters data by taking the mode (histogram-based) of a sliding window""" + nodeName = 'ModeFilter' + uiTemplate = [ + ('window', 'intSpin', {'value': 500, 'min': 1, 'max': 1000000}), + ] + + @metaArrayWrapper + def processData(self, data): + return functions.modeFilter(data, self.ctrls['window'].value()) + + +class Denoise(CtrlNode): + """Removes anomalous spikes from data, replacing with nearby values""" + nodeName = 'DenoiseFilter' + uiTemplate = [ + ('radius', 'intSpin', {'value': 2, 'min': 0, 'max': 1000000}), + ('threshold', 'doubleSpin', {'value': 4.0, 'min': 0, 'max': 1000}) + ] + + def processData(self, data): + #print "DENOISE" + s = self.stateGroup.state() + return functions.denoise(data, **s) + + +class Gaussian(CtrlNode): + """Gaussian smoothing filter.""" + nodeName = 'GaussianFilter' + uiTemplate = [ + ('sigma', 'doubleSpin', {'min': 0, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + return gaussian_filter(data, self.ctrls['sigma'].value()) + + +class Derivative(CtrlNode): + """Returns the pointwise derivative of the input""" + nodeName = 'DerivativeFilter' + + def processData(self, data): + if HAVE_METAARRAY and isinstance(data, metaarray.MetaArray): + info = data.infoCopy() + if 'values' in info[0]: + info[0]['values'] = info[0]['values'][:-1] + return MetaArray(data[1:] - data[:-1], info=info) + else: + return data[1:] - data[:-1] + + +class Integral(CtrlNode): + """Returns the pointwise integral of the input""" + nodeName = 'IntegralFilter' + + @metaArrayWrapper + def processData(self, data): + data[1:] += data[:-1] + return data + + +class Detrend(CtrlNode): + """Removes linear trend from the data""" + nodeName = 'DetrendFilter' + + @metaArrayWrapper + def processData(self, data): + return detrend(data) + + +class AdaptiveDetrend(CtrlNode): + """Removes baseline from data, ignoring anomalous events""" + nodeName = 'AdaptiveDetrend' + uiTemplate = [ + ('threshold', 'doubleSpin', {'value': 3.0, 'min': 0, 'max': 1000000}) + ] + + def processData(self, data): + return functions.adaptiveDetrend(data, threshold=self.ctrls['threshold'].value()) + +class HistogramDetrend(CtrlNode): + """Removes baseline from data by computing mode (from histogram) of beginning and end of data.""" + nodeName = 'HistogramDetrend' + uiTemplate = [ + ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000}), + ('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}) + ] + + def processData(self, data): + ws = self.ctrls['windowSize'].value() + bn = self.ctrls['numBins'].value() + return functions.histogramDetrend(data, window=ws, bins=bn) + + +class ExpDeconvolve(CtrlNode): + """Exponential deconvolution filter.""" + nodeName = 'ExpDeconvolve' + uiTemplate = [ + ('tau', 'spin', {'value': 10e-3, 'step': 1, 'minStep': 100e-6, 'dec': True, 'range': [0.0, None], 'suffix': 's', 'siPrefix': True}) + ] + + def processData(self, data): + tau = self.ctrls['tau'].value() + return functions.expDeconvolve(data, tau) + #dt = 1 + #if isinstance(data, MetaArray): + #dt = data.xvals(0)[1] - data.xvals(0)[0] + #d = data[:-1] + (self.ctrls['tau'].value() / dt) * (data[1:] - data[:-1]) + #if isinstance(data, MetaArray): + #info = data.infoCopy() + #if 'values' in info[0]: + #info[0]['values'] = info[0]['values'][:-1] + #return MetaArray(d, info=info) + #else: + #return d + +class ExpReconvolve(CtrlNode): + """Exponential reconvolution filter. Only works with MetaArrays that were previously deconvolved.""" + nodeName = 'ExpReconvolve' + #uiTemplate = [ + #('tau', 'spin', {'value': 10e-3, 'step': 1, 'minStep': 100e-6, 'dec': True, 'range': [0.0, None], 'suffix': 's', 'siPrefix': True}) + #] + + def processData(self, data): + return functions.expReconvolve(data) + +class Tauiness(CtrlNode): + """Sliding-window exponential fit""" + nodeName = 'Tauiness' + uiTemplate = [ + ('window', 'intSpin', {'value': 100, 'min': 3, 'max': 1000000}), + ('skip', 'intSpin', {'value': 10, 'min': 0, 'max': 10000000}) + ] + + def processData(self, data): + return functions.tauiness(data, self.ctrls['window'].value(), self.ctrls['skip'].value()) + + + + \ No newline at end of file diff --git a/flowchart/library/Operators.py b/flowchart/library/Operators.py new file mode 100644 index 00000000..412af573 --- /dev/null +++ b/flowchart/library/Operators.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from ..Node import Node + +class UniOpNode(Node): + """Generic node for performing any operation like Out = In.fn()""" + def __init__(self, name, fn): + self.fn = fn + Node.__init__(self, name, terminals={ + 'In': {'io': 'in'}, + 'Out': {'io': 'out', 'bypass': 'In'} + }) + + def process(self, **args): + return {'Out': getattr(args['In'], self.fn)()} + +class BinOpNode(Node): + """Generic node for performing any operation like A.fn(B)""" + def __init__(self, name, fn): + self.fn = fn + Node.__init__(self, name, terminals={ + 'A': {'io': 'in'}, + 'B': {'io': 'in'}, + 'Out': {'io': 'out', 'bypass': 'A'} + }) + + def process(self, **args): + fn = getattr(args['A'], self.fn) + out = fn(args['B']) + if out is NotImplemented: + raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B'])))) + #print " ", fn, out + return {'Out': out} + + +class AbsNode(UniOpNode): + """Returns abs(Inp). Does not check input types.""" + nodeName = 'Abs' + def __init__(self, name): + UniOpNode.__init__(self, name, '__abs__') + +class AddNode(BinOpNode): + """Returns A + B. Does not check input types.""" + nodeName = 'Add' + def __init__(self, name): + BinOpNode.__init__(self, name, '__add__') + +class SubtractNode(BinOpNode): + """Returns A - B. Does not check input types.""" + nodeName = 'Subtract' + def __init__(self, name): + BinOpNode.__init__(self, name, '__sub__') + +class MultiplyNode(BinOpNode): + """Returns A * B. Does not check input types.""" + nodeName = 'Multiply' + def __init__(self, name): + BinOpNode.__init__(self, name, '__mul__') + +class DivideNode(BinOpNode): + """Returns A / B. Does not check input types.""" + nodeName = 'Divide' + def __init__(self, name): + BinOpNode.__init__(self, name, '__div__') + diff --git a/flowchart/library/__init__.py b/flowchart/library/__init__.py new file mode 100644 index 00000000..58b5b810 --- /dev/null +++ b/flowchart/library/__init__.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +from collections import OrderedDict +import os, types +from pyqtgraph.debug import printExc +from ..Node import Node +import reload + + +NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses +NODE_TREE = OrderedDict() ## categorized tree of Node subclasses + +def getNodeType(name): + try: + return NODE_LIST[name] + except KeyError: + raise Exception("No node type called '%s'" % name) + +def getNodeTree(): + return NODE_TREE + +def registerNodeType(cls, paths, override=False): + """ + Register a new node type. If the type's name is already in use, + an exception will be raised (unless override=True). + + Arguments: + cls - a subclass of Node (must have typ.nodeName) + paths - list of tuples specifying the location(s) this + type will appear in the library tree. + override - if True, overwrite any class having the same name + """ + if not isNodeClass(cls): + raise Exception("Object %s is not a Node subclass" % str(cls)) + + name = cls.nodeName + if not override and name in NODE_LIST: + raise Exception("Node type name '%s' is already registered." % name) + + NODE_LIST[name] = cls + for path in paths: + root = NODE_TREE + for n in path: + if n not in root: + root[n] = OrderedDict() + root = root[n] + root[name] = cls + + + +def isNodeClass(cls): + try: + if not issubclass(cls, Node): + return False + except: + return False + return hasattr(cls, 'nodeName') + +def loadLibrary(reloadLibs=False, libPath=None): + """Import all Node subclasses found within files in the library module.""" + + global NODE_LIST, NODE_TREE + if libPath is None: + libPath = os.path.dirname(os.path.abspath(__file__)) + + if reloadLibs: + reload.reloadAll(libPath) + + for f in os.listdir(libPath): + pathName, ext = os.path.splitext(f) + if ext != '.py' or '__init__' in pathName: + continue + try: + #print "importing from", f + mod = __import__(pathName, globals(), locals()) + except: + printExc("Error loading flowchart library %s:" % pathName) + continue + + nodes = [] + for n in dir(mod): + o = getattr(mod, n) + if isNodeClass(o): + #print " ", str(o) + registerNodeType(o, [(pathName,)], override=reloadLibs) + #nodes.append((o.nodeName, o)) + #if len(nodes) > 0: + #NODE_TREE[name] = OrderedDict(nodes) + #NODE_LIST.extend(nodes) + #NODE_LIST = OrderedDict(NODE_LIST) + +def reloadLibrary(): + loadLibrary(reloadLibs=True) + +loadLibrary() +#NODE_LIST = [] +#for o in locals().values(): + #if type(o) is type(AddNode) and issubclass(o, Node) and o is not Node and hasattr(o, 'nodeName'): + #NODE_LIST.append((o.nodeName, o)) +#NODE_LIST.sort(lambda a,b: cmp(a[0], b[0])) +#NODE_LIST = OrderedDict(NODE_LIST) \ No newline at end of file diff --git a/flowchart/library/common.py b/flowchart/library/common.py new file mode 100644 index 00000000..ce7ff68f --- /dev/null +++ b/flowchart/library/common.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.widgets.SpinBox import SpinBox +#from pyqtgraph.SignalProxy import SignalProxy +from pyqtgraph.WidgetGroup import WidgetGroup +#from ColorMapper import ColorMapper +from ..Node import Node +import numpy as np +from pyqtgraph.widgets.ColorButton import ColorButton +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + + +def generateUi(opts): + """Convenience function for generating common UI types""" + widget = QtGui.QWidget() + l = QtGui.QFormLayout() + l.setSpacing(0) + widget.setLayout(l) + ctrls = {} + row = 0 + for opt in opts: + if len(opt) == 2: + k, t = opt + o = {} + elif len(opt) == 3: + k, t, o = opt + else: + raise Exception("Widget specification must be (name, type) or (name, type, {opts})") + if t == 'intSpin': + w = QtGui.QSpinBox() + if 'max' in o: + w.setMaximum(o['max']) + if 'min' in o: + w.setMinimum(o['min']) + if 'value' in o: + w.setValue(o['value']) + elif t == 'doubleSpin': + w = QtGui.QDoubleSpinBox() + if 'max' in o: + w.setMaximum(o['max']) + if 'min' in o: + w.setMinimum(o['min']) + if 'value' in o: + w.setValue(o['value']) + elif t == 'spin': + w = SpinBox() + w.setOpts(**o) + elif t == 'check': + w = QtGui.QCheckBox() + if 'checked' in o: + w.setChecked(o['checked']) + elif t == 'combo': + w = QtGui.QComboBox() + for i in o['values']: + w.addItem(i) + #elif t == 'colormap': + #w = ColorMapper() + elif t == 'color': + w = ColorButton() + else: + raise Exception("Unknown widget type '%s'" % str(t)) + if 'tip' in o: + w.setToolTip(o['tip']) + w.setObjectName(k) + l.addRow(k, w) + if o.get('hidden', False): + w.hide() + label = l.labelForField(w) + label.hide() + + ctrls[k] = w + w.rowNum = row + row += 1 + group = WidgetGroup(widget) + return widget, group, ctrls + + +class CtrlNode(Node): + """Abstract class for nodes with auto-generated control UI""" + + sigStateChanged = QtCore.Signal(object) + + def __init__(self, name, ui=None, terminals=None): + if ui is None: + if hasattr(self, 'uiTemplate'): + ui = self.uiTemplate + else: + ui = [] + if terminals is None: + terminals = {'In': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'In'}} + Node.__init__(self, name=name, terminals=terminals) + + self.ui, self.stateGroup, self.ctrls = generateUi(ui) + self.stateGroup.sigChanged.connect(self.changed) + + def ctrlWidget(self): + return self.ui + + def changed(self): + self.update() + self.sigStateChanged.emit(self) + + def process(self, In, display=True): + out = self.processData(In) + return {'Out': out} + + def saveState(self): + state = Node.saveState(self) + state['ctrl'] = self.stateGroup.state() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + if self.stateGroup is not None: + self.stateGroup.setState(state.get('ctrl', {})) + + def hideRow(self, name): + w = self.ctrls[name] + l = self.ui.layout().labelForField(w) + w.hide() + l.hide() + + def showRow(self, name): + w = self.ctrls[name] + l = self.ui.layout().labelForField(w) + w.show() + l.show() + + + +def metaArrayWrapper(fn): + def newFn(self, data, *args, **kargs): + if HAVE_METAARRAY and isinstance(data, metaarray.MetaArray): + d1 = fn(self, data.view(np.ndarray), *args, **kargs) + info = data.infoCopy() + if d1.shape != data.shape: + for i in range(data.ndim): + if 'values' in info[i]: + info[i]['values'] = info[i]['values'][:d1.shape[i]] + return metaarray.MetaArray(d1, info=info) + else: + return fn(self, data, *args, **kargs) + return newFn + diff --git a/functions.py b/functions.py index e5b6a41b..3a249d9c 100644 --- a/functions.py +++ b/functions.py @@ -5,7 +5,7 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -colorAbbrev = { +Colors = { 'b': (0,0,255,255), 'g': (0,255,0,255), 'r': (255,0,0,255), @@ -14,86 +14,129 @@ colorAbbrev = { 'y': (255,255,0,255), 'k': (0,0,0,255), 'w': (255,255,255,255), -} +} + +SI_PREFIXES = u'yzafpnµm kMGTPEZY' +SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY' + +USE_WEAVE = True -from PyQt4 import QtGui, QtCore +from Qt import QtGui, QtCore import numpy as np import scipy.ndimage +import decimal, re +import scipy.weave +import debug -## Copied from acq4/lib/util/functions -SI_PREFIXES = u'yzafpnµm kMGTPEZY' -def siScale(x, minVal=1e-25): - """Return the recommended scale factor and SI prefix string for x.""" +def siScale(x, minVal=1e-25, allowUnicode=True): + """ + Return the recommended scale factor and SI prefix string for x. + + Example:: + + siScale(0.0001) # returns (1e6, 'μ') + # This indicates that the number 0.0001 is best represented as 0.0001 * 1e6 = 100 μUnits + """ + + if isinstance(x, decimal.Decimal): + x = float(x) + + try: + if np.isnan(x) or np.isinf(x): + return(1, '') + except: + print x, type(x) + raise if abs(x) < minVal: m = 0 x = 0 else: m = int(np.clip(np.floor(np.log(abs(x))/np.log(1000)), -9.0, 9.0)) + if m == 0: pref = '' elif m < -8 or m > 8: pref = 'e%d' % (m*3) else: - pref = SI_PREFIXES[m+8] + if allowUnicode: + pref = SI_PREFIXES[m+8] + else: + pref = SI_PREFIXES_ASCII[m+8] p = .001**m - return (p, pref) - -def mkBrush(color): - if isinstance(color, QtGui.QBrush): - return color - return QtGui.QBrush(mkColor(color)) + + return (p, pref) -def mkPen(arg='default', color=None, width=1, style=None, cosmetic=True, hsv=None, ): - """Convenience function for making pens. Examples: - mkPen(color) - mkPen(color, width=2) - mkPen(cosmetic=False, width=4.5, color='r') - mkPen({'color': "FF0", width: 2}) - mkPen(None) (no pen) +def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, allowUnicode=True): """ - if isinstance(arg, dict): - return mkPen(**arg) - elif arg != 'default': - if isinstance(arg, QtGui.QPen): - return arg - elif arg is None: - style = QtCore.Qt.NoPen - else: - color = arg + Return the number x formatted in engineering notation with SI prefix. + + Example:: + + siFormat(0.0001, suffix='V') # returns "100 μV" + """ + + if space is True: + space = ' ' + if space is False: + space = '' - if color is None: - color = mkColor(200, 200, 200) - if hsv is not None: - color = hsvColor(*hsv) + + (p, pref) = siScale(x, minVal, allowUnicode) + if not (len(pref) > 0 and pref[0] == 'e'): + pref = space + pref + + if error is None: + fmt = "%." + str(precision) + "g%s%s" + return fmt % (x*p, pref, suffix) else: - color = mkColor(color) - - pen = QtGui.QPen(QtGui.QBrush(color), width) - pen.setCosmetic(cosmetic) - if style is not None: - pen.setStyle(style) - return pen - -def hsvColor(h, s=1.0, v=1.0, a=1.0): - c = QtGui.QColor() - c.setHsvF(h, s, v, a) - return c + plusminus = space + u"±" + space + fmt = "%." + str(precision) + u"g%s%s%s%s" + return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal)) + +def siEval(s): + """ + Convert a value written in SI notation to its equivalent prefixless value + + Example:: + + siEval("100 μV") # returns 0.0001 + """ + + s = unicode(s) + m = re.match(r'(-?((\d+(\.\d*)?)|(\.\d+))([eE]-?\d+)?)\s*([u' + SI_PREFIXES + r']?)$', s) + if m is None: + raise Exception("Can't convert string '%s' to number." % s) + v = float(m.groups()[0]) + p = m.groups()[6] + #if p not in SI_PREFIXES: + #raise Exception("Can't convert string '%s' to number--unknown prefix." % s) + if p == '': + n = 0 + elif p == 'u': + n = -2 + else: + n = SI_PREFIXES.index(p) - 8 + return v * 1000**n + def mkColor(*args): - """make a QColor from a variety of argument types - accepted types are: - r, g, b, [a] - (r, g, b, [a]) - float (greyscale, 0.0-1.0) - int (uses intColor) - (int, hues) (uses intColor) - QColor - "c" (see colorAbbrev dictionary) - "RGB" (strings may optionally begin with "#") - "RGBA" - "RRGGBB" - "RRGGBBAA" + """ + Convenience function for constructing QColor from a variety of argument types. Accepted arguments are: + + ================ ================================================ + 'c' one of: r, g, b, c, m, y, k, w + R, G, B, [A] integers 0-255 + (R, G, B, [A]) tuple of integers 0-255 + float greyscale, 0.0-1.0 + int see :func:`intColor() <pyqtgraph.intColor>` + (int, hues) see :func:`intColor() <pyqtgraph.intColor>` + "RGB" hexadecimal strings; may begin with '#' + "RGBA" + "RRGGBB" + "RRGGBBAA" + QColor QColor instance; makes a copy. + ================ ================================================ """ err = 'Not sure how to make a color from "%s"' % str(args) if len(args) == 1: @@ -107,7 +150,7 @@ def mkColor(*args): if c[0] == '#': c = c[1:] if len(c) == 1: - (r, g, b, a) = colorAbbrev[c] + (r, g, b, a) = Colors[c] if len(c) == 3: r = int(c[0]*2, 16) g = int(c[1]*2, 16) @@ -149,9 +192,86 @@ def mkColor(*args): (r, g, b, a) = args else: raise Exception(err) - return QtGui.QColor(r, g, b, a) + + args = [r,g,b,a] + args = map(lambda a: 0 if np.isnan(a) or np.isinf(a) else a, args) + args = map(int, args) + return QtGui.QColor(*args) + + +def mkBrush(*args): + """ + | Convenience function for constructing Brush. + | This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() <pyqtgraph.mkColor>` + | Calling mkBrush(None) returns an invisible brush. + """ + if len(args) == 1: + arg = args[0] + if arg is None: + return QtGui.QBrush(QtCore.Qt.NoBrush) + elif isinstance(arg, QtGui.QBrush): + return QtGui.QBrush(arg) + else: + color = arg + if len(args) > 1: + color = args + return QtGui.QBrush(mkColor(color)) + +def mkPen(*args, **kargs): + """ + Convenience function for constructing QPen. + + Examples:: + + mkPen(color) + mkPen(color, width=2) + mkPen(cosmetic=False, width=4.5, color='r') + mkPen({'color': "FF0", width: 2}) + mkPen(None) # (no pen) + + In these examples, *color* may be replaced with any arguments accepted by :func:`mkColor() <pyqtgraph.mkColor>` """ + + color = kargs.get('color', None) + width = kargs.get('width', 1) + style = kargs.get('style', None) + cosmetic = kargs.get('cosmetic', True) + hsv = kargs.get('hsv', None) + + if len(args) == 1: + arg = args[0] + if isinstance(arg, dict): + return mkPen(**arg) + if isinstance(arg, QtGui.QPen): + return arg + elif arg is None: + style = QtCore.Qt.NoPen + else: + color = arg + if len(args) > 1: + color = args + + if color is None: + color = mkColor(200, 200, 200) + if hsv is not None: + color = hsvColor(*hsv) + else: + color = mkColor(color) + + pen = QtGui.QPen(QtGui.QBrush(color), width) + pen.setCosmetic(cosmetic) + if style is not None: + pen.setStyle(style) + return pen + +def hsvColor(h, s=1.0, v=1.0, a=1.0): + """Generate a QColor from HSVa values.""" + c = QtGui.QColor() + c.setHsvF(h, s, v, a) + return c + def colorTuple(c): + """Return a tuple (R,G,B,A) from a QColor""" return (c.red(), c.green(), c.blue(), c.alpha()) def colorStr(c): @@ -159,12 +279,13 @@ def colorStr(c): return ('%02x'*4) % colorTuple(c) def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255, **kargs): - """Creates a QColor from a single index. Useful for stepping through a predefined list of colors. - - The argument "index" determines which color from the set will be returned - - All other arguments determine what the set of predefined colors will be + """ + Creates a QColor from a single index. Useful for stepping through a predefined list of colors. + + The argument *index* determines which color from the set will be returned. All other arguments determine what the set of predefined colors will be - Colors are chosen by cycling across hues while varying the value (brightness). By default, there - are 9 hues and 3 values for a total of 27 different colors. """ + Colors are chosen by cycling across hues while varying the value (brightness). + By default, this selects from a list of 9 hues.""" hues = int(hues) values = int(values) ind = int(index) % (hues * values) @@ -183,27 +304,41 @@ def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, mi def affineSlice(data, shape, origin, vectors, axes, **kargs): - """Take an arbitrary slice through an array. - Parameters: - data: the original dataset - shape: the shape of the slice to take (Note the return value may have more dimensions than len(shape)) - origin: the location in the original dataset that will become the origin in the sliced data. - vectors: list of unit vectors which point in the direction of the slice axes - each vector must be the same length as axes - If the vectors are not unit length, the result will be scaled. - If the vectors are not orthogonal, the result will be sheared. - axes: the axes in the original dataset which correspond to the slice vectors + """ + Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data. + + The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. + + For a graphical interface to this function, see :func:`ROI.getArrayRegion` + + Arguments: + + | *data* (ndarray): the original dataset + | *shape*: the shape of the slice to take (Note the return value may have more dimensions than len(shape)) + | *origin*: the location in the original dataset that will become the origin in the sliced data. + | *vectors*: list of unit vectors which point in the direction of the slice axes - Example: start with a 4D data set, take a diagonal-planar slice out of the last 3 axes - - data = array with dims (time, x, y, z) = (100, 40, 40, 40) - - The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1) - - The origin of the slice will be at (x,y,z) = (40, 0, 0) - - The we will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20) - affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3)) + * each vector must have the same length as *axes* + * If the vectors are not unit length, the result will be scaled. + * If the vectors are not orthogonal, the result will be sheared. - Note the following: - len(shape) == len(vectors) - len(origin) == len(axes) == len(vectors[0]) + *axes*: the axes in the original dataset which correspond to the slice *vectors* + + Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes + + * data = array with dims (time, x, y, z) = (100, 40, 40, 40) + * The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1) + * The origin of the slice will be at (x,y,z) = (40, 0, 0) + * We will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20) + + The call for this example would look like:: + + affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3)) + + Note the following must be true: + + | len(shape) == len(vectors) + | len(origin) == len(axes) == len(vectors[0]) """ # sanity check @@ -214,7 +349,8 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs): for v in vectors: if len(v) != len(axes): raise Exception("each vector must be same length as axes.") - shape = (np.ceil(shape[0]), np.ceil(shape[1])) + + shape = map(np.ceil, shape) ## transpose data so slice axes come first trAx = range(data.ndim) @@ -257,3 +393,243 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs): ## Untranspose array before returning return output.transpose(tr2) + + + + +def makeARGB(data, lut=None, levels=None): + """ + Convert a 2D or 3D array into an ARGB array suitable for building QImages + Will optionally do scaling and/or table lookups to determine final colors. + + Returns the ARGB array and a boolean indicating whether there is alpha channel data. + + Arguments: + data - 2D or 3D numpy array of int/float types + + For 2D arrays (x, y): + * The color will be determined using a lookup table (see argument 'lut'). + * If levels are given, the data is rescaled and converted to int + before using the lookup table. + + For 3D arrays (x, y, rgba): + * The third axis must have length 3 or 4 and will be interpreted as RGBA. + * The 'lut' argument is not allowed. + + lut - Lookup table for 2D data. May be 1D or 2D (N,rgba) and must have dtype=ubyte. + Values in data will be converted to color by indexing directly from lut. + Lookup tables can be built using GradientWidget. + levels - List [min, max]; optionally rescale data before converting through the + lookup table. rescaled = (data-min) * len(lut) / (max-min) + + """ + + prof = debug.Profiler('functions.makeARGB', disabled=True) + + ## sanity checks + if data.ndim == 3: + if data.shape[2] not in (3,4): + raise Exception("data.shape[2] must be 3 or 4") + #if lut is not None: + #raise Exception("can not use lookup table with 3D data") + elif data.ndim != 2: + raise Exception("data must be 2D or 3D") + + if lut is not None: + if lut.ndim == 2: + if lut.shape[1] not in (3,4): + raise Exception("lut.shape[1] must be 3 or 4") + elif lut.ndim != 1: + raise Exception("lut must be 1D or 2D") + if lut.dtype != np.ubyte: + raise Exception('lookup table must have dtype=ubyte (got %s instead)' % str(lut.dtype)) + + if levels is not None: + levels = np.array(levels) + if levels.shape == (2,): + pass + elif levels.shape in [(3,2), (4,2)]: + if data.ndim == 3: + raise Exception("Can not use 2D levels with 3D data.") + if lut is not None: + raise Exception('Can not use 2D levels and lookup table together.') + else: + raise Exception("Levels must have shape (2,) or (3,2) or (4,2)") + + prof.mark('1') + + if lut is not None: + lutLength = lut.shape[0] + else: + lutLength = 256 + + ## weave requires contiguous arrays + global USE_WEAVE + if (levels is not None or lut is not None) and USE_WEAVE: + data = np.ascontiguousarray(data) + + ## Apply levels if given + if levels is not None: + + try: ## use weave to speed up scaling + if not USE_WEAVE: + raise Exception('Weave is disabled; falling back to slower version.') + if levels.ndim == 1: + scale = float(lutLength) / (levels[1]-levels[0]) + offset = float(levels[0]) + data = rescaleData(data, scale, offset) + else: + if data.ndim == 2: + newData = np.empty(data.shape+(levels.shape[0],), dtype=np.uint32) + for i in xrange(levels.shape[0]): + scale = float(lutLength / (levels[i,1]-levels[i,0])) + offset = float(levels[i,0]) + newData[...,i] = rescaleData(data, scale, offset) + elif data.ndim == 3: + newData = np.empty(data.shape, dtype=np.uint32) + for i in xrange(data.shape[2]): + scale = float(lutLength / (levels[i,1]-levels[i,0])) + offset = float(levels[i,0]) + #print scale, offset, data.shape, newData.shape, levels.shape + newData[...,i] = rescaleData(data[...,i], scale, offset) + data = newData + except: + if USE_WEAVE: + debug.printExc("Error; disabling weave.") + USE_WEAVE = False + + if levels.ndim == 1: + if data.ndim == 2: + levels = levels[np.newaxis, np.newaxis, :] + else: + levels = levels[np.newaxis, np.newaxis, np.newaxis, :] + else: + levels = levels[np.newaxis, np.newaxis, ...] + if data.ndim == 2: + data = data[..., np.newaxis] + data = ((data-levels[...,0]) * lutLength) / (levels[...,1]-levels[...,0]) + + prof.mark('2') + + + ## apply LUT if given + if lut is not None and data.ndim == 2: + + if data.dtype.kind not in ('i', 'u'): + data = data.astype(int) + + data = np.clip(data, 0, lutLength-1) + try: + if not USE_WEAVE: + raise Exception('Weave is disabled; falling back to slower version.') + + newData = np.empty((data.size,) + lut.shape[1:], dtype=np.uint8) + flat = data.reshape(data.size) + size = data.size + ncol = lut.shape[1] + newStride = newData.strides[0] + newColStride = newData.strides[1] + lutStride = lut.strides[0] + lutColStride = lut.strides[1] + flatStride = flat.strides[0] / flat.dtype.itemsize + + #print "newData:", newData.shape, newData.dtype + #print "flat:", flat.shape, flat.dtype, flat.min(), flat.max() + #print "lut:", lut.shape, lut.dtype + #print "size:", size, "ncols:", ncol + #print "strides:", newStride, newColStride, lutStride, lutColStride, flatStride + + code = """ + + for( int i=0; i<size; i++ ) { + for( int j=0; j<ncol; j++ ) { + newData[i*newStride + j*newColStride] = lut[flat[i*flatStride]*lutStride + j*lutColStride]; + } + } + """ + scipy.weave.inline(code, ['flat', 'lut', 'newData', 'size', 'ncol', 'newStride', 'lutStride', 'flatStride', 'newColStride', 'lutColStride']) + data = newData.reshape(data.shape + lut.shape[1:]) + except: + if USE_WEAVE: + debug.printExc("Error; disabling weave.") + USE_WEAVE = False + data = lut[data] + else: + if data.dtype is not np.ubyte: + data = np.clip(data, 0, 255).astype(np.ubyte) + + prof.mark('3') + + + ## copy data into ARGB ordered array + imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte) + if data.ndim == 2: + data = data[..., np.newaxis] + + prof.mark('4') + + + order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. + if data.shape[2] == 1: + for i in xrange(3): + imgData[..., order[i]] = data[..., 0] + else: + for i in xrange(0, data.shape[2]): + imgData[..., order[i]] = data[..., i] + + prof.mark('5') + + if data.shape[2] == 4: + alpha = True + else: + alpha = False + imgData[..., 3] = 255 + + prof.mark('6') + + prof.finish() + return imgData, alpha + + +def makeQImage(imgData, alpha): + """Turn an ARGB array into QImage""" + ## create QImage from buffer + prof = debug.Profiler('functions.makeQImage', disabled=True) + + if alpha: + imgFormat = QtGui.QImage.Format_ARGB32 + else: + imgFormat = QtGui.QImage.Format_RGB32 + + imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite + try: + buf = imgData.data + except AttributeError: + imgData = np.ascontiguousarray(imgData) + buf = imgData.data + + prof.mark('1') + qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat) + prof.mark('2') + qimage.data = imgData + prof.finish() + return qimage + + +def rescaleData(data, scale, offset): + newData = np.empty((data.size,), dtype=np.int) + flat = data.reshape(data.size) + size = data.size + + code = """ + double sc = (double)scale; + double off = (double)offset; + for( int i=0; i<size; i++ ) { + newData[i] = (int)(((double)flat[i] - off) * sc); + } + """ + scipy.weave.inline(code, ['flat', 'newData', 'size', 'offset', 'scale'], compiler='gcc') + data = newData.reshape(data.shape) + return data + + \ No newline at end of file diff --git a/functions.pyc b/functions.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b71083c09a1b1c4693941ac288a630f9e2a49166 GIT binary patch literal 19701 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 b*`arbMu$qHgG2p8{Vz=oeR1U5LsS1B{mDK0 literal 0 HcmV?d00001 diff --git a/graphicsItems.py b/graphicsItems.py deleted file mode 100644 index 63de57e0..00000000 --- a/graphicsItems.py +++ /dev/null @@ -1,2997 +0,0 @@ -# -*- coding: utf-8 -*- -""" -graphicsItems.py - Defines several graphics item classes for use in Qt graphics/view framework -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -Provides ImageItem, PlotCurveItem, and ViewBox, amongst others. -""" - - -from PyQt4 import QtGui, QtCore -if not hasattr(QtCore, 'Signal'): - QtCore.Signal = QtCore.pyqtSignal -#from ObjectWorkaround import * -#tryWorkaround(QtCore, QtGui) -#from numpy import * -import numpy as np -try: - import scipy.weave as weave - from scipy.weave import converters -except: - pass -from scipy.fftpack import fft -#from scipy.signal import resample -import scipy.stats -#from metaarray import MetaArray -from Point import * -from functions import * -import types, sys, struct -import weakref -import debug -#from debug import * - -## QGraphicsObject didn't appear until 4.6; this is for compatibility with 4.5 -if not hasattr(QtGui, 'QGraphicsObject'): - class QGraphicsObject(QtGui.QGraphicsWidget): - def shape(self): - return QtGui.QGraphicsItem.shape(self) - QtGui.QGraphicsObject = QGraphicsObject - - -## Should probably just use QGraphicsGroupItem and instruct it to pass events on to children.. -class ItemGroup(QtGui.QGraphicsItem): - def __init__(self, *args): - QtGui.QGraphicsItem.__init__(self, *args) - if hasattr(self, "ItemHasNoContents"): - self.setFlag(self.ItemHasNoContents) - - def boundingRect(self): - return QtCore.QRectF() - - def paint(self, *args): - pass - - def addItem(self, item): - item.setParentItem(self) - - -#if hasattr(QtGui, "QGraphicsObject"): - #QGraphicsObject = QtGui.QGraphicsObject -#else: - #class QObjectWorkaround: - #def __init__(self): - #self._qObj_ = QtCore.QObject() - #def connect(self, *args): - #return QtCore.QObject.connect(self._qObj_, *args) - #def disconnect(self, *args): - #return QtCore.QObject.disconnect(self._qObj_, *args) - #def emit(self, *args): - #return QtCore.QObject.emit(self._qObj_, *args) - - #class QGraphicsObject(QtGui.QGraphicsItem, QObjectWorkaround): - #def __init__(self, *args): - #QtGui.QGraphicsItem.__init__(self, *args) - #QObjectWorkaround.__init__(self) - - - -class GraphicsObject(QtGui.QGraphicsObject): - """Extends QGraphicsObject with a few important functions. - (Most of these assume that the object is in a scene with a single view)""" - - def __init__(self, *args): - QtGui.QGraphicsObject.__init__(self, *args) - self._view = None - - def getViewWidget(self): - """Return the view widget for this item. If the scene has multiple views, only the first view is returned. - the view is remembered for the lifetime of the object, so expect trouble if the object is moved to another view.""" - if self._view is None: - scene = self.scene() - if scene is None: - return None - views = scene.views() - if len(views) < 1: - return None - self._view = weakref.ref(self.scene().views()[0]) - return self._view() - - def getBoundingParents(self): - """Return a list of parents to this item that have child clipping enabled.""" - p = self - parents = [] - while True: - p = p.parentItem() - if p is None: - break - if p.flags() & self.ItemClipsChildrenToShape: - parents.append(p) - return parents - - def viewBounds(self): - """Return the allowed visible boundaries for this item. Takes into account the viewport as well as any parents that clip.""" - bounds = QtCore.QRectF(0, 0, 1, 1) - view = self.getViewWidget() - if view is None: - return None - bounds = self.mapRectFromScene(view.visibleRange()) - - for p in self.getBoundingParents(): - bounds &= self.mapRectFromScene(p.sceneBoundingRect()) - - return bounds - - def viewTransform(self): - """Return the transform that maps from local coordinates to the item's view coordinates""" - view = self.getViewWidget() - if view is None: - return None - return self.deviceTransform(view.viewportTransform()) - - def pixelVectors(self): - """Return vectors in local coordinates representing the width and height of a view pixel.""" - vt = self.viewTransform() - if vt is None: - return None - vt = vt.inverted()[0] - orig = vt.map(QtCore.QPointF(0, 0)) - return vt.map(QtCore.QPointF(1, 0))-orig, vt.map(QtCore.QPointF(0, 1))-orig - - def pixelWidth(self): - vt = self.viewTransform() - if vt is None: - return 0 - vt = vt.inverted()[0] - return abs((vt.map(QtCore.QPointF(1, 0))-vt.map(QtCore.QPointF(0, 0))).x()) - - def pixelHeight(self): - vt = self.viewTransform() - if vt is None: - return 0 - vt = vt.inverted()[0] - return abs((vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).y()) - - def mapToView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - return vt.map(obj) - - def mapRectToView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - return vt.mapRect(obj) - - def mapFromView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - vt = vt.inverted()[0] - return vt.map(obj) - - def mapRectFromView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - vt = vt.inverted()[0] - return vt.mapRect(obj) - - - - - -class ImageItem(QtGui.QGraphicsObject): - - sigImageChanged = QtCore.Signal() - - if 'linux' not in sys.platform: ## disable weave optimization on linux--broken there. - useWeave = True - else: - useWeave = False - - def __init__(self, image=None, copy=True, parent=None, border=None, mode=None, *args): - #QObjectWorkaround.__init__(self) - QtGui.QGraphicsObject.__init__(self) - #self.pixmapItem = QtGui.QGraphicsPixmapItem(self) - self.qimage = QtGui.QImage() - self.pixmap = None - self.paintMode = mode - #self.useWeave = True - self.blackLevel = None - self.whiteLevel = None - self.alpha = 1.0 - self.image = None - self.clipLevel = None - self.drawKernel = None - if border is not None: - border = mkPen(border) - self.border = border - - #QtGui.QGraphicsPixmapItem.__init__(self, parent, *args) - #self.pixmapItem = QtGui.QGraphicsPixmapItem(self) - if image is not None: - self.updateImage(image, copy, autoRange=True) - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - - def setCompositionMode(self, mode): - self.paintMode = mode - self.update() - - def setAlpha(self, alpha): - self.alpha = alpha - self.updateImage() - - #def boundingRect(self): - #return self.pixmapItem.boundingRect() - #return QtCore.QRectF(0, 0, self.qimage.width(), self.qimage.height()) - - def width(self): - if self.pixmap is None: - return None - return self.pixmap.width() - - def height(self): - if self.pixmap is None: - return None - return self.pixmap.height() - - def boundingRect(self): - if self.pixmap is None: - return QtCore.QRectF(0., 0., 0., 0.) - return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) - - def setClipLevel(self, level=None): - self.clipLevel = level - - #def paint(self, p, opt, widget): - #pass - #if self.pixmap is not None: - #p.drawPixmap(0, 0, self.pixmap) - #print "paint" - - def setLevels(self, white=None, black=None): - if white is not None: - self.whiteLevel = white - if black is not None: - self.blackLevel = black - self.updateImage() - - def getLevels(self): - return self.whiteLevel, self.blackLevel - - def updateImage(self, image=None, copy=True, autoRange=False, clipMask=None, white=None, black=None, axes=None): - prof = debug.Profiler('ImageItem.updateImage 0x%x' %id(self), disabled=True) - #debug.printTrace() - if axes is None: - axh = {'x': 0, 'y': 1, 'c': 2} - else: - axh = axes - #print "Update image", black, white - if white is not None: - self.whiteLevel = white - if black is not None: - self.blackLevel = black - - gotNewData = False - if image is None: - if self.image is None: - return - else: - gotNewData = True - if self.image is None or image.shape != self.image.shape: - self.prepareGeometryChange() - if copy: - self.image = image.view(np.ndarray).copy() - else: - self.image = image.view(np.ndarray) - #print " image max:", self.image.max(), "min:", self.image.min() - prof.mark('1') - - # Determine scale factors - if autoRange or self.blackLevel is None: - if self.image.dtype is np.ubyte: - self.blackLevel = 0 - self.whiteLevel = 255 - else: - self.blackLevel = self.image.min() - self.whiteLevel = self.image.max() - #print "Image item using", self.blackLevel, self.whiteLevel - - if self.blackLevel != self.whiteLevel: - scale = 255. / (self.whiteLevel - self.blackLevel) - else: - scale = 0. - - prof.mark('2') - - ## Recolor and convert to 8 bit per channel - # Try using weave, then fall back to python - shape = self.image.shape - black = float(self.blackLevel) - white = float(self.whiteLevel) - - if black == 0 and white == 255 and self.image.dtype == np.ubyte: - im = self.image - - else: - try: - if not ImageItem.useWeave: - raise Exception('Skipping weave compile') - sim = np.ascontiguousarray(self.image) - sim.shape = sim.size - im = np.empty(sim.shape, dtype=np.ubyte) - n = im.size - - code = """ - for( int i=0; i<n; i++ ) { - float a = (sim(i)-black) * (float)scale; - if( a > 255.0 ) - a = 255.0; - else if( a < 0.0 ) - a = 0.0; - im(i) = a; - } - """ - - weave.inline(code, ['sim', 'im', 'n', 'black', 'scale'], type_converters=converters.blitz, compiler = 'gcc') - sim.shape = shape - im.shape = shape - except: - if ImageItem.useWeave: - ImageItem.useWeave = False - #sys.excepthook(*sys.exc_info()) - #print "==============================================================================" - print "Weave compile failed, falling back to slower version." - self.image.shape = shape - im = ((self.image - black) * scale).clip(0.,255.).astype(np.ubyte) - prof.mark('3') - - try: - im1 = np.empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=np.ubyte) - except: - print im.shape, axh - raise - alpha = np.clip(int(255 * self.alpha), 0, 255) - prof.mark('4') - # Fill image - if im.ndim == 2: - im2 = im.transpose(axh['y'], axh['x']) - im1[..., 0] = im2 - im1[..., 1] = im2 - im1[..., 2] = im2 - im1[..., 3] = alpha - elif im.ndim == 3: #color image - im2 = im.transpose(axh['y'], axh['x'], axh['c']) - ## [B G R A] Reorder colors - order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. - - for i in range(0, im.shape[axh['c']]): - im1[..., order[i]] = im2[..., i] - - ## fill in unused channels with 0 or alpha - for i in range(im.shape[axh['c']], 3): - im1[..., i] = 0 - if im.shape[axh['c']] < 4: - im1[..., 3] = alpha - - else: - raise Exception("Image must be 2 or 3 dimensions") - #self.im1 = im1 - # Display image - prof.mark('5') - if self.clipLevel is not None or clipMask is not None: - if clipMask is not None: - mask = clipMask.transpose() - else: - mask = (self.image < self.clipLevel).transpose() - im1[..., 0][mask] *= 0.5 - im1[..., 1][mask] *= 0.5 - im1[..., 2][mask] = 255 - prof.mark('6') - #print "Final image:", im1.dtype, im1.min(), im1.max(), im1.shape - self.ims = im1.tostring() ## Must be held in memory here because qImage won't do it for us :( - prof.mark('7') - qimage = QtGui.QImage(buffer(self.ims), im1.shape[1], im1.shape[0], QtGui.QImage.Format_ARGB32) - prof.mark('8') - self.pixmap = QtGui.QPixmap.fromImage(qimage) - prof.mark('9') - ##del self.ims - #self.pixmapItem.setPixmap(self.pixmap) - - self.update() - prof.mark('10') - - if gotNewData: - #self.emit(QtCore.SIGNAL('imageChanged')) - self.sigImageChanged.emit() - - prof.finish() - - def getPixmap(self): - return self.pixmap.copy() - - def getHistogram(self, bins=500, step=3): - """returns x and y arrays containing the histogram values for the current image. - The step argument causes pixels to be skipped when computing the histogram to save time.""" - stepData = self.image[::step, ::step] - hist = np.histogram(stepData, bins=bins) - return hist[1][:-1], hist[0] - - def mousePressEvent(self, ev): - if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: - self.drawAt(ev.pos(), ev) - ev.accept() - else: - ev.ignore() - - def mouseMoveEvent(self, ev): - #print "mouse move", ev.pos() - if self.drawKernel is not None: - self.drawAt(ev.pos(), ev) - - def mouseReleaseEvent(self, ev): - pass - - def tabletEvent(self, ev): - print ev.device() - print ev.pointerType() - print ev.pressure() - - def drawAt(self, pos, ev=None): - pos = [int(pos.x()), int(pos.y())] - dk = self.drawKernel - kc = self.drawKernelCenter - sx = [0,dk.shape[0]] - sy = [0,dk.shape[1]] - tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] - ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] - - for i in [0,1]: - dx1 = -min(0, tx[i]) - dx2 = min(0, self.image.shape[0]-tx[i]) - tx[i] += dx1+dx2 - sx[i] += dx1+dx2 - - dy1 = -min(0, ty[i]) - dy2 = min(0, self.image.shape[1]-ty[i]) - ty[i] += dy1+dy2 - sy[i] += dy1+dy2 - - #print sx - #print sy - #print tx - #print ty - #print self.image.shape - #print self.image[tx[0]:tx[1], ty[0]:ty[1]].shape - #print dk[sx[0]:sx[1], sy[0]:sy[1]].shape - ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) - ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) - #src = dk[sx[0]:sx[1], sy[0]:sy[1]] - #mask = self.drawMask[sx[0]:sx[1], sy[0]:sy[1]] - mask = self.drawMask - src = dk - #print self.image[ts].shape, src.shape - - if callable(self.drawMode): - self.drawMode(dk, self.image, mask, ss, ts, ev) - else: - mask = mask[ss] - src = src[ss] - if self.drawMode == 'set': - if mask is not None: - self.image[ts] = self.image[ts] * (1-mask) + src * mask - else: - self.image[ts] = src - elif self.drawMode == 'add': - self.image[ts] += src - else: - raise Exception("Unknown draw mode '%s'" % self.drawMode) - self.updateImage() - - def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): - self.drawKernel = kernel - self.drawKernelCenter = center - self.drawMode = mode - self.drawMask = mask - - def paint(self, p, *args): - - #QtGui.QGraphicsPixmapItem.paint(self, p, *args) - if self.pixmap is None: - return - if self.paintMode is not None: - p.setCompositionMode(self.paintMode) - p.drawPixmap(self.boundingRect(), self.pixmap, QtCore.QRectF(0, 0, self.pixmap.width(), self.pixmap.height())) - if self.border is not None: - p.setPen(self.border) - p.drawRect(self.boundingRect()) - - def pixelSize(self): - """return size of a single pixel in the image""" - br = self.sceneBoundingRect() - return br.width()/self.pixmap.width(), br.height()/self.pixmap.height() - -class PlotCurveItem(GraphicsObject): - - sigPlotChanged = QtCore.Signal(object) - - """Class representing a single plot curve.""" - - sigClicked = QtCore.Signal(object) - - def __init__(self, y=None, x=None, copy=False, pen=None, shadow=None, parent=None, color=None, clickable=False): - GraphicsObject.__init__(self, parent) - #GraphicsWidget.__init__(self, parent) - self.free() - #self.dispPath = None - - if pen is None: - if color is None: - self.setPen((200,200,200)) - else: - self.setPen(color) - else: - self.setPen(pen) - - self.shadow = shadow - if y is not None: - self.updateData(y, x, copy) - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - - self.metaData = {} - self.opts = { - 'spectrumMode': False, - 'logMode': [False, False], - 'pointMode': False, - 'pointStyle': None, - 'downsample': False, - 'alphaHint': 1.0, - 'alphaMode': False - } - - self.setClickable(clickable) - #self.fps = None - - def setClickable(self, s): - self.clickable = s - - - def getData(self): - if self.xData is None: - return (None, None) - if self.xDisp is None: - nanMask = np.isnan(self.xData) | np.isnan(self.yData) - if any(nanMask): - x = self.xData[~nanMask] - y = self.yData[~nanMask] - else: - x = self.xData - y = self.yData - ds = self.opts['downsample'] - if ds > 1: - x = x[::ds] - #y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing - y = y[::ds] - if self.opts['spectrumMode']: - f = fft(y) / len(y) - y = abs(f[1:len(f)/2]) - dt = x[-1] - x[0] - x = np.linspace(0, 0.5*len(x)/dt, len(y)) - if self.opts['logMode'][0]: - x = np.log10(x) - if self.opts['logMode'][1]: - y = np.log10(y) - self.xDisp = x - self.yDisp = y - #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max() - #print self.xDisp.shape, self.xDisp.min(), self.xDisp.max() - return self.xDisp, self.yDisp - - #def generateSpecData(self): - #f = fft(self.yData) / len(self.yData) - #self.ySpec = abs(f[1:len(f)/2]) - #dt = self.xData[-1] - self.xData[0] - #self.xSpec = linspace(0, 0.5*len(self.xData)/dt, len(self.ySpec)) - - def getRange(self, ax, frac=1.0): - #print "getRange", ax, frac - (x, y) = self.getData() - if x is None or len(x) == 0: - return (0, 1) - - if ax == 0: - d = x - elif ax == 1: - d = y - - if frac >= 1.0: - return (d.min(), d.max()) - elif frac <= 0.0: - raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) - else: - return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50))) - #bins = 1000 - #h = histogram(d, bins) - #s = len(d) * (1.0-frac) - #mnTot = mxTot = 0 - #mnInd = mxInd = 0 - #for i in range(bins): - #mnTot += h[0][i] - #if mnTot > s: - #mnInd = i - #break - #for i in range(bins): - #mxTot += h[0][-i-1] - #if mxTot > s: - #mxInd = -i-1 - #break - ##print mnInd, mxInd, h[1][mnInd], h[1][mxInd] - #return(h[1][mnInd], h[1][mxInd]) - - - - - def setMeta(self, data): - self.metaData = data - - def meta(self): - return self.metaData - - def setPen(self, pen): - self.pen = mkPen(pen) - self.update() - - def setColor(self, color): - self.pen.setColor(color) - self.update() - - def setAlpha(self, alpha, auto): - self.opts['alphaHint'] = alpha - self.opts['alphaMode'] = auto - self.update() - - def setSpectrumMode(self, mode): - self.opts['spectrumMode'] = mode - self.xDisp = self.yDisp = None - self.path = None - self.update() - - def setLogMode(self, mode): - self.opts['logMode'] = mode - self.xDisp = self.yDisp = None - self.path = None - self.update() - - def setPointMode(self, mode): - self.opts['pointMode'] = mode - self.update() - - def setShadowPen(self, pen): - self.shadow = pen - self.update() - - def setDownsampling(self, ds): - if self.opts['downsample'] != ds: - self.opts['downsample'] = ds - self.xDisp = self.yDisp = None - self.path = None - self.update() - - def setData(self, x, y, copy=False): - """For Qwt compatibility""" - self.updateData(y, x, copy) - - def updateData(self, data, x=None, copy=False): - prof = debug.Profiler('PlotCurveItem.updateData', disabled=True) - if isinstance(data, list): - data = np.array(data) - if isinstance(x, list): - x = np.array(x) - if not isinstance(data, np.ndarray) or data.ndim > 2: - raise Exception("Plot data must be 1 or 2D ndarray (data shape is %s)" % str(data.shape)) - if x == None: - if 'complex' in str(data.dtype): - raise Exception("Can not plot complex data types.") - else: - if 'complex' in str(data.dtype)+str(x.dtype): - raise Exception("Can not plot complex data types.") - - if data.ndim == 2: ### If data is 2D array, then assume x and y values are in first two columns or rows. - if x is not None: - raise Exception("Plot data may be 2D only if no x argument is supplied.") - ax = 0 - if data.shape[0] > 2 and data.shape[1] == 2: - ax = 1 - ind = [slice(None), slice(None)] - ind[ax] = 0 - y = data[tuple(ind)] - ind[ax] = 1 - x = data[tuple(ind)] - elif data.ndim == 1: - y = data - prof.mark("data checks") - - self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly - ## Test this bug with test_PlotWidget and zoom in on the animated plot - - self.prepareGeometryChange() - if copy: - self.yData = y.copy() - else: - self.yData = y - - if copy and x is not None: - self.xData = x.copy() - else: - self.xData = x - prof.mark('copy') - - if x is None: - self.xData = np.arange(0, self.yData.shape[0]) - - if self.xData.shape != self.yData.shape: - raise Exception("X and Y arrays must be the same shape--got %s and %s." % (str(x.shape), str(y.shape))) - - self.path = None - self.xDisp = self.yDisp = None - - prof.mark('set') - self.update() - prof.mark('update') - #self.emit(QtCore.SIGNAL('plotChanged'), self) - self.sigPlotChanged.emit(self) - prof.mark('emit') - #prof.finish() - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - prof.mark('set cache mode') - prof.finish() - - def generatePath(self, x, y): - prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) - path = QtGui.QPainterPath() - - ## Create all vertices in path. The method used below creates a binary format so that all - ## vertices can be read in at once. This binary format may change in future versions of Qt, - ## so the original (slower) method is left here for emergencies: - #path.moveTo(x[0], y[0]) - #for i in range(1, y.shape[0]): - # path.lineTo(x[i], y[i]) - - ## Speed this up using >> operator - ## Format is: - ## numVerts(i4) 0(i4) - ## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect - ## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex - ## ... - ## 0(i4) - ## - ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') - - n = x.shape[0] - # create empty array, pad with extra space on either end - arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) - # write first two integers - prof.mark('allocate empty') - arr.data[12:20] = struct.pack('>ii', n, 0) - prof.mark('pack header') - # Fill array with vertex values - arr[1:-1]['x'] = x - arr[1:-1]['y'] = y - arr[1:-1]['c'] = 1 - prof.mark('fill array') - # write last 0 - lastInd = 20*(n+1) - arr.data[lastInd:lastInd+4] = struct.pack('>i', 0) - prof.mark('footer') - # create datastream object and stream into path - buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here - prof.mark('create buffer') - ds = QtCore.QDataStream(buf) - prof.mark('create datastream') - ds >> path - prof.mark('load') - - prof.finish() - return path - - def boundingRect(self): - (x, y) = self.getData() - if x is None or y is None or len(x) == 0 or len(y) == 0: - return QtCore.QRectF() - - - if self.shadow is not None: - lineWidth = (max(self.pen.width(), self.shadow.width()) + 1) - else: - lineWidth = (self.pen.width()+1) - - - pixels = self.pixelVectors() - xmin = x.min() - pixels[0].x() * lineWidth - xmax = x.max() + pixels[0].x() * lineWidth - ymin = y.min() - abs(pixels[1].y()) * lineWidth - ymax = y.max() + abs(pixels[1].y()) * lineWidth - - - return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) - - def paint(self, p, opt, widget): - prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True) - if self.xData is None: - return - #if self.opts['spectrumMode']: - #if self.specPath is None: - - #self.specPath = self.generatePath(*self.getData()) - #path = self.specPath - #else: - if self.path is None: - self.path = self.generatePath(*self.getData()) - path = self.path - prof.mark('generate path') - - if self.shadow is not None: - sp = QtGui.QPen(self.shadow) - else: - sp = None - - ## Copy pens and apply alpha adjustment - cp = QtGui.QPen(self.pen) - for pen in [sp, cp]: - if pen is None: - continue - c = pen.color() - c.setAlpha(c.alpha() * self.opts['alphaHint']) - pen.setColor(c) - #pen.setCosmetic(True) - - if self.shadow is not None: - p.setPen(sp) - p.drawPath(path) - p.setPen(cp) - p.drawPath(path) - prof.mark('drawPath') - - prof.finish() - #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) - #p.drawRect(self.boundingRect()) - - - def free(self): - self.xData = None ## raw values - self.yData = None - self.xDisp = None ## display values (after log / fft) - self.yDisp = None - self.path = None - #del self.xData, self.yData, self.xDisp, self.yDisp, self.path - - def mousePressEvent(self, ev): - #GraphicsObject.mousePressEvent(self, ev) - if not self.clickable: - ev.ignore() - if ev.button() != QtCore.Qt.LeftButton: - ev.ignore() - self.mousePressPos = ev.pos() - self.mouseMoved = False - - def mouseMoveEvent(self, ev): - #GraphicsObject.mouseMoveEvent(self, ev) - self.mouseMoved = True - #print "move" - - def mouseReleaseEvent(self, ev): - #GraphicsObject.mouseReleaseEvent(self, ev) - if not self.mouseMoved: - self.sigClicked.emit(self) - - -class CurvePoint(QtGui.QGraphicsObject): - """A GraphicsItem that sets its location to a point on a PlotCurveItem. - The position along the curve is a property, and thus can be easily animated.""" - - def __init__(self, curve, index=0, pos=None): - """Position can be set either as an index referring to the sample number or - the position 0.0 - 1.0""" - - QtGui.QGraphicsObject.__init__(self) - #QObjectWorkaround.__init__(self) - self.curve = weakref.ref(curve) - self.setParentItem(curve) - self.setProperty('position', 0.0) - self.setProperty('index', 0) - - if hasattr(self, 'ItemHasNoContents'): - self.setFlags(self.flags() | self.ItemHasNoContents) - - if pos is not None: - self.setPos(pos) - else: - self.setIndex(index) - - def setPos(self, pos): - self.setProperty('position', float(pos))## cannot use numpy types here, MUST be python float. - - def setIndex(self, index): - self.setProperty('index', int(index)) ## cannot use numpy types here, MUST be python int. - - def event(self, ev): - if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None: - return False - - if ev.propertyName() == 'index': - index = self.property('index').toInt()[0] - elif ev.propertyName() == 'position': - index = None - else: - return False - - (x, y) = self.curve().getData() - if index is None: - #print ev.propertyName(), self.property('position').toDouble()[0], self.property('position').typeName() - index = (len(x)-1) * clip(self.property('position').toDouble()[0], 0.0, 1.0) - - if index != int(index): ## interpolate floating-point values - i1 = int(index) - i2 = clip(i1+1, 0, len(x)-1) - s2 = index-i1 - s1 = 1.0-s2 - newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2) - else: - index = int(index) - i1 = clip(index-1, 0, len(x)-1) - i2 = clip(index+1, 0, len(x)-1) - newPos = (x[index], y[index]) - - p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) - p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) - ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians - self.resetTransform() - self.rotate(180+ ang * 180 / np.pi) ## takes degrees - QtGui.QGraphicsItem.setPos(self, *newPos) - return True - - def boundingRect(self): - return QtCore.QRectF() - - def paint(self, *args): - pass - - def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1): - anim = QtCore.QPropertyAnimation(self, prop) - anim.setDuration(duration) - anim.setStartValue(start) - anim.setEndValue(end) - anim.setLoopCount(loop) - return anim - - - -class ArrowItem(QtGui.QGraphicsPolygonItem): - def __init__(self, **opts): - QtGui.QGraphicsPolygonItem.__init__(self) - defOpts = { - 'style': 'tri', - 'pxMode': True, - 'size': 20, - 'angle': -150, - 'pos': (0,0), - 'width': 8, - 'tipAngle': 25, - 'baseAngle': 90, - 'pen': (200,200,200), - 'brush': (50,50,200), - } - defOpts.update(opts) - - self.setStyle(**defOpts) - - self.setPen(mkPen(defOpts['pen'])) - self.setBrush(mkBrush(defOpts['brush'])) - - self.rotate(self.opts['angle']) - self.moveBy(*self.opts['pos']) - - def setStyle(self, **opts): - self.opts = opts - - if opts['style'] == 'tri': - points = [ - QtCore.QPointF(0,0), - QtCore.QPointF(opts['size'],-opts['width']/2.), - QtCore.QPointF(opts['size'],opts['width']/2.), - ] - poly = QtGui.QPolygonF(points) - - else: - raise Exception("Unrecognized arrow style '%s'" % opts['style']) - - self.setPolygon(poly) - - if opts['pxMode']: - self.setFlags(self.flags() | self.ItemIgnoresTransformations) - else: - self.setFlags(self.flags() & ~self.ItemIgnoresTransformations) - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - QtGui.QGraphicsPolygonItem.paint(self, p, *args) - -class CurveArrow(CurvePoint): - """Provides an arrow that points to any specific sample on a PlotCurveItem. - Provides properties that can be animated.""" - - def __init__(self, curve, index=0, pos=None, **opts): - CurvePoint.__init__(self, curve, index=index, pos=pos) - if opts.get('pxMode', True): - opts['pxMode'] = False - self.setFlags(self.flags() | self.ItemIgnoresTransformations) - opts['angle'] = 0 - self.arrow = ArrowItem(**opts) - self.arrow.setParentItem(self) - - def setStyle(**opts): - return self.arrow.setStyle(**opts) - - - -class ScatterPlotItem(GraphicsObject): - - #sigPointClicked = QtCore.Signal(object, object) - sigClicked = QtCore.Signal(object, object) ## self, points - - def __init__(self, spots=None, x=None, y=None, pxMode=True, pen='default', brush='default', size=5, identical=False, data=None): - """ - Arguments: - spots: list of dicts. Each dict specifies parameters for a single spot. - x,y: array of x,y values. Alternatively, specify spots['pos'] = (x,y) - pxMode: If True, spots are always the same size regardless of scaling - identical: If True, all spots are forced to look identical. - This can result in performance enhancement.""" - GraphicsObject.__init__(self) - self.spots = [] - self.range = [[0,0], [0,0]] - self.identical = identical - self._spotPixmap = None - - if brush == 'default': - self.brush = QtGui.QBrush(QtGui.QColor(100, 100, 150)) - else: - self.brush = mkBrush(brush) - - if pen == 'default': - self.pen = QtGui.QPen(QtGui.QColor(200, 200, 200)) - else: - self.pen = mkPen(pen) - - self.size = size - - self.pxMode = pxMode - if spots is not None or x is not None: - self.setPoints(spots, x, y, data) - - #self.optimize = optimize - #if optimize: - #self.spotImage = QtGui.QImage(size, size, QtGui.QImage.Format_ARGB32_Premultiplied) - #self.spotImage.fill(0) - #p = QtGui.QPainter(self.spotImage) - #p.setRenderHint(p.Antialiasing) - #p.setBrush(brush) - #p.setPen(pen) - #p.drawEllipse(0, 0, size, size) - #p.end() - #self.optimizePixmap = QtGui.QPixmap(self.spotImage) - #self.optimizeFragments = [] - #self.setFlags(self.flags() | self.ItemIgnoresTransformations) - - def setPxMode(self, mode): - self.pxMode = mode - - def clear(self): - for i in self.spots: - i.setParentItem(None) - s = i.scene() - if s is not None: - s.removeItem(i) - self.spots = [] - - - def getRange(self, ax, percent): - return self.range[ax] - - def setPoints(self, spots=None, x=None, y=None, data=None): - self.clear() - self.range = [[0,0],[0,0]] - self.addPoints(spots, x, y, data) - - def addPoints(self, spots=None, x=None, y=None, data=None): - xmn = ymn = xmx = ymx = None - if spots is not None: - n = len(spots) - else: - n = len(x) - - for i in range(n): - if spots is not None: - s = spots[i] - pos = Point(s['pos']) - else: - s = {} - pos = Point(x[i], y[i]) - if data is not None: - s['data'] = data[i] - - size = s.get('size', self.size) - if self.pxMode: - psize = 0 - else: - psize = size - if xmn is None: - xmn = pos[0]-psize - xmx = pos[0]+psize - ymn = pos[1]-psize - ymx = pos[1]+psize - else: - xmn = min(xmn, pos[0]-psize) - xmx = max(xmx, pos[0]+psize) - ymn = min(ymn, pos[1]-psize) - ymx = max(ymx, pos[1]+psize) - #print pos, xmn, xmx, ymn, ymx - brush = s.get('brush', self.brush) - pen = s.get('pen', self.pen) - pen.setCosmetic(True) - data2 = s.get('data', None) - item = self.mkSpot(pos, size, self.pxMode, brush, pen, data2, index=len(self.spots)) - self.spots.append(item) - #if self.optimize: - #item.hide() - #frag = QtGui.QPainter.PixmapFragment.create(pos, QtCore.QRectF(0, 0, size, size)) - #self.optimizeFragments.append(frag) - self.range = [[xmn, xmx], [ymn, ymx]] - - #def paint(self, p, *args): - #if not self.optimize: - #return - ##p.setClipRegion(self.boundingRect()) - #p.drawPixmapFragments(self.optimizeFragments, self.optimizePixmap) - - def paint(self, *args): - pass - - def spotPixmap(self): - if not self.identical: - return None - if self._spotPixmap is None: - self._spotPixmap = PixmapSpotItem.makeSpotImage(self.size, self.pen, self.brush) - return self._spotPixmap - - def mkSpot(self, pos, size, pxMode, brush, pen, data, index=None): - if pxMode: - img = self.spotPixmap() - item = PixmapSpotItem(size, brush, pen, data, image=img, index=index) - else: - item = SpotItem(size, pxMode, brush, pen, data, index=index) - item.setParentItem(self) - item.setPos(pos) - #item.sigClicked.connect(self.pointClicked) - return item - - def boundingRect(self): - ((xmn, xmx), (ymn, ymx)) = self.range - if xmn is None or xmx is None or ymn is None or ymx is None: - return QtCore.QRectF() - return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn) - return QtCore.QRectF(xmn-1, ymn-1, xmx-xmn+2, ymx-ymn+2) - - #def pointClicked(self, point): - #self.sigPointClicked.emit(self, point) - - def points(self): - return self.spots[:] - - def pointsAt(self, pos): - x = pos.x() - y = pos.y() - pw = self.pixelWidth() - ph = self.pixelHeight() - pts = [] - for s in self.spots: - sp = s.pos() - ss = s.size - sx = sp.x() - sy = sp.y() - s2x = s2y = ss * 0.5 - if self.pxMode: - s2x *= pw - s2y *= ph - if x > sx-s2x and x < sx+s2x and y > sy-s2y and y < sy+s2y: - pts.append(s) - #print "HIT:", x, y, sx, sy, s2x, s2y - #else: - #print "No hit:", (x, y), (sx, sy) - #print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y) - pts.sort(lambda a,b: cmp(b.zValue(), a.zValue())) - return pts - - - def mousePressEvent(self, ev): - QtGui.QGraphicsItem.mousePressEvent(self, ev) - if ev.button() == QtCore.Qt.LeftButton: - pts = self.pointsAt(ev.pos()) - if len(pts) > 0: - self.mouseMoved = False - self.ptsClicked = pts - ev.accept() - else: - #print "no spots" - ev.ignore() - else: - ev.ignore() - - def mouseMoveEvent(self, ev): - QtGui.QGraphicsItem.mouseMoveEvent(self, ev) - self.mouseMoved = True - pass - - def mouseReleaseEvent(self, ev): - QtGui.QGraphicsItem.mouseReleaseEvent(self, ev) - if not self.mouseMoved: - self.sigClicked.emit(self, self.ptsClicked) - - -class SpotItem(QtGui.QGraphicsWidget): - #sigClicked = QtCore.Signal(object) - - def __init__(self, size, pxMode, brush, pen, data, index=None): - QtGui.QGraphicsWidget.__init__(self) - self.pxMode = pxMode - - self.pen = pen - self.brush = brush - self.size = size - self.index = index - #s2 = size/2. - self.path = QtGui.QPainterPath() - self.path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) - if pxMode: - #self.setCacheMode(self.DeviceCoordinateCache) ## broken. - self.setFlags(self.flags() | self.ItemIgnoresTransformations) - self.spotImage = QtGui.QImage(size, size, QtGui.QImage.Format_ARGB32_Premultiplied) - self.spotImage.fill(0) - p = QtGui.QPainter(self.spotImage) - p.setRenderHint(p.Antialiasing) - p.setBrush(brush) - p.setPen(pen) - p.drawEllipse(0, 0, size, size) - p.end() - self.pixmap = QtGui.QPixmap(self.spotImage) - else: - self.scale(size, size) - self.data = data - - def setBrush(self, brush): - self.brush = mkBrush(brush) - self.update() - - def setPen(self, pen): - self.pen = mkPen(pen) - self.update() - - def boundingRect(self): - return self.path.boundingRect() - - def shape(self): - return self.path - - def paint(self, p, *opts): - if self.pxMode: - p.drawPixmap(QtCore.QPoint(int(-0.5*self.size), int(-0.5*self.size)), self.pixmap) - else: - p.setPen(self.pen) - p.setBrush(self.brush) - p.drawPath(self.path) - - #def mousePressEvent(self, ev): - #QtGui.QGraphicsItem.mousePressEvent(self, ev) - #if ev.button() == QtCore.Qt.LeftButton: - #self.mouseMoved = False - #ev.accept() - #else: - #ev.ignore() - - - - #def mouseMoveEvent(self, ev): - #QtGui.QGraphicsItem.mouseMoveEvent(self, ev) - #self.mouseMoved = True - #pass - - #def mouseReleaseEvent(self, ev): - #QtGui.QGraphicsItem.mouseReleaseEvent(self, ev) - #if not self.mouseMoved: - #self.sigClicked.emit(self) - -class PixmapSpotItem(QtGui.QGraphicsItem): - #sigClicked = QtCore.Signal(object) - - def __init__(self, size, brush, pen, data, image=None, index=None): - """This class draws a scale-invariant image centered at 0,0. - If no image is specified, then an antialiased circle is constructed instead. - It should be quite fast, but large spots will use a lot of memory.""" - - QtGui.QGraphicsItem.__init__(self) - self.pen = pen - self.brush = brush - self.size = size - self.index = index - self.setFlags(self.flags() | self.ItemIgnoresTransformations | self.ItemHasNoContents) - if image is None: - self.image = self.makeSpotImage(self.size, self.pen, self.brush) - else: - self.image = image - self.pixmap = QtGui.QPixmap(self.image) - #self.setPixmap(self.pixmap) - self.data = data - self.pi = QtGui.QGraphicsPixmapItem(self.pixmap, self) - self.pi.setPos(-0.5*size, -0.5*size) - - #self.translate(-0.5, -0.5) - def boundingRect(self): - return self.pi.boundingRect() - - @staticmethod - def makeSpotImage(size, pen, brush): - img = QtGui.QImage(size+2, size+2, QtGui.QImage.Format_ARGB32_Premultiplied) - img.fill(0) - p = QtGui.QPainter(img) - try: - p.setRenderHint(p.Antialiasing) - p.setBrush(brush) - p.setPen(pen) - p.drawEllipse(1, 1, size, size) - finally: - p.end() ## failure to end a painter properly causes crash. - return img - - - - #def paint(self, p, *args): - #p.setCompositionMode(p.CompositionMode_Plus) - #QtGui.QGraphicsPixmapItem.paint(self, p, *args) - - #def setBrush(self, brush): - #self.brush = mkBrush(brush) - #self.update() - - #def setPen(self, pen): - #self.pen = mkPen(pen) - #self.update() - - #def boundingRect(self): - #return self.path.boundingRect() - - #def shape(self): - #return self.path - - #def paint(self, p, *opts): - #if self.pxMode: - #p.drawPixmap(QtCore.QPoint(int(-0.5*self.size), int(-0.5*self.size)), self.pixmap) - #else: - #p.setPen(self.pen) - #p.setBrush(self.brush) - #p.drawPath(self.path) - - - -class ROIPlotItem(PlotCurveItem): - """Plot curve that monitors an ROI and image for changes to automatically replot.""" - def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None): - self.roi = roi - self.roiData = data - self.roiImg = img - self.axes = axes - self.xVals = xVals - PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color) - #roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) - roi.sigRegionChanged.connect(self.roiChangedEvent) - #self.roiChangedEvent() - - def getRoiData(self): - d = self.roi.getArrayRegion(self.roiData, self.roiImg, axes=self.axes) - if d is None: - return - while d.ndim > 1: - d = d.mean(axis=1) - return d - - def roiChangedEvent(self): - d = self.getRoiData() - self.updateData(d, self.xVals) - - - - -class UIGraphicsItem(GraphicsObject): - """Base class for graphics items with boundaries relative to a GraphicsView widget""" - def __init__(self, view, bounds=None): - GraphicsObject.__init__(self) - self._view = weakref.ref(view) - if bounds is None: - self._bounds = QtCore.QRectF(0, 0, 1, 1) - else: - self._bounds = bounds - self._viewRect = self._view().rect() - self._viewTransform = self.viewTransform() - self.setNewBounds() - #QtCore.QObject.connect(view, QtCore.SIGNAL('viewChanged'), self.viewChangedEvent) - view.sigRangeChanged.connect(self.viewRangeChanged) - - def viewRect(self): - """Return the viewport widget rect""" - return self._view().rect() - - def viewTransform(self): - """Returns a matrix that maps viewport coordinates onto scene coordinates""" - if self._view() is None: - return QtGui.QTransform() - else: - return self._view().viewportTransform() - - def boundingRect(self): - if self._view() is None: - self.bounds = self._bounds - else: - vr = self._view().rect() - tr = self.viewTransform() - if vr != self._viewRect or tr != self._viewTransform: - #self.viewChangedEvent(vr, self._viewRect) - self._viewRect = vr - self._viewTransform = tr - self.setNewBounds() - #print "viewRect", self._viewRect.x(), self._viewRect.y(), self._viewRect.width(), self._viewRect.height() - #print "bounds", self.bounds.x(), self.bounds.y(), self.bounds.width(), self.bounds.height() - return self.bounds - - def setNewBounds(self): - bounds = QtCore.QRectF( - QtCore.QPointF(self._bounds.left()*self._viewRect.width(), self._bounds.top()*self._viewRect.height()), - QtCore.QPointF(self._bounds.right()*self._viewRect.width(), self._bounds.bottom()*self._viewRect.height()) - ) - bounds.adjust(0.5, 0.5, 0.5, 0.5) - self.bounds = self.viewTransform().inverted()[0].mapRect(bounds) - self.prepareGeometryChange() - - def viewRangeChanged(self): - """Called when the view widget is resized""" - self.boundingRect() - self.update() - - def unitRect(self): - return self.viewTransform().inverted()[0].mapRect(QtCore.QRectF(0, 0, 1, 1)) - - def paint(self, *args): - pass - - -class DebugText(QtGui.QGraphicsTextItem): - def paint(self, *args): - p = debug.Profiler("DebugText.paint", disabled=True) - QtGui.QGraphicsTextItem.paint(self, *args) - p.finish() - -class LabelItem(QtGui.QGraphicsWidget): - def __init__(self, text, parent=None, **args): - QtGui.QGraphicsWidget.__init__(self, parent) - self.item = DebugText(self) - self.opts = args - if 'color' not in args: - self.opts['color'] = 'CCC' - else: - if isinstance(args['color'], QtGui.QColor): - self.opts['color'] = colorStr(args['color'])[:6] - self.sizeHint = {} - self.setText(text) - - - def setAttr(self, attr, value): - """Set default text properties. See setText() for accepted parameters.""" - self.opts[attr] = value - - def setText(self, text, **args): - """Set the text and text properties in the label. Accepts optional arguments for auto-generating - a CSS style string: - color: string (example: 'CCFF00') - size: string (example: '8pt') - bold: boolean - italic: boolean - """ - self.text = text - opts = self.opts.copy() - for k in args: - opts[k] = args[k] - - optlist = [] - if 'color' in opts: - optlist.append('color: #' + opts['color']) - if 'size' in opts: - optlist.append('font-size: ' + opts['size']) - if 'bold' in opts and opts['bold'] in [True, False]: - optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']]) - if 'italic' in opts and opts['italic'] in [True, False]: - optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']]) - full = "<span style='%s'>%s</span>" % ('; '.join(optlist), text) - #print full - self.item.setHtml(full) - self.updateMin() - - def resizeEvent(self, ev): - c1 = self.boundingRect().center() - c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos() - dif = c1 - c2 - self.item.moveBy(dif.x(), dif.y()) - #print c1, c2, dif, self.item.pos() - - def setAngle(self, angle): - self.angle = angle - self.item.resetTransform() - self.item.rotate(angle) - self.updateMin() - - def updateMin(self): - bounds = self.item.mapRectToParent(self.item.boundingRect()) - self.setMinimumWidth(bounds.width()) - self.setMinimumHeight(bounds.height()) - #print self.text, bounds.width(), bounds.height() - - #self.sizeHint = { - #QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()), - #QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()), - #QtCore.Qt.MaximumSize: (bounds.width()*2, bounds.height()*2), - #QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this? - #} - - - #def sizeHint(self, hint, constraint): - #return self.sizeHint[hint] - - - - - -class ScaleItem(QtGui.QGraphicsWidget): - def __init__(self, orientation, pen=None, linkView=None, parent=None): - """GraphicsItem showing a single plot axis with ticks, values, and label. - Can be configured to fit on any side of a plot, and can automatically synchronize its displayed scale with ViewBox items. - Ticks can be extended to make a grid.""" - QtGui.QGraphicsWidget.__init__(self, parent) - self.label = QtGui.QGraphicsTextItem(self) - self.orientation = orientation - if orientation not in ['left', 'right', 'top', 'bottom']: - raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.") - if orientation in ['left', 'right']: - #self.setMinimumWidth(25) - #self.setSizePolicy(QtGui.QSizePolicy( - #QtGui.QSizePolicy.Minimum, - #QtGui.QSizePolicy.Expanding - #)) - self.label.rotate(-90) - #else: - #self.setMinimumHeight(50) - #self.setSizePolicy(QtGui.QSizePolicy( - #QtGui.QSizePolicy.Expanding, - #QtGui.QSizePolicy.Minimum - #)) - #self.drawLabel = False - - self.labelText = '' - self.labelUnits = '' - self.labelUnitPrefix='' - self.labelStyle = {'color': '#CCC'} - - self.textHeight = 18 - self.tickLength = 10 - self.scale = 1.0 - self.autoScale = True - - self.setRange(0, 1) - - if pen is None: - pen = QtGui.QPen(QtGui.QColor(100, 100, 100)) - self.setPen(pen) - - self.linkedView = None - if linkView is not None: - self.linkToView(linkView) - - self.showLabel(False) - - self.grid = False - self.setCacheMode(self.DeviceCoordinateCache) - - def close(self): - self.scene().removeItem(self.label) - self.label = None - self.scene().removeItem(self) - - def setGrid(self, grid): - """Set the alpha value for the grid, or False to disable.""" - self.grid = grid - self.update() - - - def resizeEvent(self, ev=None): - #s = self.size() - - ## Set the position of the label - nudge = 5 - br = self.label.boundingRect() - p = QtCore.QPointF(0, 0) - if self.orientation == 'left': - p.setY(int(self.size().height()/2 + br.width()/2)) - p.setX(-nudge) - #s.setWidth(10) - elif self.orientation == 'right': - #s.setWidth(10) - p.setY(int(self.size().height()/2 + br.width()/2)) - p.setX(int(self.size().width()-br.height()+nudge)) - elif self.orientation == 'top': - #s.setHeight(10) - p.setY(-nudge) - p.setX(int(self.size().width()/2. - br.width()/2.)) - elif self.orientation == 'bottom': - p.setX(int(self.size().width()/2. - br.width()/2.)) - #s.setHeight(10) - p.setY(int(self.size().height()-br.height()+nudge)) - #self.label.resize(s) - self.label.setPos(p) - - def showLabel(self, show=True): - #self.drawLabel = show - self.label.setVisible(show) - if self.orientation in ['left', 'right']: - self.setWidth() - else: - self.setHeight() - if self.autoScale: - self.setScale() - - def setLabel(self, text=None, units=None, unitPrefix=None, **args): - if text is not None: - self.labelText = text - self.showLabel() - if units is not None: - self.labelUnits = units - self.showLabel() - if unitPrefix is not None: - self.labelUnitPrefix = unitPrefix - if len(args) > 0: - self.labelStyle = args - self.label.setHtml(self.labelString()) - self.resizeEvent() - self.update() - - def labelString(self): - if self.labelUnits == '': - if self.scale == 1.0: - units = '' - else: - units = u'(x%g)' % (1.0/self.scale) - else: - #print repr(self.labelUnitPrefix), repr(self.labelUnits) - units = u'(%s%s)' % (self.labelUnitPrefix, self.labelUnits) - - s = u'%s %s' % (self.labelText, units) - - style = ';'.join(['%s: "%s"' % (k, self.labelStyle[k]) for k in self.labelStyle]) - - return u"<span style='%s'>%s</span>" % (style, s) - - def setHeight(self, h=None): - if h is None: - h = self.textHeight + self.tickLength - if self.label.isVisible(): - h += self.textHeight - self.setMaximumHeight(h) - self.setMinimumHeight(h) - - - def setWidth(self, w=None): - if w is None: - w = self.tickLength + 40 - if self.label.isVisible(): - w += self.textHeight - self.setMaximumWidth(w) - self.setMinimumWidth(w) - - def setPen(self, pen): - self.pen = pen - self.update() - - def setScale(self, scale=None): - if scale is None: - #if self.drawLabel: ## If there is a label, then we are free to rescale the values - if self.label.isVisible(): - d = self.range[1] - self.range[0] - #pl = 1-int(log10(d)) - #scale = 10 ** pl - (scale, prefix) = siScale(d / 2.) - if self.labelUnits == '' and prefix in ['k', 'm']: ## If we are not showing units, wait until 1e6 before scaling. - scale = 1.0 - prefix = '' - self.setLabel(unitPrefix=prefix) - else: - scale = 1.0 - - - if scale != self.scale: - self.scale = scale - self.setLabel() - self.update() - - def setRange(self, mn, mx): - if mn in [np.nan, np.inf, -np.inf] or mx in [np.nan, np.inf, -np.inf]: - raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) - self.range = [mn, mx] - if self.autoScale: - self.setScale() - self.update() - - def linkToView(self, view): - if self.orientation in ['right', 'left']: - if self.linkedView is not None and self.linkedView() is not None: - #view.sigYRangeChanged.disconnect(self.linkedViewChanged) - ## should be this instead? - self.linkedView().sigYRangeChanged.disconnect(self.linkedViewChanged) - self.linkedView = weakref.ref(view) - view.sigYRangeChanged.connect(self.linkedViewChanged) - #signal = QtCore.SIGNAL('yRangeChanged') - else: - if self.linkedView is not None and self.linkedView() is not None: - #view.sigYRangeChanged.disconnect(self.linkedViewChanged) - ## should be this instead? - self.linkedView().sigXRangeChanged.disconnect(self.linkedViewChanged) - self.linkedView = weakref.ref(view) - view.sigXRangeChanged.connect(self.linkedViewChanged) - #signal = QtCore.SIGNAL('xRangeChanged') - - - def linkedViewChanged(self, view, newRange): - self.setRange(*newRange) - - def boundingRect(self): - if self.linkedView is None or self.linkedView() is None or self.grid is False: - return self.mapRectFromParent(self.geometry()) - else: - return self.mapRectFromParent(self.geometry()) | self.mapRectFromScene(self.linkedView().mapRectToScene(self.linkedView().boundingRect())) - - def paint(self, p, opt, widget): - prof = debug.Profiler("ScaleItem.paint", disabled=True) - p.setPen(self.pen) - - #bounds = self.boundingRect() - bounds = self.mapRectFromParent(self.geometry()) - - if self.linkedView is None or self.linkedView() is None or self.grid is False: - tbounds = bounds - else: - tbounds = self.mapRectFromScene(self.linkedView().mapRectToScene(self.linkedView().boundingRect())) - - if self.orientation == 'left': - p.drawLine(bounds.topRight(), bounds.bottomRight()) - tickStart = tbounds.right() - tickStop = bounds.right() - tickDir = -1 - axis = 0 - elif self.orientation == 'right': - p.drawLine(bounds.topLeft(), bounds.bottomLeft()) - tickStart = tbounds.left() - tickStop = bounds.left() - tickDir = 1 - axis = 0 - elif self.orientation == 'top': - p.drawLine(bounds.bottomLeft(), bounds.bottomRight()) - tickStart = tbounds.bottom() - tickStop = bounds.bottom() - tickDir = -1 - axis = 1 - elif self.orientation == 'bottom': - p.drawLine(bounds.topLeft(), bounds.topRight()) - tickStart = tbounds.top() - tickStop = bounds.top() - tickDir = 1 - axis = 1 - - ## Determine optimal tick spacing - #intervals = [1., 2., 5., 10., 20., 50.] - #intervals = [1., 2.5, 5., 10., 25., 50.] - intervals = [1., 2., 10., 20., 100.] - dif = abs(self.range[1] - self.range[0]) - if dif == 0.0: - return - #print "dif:", dif - pw = 10 ** (np.floor(np.log10(dif))-1) - for i in range(len(intervals)): - i1 = i - if dif / (pw*intervals[i]) < 10: - break - - textLevel = i1 ## draw text at this scale level - - #print "range: %s dif: %f power: %f interval: %f spacing: %f" % (str(self.range), dif, pw, intervals[i1], sp) - - #print " start at %f, %d ticks" % (start, num) - - - if axis == 0: - xs = -bounds.height() / dif - else: - xs = bounds.width() / dif - - prof.mark('init') - - tickPositions = set() # remembers positions of previously drawn ticks - ## draw ticks and generate list of texts to draw - ## (to improve performance, we do not interleave line and text drawing, since this causes unnecessary pipeline switching) - ## draw three different intervals, long ticks first - texts = [] - for i in reversed([i1, i1+1, i1+2]): - if i > len(intervals): - continue - ## spacing for this interval - sp = pw*intervals[i] - - ## determine starting tick - start = np.ceil(self.range[0] / sp) * sp - - ## determine number of ticks - num = int(dif / sp) + 1 - - ## last tick value - last = start + sp * num - - ## Number of decimal places to print - maxVal = max(abs(start), abs(last)) - places = max(0, 1-int(np.log10(sp*self.scale))) - - ## length of tick - h = min(self.tickLength, (self.tickLength*3 / num) - 1.) - - ## alpha - a = min(255, (765. / num) - 1.) - - if axis == 0: - offset = self.range[0] * xs - bounds.height() - else: - offset = self.range[0] * xs - - for j in range(num): - v = start + sp * j - x = (v * xs) - offset - p1 = [0, 0] - p2 = [0, 0] - p1[axis] = tickStart - p2[axis] = tickStop + h*tickDir - p1[1-axis] = p2[1-axis] = x - - if p1[1-axis] > [bounds.width(), bounds.height()][1-axis]: - continue - if p1[1-axis] < 0: - continue - p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100, a))) - # draw tick only if there is none - tickPos = p1[1-axis] - if tickPos not in tickPositions: - p.drawLine(Point(p1), Point(p2)) - tickPositions.add(tickPos) - if i == textLevel: - if abs(v) < .001 or abs(v) >= 10000: - vstr = "%g" % (v * self.scale) - else: - vstr = ("%%0.%df" % places) % (v * self.scale) - - textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr) - height = textRect.height() - self.textHeight = height - if self.orientation == 'left': - textFlags = QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter - rect = QtCore.QRectF(tickStop-100, x-(height/2), 100-self.tickLength, height) - elif self.orientation == 'right': - textFlags = QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter - rect = QtCore.QRectF(tickStop+self.tickLength, x-(height/2), 100-self.tickLength, height) - elif self.orientation == 'top': - textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom - rect = QtCore.QRectF(x-100, tickStop-self.tickLength-height, 200, height) - elif self.orientation == 'bottom': - textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop - rect = QtCore.QRectF(x-100, tickStop+self.tickLength, 200, height) - - p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) - #p.drawText(rect, textFlags, vstr) - texts.append((rect, textFlags, vstr)) - - prof.mark('draw ticks') - for args in texts: - p.drawText(*args) - prof.mark('draw text') - prof.finish() - - def show(self): - - if self.orientation in ['left', 'right']: - self.setWidth() - else: - self.setHeight() - QtGui.QGraphicsWidget.show(self) - - def hide(self): - if self.orientation in ['left', 'right']: - self.setWidth(0) - else: - self.setHeight(0) - QtGui.QGraphicsWidget.hide(self) - - def wheelEvent(self, ev): - if self.linkedView is None or self.linkedView() is None: return - if self.orientation in ['left', 'right']: - self.linkedView().wheelEvent(ev, axis=1) - else: - self.linkedView().wheelEvent(ev, axis=0) - ev.accept() - - - -class ViewBox(QtGui.QGraphicsWidget): - - sigYRangeChanged = QtCore.Signal(object, object) - sigXRangeChanged = QtCore.Signal(object, object) - sigRangeChangedManually = QtCore.Signal(object) - sigRangeChanged = QtCore.Signal(object, object) - - """Box that allows internal scaling/panning of children by mouse drag. Not compatible with GraphicsView having the same functionality.""" - def __init__(self, parent=None, border=None): - QtGui.QGraphicsWidget.__init__(self, parent) - #self.gView = view - #self.showGrid = showGrid - - ## separating targetRange and viewRange allows the view to be resized - ## while keeping all previously viewed contents visible - self.targetRange = [[0,1], [0,1]] ## child coord. range visible [[xmin, xmax], [ymin, ymax]] - self.viewRange = [[0,1], [0,1]] ## actual range viewed - - self.wheelScaleFactor = -1.0 / 8.0 - self.aspectLocked = False - self.setFlag(QtGui.QGraphicsItem.ItemClipsChildrenToShape) - #self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape) - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - - #self.childGroup = QtGui.QGraphicsItemGroup(self) - self.childGroup = ItemGroup(self) - self.currentScale = Point(1, 1) - - self.yInverted = False - #self.invertY() - self.setZValue(-100) - #self.picture = None - self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) - - self.border = border - - self.mouseEnabled = [True, True] - - def setMouseEnabled(self, x, y): - self.mouseEnabled = [x, y] - - def addItem(self, item): - if item.zValue() < self.zValue(): - item.setZValue(self.zValue()+1) - item.setParentItem(self.childGroup) - #print "addItem:", item, item.boundingRect() - - def removeItem(self, item): - self.scene().removeItem(item) - - def resizeEvent(self, ev): - #self.setRange(self.range, padding=0) - self.updateMatrix() - - - def viewRect(self): - try: - vr0 = self.viewRange[0] - vr1 = self.viewRange[1] - return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) - except: - print "make qrectf failed:", self.viewRange - raise - - def targetRect(self): - """Return the region which has been requested to be visible. - (this is not necessarily the same as the region that is *actually* visible)""" - try: - tr0 = self.targetRange[0] - tr1 = self.targetRange[1] - return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) - except: - print "make qrectf failed:", self.targetRange - raise - - def invertY(self, b=True): - self.yInverted = b - self.updateMatrix() - - def setAspectLocked(self, lock=True, ratio=1): - """If the aspect ratio is locked, view scaling is always forced to be isotropic. - By default, the ratio is set to 1; x and y both have the same scaling. - This ratio can be overridden (width/height), or use None to lock in the current ratio. - """ - if not lock: - self.aspectLocked = False - else: - vr = self.viewRect() - currentRatio = vr.width() / vr.height() - if ratio is None: - ratio = currentRatio - self.aspectLocked = ratio - if ratio != currentRatio: ## If this would change the current range, do that now - #self.setRange(0, self.viewRange[0][0], self.viewRange[0][1]) - self.updateMatrix() - - def childTransform(self): - m = self.childGroup.transform() - m1 = QtGui.QTransform() - m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y()) - return m*m1 - - - def viewScale(self): - vr = self.viewRect() - #print "viewScale:", self.range - xd = vr.width() - yd = vr.height() - if xd == 0 or yd == 0: - print "Warning: 0 range in view:", xd, yd - return np.array([1,1]) - - #cs = self.canvas().size() - cs = self.boundingRect() - scale = np.array([cs.width() / xd, cs.height() / yd]) - #print "view scale:", scale - return scale - - def scaleBy(self, s, center=None): - """Scale by s around given center point (or center of view)""" - #print "scaleBy", s, center - #if self.aspectLocked: - #s[0] = s[1] - scale = Point(s) - if self.aspectLocked is not False: - scale[0] = self.aspectLocked * scale[1] - - - #xr, yr = self.range - vr = self.viewRect() - if center is None: - center = Point(vr.center()) - #xc = (xr[1] + xr[0]) * 0.5 - #yc = (yr[1] + yr[0]) * 0.5 - else: - center = Point(center) - #(xc, yc) = center - - #x1 = xc + (xr[0]-xc) * s[0] - #x2 = xc + (xr[1]-xc) * s[0] - #y1 = yc + (yr[0]-yc) * s[1] - #y2 = yc + (yr[1]-yc) * s[1] - tl = center + (vr.topLeft()-center) * scale - br = center + (vr.bottomRight()-center) * scale - - #print xr, xc, s, (xr[0]-xc) * s[0], (xr[1]-xc) * s[0] - #print [[x1, x2], [y1, y2]] - - #if not self.aspectLocked: - #self.setXRange(x1, x2, update=False, padding=0) - #self.setYRange(y1, y2, padding=0) - #print self.range - - self.setRange(QtCore.QRectF(tl, br), padding=0) - - def translateBy(self, t, viewCoords=False): - t = t.astype(np.float) - #print "translate:", t, self.viewScale() - if viewCoords: ## scale from pixels - t /= self.viewScale() - #xr, yr = self.range - - vr = self.viewRect() - #print xr, yr, t - #self.setXRange(xr[0] + t[0], xr[1] + t[0], update=False, padding=0) - #self.setYRange(yr[0] + t[1], yr[1] + t[1], padding=0) - self.setRange(vr.translated(Point(t)), padding=0) - - def wheelEvent(self, ev, axis=None): - mask = np.array(self.mouseEnabled, dtype=np.float) - if axis is not None and axis >= 0 and axis < len(mask): - mv = mask[axis] - mask[:] = 0 - mask[axis] = mv - s = ((mask * 0.02) + 1) ** (ev.delta() * self.wheelScaleFactor) # actual scaling factor - # scale 'around' mouse cursor position - center = Point(self.childGroup.transform().inverted()[0].map(ev.pos())) - self.scaleBy(s, center) - #self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled) - self.sigRangeChangedManually.emit(self.mouseEnabled) - ev.accept() - - def mouseMoveEvent(self, ev): - QtGui.QGraphicsWidget.mouseMoveEvent(self, ev) - pos = np.array([ev.pos().x(), ev.pos().y()]) - dif = pos - self.mousePos - dif *= -1 - self.mousePos = pos - - ## Ignore axes if mouse is disabled - mask = np.array(self.mouseEnabled, dtype=np.float) - - ## Scale or translate based on mouse button - if ev.buttons() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): - if not self.yInverted: - mask *= np.array([1, -1]) - tr = dif*mask - self.translateBy(tr, viewCoords=True) - #self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled) - self.sigRangeChangedManually.emit(self.mouseEnabled) - ev.accept() - elif ev.buttons() & QtCore.Qt.RightButton: - if self.aspectLocked is not False: - mask[0] = 0 - dif = ev.screenPos() - ev.lastScreenPos() - dif = np.array([dif.x(), dif.y()]) - dif[0] *= -1 - s = ((mask * 0.02) + 1) ** dif - #print mask, dif, s - center = Point(self.childGroup.transform().inverted()[0].map(ev.buttonDownPos(QtCore.Qt.RightButton))) - self.scaleBy(s, center) - #self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled) - self.sigRangeChangedManually.emit(self.mouseEnabled) - ev.accept() - else: - ev.ignore() - - def mousePressEvent(self, ev): - QtGui.QGraphicsWidget.mousePressEvent(self, ev) - - self.mousePos = np.array([ev.pos().x(), ev.pos().y()]) - self.pressPos = self.mousePos.copy() - ev.accept() - - def mouseReleaseEvent(self, ev): - QtGui.QGraphicsWidget.mouseReleaseEvent(self, ev) - pos = np.array([ev.pos().x(), ev.pos().y()]) - #if sum(abs(self.pressPos - pos)) < 3: ## Detect click - #if ev.button() == QtCore.Qt.RightButton: - #self.ctrlMenu.popup(self.mapToGlobal(ev.pos())) - self.mousePos = pos - ev.accept() - - def setRange(self, ax, min=None, max=None, padding=0.02, update=True): - if isinstance(ax, QtCore.QRectF): - changes = {0: [ax.left(), ax.right()], 1: [ax.top(), ax.bottom()]} - #if self.aspectLocked is not False: - #sbr = self.boundingRect() - #if sbr.width() == 0 or (ax.height()/ax.width()) > (sbr.height()/sbr.width()): - #chax = 0 - #else: - #chax = 1 - - - - - elif ax in [1,0]: - changes = {ax: [min,max]} - #if self.aspectLocked is not False: - #ax2 = 1 - ax - #ratio = self.aspectLocked - #r2 = self.range[ax2] - #d = ratio * (max-min) * 0.5 - #c = (self.range[ax2][1] + self.range[ax2][0]) * 0.5 - #changes[ax2] = [c-d, c+d] - - else: - print ax - raise Exception("argument 'ax' must be 0, 1, or QRectF.") - - - changed = [False, False] - for ax, range in changes.iteritems(): - min, max = range - if min == max: ## If we requested 0 range, try to preserve previous scale. Otherwise just pick an arbitrary scale. - dy = self.viewRange[ax][1] - self.viewRange[ax][0] - if dy == 0: - dy = 1 - min -= dy*0.5 - max += dy*0.5 - padding = 0.0 - if any(np.isnan([min, max])) or any(np.isinf([min, max])): - raise Exception("Not setting range [%s, %s]" % (str(min), str(max))) - - p = (max-min) * padding - min -= p - max += p - - if self.targetRange[ax] != [min, max]: - self.targetRange[ax] = [min, max] - changed[ax] = True - - if update: - self.updateMatrix(changed) - - - - - def setYRange(self, min, max, update=True, padding=0.02): - self.setRange(1, min, max, update=update, padding=padding) - - def setXRange(self, min, max, update=True, padding=0.02): - self.setRange(0, min, max, update=update, padding=padding) - - def autoRange(self, padding=0.02): - br = self.childGroup.childrenBoundingRect() - self.setRange(br, padding=padding) - - - def updateMatrix(self, changed=None): - if changed is None: - changed = [False, False] - #print "udpateMatrix:" - #print " range:", self.range - tr = self.targetRect() - bounds = self.boundingRect() - - ## set viewRect, given targetRect and possibly aspect ratio constraint - if self.aspectLocked is False or bounds.height() == 0: - self.viewRange = [self.targetRange[0][:], self.targetRange[1][:]] - else: - viewRatio = bounds.width() / bounds.height() - targetRatio = self.aspectLocked * tr.width() / tr.height() - if targetRatio > viewRatio: - ## target is wider than view - dy = 0.5 * (tr.width() / (self.aspectLocked * viewRatio) - tr.height()) - if dy != 0: - changed[1] = True - self.viewRange = [self.targetRange[0][:], [self.targetRange[1][0] - dy, self.targetRange[1][1] + dy]] - else: - dx = 0.5 * (tr.height() * viewRatio * self.aspectLocked - tr.width()) - if dx != 0: - changed[0] = True - self.viewRange = [[self.targetRange[0][0] - dx, self.targetRange[0][1] + dx], self.targetRange[1][:]] - - - vr = self.viewRect() - translate = Point(vr.center()) - #print " bounds:", bounds - if vr.height() == 0 or vr.width() == 0: - return - scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) - #print " scale:", scale - m = QtGui.QTransform() - - ## First center the viewport at 0 - self.childGroup.resetTransform() - center = self.transform().inverted()[0].map(bounds.center()) - #print " transform to center:", center - if self.yInverted: - m.translate(center.x(), -center.y()) - #print " inverted; translate", center.x(), center.y() - else: - m.translate(center.x(), center.y()) - #print " not inverted; translate", center.x(), -center.y() - - ## Now scale and translate properly - if not self.yInverted: - scale = scale * Point(1, -1) - m.scale(scale[0], scale[1]) - st = translate - m.translate(-st[0], -st[1]) - self.childGroup.setTransform(m) - self.currentScale = scale - - - if changed[0]: - self.sigXRangeChanged.emit(self, tuple(self.viewRange[0])) - if changed[1]: - self.sigYRangeChanged.emit(self, tuple(self.viewRange[1])) - if any(changed): - self.sigRangeChanged.emit(self, self.viewRange) - - - - def boundingRect(self): - return QtCore.QRectF(0, 0, self.size().width(), self.size().height()) - - def paint(self, p, opt, widget): - if self.border is not None: - bounds = self.boundingRect() - p.setPen(self.border) - #p.fillRect(bounds, QtGui.QColor(0, 0, 0)) - p.drawRect(bounds) - - -class InfiniteLine(GraphicsObject): - - sigDragged = QtCore.Signal(object) - sigPositionChangeFinished = QtCore.Signal(object) - sigPositionChanged = QtCore.Signal(object) - - def __init__(self, view, pos=0, angle=90, pen=None, movable=False, bounds=None): - GraphicsObject.__init__(self) - self.bounds = QtCore.QRectF() ## graphicsitem boundary - - if bounds is None: ## allowed value boundaries for orthogonal lines - self.maxRange = [None, None] - else: - self.maxRange = bounds - self.setMovable(movable) - self.view = weakref.ref(view) - self.p = [0, 0] - self.setAngle(angle) - self.setPos(pos) - - - self.hasMoved = False - - - if pen is None: - pen = QtGui.QPen(QtGui.QColor(200, 200, 100)) - self.setPen(pen) - self.currentPen = self.pen - #self.setFlag(self.ItemSendsScenePositionChanges) - #for p in self.getBoundingParents(): - #QtCore.QObject.connect(p, QtCore.SIGNAL('viewChanged'), self.updateLine) - #QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateLine) - self.view().sigRangeChanged.connect(self.updateLine) - - def setMovable(self, m): - self.movable = m - self.setAcceptHoverEvents(m) - - - def setBounds(self, bounds): - self.maxRange = bounds - self.setValue(self.value()) - - def hoverEnterEvent(self, ev): - self.currentPen = QtGui.QPen(QtGui.QColor(255, 0,0)) - self.update() - ev.ignore() - - def hoverLeaveEvent(self, ev): - self.currentPen = self.pen - self.update() - ev.ignore() - - def setPen(self, pen): - self.pen = pen - self.currentPen = self.pen - - def setAngle(self, angle): - """Takes angle argument in degrees.""" - self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135 - self.updateLine() - - def setPos(self, pos): - if type(pos) in [list, tuple]: - newPos = pos - elif isinstance(pos, QtCore.QPointF): - newPos = [pos.x(), pos.y()] - else: - if self.angle == 90: - newPos = [pos, 0] - elif self.angle == 0: - newPos = [0, pos] - else: - raise Exception("Must specify 2D coordinate for non-orthogonal lines.") - - ## check bounds (only works for orthogonal lines) - if self.angle == 90: - if self.maxRange[0] is not None: - newPos[0] = max(newPos[0], self.maxRange[0]) - if self.maxRange[1] is not None: - newPos[0] = min(newPos[0], self.maxRange[1]) - elif self.angle == 0: - if self.maxRange[0] is not None: - newPos[1] = max(newPos[1], self.maxRange[0]) - if self.maxRange[1] is not None: - newPos[1] = min(newPos[1], self.maxRange[1]) - - - if self.p != newPos: - self.p = newPos - self.updateLine() - #self.emit(QtCore.SIGNAL('positionChanged'), self) - self.sigPositionChanged.emit(self) - - def getXPos(self): - return self.p[0] - - def getYPos(self): - return self.p[1] - - def getPos(self): - return self.p - - def value(self): - if self.angle%180 == 0: - return self.getYPos() - elif self.angle%180 == 90: - return self.getXPos() - else: - return self.getPos() - - def setValue(self, v): - self.setPos(v) - - ## broken in 4.7 - #def itemChange(self, change, val): - #if change in [self.ItemScenePositionHasChanged, self.ItemSceneHasChanged]: - #self.updateLine() - #print "update", change - #print self.getBoundingParents() - #else: - #print "ignore", change - #return GraphicsObject.itemChange(self, change, val) - - def updateLine(self): - - #unit = QtCore.QRect(0, 0, 10, 10) - #if self.scene() is not None: - #gv = self.scene().views()[0] - #unit = gv.mapToScene(unit).boundingRect() - ##print unit - #unit = self.mapRectFromScene(unit) - ##print unit - - vr = self.view().viewRect() - #vr = self.viewBounds() - if vr is None: - return - #print 'before', self.bounds - - if self.angle > 45: - m = np.tan((90-self.angle) * np.pi / 180.) - y2 = vr.bottom() - y1 = vr.top() - x1 = self.p[0] + (y1 - self.p[1]) * m - x2 = self.p[0] + (y2 - self.p[1]) * m - else: - m = np.tan(self.angle * np.pi / 180.) - x1 = vr.left() - x2 = vr.right() - y2 = self.p[1] + (x1 - self.p[0]) * m - y1 = self.p[1] + (x2 - self.p[0]) * m - #print vr, x1, y1, x2, y2 - self.prepareGeometryChange() - self.line = (QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2)) - self.bounds = QtCore.QRectF(self.line[0], self.line[1]) - ## Stupid bug causes lines to disappear: - if self.angle % 180 == 90: - px = self.pixelWidth() - #self.bounds.setWidth(1e-9) - self.bounds.setX(x1 + px*-5) - self.bounds.setWidth(px*10) - if self.angle % 180 == 0: - px = self.pixelHeight() - #self.bounds.setHeight(1e-9) - self.bounds.setY(y1 + px*-5) - self.bounds.setHeight(px*10) - - #QtGui.QGraphicsLineItem.setLine(self, x1, y1, x2, y2) - #self.update() - - def boundingRect(self): - #self.updateLine() - #return QtGui.QGraphicsLineItem.boundingRect(self) - #print "bounds", self.bounds - return self.bounds - - def paint(self, p, *args): - w,h = self.pixelWidth()*5, self.pixelHeight()*5*1.1547 - #self.updateLine() - l = self.line - - p.setPen(self.currentPen) - #print "paint", self.line - p.drawLine(l[0], l[1]) - - p.setBrush(QtGui.QBrush(self.currentPen.color())) - p.drawConvexPolygon(QtGui.QPolygonF([ - l[0] + QtCore.QPointF(-w, 0), - l[0] + QtCore.QPointF(0, h), - l[0] + QtCore.QPointF(w, 0), - ])) - - #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) - #p.drawRect(self.boundingRect()) - - def mousePressEvent(self, ev): - if self.movable and ev.button() == QtCore.Qt.LeftButton: - ev.accept() - self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) - else: - ev.ignore() - - def mouseMoveEvent(self, ev): - self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) - #self.emit(QtCore.SIGNAL('dragged'), self) - self.sigDragged.emit(self) - self.hasMoved = True - - def mouseReleaseEvent(self, ev): - if self.hasMoved and ev.button() == QtCore.Qt.LeftButton: - self.hasMoved = False - #self.emit(QtCore.SIGNAL('positionChangeFinished'), self) - self.sigPositionChangeFinished.emit(self) - - - -class LinearRegionItem(GraphicsObject): - - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - - """Used for marking a horizontal or vertical region in plots.""" - def __init__(self, view, orientation="vertical", vals=[0,1], brush=None, movable=True, bounds=None): - GraphicsObject.__init__(self) - self.orientation = orientation - if hasattr(self, "ItemHasNoContents"): - self.setFlag(self.ItemHasNoContents) - self.rect = QtGui.QGraphicsRectItem(self) - self.rect.setParentItem(self) - self.bounds = QtCore.QRectF() - self.view = weakref.ref(view) - self.setBrush = self.rect.setBrush - self.brush = self.rect.brush - - if orientation[0] == 'h': - self.lines = [ - InfiniteLine(view, QtCore.QPointF(0, vals[0]), 0, movable=movable, bounds=bounds), - InfiniteLine(view, QtCore.QPointF(0, vals[1]), 0, movable=movable, bounds=bounds)] - else: - self.lines = [ - InfiniteLine(view, QtCore.QPointF(vals[0], 0), 90, movable=movable, bounds=bounds), - InfiniteLine(view, QtCore.QPointF(vals[1], 0), 90, movable=movable, bounds=bounds)] - #QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateBounds) - self.view().sigRangeChanged.connect(self.updateBounds) - - for l in self.lines: - l.setParentItem(self) - #l.connect(l, QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished) - l.sigPositionChangeFinished.connect(self.lineMoveFinished) - #l.connect(l, QtCore.SIGNAL('positionChanged'), self.lineMoved) - l.sigPositionChanged.connect(self.lineMoved) - - if brush is None: - brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) - self.setBrush(brush) - self.setMovable(movable) - - def setBounds(self, bounds): - for l in self.lines: - l.setBounds(bounds) - - def setMovable(self, m): - for l in self.lines: - l.setMovable(m) - self.movable = m - - def boundingRect(self): - return self.rect.boundingRect() - - def lineMoved(self): - self.updateBounds() - #self.emit(QtCore.SIGNAL('regionChanged'), self) - self.sigRegionChanged.emit(self) - - def lineMoveFinished(self): - #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) - self.sigRegionChangeFinished.emit(self) - - - def updateBounds(self): - vb = self.view().viewRect() - vals = [self.lines[0].value(), self.lines[1].value()] - if self.orientation[0] == 'h': - vb.setTop(min(vals)) - vb.setBottom(max(vals)) - else: - vb.setLeft(min(vals)) - vb.setRight(max(vals)) - if vb != self.bounds: - self.bounds = vb - self.rect.setRect(vb) - - def mousePressEvent(self, ev): - if not self.movable: - ev.ignore() - return - for l in self.lines: - l.mousePressEvent(ev) ## pass event to both lines so they move together - #if self.movable and ev.button() == QtCore.Qt.LeftButton: - #ev.accept() - #self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) - #else: - #ev.ignore() - - def mouseReleaseEvent(self, ev): - for l in self.lines: - l.mouseReleaseEvent(ev) - - def mouseMoveEvent(self, ev): - #print "move", ev.pos() - if not self.movable: - return - self.lines[0].blockSignals(True) # only want to update once - for l in self.lines: - l.mouseMoveEvent(ev) - self.lines[0].blockSignals(False) - #self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) - #self.emit(QtCore.SIGNAL('dragged'), self) - - def getRegion(self): - if self.orientation[0] == 'h': - r = (self.bounds.top(), self.bounds.bottom()) - else: - r = (self.bounds.left(), self.bounds.right()) - return (min(r), max(r)) - - def setRegion(self, rgn): - self.lines[0].setValue(rgn[0]) - self.lines[1].setValue(rgn[1]) - - -class VTickGroup(QtGui.QGraphicsPathItem): - def __init__(self, xvals=None, yrange=None, pen=None, relative=False, view=None): - QtGui.QGraphicsPathItem.__init__(self) - if yrange is None: - yrange = [0, 1] - if xvals is None: - xvals = [] - if pen is None: - pen = (200, 200, 200) - self.ticks = [] - self.xvals = [] - if view is None: - self.view = None - else: - self.view = weakref.ref(view) - self.yrange = [0,1] - self.setPen(pen) - self.setYRange(yrange, relative) - self.setXVals(xvals) - self.valid = False - - def setPen(self, pen): - pen = mkPen(pen) - QtGui.QGraphicsPathItem.setPen(self, pen) - - def setXVals(self, vals): - self.xvals = vals - self.rebuildTicks() - self.valid = False - - def setYRange(self, vals, relative=False): - self.yrange = vals - self.relative = relative - if self.view is not None: - if relative: - #QtCore.QObject.connect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks) - #QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.rescale) - self.view().sigRangeChanged.connect(self.rescale) - else: - try: - #QtCore.QObject.disconnect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks) - #QtCore.QObject.disconnect(self.view(), QtCore.SIGNAL('viewChanged'), self.rescale) - self.view().sigRangeChanged.disconnect(self.rescale) - except: - pass - self.rebuildTicks() - self.valid = False - - def rescale(self): - #print "RESCALE:" - self.resetTransform() - #height = self.view.size().height() - #p1 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[0])))) - #p2 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[1])))) - #yr = [p1.y(), p2.y()] - vb = self.view().viewRect() - p1 = vb.bottom() - vb.height() * self.yrange[0] - p2 = vb.bottom() - vb.height() * self.yrange[1] - yr = [p1, p2] - - #print " ", vb, yr - self.translate(0.0, yr[0]) - self.scale(1.0, (yr[1]-yr[0])) - #print " ", self.mapRectToScene(self.boundingRect()) - self.boundingRect() - self.update() - - def boundingRect(self): - #print "--request bounds:" - b = QtGui.QGraphicsPathItem.boundingRect(self) - #print " ", self.mapRectToScene(b) - return b - - def yRange(self): - #if self.relative: - #height = self.view.size().height() - #p1 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[0])))) - #p2 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[1])))) - #return [p1.y(), p2.y()] - #else: - #return self.yrange - - return self.yrange - - def rebuildTicks(self): - self.path = QtGui.QPainterPath() - yrange = self.yRange() - #print "rebuild ticks:", yrange - for x in self.xvals: - #path.moveTo(x, yrange[0]) - #path.lineTo(x, yrange[1]) - self.path.moveTo(x, 0.) - self.path.lineTo(x, 1.) - self.setPath(self.path) - self.valid = True - self.rescale() - #print " done..", self.boundingRect() - - def paint(self, *args): - if not self.valid: - self.rebuildTicks() - #print "Paint", self.boundingRect() - QtGui.QGraphicsPathItem.paint(self, *args) - - -class GridItem(UIGraphicsItem): - """Class used to make square grids in plots. NOT the grid used for running scanner sequences.""" - - def __init__(self, view, bounds=None, *args): - UIGraphicsItem.__init__(self, view, bounds) - #QtGui.QGraphicsItem.__init__(self, *args) - self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape) - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - - self.picture = None - - - def viewRangeChanged(self): - self.picture = None - UIGraphicsItem.viewRangeChanged(self) - #self.update() - - def paint(self, p, opt, widget): - #p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) - #p.drawRect(self.boundingRect()) - - ## draw picture - if self.picture is None: - #print "no pic, draw.." - self.generatePicture() - p.drawPicture(0, 0, self.picture) - #print "drawing Grid." - - - def generatePicture(self): - self.picture = QtGui.QPicture() - p = QtGui.QPainter() - p.begin(self.picture) - - dt = self.viewTransform().inverted()[0] - vr = self.viewRect() - unit = self.unitRect() - dim = [vr.width(), vr.height()] - lvr = self.boundingRect() - ul = np.array([lvr.left(), lvr.top()]) - br = np.array([lvr.right(), lvr.bottom()]) - - texts = [] - - if ul[1] > br[1]: - x = ul[1] - ul[1] = br[1] - br[1] = x - for i in range(2, -1, -1): ## Draw three different scales of grid - - dist = br-ul - nlTarget = 10.**i - d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5) - ul1 = np.floor(ul / d) * d - br1 = np.ceil(br / d) * d - dist = br1-ul1 - nl = (dist / d) + 0.5 - for ax in range(0,2): ## Draw grid for both axes - ppl = dim[ax] / nl[ax] - c = np.clip(3.*(ppl-3), 0., 30.) - linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) - textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2)) - #linePen.setCosmetic(True) - #linePen.setWidth(1) - bx = (ax+1) % 2 - for x in range(0, int(nl[ax])): - linePen.setCosmetic(False) - if ax == 0: - linePen.setWidthF(self.pixelHeight()) - else: - linePen.setWidthF(self.pixelWidth()) - p.setPen(linePen) - p1 = np.array([0.,0.]) - p2 = np.array([0.,0.]) - p1[ax] = ul1[ax] + x * d[ax] - p2[ax] = p1[ax] - p1[bx] = ul[bx] - p2[bx] = br[bx] - p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) - if i < 2: - p.setPen(textPen) - if ax == 0: - x = p1[0] + unit.width() - y = ul[1] + unit.height() * 8. - else: - x = ul[0] + unit.width()*3 - y = p1[1] + unit.height() - texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) - tr = self.viewTransform() - tr.scale(1.5, 1.5) - p.setWorldTransform(tr.inverted()[0]) - for t in texts: - x = tr.map(t[0]) - p.drawText(x, t[1]) - p.end() - -class ScaleBar(UIGraphicsItem): - def __init__(self, view, size, width=5, color=(100, 100, 255)): - self.size = size - UIGraphicsItem.__init__(self, view) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - #self.pen = QtGui.QPen(QtGui.QColor(*color)) - #self.pen.setWidth(width) - #self.pen.setCosmetic(True) - #self.pen2 = QtGui.QPen(QtGui.QColor(0,0,0)) - #self.pen2.setWidth(width+2) - #self.pen2.setCosmetic(True) - self.brush = QtGui.QBrush(QtGui.QColor(*color)) - self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) - self.width = width - - def paint(self, p, opt, widget): - rect = self.boundingRect() - unit = self.unitRect() - y = rect.bottom() + (rect.top()-rect.bottom()) * 0.02 - y1 = y + unit.height()*self.width - x = rect.right() + (rect.left()-rect.right()) * 0.02 - x1 = x - self.size - - - p.setPen(self.pen) - p.setBrush(self.brush) - rect = QtCore.QRectF( - QtCore.QPointF(x1, y1), - QtCore.QPointF(x, y) - ) - p.translate(x1, y1) - p.scale(rect.width(), rect.height()) - p.drawRect(0, 0, 1, 1) - - alpha = np.clip(((self.size/unit.width()) - 40.) * 255. / 80., 0, 255) - p.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, alpha))) - for i in range(1, 10): - #x2 = x + (x1-x) * 0.1 * i - x2 = 0.1 * i - p.drawLine(QtCore.QPointF(x2, 0), QtCore.QPointF(x2, 1)) - - - def setSize(self, s): - self.size = s - -class ColorScaleBar(UIGraphicsItem): - def __init__(self, view, size, offset): - self.size = size - self.offset = offset - UIGraphicsItem.__init__(self, view) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - self.brush = QtGui.QBrush(QtGui.QColor(200,0,0)) - self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) - self.labels = {'max': 1, 'min': 0} - self.gradient = QtGui.QLinearGradient() - self.gradient.setColorAt(0, QtGui.QColor(0,0,0)) - self.gradient.setColorAt(1, QtGui.QColor(255,0,0)) - - def setGradient(self, g): - self.gradient = g - self.update() - - def setIntColorScale(self, minVal, maxVal, *args, **kargs): - colors = [intColor(i, maxVal-minVal, *args, **kargs) for i in range(minVal, maxVal)] - g = QtGui.QLinearGradient() - for i in range(len(colors)): - x = float(i)/len(colors) - g.setColorAt(x, colors[i]) - self.setGradient(g) - if 'labels' not in kargs: - self.setLabels({str(minVal/10.): 0, str(maxVal): 1}) - else: - self.setLabels({kargs['labels'][0]:0, kargs['labels'][1]:1}) - - def setLabels(self, l): - """Defines labels to appear next to the color scale""" - self.labels = l - self.update() - - def paint(self, p, opt, widget): - rect = self.boundingRect() ## Boundaries of visible area in scene coords. - unit = self.unitRect() ## Size of one view pixel in scene coords. - - ## determine max width of all labels - labelWidth = 0 - labelHeight = 0 - for k in self.labels: - b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) - labelWidth = max(labelWidth, b.width()) - labelHeight = max(labelHeight, b.height()) - - labelWidth *= unit.width() - labelHeight *= unit.height() - - textPadding = 2 # in px - - if self.offset[0] < 0: - x3 = rect.right() + unit.width() * self.offset[0] - x2 = x3 - labelWidth - unit.width()*textPadding*2 - x1 = x2 - unit.width() * self.size[0] - else: - x1 = rect.left() + unit.width() * self.offset[0] - x2 = x1 + unit.width() * self.size[0] - x3 = x2 + labelWidth + unit.width()*textPadding*2 - if self.offset[1] < 0: - y2 = rect.top() - unit.height() * self.offset[1] - y1 = y2 + unit.height() * self.size[1] - else: - y1 = rect.bottom() - unit.height() * self.offset[1] - y2 = y1 - unit.height() * self.size[1] - self.b = [x1,x2,x3,y1,y2,labelWidth] - - ## Draw background - p.setPen(self.pen) - p.setBrush(QtGui.QBrush(QtGui.QColor(255,255,255,100))) - rect = QtCore.QRectF( - QtCore.QPointF(x1 - unit.width()*textPadding, y1 + labelHeight/2 + unit.height()*textPadding), - QtCore.QPointF(x3, y2 - labelHeight/2 - unit.height()*textPadding) - ) - p.drawRect(rect) - - - ## Have to scale painter so that text and gradients are correct size. Bleh. - p.scale(unit.width(), unit.height()) - - ## Draw color bar - self.gradient.setStart(0, y1/unit.height()) - self.gradient.setFinalStop(0, y2/unit.height()) - p.setBrush(self.gradient) - rect = QtCore.QRectF( - QtCore.QPointF(x1/unit.width(), y1/unit.height()), - QtCore.QPointF(x2/unit.width(), y2/unit.height()) - ) - p.drawRect(rect) - - - ## draw labels - p.setPen(QtGui.QPen(QtGui.QColor(0,0,0))) - tx = x2 + unit.width()*textPadding - lh = labelHeight/unit.height() - for k in self.labels: - y = y1 + self.labels[k] * (y2-y1) - p.drawText(QtCore.QRectF(tx/unit.width(), y/unit.height() - lh/2.0, 1000, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) - - diff --git a/graphicsItems/ArrowItem.py b/graphicsItems/ArrowItem.py new file mode 100644 index 00000000..d9cf9663 --- /dev/null +++ b/graphicsItems/ArrowItem.py @@ -0,0 +1,60 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn + +__all__ = ['ArrowItem'] +class ArrowItem(QtGui.QGraphicsPolygonItem): + """ + For displaying scale-invariant arrows. + For arrows pointing to a location on a curve, see CurveArrow + + """ + + + def __init__(self, **opts): + QtGui.QGraphicsPolygonItem.__init__(self) + defOpts = { + 'style': 'tri', + 'pxMode': True, + 'size': 20, + 'angle': -150, + 'pos': (0,0), + 'width': 8, + 'tipAngle': 25, + 'baseAngle': 90, + 'pen': (200,200,200), + 'brush': (50,50,200), + } + defOpts.update(opts) + + self.setStyle(**defOpts) + + self.setPen(fn.mkPen(defOpts['pen'])) + self.setBrush(fn.mkBrush(defOpts['brush'])) + + self.rotate(self.opts['angle']) + self.moveBy(*self.opts['pos']) + + def setStyle(self, **opts): + self.opts = opts + + if opts['style'] == 'tri': + points = [ + QtCore.QPointF(0,0), + QtCore.QPointF(opts['size'],-opts['width']/2.), + QtCore.QPointF(opts['size'],opts['width']/2.), + ] + poly = QtGui.QPolygonF(points) + + else: + raise Exception("Unrecognized arrow style '%s'" % opts['style']) + + self.setPolygon(poly) + + if opts['pxMode']: + self.setFlags(self.flags() | self.ItemIgnoresTransformations) + else: + self.setFlags(self.flags() & ~self.ItemIgnoresTransformations) + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + QtGui.QGraphicsPolygonItem.paint(self, p, *args) diff --git a/graphicsItems/AxisItem.py b/graphicsItems/AxisItem.py new file mode 100644 index 00000000..98faa152 --- /dev/null +++ b/graphicsItems/AxisItem.py @@ -0,0 +1,441 @@ +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +from pyqtgraph.Point import Point +import pyqtgraph.debug as debug +import weakref +import pyqtgraph.functions as fn +from GraphicsWidget import GraphicsWidget + +__all__ = ['AxisItem'] +class AxisItem(GraphicsWidget): + def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True): + """ + GraphicsItem showing a single plot axis with ticks, values, and label. + Can be configured to fit on any side of a plot, and can automatically synchronize its displayed scale with ViewBox items. + Ticks can be extended to make a grid. + """ + + + GraphicsWidget.__init__(self, parent) + self.label = QtGui.QGraphicsTextItem(self) + self.showValues = showValues + self.orientation = orientation + if orientation not in ['left', 'right', 'top', 'bottom']: + raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.") + if orientation in ['left', 'right']: + #self.setMinimumWidth(25) + #self.setSizePolicy(QtGui.QSizePolicy( + #QtGui.QSizePolicy.Minimum, + #QtGui.QSizePolicy.Expanding + #)) + self.label.rotate(-90) + #else: + #self.setMinimumHeight(50) + #self.setSizePolicy(QtGui.QSizePolicy( + #QtGui.QSizePolicy.Expanding, + #QtGui.QSizePolicy.Minimum + #)) + #self.drawLabel = False + + self.labelText = '' + self.labelUnits = '' + self.labelUnitPrefix='' + self.labelStyle = {'color': '#CCC'} + + self.textHeight = 18 + self.tickLength = maxTickLength + self.scale = 1.0 + self.autoScale = True + + self.setRange(0, 1) + + if pen is None: + pen = QtGui.QPen(QtGui.QColor(100, 100, 100)) + self.setPen(pen) + + self.linkedView = None + if linkView is not None: + self.linkToView(linkView) + + self.showLabel(False) + + self.grid = False + #self.setCacheMode(self.DeviceCoordinateCache) + + def close(self): + self.scene().removeItem(self.label) + self.label = None + self.scene().removeItem(self) + + def setGrid(self, grid): + """Set the alpha value for the grid, or False to disable.""" + self.grid = grid + self.update() + + + def resizeEvent(self, ev=None): + #s = self.size() + + ## Set the position of the label + nudge = 5 + br = self.label.boundingRect() + p = QtCore.QPointF(0, 0) + if self.orientation == 'left': + p.setY(int(self.size().height()/2 + br.width()/2)) + p.setX(-nudge) + #s.setWidth(10) + elif self.orientation == 'right': + #s.setWidth(10) + p.setY(int(self.size().height()/2 + br.width()/2)) + p.setX(int(self.size().width()-br.height()+nudge)) + elif self.orientation == 'top': + #s.setHeight(10) + p.setY(-nudge) + p.setX(int(self.size().width()/2. - br.width()/2.)) + elif self.orientation == 'bottom': + p.setX(int(self.size().width()/2. - br.width()/2.)) + #s.setHeight(10) + p.setY(int(self.size().height()-br.height()+nudge)) + #self.label.resize(s) + self.label.setPos(p) + + def showLabel(self, show=True): + #self.drawLabel = show + self.label.setVisible(show) + if self.orientation in ['left', 'right']: + self.setWidth() + else: + self.setHeight() + if self.autoScale: + self.setScale() + + def setLabel(self, text=None, units=None, unitPrefix=None, **args): + if text is not None: + self.labelText = text + self.showLabel() + if units is not None: + self.labelUnits = units + self.showLabel() + if unitPrefix is not None: + self.labelUnitPrefix = unitPrefix + if len(args) > 0: + self.labelStyle = args + self.label.setHtml(self.labelString()) + self.resizeEvent() + self.update() + + def labelString(self): + if self.labelUnits == '': + if self.scale == 1.0: + units = '' + else: + units = u'(x%g)' % (1.0/self.scale) + else: + #print repr(self.labelUnitPrefix), repr(self.labelUnits) + units = u'(%s%s)' % (self.labelUnitPrefix, self.labelUnits) + + s = u'%s %s' % (self.labelText, units) + + style = ';'.join(['%s: "%s"' % (k, self.labelStyle[k]) for k in self.labelStyle]) + + return u"<span style='%s'>%s</span>" % (style, s) + + def setHeight(self, h=None): + if h is None: + h = self.textHeight + max(0, self.tickLength) + if self.label.isVisible(): + h += self.textHeight + self.setMaximumHeight(h) + self.setMinimumHeight(h) + + + def setWidth(self, w=None): + if w is None: + w = max(0, self.tickLength) + 40 + if self.label.isVisible(): + w += self.textHeight + self.setMaximumWidth(w) + self.setMinimumWidth(w) + + def setPen(self, pen): + self.pen = pen + self.update() + + def setScale(self, scale=None): + """ + Set the value scaling for this axis. + The scaling value 1) multiplies the values displayed along the axis + and 2) changes the way units are displayed in the label. + For example: + If the axis spans values from -0.1 to 0.1 and has units set to 'V' + then a scale of 1000 would cause the axis to display values -100 to 100 + and the units would appear as 'mV' + If scale is None, then it will be determined automatically based on the current + range displayed by the axis. + """ + if scale is None: + #if self.drawLabel: ## If there is a label, then we are free to rescale the values + if self.label.isVisible(): + d = self.range[1] - self.range[0] + #pl = 1-int(log10(d)) + #scale = 10 ** pl + (scale, prefix) = fn.siScale(d / 2.) + if self.labelUnits == '' and prefix in ['k', 'm']: ## If we are not showing units, wait until 1e6 before scaling. + scale = 1.0 + prefix = '' + self.setLabel(unitPrefix=prefix) + else: + scale = 1.0 + + + if scale != self.scale: + self.scale = scale + self.setLabel() + self.update() + + def setRange(self, mn, mx): + if mn in [np.nan, np.inf, -np.inf] or mx in [np.nan, np.inf, -np.inf]: + raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) + self.range = [mn, mx] + if self.autoScale: + self.setScale() + self.update() + + def linkToView(self, view): + if self.orientation in ['right', 'left']: + if self.linkedView is not None and self.linkedView() is not None: + #view.sigYRangeChanged.disconnect(self.linkedViewChanged) + ## should be this instead? + self.linkedView().sigYRangeChanged.disconnect(self.linkedViewChanged) + self.linkedView = weakref.ref(view) + view.sigYRangeChanged.connect(self.linkedViewChanged) + #signal = QtCore.SIGNAL('yRangeChanged') + else: + if self.linkedView is not None and self.linkedView() is not None: + #view.sigYRangeChanged.disconnect(self.linkedViewChanged) + ## should be this instead? + self.linkedView().sigXRangeChanged.disconnect(self.linkedViewChanged) + self.linkedView = weakref.ref(view) + view.sigXRangeChanged.connect(self.linkedViewChanged) + #signal = QtCore.SIGNAL('xRangeChanged') + + + def linkedViewChanged(self, view, newRange): + self.setRange(*newRange) + + def boundingRect(self): + if self.linkedView is None or self.linkedView() is None or self.grid is False: + rect = self.mapRectFromParent(self.geometry()) + ## extend rect if ticks go in negative direction + if self.orientation == 'left': + rect.setRight(rect.right() - min(0,self.tickLength)) + elif self.orientation == 'right': + rect.setLeft(rect.left() + min(0,self.tickLength)) + elif self.orientation == 'top': + rect.setBottom(rect.bottom() - min(0,self.tickLength)) + elif self.orientation == 'bottom': + rect.setTop(rect.top() + min(0,self.tickLength)) + return rect + else: + return self.mapRectFromParent(self.geometry()) | self.mapRectFromScene(self.linkedView().mapRectToScene(self.linkedView().boundingRect())) + + def paint(self, p, opt, widget): + prof = debug.Profiler("AxisItem.paint", disabled=True) + p.setPen(self.pen) + + #bounds = self.boundingRect() + bounds = self.mapRectFromParent(self.geometry()) + + if self.linkedView is None or self.linkedView() is None or self.grid is False: + tbounds = bounds + else: + tbounds = self.mapRectFromScene(self.linkedView().mapRectToScene(self.linkedView().boundingRect())) + + if self.orientation == 'left': + span = (bounds.topRight(), bounds.bottomRight()) + tickStart = tbounds.right() + tickStop = bounds.right() + tickDir = -1 + axis = 0 + elif self.orientation == 'right': + span = (bounds.topLeft(), bounds.bottomLeft()) + tickStart = tbounds.left() + tickStop = bounds.left() + tickDir = 1 + axis = 0 + elif self.orientation == 'top': + span = (bounds.bottomLeft(), bounds.bottomRight()) + tickStart = tbounds.bottom() + tickStop = bounds.bottom() + tickDir = -1 + axis = 1 + elif self.orientation == 'bottom': + span = (bounds.topLeft(), bounds.topRight()) + tickStart = tbounds.top() + tickStop = bounds.top() + tickDir = 1 + axis = 1 + + ## draw long line along axis + p.drawLine(*span) + + ## determine size of this item in pixels + points = map(self.mapToDevice, span) + lengthInPixels = Point(points[1] - points[0]).length() + + ## decide optimal tick spacing in pixels + pixelSpacing = np.log(lengthInPixels+10) * 3 + optimalTickCount = lengthInPixels / pixelSpacing + + ## Determine optimal tick spacing + #intervals = [1., 2., 5., 10., 20., 50.] + #intervals = [1., 2.5, 5., 10., 25., 50.] + intervals = np.array([0.1, 0.2, 1., 2., 10., 20., 100., 200.]) + dif = abs(self.range[1] - self.range[0]) + if dif == 0.0: + return + pw = 10 ** (np.floor(np.log10(dif))-1) + scaledIntervals = intervals * pw + scaledTickCounts = dif / scaledIntervals + i1 = np.argwhere(scaledTickCounts < optimalTickCount)[0,0] + + distBetweenIntervals = (optimalTickCount-scaledTickCounts[i1]) / (scaledTickCounts[i1-1]-scaledTickCounts[i1]) + + #print optimalTickCount, i1, scaledIntervals, distBetweenIntervals + + #for i in range(len(intervals)): + #i1 = i + #if dif / (pw*intervals[i]) < 10: + #break + + textLevel = 0 ## draw text at this scale level + + #print "range: %s dif: %f power: %f interval: %f spacing: %f" % (str(self.range), dif, pw, intervals[i1], sp) + + #print " start at %f, %d ticks" % (start, num) + + + if axis == 0: + xs = -bounds.height() / dif + else: + xs = bounds.width() / dif + + prof.mark('init') + + tickPositions = set() # remembers positions of previously drawn ticks + ## draw ticks and generate list of texts to draw + ## (to improve performance, we do not interleave line and text drawing, since this causes unnecessary pipeline switching) + ## draw three different intervals, long ticks first + texts = [] + for i in [2,1,0]: + if i1+i > len(intervals): + continue + ## spacing for this interval + sp = pw*intervals[i1+i] + + ## determine starting tick + start = np.ceil(self.range[0] / sp) * sp + + ## determine number of ticks + num = int(dif / sp) + 1 + + ## last tick value + last = start + sp * num + + ## Number of decimal places to print + maxVal = max(abs(start), abs(last)) + places = max(0, 1-int(np.log10(sp*self.scale))) + + ## length of tick + #h = np.clip((self.tickLength*3 / num) - 1., min(0, self.tickLength), max(0, self.tickLength)) + if i == 0: + h = self.tickLength * distBetweenIntervals / 2. + else: + h = self.tickLength*i/2. + + ## alpha + if i == 0: + #a = min(255, (765. / num) - 1.) + a = 255 * distBetweenIntervals + else: + a = 255 + + if axis == 0: + offset = self.range[0] * xs - bounds.height() + else: + offset = self.range[0] * xs + + for j in range(num): + v = start + sp * j + x = (v * xs) - offset + p1 = [0, 0] + p2 = [0, 0] + p1[axis] = tickStart + p2[axis] = tickStop + h*tickDir + p1[1-axis] = p2[1-axis] = x + + if p1[1-axis] > [bounds.width(), bounds.height()][1-axis]: + continue + if p1[1-axis] < 0: + continue + p.setPen(QtGui.QPen(QtGui.QColor(150, 150, 150, a))) + # draw tick only if there is none + tickPos = p1[1-axis] + if tickPos not in tickPositions: + p.drawLine(Point(p1), Point(p2)) + tickPositions.add(tickPos) + if i >= textLevel: + if abs(v) < .001 or abs(v) >= 10000: + vstr = "%g" % (v * self.scale) + else: + vstr = ("%%0.%df" % places) % (v * self.scale) + + textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr) + height = textRect.height() + self.textHeight = height + if self.orientation == 'left': + textFlags = QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter + rect = QtCore.QRectF(tickStop-100, x-(height/2), 99-max(0,self.tickLength), height) + elif self.orientation == 'right': + textFlags = QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter + rect = QtCore.QRectF(tickStop+max(0,self.tickLength)+1, x-(height/2), 100-max(0,self.tickLength), height) + elif self.orientation == 'top': + textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom + rect = QtCore.QRectF(x-100, tickStop-max(0,self.tickLength)-height, 200, height) + elif self.orientation == 'bottom': + textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop + rect = QtCore.QRectF(x-100, tickStop+max(0,self.tickLength), 200, height) + + #p.setPen(QtGui.QPen(QtGui.QColor(150, 150, 150, a))) + #p.drawText(rect, textFlags, vstr) + texts.append((rect, textFlags, vstr, a)) + + prof.mark('draw ticks') + for args in texts: + p.setPen(QtGui.QPen(QtGui.QColor(150, 150, 150, args[3]))) + p.drawText(*args[:3]) + prof.mark('draw text') + prof.finish() + + def show(self): + + if self.orientation in ['left', 'right']: + self.setWidth() + else: + self.setHeight() + GraphicsWidget.show(self) + + def hide(self): + if self.orientation in ['left', 'right']: + self.setWidth(0) + else: + self.setHeight(0) + GraphicsWidget.hide(self) + + def wheelEvent(self, ev): + if self.linkedView is None or self.linkedView() is None: return + if self.orientation in ['left', 'right']: + self.linkedView().wheelEvent(ev, axis=1) + else: + self.linkedView().wheelEvent(ev, axis=0) + ev.accept() diff --git a/graphicsItems/ButtonItem.py b/graphicsItems/ButtonItem.py new file mode 100644 index 00000000..2de8cfdc --- /dev/null +++ b/graphicsItems/ButtonItem.py @@ -0,0 +1,51 @@ +from pyqtgraph.Qt import QtGui, QtCore +from GraphicsObject import GraphicsObject + +__all__ = ['ButtonItem'] +class ButtonItem(GraphicsObject): + """Button graphicsItem displaying an image.""" + + clicked = QtCore.Signal(object) + + def __init__(self, imageFile, width=None, parentItem=None): + self.enabled = True + GraphicsObject.__init__(self) + self.setImageFile(imageFile) + if width is not None: + s = float(width) / self.pixmap.width() + self.scale(s, s) + if parentItem is not None: + self.setParentItem(parentItem) + self.setOpacity(0.7) + + def setImageFile(self, imageFile): + self.pixmap = QtGui.QPixmap(imageFile) + self.update() + + def mouseClickEvent(self, ev): + if self.enabled: + self.clicked.emit(self) + + def mouseHoverEvent(self, ev): + if not self.enabled: + return + if ev.isEnter(): + self.setOpacity(1.0) + else: + self.setOpacity(0.7) + + def disable(self): + self.enabled = False + self.setOpacity(0.4) + + def enable(self): + self.enabled = True + self.setOpacity(0.7) + + def paint(self, p, *args): + p.setRenderHint(p.Antialiasing) + p.drawPixmap(0, 0, self.pixmap) + + def boundingRect(self): + return QtCore.QRectF(self.pixmap.rect()) + diff --git a/graphicsItems/CurvePoint.py b/graphicsItems/CurvePoint.py new file mode 100644 index 00000000..a1ec5ae4 --- /dev/null +++ b/graphicsItems/CurvePoint.py @@ -0,0 +1,113 @@ +from pyqtgraph.Qt import QtGui, QtCore +import ArrowItem +import numpy as np +from pyqtgraph.Point import Point +import weakref +from GraphicsObject import GraphicsObject + +__all__ = ['CurvePoint', 'CurveArrow'] +class CurvePoint(GraphicsObject): + """A GraphicsItem that sets its location to a point on a PlotCurveItem. + Also rotates to be tangent to the curve. + The position along the curve is a Qt property, and thus can be easily animated. + + Note: This class does not display anything; see CurveArrow for an applied example + """ + + def __init__(self, curve, index=0, pos=None): + """Position can be set either as an index referring to the sample number or + the position 0.0 - 1.0""" + + GraphicsObject.__init__(self) + #QObjectWorkaround.__init__(self) + self.curve = weakref.ref(curve) + self.setParentItem(curve) + self.setProperty('position', 0.0) + self.setProperty('index', 0) + + if hasattr(self, 'ItemHasNoContents'): + self.setFlags(self.flags() | self.ItemHasNoContents) + + if pos is not None: + self.setPos(pos) + else: + self.setIndex(index) + + def setPos(self, pos): + self.setProperty('position', float(pos))## cannot use numpy types here, MUST be python float. + + def setIndex(self, index): + self.setProperty('index', int(index)) ## cannot use numpy types here, MUST be python int. + + def event(self, ev): + if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None: + return False + + if ev.propertyName() == 'index': + index = self.property('index') + if 'QVariant' in repr(index): + index = index.toInt()[0] + elif ev.propertyName() == 'position': + index = None + else: + return False + + (x, y) = self.curve().getData() + if index is None: + #print ev.propertyName(), self.property('position').toDouble()[0], self.property('position').typeName() + pos = self.property('position') + if 'QVariant' in repr(pos): ## need to support 2 APIs :( + pos = pos.toDouble()[0] + index = (len(x)-1) * np.clip(pos, 0.0, 1.0) + + if index != int(index): ## interpolate floating-point values + i1 = int(index) + i2 = np.clip(i1+1, 0, len(x)-1) + s2 = index-i1 + s1 = 1.0-s2 + newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2) + else: + index = int(index) + i1 = np.clip(index-1, 0, len(x)-1) + i2 = np.clip(index+1, 0, len(x)-1) + newPos = (x[index], y[index]) + + p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) + p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) + ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians + self.resetTransform() + self.rotate(180+ ang * 180 / np.pi) ## takes degrees + QtGui.QGraphicsItem.setPos(self, *newPos) + return True + + def boundingRect(self): + return QtCore.QRectF() + + def paint(self, *args): + pass + + def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1): + anim = QtCore.QPropertyAnimation(self, prop) + anim.setDuration(duration) + anim.setStartValue(start) + anim.setEndValue(end) + anim.setLoopCount(loop) + return anim + + +class CurveArrow(CurvePoint): + """Provides an arrow that points to any specific sample on a PlotCurveItem. + Provides properties that can be animated.""" + + def __init__(self, curve, index=0, pos=None, **opts): + CurvePoint.__init__(self, curve, index=index, pos=pos) + if opts.get('pxMode', True): + opts['pxMode'] = False + self.setFlags(self.flags() | self.ItemIgnoresTransformations) + opts['angle'] = 0 + self.arrow = ArrowItem.ArrowItem(**opts) + self.arrow.setParentItem(self) + + def setStyle(**opts): + return self.arrow.setStyle(**opts) + diff --git a/graphicsItems/GradientEditorItem.py b/graphicsItems/GradientEditorItem.py new file mode 100644 index 00000000..1ae2e4cc --- /dev/null +++ b/graphicsItems/GradientEditorItem.py @@ -0,0 +1,624 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +from GraphicsObject import GraphicsObject +from GraphicsWidget import GraphicsWidget +import weakref, collections +import numpy as np + +__all__ = ['TickSliderItem', 'GradientEditorItem'] + + +Gradients = collections.OrderedDict([ + ('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}), + ('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}), + ('yellowy', {'ticks': [(0.0, (0, 0, 0, 255)), (0.2328863796753704, (32, 0, 129, 255)), (0.8362738179251941, (255, 255, 0, 255)), (0.5257586450247, (115, 15, 255, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'} ), + ('bipolar', {'ticks': [(0.0, (0, 255, 255, 255)), (1.0, (255, 255, 0, 255)), (0.5, (0, 0, 0, 255)), (0.25, (0, 0, 255, 255)), (0.75, (255, 0, 0, 255))], 'mode': 'rgb'}), + ('spectrum', {'ticks': [(1.0, (255, 0, 255, 255)), (0.0, (255, 0, 0, 255))], 'mode': 'hsv'}), + ('cyclic', {'ticks': [(0.0, (255, 0, 4, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}), + ('greyclip', {'ticks': [(0.0, (0, 0, 0, 255)), (0.99, (255, 255, 255, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'rgb'}), + ('grey', {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'}), +]) + + +class TickSliderItem(GraphicsWidget): + + def __init__(self, orientation='bottom', allowAdd=True, **kargs): + GraphicsWidget.__init__(self) + self.orientation = orientation + self.length = 100 + self.tickSize = 15 + self.ticks = {} + self.maxDim = 20 + self.allowAdd = allowAdd + if 'tickPen' in kargs: + self.tickPen = fn.mkPen(kargs['tickPen']) + else: + self.tickPen = fn.mkPen('w') + + self.orientations = { + 'left': (90, 1, 1), + 'right': (90, 1, 1), + 'top': (0, 1, -1), + 'bottom': (0, 1, 1) + } + + self.setOrientation(orientation) + #self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) + #self.setBackgroundRole(QtGui.QPalette.NoRole) + #self.setMouseTracking(True) + + #def boundingRect(self): + #return self.mapRectFromParent(self.geometry()).normalized() + + #def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. + #p = QtGui.QPainterPath() + #p.addRect(self.boundingRect()) + #return p + + def paint(self, p, opt, widget): + #p.setPen(fn.mkPen('g', width=3)) + #p.drawRect(self.boundingRect()) + return + + def keyPressEvent(self, ev): + ev.ignore() + + def setMaxDim(self, mx=None): + if mx is None: + mx = self.maxDim + else: + self.maxDim = mx + + if self.orientation in ['bottom', 'top']: + self.setFixedHeight(mx) + self.setMaximumWidth(16777215) + else: + self.setFixedWidth(mx) + self.setMaximumHeight(16777215) + + + def setOrientation(self, ort): + self.orientation = ort + self.setMaxDim() + self.resetTransform() + if ort == 'top': + self.scale(1, -1) + self.translate(0, -self.height()) + elif ort == 'left': + self.rotate(270) + self.scale(1, -1) + self.translate(-self.height(), -self.maxDim) + elif ort == 'right': + self.rotate(270) + self.translate(-self.height(), 0) + #self.setPos(0, -self.height()) + + self.translate(self.tickSize/2., 0) + + def addTick(self, x, color=None, movable=True): + if color is None: + color = QtGui.QColor(255,255,255) + tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize, pen=self.tickPen) + self.ticks[tick] = x + tick.setParentItem(self) + return tick + + def removeTick(self, tick): + del self.ticks[tick] + tick.setParentItem(None) + if self.scene() is not None: + self.scene().removeItem(tick) + + def tickMoved(self, tick, pos): + #print "tick changed" + ## Correct position of tick if it has left bounds. + newX = min(max(0, pos.x()), self.length) + pos.setX(newX) + tick.setPos(pos) + self.ticks[tick] = float(newX) / self.length + + def tickClicked(self, tick, ev): + if ev.button() == QtCore.Qt.RightButton: + self.removeTick(tick) + + def widgetLength(self): + if self.orientation in ['bottom', 'top']: + return self.width() + else: + return self.height() + + def resizeEvent(self, ev): + wlen = max(40, self.widgetLength()) + self.setLength(wlen-self.tickSize) + self.setOrientation(self.orientation) + #bounds = self.scene().itemsBoundingRect() + #bounds.setLeft(min(-self.tickSize*0.5, bounds.left())) + #bounds.setRight(max(self.length + self.tickSize, bounds.right())) + #self.setSceneRect(bounds) + #self.fitInView(bounds, QtCore.Qt.KeepAspectRatio) + + def setLength(self, newLen): + for t, x in self.ticks.items(): + t.setPos(x * newLen, t.pos().y()) + self.length = float(newLen) + + #def mousePressEvent(self, ev): + #QtGui.QGraphicsView.mousePressEvent(self, ev) + #self.ignoreRelease = False + #for i in self.items(ev.pos()): + #if isinstance(i, Tick): + #self.ignoreRelease = True + #break + ##if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks + ##self.ignoreRelease = True + + #def mouseReleaseEvent(self, ev): + #QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + #if self.ignoreRelease: + #return + + #pos = self.mapToScene(ev.pos()) + + #if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: + #if pos.x() < 0 or pos.x() > self.length: + #return + #if pos.y() < 0 or pos.y() > self.tickSize: + #return + #pos.setX(min(max(pos.x(), 0), self.length)) + #self.addTick(pos.x()/self.length) + #elif ev.button() == QtCore.Qt.RightButton: + #self.showMenu(ev) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: + pos = ev.pos() + if pos.x() < 0 or pos.x() > self.length: + return + if pos.y() < 0 or pos.y() > self.tickSize: + return + pos.setX(min(max(pos.x(), 0), self.length)) + self.addTick(pos.x()/self.length) + elif ev.button() == QtCore.Qt.RightButton: + self.showMenu(ev) + + #if ev.button() == QtCore.Qt.RightButton: + #if self.moving: + #ev.accept() + #self.setPos(self.startPosition) + #self.moving = False + #self.sigMoving.emit(self) + #self.sigMoved.emit(self) + #else: + #pass + #self.view().tickClicked(self, ev) + ###remove + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.RightButton) + ## show ghost tick + #self.currentPen = fn.mkPen(255, 0,0) + #else: + #self.currentPen = self.pen + #self.update() + + def showMenu(self, ev): + pass + + def setTickColor(self, tick, color): + tick = self.getTick(tick) + tick.color = color + tick.update() + #tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color))) + + def setTickValue(self, tick, val): + tick = self.getTick(tick) + val = min(max(0.0, val), 1.0) + x = val * self.length + pos = tick.pos() + pos.setX(x) + tick.setPos(pos) + self.ticks[tick] = val + + def tickValue(self, tick): + tick = self.getTick(tick) + return self.ticks[tick] + + def getTick(self, tick): + if type(tick) is int: + tick = self.listTicks()[tick][0] + return tick + + #def mouseMoveEvent(self, ev): + #QtGui.QGraphicsView.mouseMoveEvent(self, ev) + + def listTicks(self): + ticks = self.ticks.items() + ticks.sort(lambda a,b: cmp(a[1], b[1])) + return ticks + + +class GradientEditorItem(TickSliderItem): + + sigGradientChanged = QtCore.Signal(object) + + def __init__(self, *args, **kargs): + + self.currentTick = None + self.currentTickColor = None + self.rectSize = 15 + self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, self.rectSize, 100, self.rectSize)) + self.backgroundRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) + self.backgroundRect.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) + self.colorMode = 'rgb' + + TickSliderItem.__init__(self, *args, **kargs) + + self.colorDialog = QtGui.QColorDialog() + self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) + self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) + + self.colorDialog.currentColorChanged.connect(self.currentColorChanged) + self.colorDialog.rejected.connect(self.currentColorRejected) + + self.backgroundRect.setParentItem(self) + self.gradRect.setParentItem(self) + + self.setMaxDim(self.rectSize + self.tickSize) + + self.rgbAction = QtGui.QAction('RGB', self) + self.rgbAction.setCheckable(True) + self.rgbAction.triggered.connect(lambda: self.setColorMode('rgb')) + self.hsvAction = QtGui.QAction('HSV', self) + self.hsvAction.setCheckable(True) + self.hsvAction.triggered.connect(lambda: self.setColorMode('hsv')) + + self.menu = QtGui.QMenu() + + ## build context menu of gradients + l = self.length + self.length = 100 + global Gradients + for g in Gradients: + px = QtGui.QPixmap(100, 15) + p = QtGui.QPainter(px) + self.restoreState(Gradients[g]) + grad = self.getGradient() + brush = QtGui.QBrush(grad) + p.fillRect(QtCore.QRect(0, 0, 100, 15), brush) + p.end() + label = QtGui.QLabel() + label.setPixmap(px) + label.setContentsMargins(1, 1, 1, 1) + act = QtGui.QWidgetAction(self) + act.setDefaultWidget(label) + act.triggered.connect(self.contextMenuClicked) + act.name = g + self.menu.addAction(act) + self.length = l + self.menu.addSeparator() + self.menu.addAction(self.rgbAction) + self.menu.addAction(self.hsvAction) + + + for t in self.ticks.keys(): + self.removeTick(t) + self.addTick(0, QtGui.QColor(0,0,0), True) + self.addTick(1, QtGui.QColor(255,0,0), True) + self.setColorMode('rgb') + self.updateGradient() + + def setOrientation(self, ort): + TickSliderItem.setOrientation(self, ort) + self.translate(0, self.rectSize) + + def showMenu(self, ev): + self.menu.popup(ev.screenPos().toQPoint()) + + def contextMenuClicked(self, b): + global Gradients + act = self.sender() + self.loadPreset(act.name) + + def loadPreset(self, name): + self.restoreState(Gradients[name]) + + def setColorMode(self, cm): + if cm not in ['rgb', 'hsv']: + raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm)) + + try: + self.rgbAction.blockSignals(True) + self.hsvAction.blockSignals(True) + self.rgbAction.setChecked(cm == 'rgb') + self.hsvAction.setChecked(cm == 'hsv') + finally: + self.rgbAction.blockSignals(False) + self.hsvAction.blockSignals(False) + self.colorMode = cm + self.updateGradient() + + def updateGradient(self): + self.gradient = self.getGradient() + self.gradRect.setBrush(QtGui.QBrush(self.gradient)) + self.sigGradientChanged.emit(self) + + def setLength(self, newLen): + TickSliderItem.setLength(self, newLen) + self.backgroundRect.setRect(0, -self.rectSize, newLen, self.rectSize) + self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize) + self.updateGradient() + + def currentColorChanged(self, color): + if color.isValid() and self.currentTick is not None: + self.setTickColor(self.currentTick, color) + self.updateGradient() + + def currentColorRejected(self): + self.setTickColor(self.currentTick, self.currentTickColor) + self.updateGradient() + + def tickClicked(self, tick, ev): + if ev.button() == QtCore.Qt.LeftButton: + if not tick.colorChangeAllowed: + return + self.currentTick = tick + self.currentTickColor = tick.color + self.colorDialog.setCurrentColor(tick.color) + self.colorDialog.open() + #color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) + #if color.isValid(): + #self.setTickColor(tick, color) + #self.updateGradient() + elif ev.button() == QtCore.Qt.RightButton: + if not tick.removeAllowed: + return + if len(self.ticks) > 2: + self.removeTick(tick) + self.updateGradient() + + def tickMoved(self, tick, pos): + TickSliderItem.tickMoved(self, tick, pos) + self.updateGradient() + + + def getGradient(self): + g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) + if self.colorMode == 'rgb': + ticks = self.listTicks() + g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) + elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop + ticks = self.listTicks() + stops = [] + stops.append((ticks[0][1], ticks[0][0].color)) + for i in range(1,len(ticks)): + x1 = ticks[i-1][1] + x2 = ticks[i][1] + dx = (x2-x1) / 10. + for j in range(1,10): + x = x1 + dx*j + stops.append((x, self.getColor(x))) + stops.append((x2, self.getColor(x2))) + g.setStops(stops) + return g + + def getColor(self, x, toQColor=True): + ticks = self.listTicks() + if x <= ticks[0][1]: + c = ticks[0][0].color + if toQColor: + return QtGui.QColor(c) # always copy colors before handing them out + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + if x >= ticks[-1][1]: + c = ticks[-1][0].color + if toQColor: + return QtGui.QColor(c) # always copy colors before handing them out + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + + x2 = ticks[0][1] + for i in range(1,len(ticks)): + x1 = x2 + x2 = ticks[i][1] + if x1 <= x and x2 >= x: + break + + dx = (x2-x1) + if dx == 0: + f = 0. + else: + f = (x-x1) / dx + c1 = ticks[i-1][0].color + c2 = ticks[i][0].color + if self.colorMode == 'rgb': + r = c1.red() * (1.-f) + c2.red() * f + g = c1.green() * (1.-f) + c2.green() * f + b = c1.blue() * (1.-f) + c2.blue() * f + a = c1.alpha() * (1.-f) + c2.alpha() * f + if toQColor: + return QtGui.QColor(r, g, b,a) + else: + return (r,g,b,a) + elif self.colorMode == 'hsv': + h1,s1,v1,_ = c1.getHsv() + h2,s2,v2,_ = c2.getHsv() + h = h1 * (1.-f) + h2 * f + s = s1 * (1.-f) + s2 * f + v = v1 * (1.-f) + v2 * f + c = QtGui.QColor() + c.setHsv(h,s,v) + if toQColor: + return c + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + + def getLookupTable(self, nPts, alpha=True): + """Return an RGB/A lookup table.""" + if alpha: + table = np.empty((nPts,4), dtype=np.ubyte) + else: + table = np.empty((nPts,3), dtype=np.ubyte) + + for i in range(nPts): + x = float(i)/(nPts-1) + color = self.getColor(x, toQColor=False) + table[i] = color[:table.shape[1]] + + return table + + + + def mouseReleaseEvent(self, ev): + TickSliderItem.mouseReleaseEvent(self, ev) + self.updateGradient() + + def addTick(self, x, color=None, movable=True): + if color is None: + color = self.getColor(x) + t = TickSliderItem.addTick(self, x, color=color, movable=movable) + t.colorChangeAllowed = True + t.removeAllowed = True + return t + + def saveState(self): + ticks = [] + for t in self.ticks: + c = t.color + ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha()))) + state = {'mode': self.colorMode, 'ticks': ticks} + return state + + def restoreState(self, state): + self.setColorMode(state['mode']) + for t in self.ticks.keys(): + self.removeTick(t) + for t in state['ticks']: + c = QtGui.QColor(*t[1]) + self.addTick(t[0], c) + self.updateGradient() + + +class Tick(GraphicsObject): + + sigMoving = QtCore.Signal(object) + sigMoved = QtCore.Signal(object) + + def __init__(self, view, pos, color, movable=True, scale=10, pen='w'): + self.movable = movable + self.moving = False + self.view = weakref.ref(view) + self.scale = scale + self.color = color + self.pen = fn.mkPen(pen) + self.hoverPen = fn.mkPen(255,255,0) + self.currentPen = self.pen + self.pg = QtGui.QPainterPath(QtCore.QPointF(0,0)) + self.pg.lineTo(QtCore.QPointF(-scale/3**0.5, scale)) + self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale)) + self.pg.closeSubpath() + + GraphicsObject.__init__(self) + self.setPos(pos[0], pos[1]) + if self.movable: + self.setZValue(1) + else: + self.setZValue(0) + + def boundingRect(self): + return self.pg.boundingRect() + + def shape(self): + return self.pg + + def paint(self, p, *args): + p.setRenderHints(QtGui.QPainter.Antialiasing) + p.fillPath(self.pg, fn.mkBrush(self.color)) + + p.setPen(self.currentPen) + p.drawPath(self.pg) + + + def mouseDragEvent(self, ev): + if self.movable and ev.button() == QtCore.Qt.LeftButton: + if ev.isStart(): + self.moving = True + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.startPosition = self.pos() + ev.accept() + + if not self.moving: + return + + newPos = self.cursorOffset + self.mapToParent(ev.pos()) + newPos.setY(self.pos().y()) + + self.setPos(newPos) + self.view().tickMoved(self, newPos) + self.sigMoving.emit(self) + if ev.isFinish(): + self.moving = False + self.sigMoved.emit(self) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton and self.moving: + ev.accept() + self.setPos(self.startPosition) + self.view().tickMoved(self, self.startPosition) + self.moving = False + self.sigMoving.emit(self) + self.sigMoved.emit(self) + else: + self.view().tickClicked(self, ev) + ##remove + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.LeftButton) + ev.acceptClicks(QtCore.Qt.RightButton) + self.currentPen = self.hoverPen + else: + self.currentPen = self.pen + self.update() + + #def mouseMoveEvent(self, ev): + ##print self, "move", ev.scenePos() + #if not self.movable: + #return + #if not ev.buttons() & QtCore.Qt.LeftButton: + #return + + + #newPos = ev.scenePos() + self.mouseOffset + #newPos.setY(self.pos().y()) + ##newPos.setX(min(max(newPos.x(), 0), 100)) + #self.setPos(newPos) + #self.view().tickMoved(self, newPos) + #self.movedSincePress = True + ##self.emit(QtCore.SIGNAL('tickChanged'), self) + #ev.accept() + + #def mousePressEvent(self, ev): + #self.movedSincePress = False + #if ev.button() == QtCore.Qt.LeftButton: + #ev.accept() + #self.mouseOffset = self.pos() - ev.scenePos() + #self.pressPos = ev.scenePos() + #elif ev.button() == QtCore.Qt.RightButton: + #ev.accept() + ##if self.endTick: + ##return + ##self.view.tickChanged(self, delete=True) + + #def mouseReleaseEvent(self, ev): + ##print self, "release", ev.scenePos() + #if not self.movedSincePress: + #self.view().tickClicked(self, ev) + + ##if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos: + ##color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) + ##if color.isValid(): + ##self.color = color + ##self.setBrush(QtGui.QBrush(QtGui.QColor(self.color))) + ###self.emit(QtCore.SIGNAL('tickChanged'), self) + ##self.view.tickChanged(self) diff --git a/graphicsItems/GradientLegend.py b/graphicsItems/GradientLegend.py new file mode 100644 index 00000000..442b8f09 --- /dev/null +++ b/graphicsItems/GradientLegend.py @@ -0,0 +1,112 @@ +from pyqtgraph.Qt import QtGui, QtCore +from UIGraphicsItem import * +import pyqtgraph.functions as fn + +__all__ = ['GradientLegend'] + +class GradientLegend(UIGraphicsItem): + """ + Draws a color gradient rectangle along with text labels denoting the value at specific + points along the gradient. + """ + + def __init__(self, view, size, offset): + self.size = size + self.offset = offset + UIGraphicsItem.__init__(self, view) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + self.brush = QtGui.QBrush(QtGui.QColor(200,0,0)) + self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) + self.labels = {'max': 1, 'min': 0} + self.gradient = QtGui.QLinearGradient() + self.gradient.setColorAt(0, QtGui.QColor(0,0,0)) + self.gradient.setColorAt(1, QtGui.QColor(255,0,0)) + + def setGradient(self, g): + self.gradient = g + self.update() + + def setIntColorScale(self, minVal, maxVal, *args, **kargs): + colors = [fn.intColor(i, maxVal-minVal, *args, **kargs) for i in range(minVal, maxVal)] + g = QtGui.QLinearGradient() + for i in range(len(colors)): + x = float(i)/len(colors) + g.setColorAt(x, colors[i]) + self.setGradient(g) + if 'labels' not in kargs: + self.setLabels({str(minVal/10.): 0, str(maxVal): 1}) + else: + self.setLabels({kargs['labels'][0]:0, kargs['labels'][1]:1}) + + def setLabels(self, l): + """Defines labels to appear next to the color scale. Accepts a dict of {text: value} pairs""" + self.labels = l + self.update() + + def paint(self, p, opt, widget): + UIGraphicsItem.paint(self, p, opt, widget) + rect = self.boundingRect() ## Boundaries of visible area in scene coords. + unit = self.pixelSize() ## Size of one view pixel in scene coords. + + ## determine max width of all labels + labelWidth = 0 + labelHeight = 0 + for k in self.labels: + b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) + labelWidth = max(labelWidth, b.width()) + labelHeight = max(labelHeight, b.height()) + + labelWidth *= unit[0] + labelHeight *= unit[1] + + textPadding = 2 # in px + + if self.offset[0] < 0: + x3 = rect.right() + unit[0] * self.offset[0] + x2 = x3 - labelWidth - unit[0]*textPadding*2 + x1 = x2 - unit[0] * self.size[0] + else: + x1 = rect.left() + unit[0] * self.offset[0] + x2 = x1 + unit[0] * self.size[0] + x3 = x2 + labelWidth + unit[0]*textPadding*2 + if self.offset[1] < 0: + y2 = rect.top() - unit[1] * self.offset[1] + y1 = y2 + unit[1] * self.size[1] + else: + y1 = rect.bottom() - unit[1] * self.offset[1] + y2 = y1 - unit[1] * self.size[1] + self.b = [x1,x2,x3,y1,y2,labelWidth] + + ## Draw background + p.setPen(self.pen) + p.setBrush(QtGui.QBrush(QtGui.QColor(255,255,255,100))) + rect = QtCore.QRectF( + QtCore.QPointF(x1 - unit[0]*textPadding, y1 + labelHeight/2 + unit[1]*textPadding), + QtCore.QPointF(x3, y2 - labelHeight/2 - unit[1]*textPadding) + ) + p.drawRect(rect) + + + ## Have to scale painter so that text and gradients are correct size. Bleh. + p.scale(unit[0], unit[1]) + + ## Draw color bar + self.gradient.setStart(0, y1/unit[1]) + self.gradient.setFinalStop(0, y2/unit[1]) + p.setBrush(self.gradient) + rect = QtCore.QRectF( + QtCore.QPointF(x1/unit[0], y1/unit[1]), + QtCore.QPointF(x2/unit[0], y2/unit[1]) + ) + p.drawRect(rect) + + + ## draw labels + p.setPen(QtGui.QPen(QtGui.QColor(0,0,0))) + tx = x2 + unit[0]*textPadding + lh = labelHeight/unit[1] + for k in self.labels: + y = y1 + self.labels[k] * (y2-y1) + p.drawText(QtCore.QRectF(tx/unit[0], y/unit[1] - lh/2.0, 1000, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) + + diff --git a/graphicsItems/GraphicsItemMethods.py b/graphicsItems/GraphicsItemMethods.py new file mode 100644 index 00000000..63e95476 --- /dev/null +++ b/graphicsItems/GraphicsItemMethods.py @@ -0,0 +1,256 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.GraphicsScene import GraphicsScene +from pyqtgraph.Point import Point +import weakref + +class GraphicsItemMethods: + """ + Class providing useful methods to GraphicsObject and GraphicsWidget. + """ + def __init__(self): + self._viewWidget = None + self._viewBox = None + GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() + + def getViewWidget(self): + """ + Return the view widget for this item. If the scene has multiple views, only the first view is returned. + The return value is cached; clear the cached value with forgetViewWidget() + """ + if self._viewWidget is None: + scene = self.scene() + if scene is None: + return None + views = scene.views() + if len(views) < 1: + return None + self._viewWidget = weakref.ref(self.scene().views()[0]) + return self._viewWidget() + + def forgetViewWidget(self): + self._viewWidget = None + + def getViewBox(self): + """ + Return the first ViewBox or GraphicsView which bounds this item's visible space. + If this item is not contained within a ViewBox, then the GraphicsView is returned. + If the item is contained inside nested ViewBoxes, then the inner-most ViewBox is returned. + The result is cached; clear the cache with forgetViewBox() + """ + if self._viewBox is None: + p = self + while True: + p = p.parentItem() + if p is None: + vb = self.getViewWidget() + if vb is None: + return None + else: + self._viewBox = weakref.ref(vb) + break + if hasattr(p, 'implements') and p.implements('ViewBox'): + self._viewBox = weakref.ref(p) + break + + return self._viewBox() ## If we made it this far, _viewBox is definitely not None + + def forgetViewBox(self): + self._viewBox = None + + + def deviceTransform(self, viewportTransform=None): + """ + Return the transform that converts local item coordinates to device coordinates (usually pixels). + Extends deviceTransform to automatically determine the viewportTransform. + """ + if viewportTransform is None: + view = self.getViewWidget() + if view is None: + return None + viewportTransform = view.viewportTransform() + return QtGui.QGraphicsObject.deviceTransform(self, viewportTransform) + + def viewTransform(self): + """Return the transform that maps from local coordinates to the item's ViewBox coordinates + If there is no ViewBox, return the scene transform. + Returns None if the item does not have a view.""" + view = self.getViewBox() + if view is None: + return None + if hasattr(view, 'implements') and view.implements('ViewBox'): + return self.itemTransform(view.innerSceneItem())[0] + else: + return self.sceneTransform() + #return self.deviceTransform(view.viewportTransform()) + + + + def getBoundingParents(self): + """Return a list of parents to this item that have child clipping enabled.""" + p = self + parents = [] + while True: + p = p.parentItem() + if p is None: + break + if p.flags() & self.ItemClipsChildrenToShape: + parents.append(p) + return parents + + def viewRect(self): + """Return the bounds (in item coordinates) of this item's ViewBox or GraphicsWidget""" + view = self.getViewBox() + if view is None: + return None + bounds = self.mapRectFromView(view.viewRect()).normalized() + + ## nah. + #for p in self.getBoundingParents(): + #bounds &= self.mapRectFromScene(p.sceneBoundingRect()) + + return bounds + + + + def pixelVectors(self): + """Return vectors in local coordinates representing the width and height of a view pixel.""" + vt = self.deviceTransform() + if vt is None: + return None + vt = vt.inverted()[0] + orig = vt.map(QtCore.QPointF(0, 0)) + return vt.map(QtCore.QPointF(1, 0))-orig, vt.map(QtCore.QPointF(0, 1))-orig + + def pixelLength(self, direction): + """Return the length of one pixel in the direction indicated (in local coordinates)""" + dt = self.deviceTransform() + if dt is None: + return None + viewDir = Point(dt.map(direction) - dt.map(Point(0,0))) + norm = viewDir.norm() + dti = dt.inverted()[0] + return Point(dti.map(norm)-dti.map(Point(0,0))).length() + + + def pixelSize(self): + v = self.pixelVectors() + return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5 + + def pixelWidth(self): + vt = self.deviceTransform() + if vt is None: + return 0 + vt = vt.inverted()[0] + return Point(vt.map(QtCore.QPointF(1, 0))-vt.map(QtCore.QPointF(0, 0))).length() + + def pixelHeight(self): + vt = self.deviceTransform() + if vt is None: + return 0 + vt = vt.inverted()[0] + return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length() + + + def mapToDevice(self, obj): + """ + Return *obj* mapped from local coordinates to device coordinates (pixels). + """ + vt = self.deviceTransform() + if vt is None: + return None + return vt.map(obj) + + def mapFromDevice(self, obj): + """ + Return *obj* mapped from device coordinates (pixels) to local coordinates. + """ + vt = self.deviceTransform() + if vt is None: + return None + vt = vt.inverted()[0] + return vt.map(obj) + + def mapToView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + return vt.map(obj) + + def mapRectToView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + return vt.mapRect(obj) + + def mapFromView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + vt = vt.inverted()[0] + return vt.map(obj) + + def mapRectFromView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + vt = vt.inverted()[0] + return vt.mapRect(obj) + + def pos(self): + return Point(QtGui.QGraphicsObject.pos(self)) + + def viewPos(self): + return self.mapToView(self.pos()) + + #def itemChange(self, change, value): + #ret = QtGui.QGraphicsObject.itemChange(self, change, value) + #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged: + #print "Item scene changed:", self + #self.setChildScene(self) ## This is bizarre. + #return ret + + #def setChildScene(self, ch): + #scene = self.scene() + #for ch2 in ch.childItems(): + #if ch2.scene() is not scene: + #print "item", ch2, "has different scene:", ch2.scene(), scene + #scene.addItem(ch2) + #QtGui.QApplication.processEvents() + #print " --> ", ch2.scene() + #self.setChildScene(ch2) + + def parentItem(self): + ## PyQt bug -- some items are returned incorrectly. + return GraphicsScene.translateGraphicsItem(QtGui.QGraphicsObject.parentItem(self)) + + + def childItems(self): + ## PyQt bug -- some child items are returned incorrectly. + return map(GraphicsScene.translateGraphicsItem, QtGui.QGraphicsObject.childItems(self)) + + + def sceneTransform(self): + ## Qt bug: do no allow access to sceneTransform() until + ## the item has a scene. + + if self.scene() is None: + return self.transform() + else: + return QtGui.QGraphicsObject.sceneTransform(self) + + + def transformAngle(self, relativeItem=None): + """Return the rotation produced by this item's transform (this assumes there is no shear in the transform) + If relativeItem is given, then the angle is determined relative to that item. + """ + if relativeItem is None: + relativeItem = self.parentItem() + + tr = self.itemTransform(relativeItem)[0] + vec = tr.map(Point(1,0)) - tr.map(Point(0,0)) + return Point(vec).angle(Point(1,0)) + + + + + \ No newline at end of file diff --git a/graphicsItems/GraphicsLayout.py b/graphicsItems/GraphicsLayout.py new file mode 100644 index 00000000..c1d28c28 --- /dev/null +++ b/graphicsItems/GraphicsLayout.py @@ -0,0 +1,97 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +from GraphicsWidget import GraphicsWidget + +__all__ = ['GraphicsLayout'] +class GraphicsLayout(GraphicsWidget): + """ + Used for laying out GraphicsWidgets in a grid. + """ + + + def __init__(self, parent=None, border=None): + GraphicsWidget.__init__(self, parent) + if border is True: + border = (100,100,100) + self.border = border + self.layout = QtGui.QGraphicsGridLayout() + self.setLayout(self.layout) + self.items = {} + self.rows = {} + self.currentRow = 0 + self.currentCol = 0 + + def nextRow(self): + """Advance to next row for automatic item placement""" + self.currentRow += 1 + self.currentCol = 0 + + def nextCol(self, colspan=1): + """Advance to next column, while returning the current column number + (generally only for internal use--called by addItem)""" + self.currentCol += colspan + return self.currentCol-colspan + + def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + from PlotItem import PlotItem + plot = PlotItem(**kargs) + self.addItem(plot, row, col, rowspan, colspan) + return plot + + def addViewBox(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + vb = ViewBox(**kargs) + self.addItem(vb, row, col, rowspan, colspan) + return vb + + + def addItem(self, item, row=None, col=None, rowspan=1, colspan=1): + if row is None: + row = self.currentRow + if col is None: + col = self.nextCol(colspan) + + if row not in self.rows: + self.rows[row] = {} + self.rows[row][col] = item + self.items[item] = (row, col) + + self.layout.addItem(item, row, col, rowspan, colspan) + + def getItem(self, row, col): + return self.row[row][col] + + def boundingRect(self): + return self.rect() + + def paint(self, p, *args): + if self.border is None: + return + p.setPen(fn.mkPen(self.border)) + for i in self.items: + r = i.mapRectToParent(i.boundingRect()) + p.drawRect(r) + + def itemIndex(self, item): + for i in range(self.layout.count()): + if self.layout.itemAt(i).graphicsItem() is item: + return i + raise Exception("Could not determine index of item " + str(item)) + + def removeItem(self, item): + ind = self.itemIndex(item) + self.layout.removeAt(ind) + self.scene().removeItem(item) + r,c = self.items[item] + del self.items[item] + del self.rows[r][c] + self.update() + + def clear(self): + items = [] + for i in self.items.keys(): + self.removeItem(i) + + +## Must be imported at the end to avoid cyclic-dependency hell: +from ViewBox import ViewBox +from PlotItem import PlotItem diff --git a/graphicsItems/GraphicsObject.py b/graphicsItems/GraphicsObject.py new file mode 100644 index 00000000..af727315 --- /dev/null +++ b/graphicsItems/GraphicsObject.py @@ -0,0 +1,19 @@ +from pyqtgraph.Qt import QtGui, QtCore +from GraphicsItemMethods import GraphicsItemMethods + +__all__ = ['GraphicsObject'] +class GraphicsObject(GraphicsItemMethods, QtGui.QGraphicsObject): + """Extends QGraphicsObject with a few important functions. + (Most of these assume that the object is in a scene with a single view) + + This class also generates a cache of the Qt-internal addresses of each item + so that GraphicsScene.items() can return the correct objects (this is a PyQt bug) + + Note: most of the extended functionality is inherited from GraphicsItemMethods, + which is shared between GraphicsObject and GraphicsWidget. + """ + def __init__(self, *args): + QtGui.QGraphicsObject.__init__(self, *args) + GraphicsItemMethods.__init__(self) + + diff --git a/graphicsItems/GraphicsWidget.py b/graphicsItems/GraphicsWidget.py new file mode 100644 index 00000000..0181ea17 --- /dev/null +++ b/graphicsItems/GraphicsWidget.py @@ -0,0 +1,44 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.GraphicsScene import GraphicsScene +from GraphicsItemMethods import GraphicsItemMethods + +__all__ = ['GraphicsWidget'] +class GraphicsWidget(GraphicsItemMethods, QtGui.QGraphicsWidget): + def __init__(self, *args, **kargs): + """ + Extends QGraphicsWidget with several helpful methods and workarounds for PyQt bugs. + Most of the extra functionality is inherited from GraphicsObjectSuperclass. + """ + QtGui.QGraphicsWidget.__init__(self, *args, **kargs) + GraphicsItemMethods.__init__(self) + GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() + + #def getMenu(self): + #pass + + def setFixedHeight(self, h): + self.setMaximumHeight(h) + self.setMinimumHeight(h) + + def setFixedWidth(self, h): + self.setMaximumWidth(h) + self.setMinimumWidth(h) + + def height(self): + return self.geometry().height() + + def width(self): + return self.geometry().width() + + def boundingRect(self): + br = self.mapRectFromParent(self.geometry()).normalized() + #print "bounds:", br + return br + + def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. + p = QtGui.QPainterPath() + p.addRect(self.boundingRect()) + #print "shape:", p.boundingRect() + return p + + diff --git a/graphicsItems/GridItem.py b/graphicsItems/GridItem.py new file mode 100644 index 00000000..e3a4f1a0 --- /dev/null +++ b/graphicsItems/GridItem.py @@ -0,0 +1,116 @@ +from pyqtgraph.Qt import QtGui, QtCore +from UIGraphicsItem import * +import numpy as np +from pyqtgraph.Point import Point + +__all__ = ['GridItem'] +class GridItem(UIGraphicsItem): + """ + Displays a rectangular grid of lines indicating major divisions within a coordinate system. + Automatically determines what divisions to use. + """ + + def __init__(self): + UIGraphicsItem.__init__(self) + #QtGui.QGraphicsItem.__init__(self, *args) + #self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape) + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + + self.picture = None + + + def viewChangedEvent(self): + self.picture = None + #UIGraphicsItem.viewRangeChanged(self) + #self.update() + + def paint(self, p, opt, widget): + #p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) + #p.drawRect(self.boundingRect()) + #UIGraphicsItem.paint(self, p, opt, widget) + ### draw picture + if self.picture is None: + #print "no pic, draw.." + self.generatePicture() + p.drawPicture(QtCore.QPointF(0, 0), self.picture) + #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + #p.drawLine(0, -100, 0, 100) + #p.drawLine(-100, 0, 100, 0) + #print "drawing Grid." + + + def generatePicture(self): + self.picture = QtGui.QPicture() + p = QtGui.QPainter() + p.begin(self.picture) + + dt = self.viewTransform().inverted()[0] + vr = self.getViewWidget().rect() + unit = self.pixelWidth(), self.pixelHeight() + dim = [vr.width(), vr.height()] + lvr = self.boundingRect() + ul = np.array([lvr.left(), lvr.top()]) + br = np.array([lvr.right(), lvr.bottom()]) + + texts = [] + + if ul[1] > br[1]: + x = ul[1] + ul[1] = br[1] + br[1] = x + for i in [2,1,0]: ## Draw three different scales of grid + dist = br-ul + nlTarget = 10.**i + d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5) + ul1 = np.floor(ul / d) * d + br1 = np.ceil(br / d) * d + dist = br1-ul1 + nl = (dist / d) + 0.5 + #print "level", i + #print " dim", dim + #print " dist", dist + #print " d", d + #print " nl", nl + for ax in range(0,2): ## Draw grid for both axes + ppl = dim[ax] / nl[ax] + c = np.clip(3.*(ppl-3), 0., 30.) + linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) + textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2)) + #linePen.setCosmetic(True) + #linePen.setWidth(1) + bx = (ax+1) % 2 + for x in range(0, int(nl[ax])): + linePen.setCosmetic(False) + if ax == 0: + linePen.setWidthF(self.pixelWidth()) + #print "ax 0 height", self.pixelHeight() + else: + linePen.setWidthF(self.pixelHeight()) + #print "ax 1 width", self.pixelWidth() + p.setPen(linePen) + p1 = np.array([0.,0.]) + p2 = np.array([0.,0.]) + p1[ax] = ul1[ax] + x * d[ax] + p2[ax] = p1[ax] + p1[bx] = ul[bx] + p2[bx] = br[bx] + ## don't draw lines that are out of bounds. + if p1[ax] < min(ul[ax], br[ax]) or p1[ax] > max(ul[ax], br[ax]): + continue + p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) + if i < 2: + p.setPen(textPen) + if ax == 0: + x = p1[0] + unit[0] + y = ul[1] + unit[1] * 8. + else: + x = ul[0] + unit[0]*3 + y = p1[1] + unit[1] + texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) + tr = self.deviceTransform() + #tr.scale(1.5, 1.5) + p.setWorldTransform(tr.inverted()[0]) + for t in texts: + x = tr.map(t[0]) + Point(0.5, 0.5) + p.drawText(x, t[1]) + p.end() diff --git a/graphicsItems/HistogramLUTItem.py b/graphicsItems/HistogramLUTItem.py new file mode 100644 index 00000000..19599720 --- /dev/null +++ b/graphicsItems/HistogramLUTItem.py @@ -0,0 +1,178 @@ +""" +GraphicsWidget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. +""" + + +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +from GraphicsWidget import GraphicsWidget +from ViewBox import * +from GradientEditorItem import * +from LinearRegionItem import * +from PlotDataItem import * +from AxisItem import * +from GridItem import * +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +import numpy as np + + +__all__ = ['HistogramLUTItem'] + + +class HistogramLUTItem(GraphicsWidget): + sigLookupTableChanged = QtCore.Signal(object) + sigLevelsChanged = QtCore.Signal(object) + sigLevelChangeFinished = QtCore.Signal(object) + + def __init__(self, image=None): + GraphicsWidget.__init__(self) + self.lut = None + self.imageItem = None + + self.layout = QtGui.QGraphicsGridLayout() + self.setLayout(self.layout) + self.layout.setContentsMargins(1,1,1,1) + self.layout.setSpacing(0) + self.vb = ViewBox() + self.vb.setMaximumWidth(152) + self.vb.setMinimumWidth(52) + self.vb.setMouseEnabled(x=False, y=True) + self.gradient = GradientEditorItem() + self.gradient.setOrientation('right') + self.gradient.loadPreset('grey') + self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal) + self.region.setZValue(1000) + self.vb.addItem(self.region) + self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, showValues=False) + self.layout.addItem(self.axis, 0, 0) + self.layout.addItem(self.vb, 0, 1) + self.layout.addItem(self.gradient, 0, 2) + self.range = None + self.gradient.setFlag(self.gradient.ItemStacksBehindParent) + self.vb.setFlag(self.gradient.ItemStacksBehindParent) + + #self.grid = GridItem() + #self.vb.addItem(self.grid) + + self.gradient.sigGradientChanged.connect(self.gradientChanged) + self.region.sigRegionChanged.connect(self.regionChanging) + self.region.sigRegionChangeFinished.connect(self.regionChanged) + self.vb.sigRangeChanged.connect(self.viewRangeChanged) + self.plot = PlotDataItem() + self.plot.rotate(90) + self.vb.addItem(self.plot) + self.autoHistogramRange() + + if image is not None: + self.setImageItem(image) + #self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + + + #def sizeHint(self, *args): + #return QtCore.QSizeF(115, 200) + + def paint(self, p, *args): + pen = self.region.lines[0].pen + rgn = self.getLevels() + p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0])) + p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1])) + gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect()) + for pen in [fn.mkPen('k', width=3), pen]: + p.setPen(pen) + p.drawLine(p1, gradRect.bottomLeft()) + p.drawLine(p2, gradRect.topLeft()) + p.drawLine(gradRect.topLeft(), gradRect.topRight()) + p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight()) + #p.drawRect(self.boundingRect()) + + + def setHistogramRange(self, mn, mx, padding=0.1): + """Set the Y range on the histogram plot. This disables auto-scaling.""" + self.vb.enableAutoRange(self.vb.YAxis, False) + self.vb.setYRange(mn, mx, padding) + + #d = mx-mn + #mn -= d*padding + #mx += d*padding + #self.range = [mn,mx] + #self.updateRange() + #self.vb.setMouseEnabled(False, True) + #self.region.setBounds([mn,mx]) + + def autoHistogramRange(self): + """Enable auto-scaling on the histogram plot.""" + self.vb.enableAutoRange(self.vb.XYAxes) + #self.range = None + #self.updateRange() + #self.vb.setMouseEnabled(False, False) + + #def updateRange(self): + #self.vb.autoRange() + #if self.range is not None: + #self.vb.setYRange(*self.range) + #vr = self.vb.viewRect() + + #self.region.setBounds([vr.top(), vr.bottom()]) + + def setImageItem(self, img): + self.imageItem = img + img.sigImageChanged.connect(self.imageChanged) + img.setLookupTable(self.getLookupTable) ## send function pointer, not the result + #self.gradientChanged() + self.regionChanged() + self.imageChanged(autoLevel=True) + #self.vb.autoRange() + + def viewRangeChanged(self): + self.update() + + def gradientChanged(self): + if self.imageItem is not None: + self.imageItem.setLookupTable(self.getLookupTable) ## send function pointer, not the result + + self.lut = None + #if self.imageItem is not None: + #self.imageItem.setLookupTable(self.gradient.getLookupTable(512)) + self.sigLookupTableChanged.emit(self) + + def getLookupTable(self, img=None, n=None): + if n is None: + if img.dtype == np.uint8: + n = 256 + else: + n = 512 + if self.lut is None: + self.lut = self.gradient.getLookupTable(n) + return self.lut + + def regionChanged(self): + #if self.imageItem is not None: + #self.imageItem.setLevels(self.region.getRegion()) + self.sigLevelChangeFinished.emit(self) + #self.update() + + def regionChanging(self): + if self.imageItem is not None: + self.imageItem.setLevels(self.region.getRegion()) + self.sigLevelsChanged.emit(self) + self.update() + + def imageChanged(self, autoLevel=False, autoRange=False): + h = self.imageItem.getHistogram() + if h[0] is None: + return + self.plot.setData(*h, fillLevel=0.0, brush=(100, 100, 200)) + if autoLevel: + mn = h[0][0] + mx = h[0][-1] + self.region.setRegion([mn, mx]) + #self.updateRange() + #if autoRange: + #self.updateRange() + + def getLevels(self): + return self.region.getRegion() + + def setLevels(self, mn, mx): + self.region.setRegion([mn, mx]) \ No newline at end of file diff --git a/graphicsItems/ImageItem.old b/graphicsItems/ImageItem.old new file mode 100644 index 00000000..726814e0 --- /dev/null +++ b/graphicsItems/ImageItem.old @@ -0,0 +1,398 @@ +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +try: + import scipy.weave as weave + from scipy.weave import converters +except: + pass +import pyqtgraph.functions as fn +import pyqtgraph.debug as debug +from GraphicsObject import GraphicsObject + +__all__ = ['ImageItem'] +class ImageItem(GraphicsObject): + """ + GraphicsObject displaying an image. Optimized for rapid update (ie video display) + + """ + + + sigImageChanged = QtCore.Signal() + + ## performance gains from this are marginal, and it's rather unreliable. + useWeave = False + + def __init__(self, image=None, copy=True, parent=None, border=None, mode=None, *args): + #QObjectWorkaround.__init__(self) + GraphicsObject.__init__(self) + #self.pixmapItem = QtGui.QGraphicsPixmapItem(self) + self.qimage = QtGui.QImage() + self.pixmap = None + self.paintMode = mode + #self.useWeave = True + self.blackLevel = None + self.whiteLevel = None + self.alpha = 1.0 + self.image = None + self.clipLevel = None + self.drawKernel = None + if border is not None: + border = fn.mkPen(border) + self.border = border + + #QtGui.QGraphicsPixmapItem.__init__(self, parent, *args) + #self.pixmapItem = QtGui.QGraphicsPixmapItem(self) + if image is not None: + self.updateImage(image, copy, autoRange=True) + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + + #self.item = QtGui.QGraphicsPixmapItem(parent=self) + + def setCompositionMode(self, mode): + self.paintMode = mode + self.update() + + def setAlpha(self, alpha): + self.alpha = alpha + self.updateImage() + + #def boundingRect(self): + #return self.pixmapItem.boundingRect() + #return QtCore.QRectF(0, 0, self.qimage.width(), self.qimage.height()) + + def width(self): + if self.pixmap is None: + return None + return self.pixmap.width() + + def height(self): + if self.pixmap is None: + return None + return self.pixmap.height() + + def boundingRect(self): + if self.pixmap is None: + return QtCore.QRectF(0., 0., 0., 0.) + return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) + + def setClipLevel(self, level=None): + self.clipLevel = level + + #def paint(self, p, opt, widget): + #pass + #if self.pixmap is not None: + #p.drawPixmap(0, 0, self.pixmap) + #print "paint" + + def setLevels(self, white=None, black=None): + if white is not None: + self.whiteLevel = white + if black is not None: + self.blackLevel = black + self.updateImage() + + def getLevels(self): + return self.whiteLevel, self.blackLevel + + def updateImage(self, *args, **kargs): + ## can we make any assumptions here that speed things up? + ## dtype, range, size are all the same? + defaults = { + 'autoRange': False, + } + defaults.update(kargs) + return self.setImage(*args, **defaults) + + def setImage(self, image=None, copy=True, autoRange=True, clipMask=None, white=None, black=None, axes=None): + prof = debug.Profiler('ImageItem.updateImage 0x%x' %id(self)) + #debug.printTrace() + if axes is None: + axh = {'x': 0, 'y': 1, 'c': 2} + else: + axh = axes + #print "Update image", black, white + if white is not None: + self.whiteLevel = white + if black is not None: + self.blackLevel = black + + gotNewData = False + if image is None: + if self.image is None: + return + else: + gotNewData = True + if self.image is None or image.shape != self.image.shape: + self.prepareGeometryChange() + if copy: + self.image = image.view(np.ndarray).copy() + else: + self.image = image.view(np.ndarray) + #print " image max:", self.image.max(), "min:", self.image.min() + prof.mark('1') + + # Determine scale factors + if autoRange or self.blackLevel is None: + if self.image.dtype is np.ubyte: + self.blackLevel = 0 + self.whiteLevel = 255 + else: + self.blackLevel = self.image.min() + self.whiteLevel = self.image.max() + #print "Image item using", self.blackLevel, self.whiteLevel + + if self.blackLevel != self.whiteLevel: + scale = 255. / (self.whiteLevel - self.blackLevel) + else: + scale = 0. + + prof.mark('2') + + ## Recolor and convert to 8 bit per channel + # Try using weave, then fall back to python + shape = self.image.shape + black = float(self.blackLevel) + white = float(self.whiteLevel) + + if black == 0 and white == 255 and self.image.dtype == np.ubyte: + im = self.image + elif self.image.dtype in [np.ubyte, np.uint16]: + # use lookup table instead + npts = 2**(self.image.itemsize * 8) + lut = self.getLookupTable(npts, black, white) + im = lut[self.image] + else: + im = self.applyColorScaling(self.image, black, scale) + + prof.mark('3') + + try: + im1 = np.empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=np.ubyte) + except: + print im.shape, axh + raise + alpha = np.clip(int(255 * self.alpha), 0, 255) + prof.mark('4') + # Fill image + if im.ndim == 2: + im2 = im.transpose(axh['y'], axh['x']) + im1[..., 0] = im2 + im1[..., 1] = im2 + im1[..., 2] = im2 + im1[..., 3] = alpha + elif im.ndim == 3: #color image + im2 = im.transpose(axh['y'], axh['x'], axh['c']) + if im2.shape[2] > 4: + raise Exception("ImageItem got image with more than 4 color channels (shape is %s; axes are %s)" % (str(im.shape), str(axh))) + ## [B G R A] Reorder colors + order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. + + for i in range(0, im.shape[axh['c']]): + im1[..., order[i]] = im2[..., i] + + ## fill in unused channels with 0 or alpha + for i in range(im.shape[axh['c']], 3): + im1[..., i] = 0 + if im.shape[axh['c']] < 4: + im1[..., 3] = alpha + + else: + raise Exception("Image must be 2 or 3 dimensions") + #self.im1 = im1 + # Display image + prof.mark('5') + if self.clipLevel is not None or clipMask is not None: + if clipMask is not None: + mask = clipMask.transpose() + else: + mask = (self.image < self.clipLevel).transpose() + im1[..., 0][mask] *= 0.5 + im1[..., 1][mask] *= 0.5 + im1[..., 2][mask] = 255 + prof.mark('6') + #print "Final image:", im1.dtype, im1.min(), im1.max(), im1.shape + self.ims = im1.tostring() ## Must be held in memory here because qImage won't do it for us :( + prof.mark('7') + qimage = QtGui.QImage(buffer(self.ims), im1.shape[1], im1.shape[0], QtGui.QImage.Format_ARGB32) + prof.mark('8') + self.pixmap = QtGui.QPixmap.fromImage(qimage) + prof.mark('9') + ##del self.ims + #self.item.setPixmap(self.pixmap) + + self.update() + prof.mark('10') + + if gotNewData: + #self.emit(QtCore.SIGNAL('imageChanged')) + self.sigImageChanged.emit() + + prof.finish() + + def getLookupTable(self, num, black, white): + num = int(num) + black = int(black) + white = int(white) + if white < black: + b = black + black = white + white = b + key = (num, black, white) + lut = np.empty(num, dtype=np.ubyte) + lut[:black] = 0 + rng = lut[black:white] + try: + rng[:] = np.linspace(0, 255, white-black)[:len(rng)] + except: + print key, rng.shape + lut[white:] = 255 + return lut + + + def applyColorScaling(self, img, offset, scale): + try: + if not ImageItem.useWeave: + raise Exception('Skipping weave compile') + #sim = np.ascontiguousarray(self.image) ## should not be needed + sim = img.reshape(img.size) + #sim.shape = sim.size + im = np.empty(sim.shape, dtype=np.ubyte) + n = im.size + + code = """ + for( int i=0; i<n; i++ ) { + float a = (sim(i)-offset) * (float)scale; + if( a > 255.0 ) + a = 255.0; + else if( a < 0.0 ) + a = 0.0; + im(i) = a; + } + """ + + weave.inline(code, ['sim', 'im', 'n', 'offset', 'scale'], type_converters=converters.blitz, compiler = 'gcc') + #sim.shape = shape + im.shape = img.shape + except: + if ImageItem.useWeave: + ImageItem.useWeave = False + #sys.excepthook(*sys.exc_info()) + #print "==============================================================================" + #print "Weave compile failed, falling back to slower version." + #img.shape = shape + im = ((img - offset) * scale).clip(0.,255.).astype(np.ubyte) + return im + + + def getPixmap(self): + return self.pixmap.copy() + + def getHistogram(self, bins=500, step=3): + """returns x and y arrays containing the histogram values for the current image. + The step argument causes pixels to be skipped when computing the histogram to save time.""" + if self.image is None: + return None,None + stepData = self.image[::step, ::step] + hist = np.histogram(stepData, bins=bins) + return hist[1][:-1], hist[0] + + def setPxMode(self, b): + """Set whether the item ignores transformations and draws directly to screen pixels.""" + self.setFlag(self.ItemIgnoresTransformations, b) + + def setScaledMode(self): + self.setPxMode(False) + + def mousePressEvent(self, ev): + if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: + self.drawAt(ev.pos(), ev) + ev.accept() + else: + ev.ignore() + + def mouseMoveEvent(self, ev): + #print "mouse move", ev.pos() + if self.drawKernel is not None: + self.drawAt(ev.pos(), ev) + + def mouseReleaseEvent(self, ev): + pass + + def tabletEvent(self, ev): + print ev.device() + print ev.pointerType() + print ev.pressure() + + def drawAt(self, pos, ev=None): + pos = [int(pos.x()), int(pos.y())] + dk = self.drawKernel + kc = self.drawKernelCenter + sx = [0,dk.shape[0]] + sy = [0,dk.shape[1]] + tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] + ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] + + for i in [0,1]: + dx1 = -min(0, tx[i]) + dx2 = min(0, self.image.shape[0]-tx[i]) + tx[i] += dx1+dx2 + sx[i] += dx1+dx2 + + dy1 = -min(0, ty[i]) + dy2 = min(0, self.image.shape[1]-ty[i]) + ty[i] += dy1+dy2 + sy[i] += dy1+dy2 + + #print sx + #print sy + #print tx + #print ty + #print self.image.shape + #print self.image[tx[0]:tx[1], ty[0]:ty[1]].shape + #print dk[sx[0]:sx[1], sy[0]:sy[1]].shape + ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) + ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) + #src = dk[sx[0]:sx[1], sy[0]:sy[1]] + #mask = self.drawMask[sx[0]:sx[1], sy[0]:sy[1]] + mask = self.drawMask + src = dk + #print self.image[ts].shape, src.shape + + if callable(self.drawMode): + self.drawMode(dk, self.image, mask, ss, ts, ev) + else: + src = src[ss] + if self.drawMode == 'set': + if mask is not None: + mask = mask[ss] + self.image[ts] = self.image[ts] * (1-mask) + src * mask + else: + self.image[ts] = src + elif self.drawMode == 'add': + self.image[ts] += src + else: + raise Exception("Unknown draw mode '%s'" % self.drawMode) + self.updateImage() + + def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): + self.drawKernel = kernel + self.drawKernelCenter = center + self.drawMode = mode + self.drawMask = mask + + def paint(self, p, *args): + + #QtGui.QGraphicsPixmapItem.paint(self, p, *args) + if self.pixmap is None: + return + if self.paintMode is not None: + p.setCompositionMode(self.paintMode) + p.drawPixmap(self.boundingRect(), self.pixmap, QtCore.QRectF(0, 0, self.pixmap.width(), self.pixmap.height())) + if self.border is not None: + p.setPen(self.border) + p.drawRect(self.boundingRect()) + + def pixelSize(self): + """return size of a single pixel in the image""" + br = self.sceneBoundingRect() + return br.width()/self.pixmap.width(), br.height()/self.pixmap.height() diff --git a/graphicsItems/ImageItem.py b/graphicsItems/ImageItem.py new file mode 100644 index 00000000..088b5891 --- /dev/null +++ b/graphicsItems/ImageItem.py @@ -0,0 +1,537 @@ +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +try: + import scipy.weave as weave + from scipy.weave import converters +except: + pass +import pyqtgraph.functions as fn +import pyqtgraph.debug as debug +from GraphicsObject import GraphicsObject + +__all__ = ['ImageItem'] +class ImageItem(GraphicsObject): + """ + GraphicsObject displaying an image. Optimized for rapid update (ie video display) + + """ + + + sigImageChanged = QtCore.Signal() + + ## performance gains from this are marginal, and it's rather unreliable. + useWeave = False + + def __init__(self, image=None, **kargs): + """ + See setImage for all allowed arguments. + """ + GraphicsObject.__init__(self) + #self.pixmapItem = QtGui.QGraphicsPixmapItem(self) + #self.qimage = QtGui.QImage() + #self._pixmap = None + + self.image = None ## original image data + self.qimage = None ## rendered image for display + #self.clipMask = None + + self.paintMode = None + #self.useWeave = True + + self.levels = None ## [min, max] or [[redMin, redMax], ...] + self.lut = None + + #self.clipLevel = None + self.drawKernel = None + self.border = None + + if image is not None: + self.setImage(image, **kargs) + else: + self.setOpts(**kargs) + + def setCompositionMode(self, mode): + self.paintMode = mode + self.update() + + ## use setOpacity instead. + #def setAlpha(self, alpha): + #self.setOpacity(alpha) + #self.updateImage() + + def setBorder(self, b): + self.border = fn.mkPen(b) + self.update() + + def width(self): + if self.image is None: + return None + return self.image.shape[0] + + def height(self): + if self.image is None: + return None + return self.image.shape[1] + + def boundingRect(self): + if self.image is None: + return QtCore.QRectF(0., 0., 0., 0.) + return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) + + #def setClipLevel(self, level=None): + #self.clipLevel = level + #self.updateImage() + + #def paint(self, p, opt, widget): + #pass + #if self.pixmap is not None: + #p.drawPixmap(0, 0, self.pixmap) + #print "paint" + + def setLevels(self, levels, update=True): + """ + Set image scaling levels. Can be one of: + [blackLevel, whiteLevel] + [[minRed, maxRed], [minGreen, maxGreen], [minBlue, maxBlue]] + Only the first format is compatible with lookup tables. + """ + self.levels = levels + if update: + self.updateImage() + + def getLevels(self): + return self.levels + #return self.whiteLevel, self.blackLevel + + def setLookupTable(self, lut, update=True): + """ + Set the lookup table to use for this image. (see functions.makeARGB for more information on how this is used) + Optionally, lut can be a callable that accepts the current image as an argument and returns the lookup table to use.""" + self.lut = lut + if update: + self.updateImage() + + def setOpts(self, update=True, **kargs): + if 'lut' in kargs: + self.setLookupTable(kargs['lut'], update=update) + if 'levels' in kargs: + self.setLevels(kargs['levels'], update=update) + #if 'clipLevel' in kargs: + #self.setClipLevel(kargs['clipLevel']) + if 'opacity' in kargs: + self.setOpacity(kargs['opacity']) + if 'compositionMode' in kargs: + self.setCompositionMode(kargs['compositionMode']) + if 'border' in kargs: + self.setBorder(kargs['border']) + + def setRect(self, rect): + """Scale and translate the image to fit within rect.""" + self.resetTransform() + self.scale(rect.width() / self.width(), rect.height() / self.height()) + self.translate(rect.left(), rect.top()) + + def setImage(self, image=None, autoLevels=None, **kargs): + """ + Update the image displayed by this item. + Arguments: + image + autoLevels + lut + levels + opacity + compositionMode + border + + """ + prof = debug.Profiler('ImageItem.setImage', disabled=True) + + gotNewData = False + if image is None: + if self.image is None: + return + else: + gotNewData = True + if self.image is None or image.shape != self.image.shape: + self.prepareGeometryChange() + self.image = image.view(np.ndarray) + + prof.mark('1') + + if autoLevels is None: + if 'levels' in kargs: + autoLevels = False + else: + autoLevels = True + if autoLevels: + img = self.image + while img.size > 2**16: + img = img[::2, ::2] + mn, mx = img.min(), img.max() + if mn == mx: + mn = 0 + mx = 255 + kargs['levels'] = [mn,mx] + prof.mark('2') + + self.setOpts(update=False, **kargs) + prof.mark('3') + + self.qimage = None + self.update() + prof.mark('4') + + if gotNewData: + self.sigImageChanged.emit() + + + prof.finish() + + + + def updateImage(self, *args, **kargs): + ## used for re-rendering qimage from self.image. + + ## can we make any assumptions here that speed things up? + ## dtype, range, size are all the same? + defaults = { + 'autoLevels': False, + } + defaults.update(kargs) + return self.setImage(*args, **defaults) + + + + + def render(self): + prof = debug.Profiler('ImageItem.render', disabled=True) + if self.image is None: + return + if callable(self.lut): + lut = self.lut(self.image) + else: + lut = self.lut + + argb, alpha = fn.makeARGB(self.image, lut=lut, levels=self.levels) + self.qimage = fn.makeQImage(argb, alpha) + #self.pixmap = QtGui.QPixmap.fromImage(self.qimage) + prof.finish() + + + def paint(self, p, *args): + prof = debug.Profiler('ImageItem.paint', disabled=True) + if self.image is None: + return + if self.qimage is None: + self.render() + if self.paintMode is not None: + p.setCompositionMode(self.paintMode) + + p.drawImage(QtCore.QPointF(0,0), self.qimage) + if self.border is not None: + p.setPen(self.border) + p.drawRect(self.boundingRect()) + prof.finish() + + + def getHistogram(self, bins=500, step=3): + """returns x and y arrays containing the histogram values for the current image. + The step argument causes pixels to be skipped when computing the histogram to save time.""" + if self.image is None: + return None,None + stepData = self.image[::step, ::step] + hist = np.histogram(stepData, bins=bins) + return hist[1][:-1], hist[0] + + def setPxMode(self, b): + """Set whether the item ignores transformations and draws directly to screen pixels.""" + self.setFlag(self.ItemIgnoresTransformations, b) + + def setScaledMode(self): + self.setPxMode(False) + + def getPixmap(self): + if self.qimage is None: + self.render() + if self.qimage is None: + return None + return QtGui.QPixmap.fromImage(self.qimage) + + def pixelSize(self): + """return scene-size of a single pixel in the image""" + br = self.sceneBoundingRect() + if self.image is None: + return 1,1 + return br.width()/self.width(), br.height()/self.height() + + def mousePressEvent(self, ev): + if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: + self.drawAt(ev.pos(), ev) + ev.accept() + else: + ev.ignore() + + def mouseMoveEvent(self, ev): + #print "mouse move", ev.pos() + if self.drawKernel is not None: + self.drawAt(ev.pos(), ev) + + def mouseReleaseEvent(self, ev): + pass + + def tabletEvent(self, ev): + print ev.device() + print ev.pointerType() + print ev.pressure() + + def drawAt(self, pos, ev=None): + pos = [int(pos.x()), int(pos.y())] + dk = self.drawKernel + kc = self.drawKernelCenter + sx = [0,dk.shape[0]] + sy = [0,dk.shape[1]] + tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] + ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] + + for i in [0,1]: + dx1 = -min(0, tx[i]) + dx2 = min(0, self.image.shape[0]-tx[i]) + tx[i] += dx1+dx2 + sx[i] += dx1+dx2 + + dy1 = -min(0, ty[i]) + dy2 = min(0, self.image.shape[1]-ty[i]) + ty[i] += dy1+dy2 + sy[i] += dy1+dy2 + + #print sx + #print sy + #print tx + #print ty + #print self.image.shape + #print self.image[tx[0]:tx[1], ty[0]:ty[1]].shape + #print dk[sx[0]:sx[1], sy[0]:sy[1]].shape + ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) + ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) + #src = dk[sx[0]:sx[1], sy[0]:sy[1]] + #mask = self.drawMask[sx[0]:sx[1], sy[0]:sy[1]] + mask = self.drawMask + src = dk + #print self.image[ts].shape, src.shape + + if callable(self.drawMode): + self.drawMode(dk, self.image, mask, ss, ts, ev) + else: + src = src[ss] + if self.drawMode == 'set': + if mask is not None: + mask = mask[ss] + self.image[ts] = self.image[ts] * (1-mask) + src * mask + else: + self.image[ts] = src + elif self.drawMode == 'add': + self.image[ts] += src + else: + raise Exception("Unknown draw mode '%s'" % self.drawMode) + self.updateImage() + + def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): + self.drawKernel = kernel + self.drawKernelCenter = center + self.drawMode = mode + self.drawMask = mask + + + + + + #def setImage(self, image=None, copy=True, autoRange=True, clipMask=None, white=None, black=None, axes=None): + #prof = debug.Profiler('ImageItem.updateImage 0x%x' %id(self), disabled=True) + ##debug.printTrace() + #if axes is None: + #axh = {'x': 0, 'y': 1, 'c': 2} + #else: + #axh = axes + ##print "Update image", black, white + #if white is not None: + #self.whiteLevel = white + #if black is not None: + #self.blackLevel = black + + #gotNewData = False + #if image is None: + #if self.image is None: + #return + #else: + #gotNewData = True + #if self.image is None or image.shape != self.image.shape: + #self.prepareGeometryChange() + #if copy: + #self.image = image.view(np.ndarray).copy() + #else: + #self.image = image.view(np.ndarray) + ##print " image max:", self.image.max(), "min:", self.image.min() + #prof.mark('1') + + ## Determine scale factors + #if autoRange or self.blackLevel is None: + #if self.image.dtype is np.ubyte: + #self.blackLevel = 0 + #self.whiteLevel = 255 + #else: + #self.blackLevel = self.image.min() + #self.whiteLevel = self.image.max() + ##print "Image item using", self.blackLevel, self.whiteLevel + + #if self.blackLevel != self.whiteLevel: + #scale = 255. / (self.whiteLevel - self.blackLevel) + #else: + #scale = 0. + + #prof.mark('2') + + ### Recolor and convert to 8 bit per channel + ## Try using weave, then fall back to python + #shape = self.image.shape + #black = float(self.blackLevel) + #white = float(self.whiteLevel) + + #if black == 0 and white == 255 and self.image.dtype == np.ubyte: + #im = self.image + #elif self.image.dtype in [np.ubyte, np.uint16]: + ## use lookup table instead + #npts = 2**(self.image.itemsize * 8) + #lut = self.getLookupTable(npts, black, white) + #im = lut[self.image] + #else: + #im = self.applyColorScaling(self.image, black, scale) + + #prof.mark('3') + + #try: + #im1 = np.empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=np.ubyte) + #except: + #print im.shape, axh + #raise + #alpha = np.clip(int(255 * self.alpha), 0, 255) + #prof.mark('4') + ## Fill image + #if im.ndim == 2: + #im2 = im.transpose(axh['y'], axh['x']) + #im1[..., 0] = im2 + #im1[..., 1] = im2 + #im1[..., 2] = im2 + #im1[..., 3] = alpha + #elif im.ndim == 3: #color image + #im2 = im.transpose(axh['y'], axh['x'], axh['c']) + #if im2.shape[2] > 4: + #raise Exception("ImageItem got image with more than 4 color channels (shape is %s; axes are %s)" % (str(im.shape), str(axh))) + ### [B G R A] Reorder colors + #order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. + + #for i in range(0, im.shape[axh['c']]): + #im1[..., order[i]] = im2[..., i] + + ### fill in unused channels with 0 or alpha + #for i in range(im.shape[axh['c']], 3): + #im1[..., i] = 0 + #if im.shape[axh['c']] < 4: + #im1[..., 3] = alpha + + #else: + #raise Exception("Image must be 2 or 3 dimensions") + ##self.im1 = im1 + ## Display image + #prof.mark('5') + #if self.clipLevel is not None or clipMask is not None: + #if clipMask is not None: + #mask = clipMask.transpose() + #else: + #mask = (self.image < self.clipLevel).transpose() + #im1[..., 0][mask] *= 0.5 + #im1[..., 1][mask] *= 0.5 + #im1[..., 2][mask] = 255 + #prof.mark('6') + ##print "Final image:", im1.dtype, im1.min(), im1.max(), im1.shape + ##self.ims = im1.tostring() ## Must be held in memory here because qImage won't do it for us :( + #prof.mark('7') + #try: + #buf = im1.data + #except AttributeError: + #im1 = np.ascontiguousarray(im1) + #buf = im1.data + + #qimage = QtGui.QImage(buf, im1.shape[1], im1.shape[0], QtGui.QImage.Format_ARGB32) + #self.qimage = qimage + #self.qimage.data = im1 + #self._pixmap = None + #prof.mark('8') + + ##self.pixmap = QtGui.QPixmap.fromImage(qimage) + #prof.mark('9') + ###del self.ims + ##self.item.setPixmap(self.pixmap) + + #self.update() + #prof.mark('10') + + #if gotNewData: + ##self.emit(QtCore.SIGNAL('imageChanged')) + #self.sigImageChanged.emit() + + #prof.finish() + + #def getLookupTable(self, num, black, white): + #num = int(num) + #black = int(black) + #white = int(white) + #if white < black: + #b = black + #black = white + #white = b + #key = (num, black, white) + #lut = np.empty(num, dtype=np.ubyte) + #lut[:black] = 0 + #rng = lut[black:white] + #try: + #rng[:] = np.linspace(0, 255, white-black)[:len(rng)] + #except: + #print key, rng.shape + #lut[white:] = 255 + #return lut + + + #def applyColorScaling(self, img, offset, scale): + #try: + #if not ImageItem.useWeave: + #raise Exception('Skipping weave compile') + ##sim = np.ascontiguousarray(self.image) ## should not be needed + #sim = img.reshape(img.size) + ##sim.shape = sim.size + #im = np.empty(sim.shape, dtype=np.ubyte) + #n = im.size + + #code = """ + #for( int i=0; i<n; i++ ) { + #float a = (sim(i)-offset) * (float)scale; + #if( a > 255.0 ) + #a = 255.0; + #else if( a < 0.0 ) + #a = 0.0; + #im(i) = a; + #} + #""" + + #weave.inline(code, ['sim', 'im', 'n', 'offset', 'scale'], type_converters=converters.blitz, compiler = 'gcc') + ##sim.shape = shape + #im.shape = img.shape + #except: + #if ImageItem.useWeave: + #ImageItem.useWeave = False + ##sys.excepthook(*sys.exc_info()) + ##print "==============================================================================" + ##print "Weave compile failed, falling back to slower version." + ##img.shape = shape + #im = ((img - offset) * scale).clip(0.,255.).astype(np.ubyte) + #return im + diff --git a/graphicsItems/InfiniteLine.py b/graphicsItems/InfiniteLine.py new file mode 100644 index 00000000..ebf24502 --- /dev/null +++ b/graphicsItems/InfiniteLine.py @@ -0,0 +1,255 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.Point import Point +from UIGraphicsItem import UIGraphicsItem +import pyqtgraph.functions as fn +import numpy as np +import weakref + + +__all__ = ['InfiniteLine'] +class InfiniteLine(UIGraphicsItem): + """ + Displays a line of infinite length. + This line may be dragged to indicate a position in data coordinates. + """ + + sigDragged = QtCore.Signal(object) + sigPositionChangeFinished = QtCore.Signal(object) + sigPositionChanged = QtCore.Signal(object) + + def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None): + """ + Initialization options: + pos - Position of the line. This can be a QPointF or a single value for vertical/horizontal lines. + angle - Angle of line in degrees. 0 is horizontal, 90 is vertical. + pen - Pen to use when drawing line + movable - If True, the line can be dragged to a new position by the user + bounds - Optional [min, max] bounding values. Bounds are only valid if the line is vertical or horizontal. + """ + + UIGraphicsItem.__init__(self) + + if bounds is None: ## allowed value boundaries for orthogonal lines + self.maxRange = [None, None] + else: + self.maxRange = bounds + self.moving = False + self.setMovable(movable) + self.p = [0, 0] + self.setAngle(angle) + if pos is None: + pos = Point(0,0) + self.setPos(pos) + + if pen is None: + pen = (200, 200, 100) + self.setPen(pen) + self.currentPen = self.pen + #self.setFlag(self.ItemSendsScenePositionChanges) + + def setMovable(self, m): + self.movable = m + self.setAcceptHoverEvents(m) + + def setBounds(self, bounds): + """Set the (minimum, maximum) allowable values when dragging.""" + self.maxRange = bounds + self.setValue(self.value()) + + def setPen(self, pen): + self.pen = fn.mkPen(pen) + self.currentPen = self.pen + self.update() + + def setAngle(self, angle): + """ + Takes angle argument in degrees. + 0 is horizontal; 90 is vertical. + + Note that the use of value() and setValue() changes if the line is + not vertical or horizontal. + """ + self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135 + self.resetTransform() + self.rotate(self.angle) + self.update() + + def setPos(self, pos): + if type(pos) in [list, tuple]: + newPos = pos + elif isinstance(pos, QtCore.QPointF): + newPos = [pos.x(), pos.y()] + else: + if self.angle == 90: + newPos = [pos, 0] + elif self.angle == 0: + newPos = [0, pos] + else: + raise Exception("Must specify 2D coordinate for non-orthogonal lines.") + + ## check bounds (only works for orthogonal lines) + if self.angle == 90: + if self.maxRange[0] is not None: + newPos[0] = max(newPos[0], self.maxRange[0]) + if self.maxRange[1] is not None: + newPos[0] = min(newPos[0], self.maxRange[1]) + elif self.angle == 0: + if self.maxRange[0] is not None: + newPos[1] = max(newPos[1], self.maxRange[0]) + if self.maxRange[1] is not None: + newPos[1] = min(newPos[1], self.maxRange[1]) + + if self.p != newPos: + self.p = newPos + UIGraphicsItem.setPos(self, Point(self.p)) + self.update() + self.sigPositionChanged.emit(self) + + def getXPos(self): + return self.p[0] + + def getYPos(self): + return self.p[1] + + def getPos(self): + return self.p + + def value(self): + if self.angle%180 == 0: + return self.getYPos() + elif self.angle%180 == 90: + return self.getXPos() + else: + return self.getPos() + + def setValue(self, v): + self.setPos(v) + + ## broken in 4.7 + #def itemChange(self, change, val): + #if change in [self.ItemScenePositionHasChanged, self.ItemSceneHasChanged]: + #self.updateLine() + #print "update", change + #print self.getBoundingParents() + #else: + #print "ignore", change + #return GraphicsObject.itemChange(self, change, val) + + def boundingRect(self): + br = UIGraphicsItem.boundingRect(self) + + ## add a 4-pixel radius around the line for mouse interaction. + + #print "line bounds:", self, br + dt = self.deviceTransform() + if dt is None: + return QtCore.QRectF() + lineDir = Point(dt.map(Point(1, 0)) - dt.map(Point(0,0))) ## direction of line in pixel-space + orthoDir = Point(lineDir[1], -lineDir[0]) ## orthogonal to line in pixel-space + try: + norm = orthoDir.norm() ## direction of one pixel orthogonal to line + except ZeroDivisionError: + return br + + dti = dt.inverted()[0] + px = Point(dti.map(norm)-dti.map(Point(0,0))) ## orthogonal pixel mapped back to item coords + px = px[1] ## project to y-direction + + br.setBottom(-px*4) + br.setTop(px*4) + return br.normalized() + + def paint(self, p, *args): + UIGraphicsItem.paint(self, p, *args) + br = self.boundingRect() + p.setPen(self.currentPen) + p.drawLine(Point(br.right(), 0), Point(br.left(), 0)) + #p.drawRect(self.boundingRect()) + + def dataBounds(self, axis, frac=1.0): + if axis == 0: + return None ## x axis should never be auto-scaled + else: + return (0,0) + + #def mousePressEvent(self, ev): + #if self.movable and ev.button() == QtCore.Qt.LeftButton: + #ev.accept() + #self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) + #else: + #ev.ignore() + + #def mouseMoveEvent(self, ev): + #self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) + ##self.emit(QtCore.SIGNAL('dragged'), self) + #self.sigDragged.emit(self) + #self.hasMoved = True + + #def mouseReleaseEvent(self, ev): + #if self.hasMoved and ev.button() == QtCore.Qt.LeftButton: + #self.hasMoved = False + ##self.emit(QtCore.SIGNAL('positionChangeFinished'), self) + #self.sigPositionChangeFinished.emit(self) + + def mouseDragEvent(self, ev): + if self.movable and ev.button() == QtCore.Qt.LeftButton: + if ev.isStart(): + self.moving = True + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.startPosition = self.pos() + ev.accept() + + if not self.moving: + return + + #pressDelta = self.mapToParent(ev.buttonDownPos()) - Point(self.p) + self.setPos(self.cursorOffset + self.mapToParent(ev.pos())) + self.sigDragged.emit(self) + if ev.isFinish(): + self.moving = False + self.sigPositionChangeFinished.emit(self) + #else: + #print ev + + + def mouseClickEvent(self, ev): + if self.moving and ev.button() == QtCore.Qt.RightButton: + ev.accept() + self.setPos(self.startPosition) + self.moving = False + self.sigDragged.emit(self) + self.sigPositionChangeFinished.emit(self) + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + self.currentPen = fn.mkPen(255, 0,0) + else: + self.currentPen = self.pen + self.update() + + #def hoverEnterEvent(self, ev): + #print "line hover enter" + #ev.ignore() + #self.updateHoverPen() + + #def hoverMoveEvent(self, ev): + #print "line hover move" + #ev.ignore() + #self.updateHoverPen() + + #def hoverLeaveEvent(self, ev): + #print "line hover leave" + #ev.ignore() + #self.updateHoverPen(False) + + #def updateHoverPen(self, hover=None): + #if hover is None: + #scene = self.scene() + #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) + + #if hover: + #self.currentPen = fn.mkPen(255, 0,0) + #else: + #self.currentPen = self.pen + #self.update() + diff --git a/graphicsItems/ItemGroup.py b/graphicsItems/ItemGroup.py new file mode 100644 index 00000000..a328a645 --- /dev/null +++ b/graphicsItems/ItemGroup.py @@ -0,0 +1,23 @@ +from pyqtgraph.Qt import QtGui, QtCore +from GraphicsObject import GraphicsObject + +__all__ = ['ItemGroup'] +class ItemGroup(GraphicsObject): + """ + Replacement for QGraphicsItemGroup + """ + + def __init__(self, *args): + GraphicsObject.__init__(self, *args) + if hasattr(self, "ItemHasNoContents"): + self.setFlag(self.ItemHasNoContents) + + def boundingRect(self): + return QtCore.QRectF() + + def paint(self, *args): + pass + + def addItem(self, item): + item.setParentItem(self) + diff --git a/graphicsItems/LabelItem.py b/graphicsItems/LabelItem.py new file mode 100644 index 00000000..c9d88dd6 --- /dev/null +++ b/graphicsItems/LabelItem.py @@ -0,0 +1,91 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +from GraphicsWidget import GraphicsWidget + + +__all__ = ['LabelItem'] + +class LabelItem(GraphicsWidget): + """ + GraphicsWidget displaying text. + Used mainly as axis labels, titles, etc. + + Note: To display text inside a scaled view (ViewBox, PlotWidget, etc) use QGraphicsTextItem + with the flag ItemIgnoresTransformations set. + """ + + + def __init__(self, text, parent=None, **args): + GraphicsWidget.__init__(self, parent) + self.item = QtGui.QGraphicsTextItem(self) + self.opts = args + if 'color' not in args: + self.opts['color'] = 'CCC' + else: + if isinstance(args['color'], QtGui.QColor): + self.opts['color'] = fn.colorStr(args['color'])[:6] + self.sizeHint = {} + self.setText(text) + + + def setAttr(self, attr, value): + """Set default text properties. See setText() for accepted parameters.""" + self.opts[attr] = value + + def setText(self, text, **args): + """Set the text and text properties in the label. Accepts optional arguments for auto-generating + a CSS style string: + color: string (example: 'CCFF00') + size: string (example: '8pt') + bold: boolean + italic: boolean + """ + self.text = text + opts = self.opts.copy() + for k in args: + opts[k] = args[k] + + optlist = [] + if 'color' in opts: + optlist.append('color: #' + opts['color']) + if 'size' in opts: + optlist.append('font-size: ' + opts['size']) + if 'bold' in opts and opts['bold'] in [True, False]: + optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']]) + if 'italic' in opts and opts['italic'] in [True, False]: + optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']]) + full = "<span style='%s'>%s</span>" % ('; '.join(optlist), text) + #print full + self.item.setHtml(full) + self.updateMin() + + def resizeEvent(self, ev): + c1 = self.boundingRect().center() + c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos() + dif = c1 - c2 + self.item.moveBy(dif.x(), dif.y()) + #print c1, c2, dif, self.item.pos() + + def setAngle(self, angle): + self.angle = angle + self.item.resetTransform() + self.item.rotate(angle) + self.updateMin() + + def updateMin(self): + bounds = self.item.mapRectToParent(self.item.boundingRect()) + self.setMinimumWidth(bounds.width()) + self.setMinimumHeight(bounds.height()) + #print self.text, bounds.width(), bounds.height() + + #self.sizeHint = { + #QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()), + #QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()), + #QtCore.Qt.MaximumSize: (bounds.width()*2, bounds.height()*2), + #QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this? + #} + + + #def sizeHint(self, hint, constraint): + #return self.sizeHint[hint] + diff --git a/graphicsItems/LinearRegionItem.py b/graphicsItems/LinearRegionItem.py new file mode 100644 index 00000000..1b546cb7 --- /dev/null +++ b/graphicsItems/LinearRegionItem.py @@ -0,0 +1,232 @@ +from pyqtgraph.Qt import QtGui, QtCore +from UIGraphicsItem import UIGraphicsItem +from InfiniteLine import InfiniteLine +import pyqtgraph.functions as fn + +__all__ = ['LinearRegionItem'] + +class LinearRegionItem(UIGraphicsItem): + """ + Used for marking a horizontal or vertical region in plots. + The region can be dragged and is bounded by lines which can be dragged individually. + """ + + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + Vertical = 0 + Horizontal = 1 + + def __init__(self, values=[0,1], orientation=None, brush=None, movable=True, bounds=None): + UIGraphicsItem.__init__(self) + if orientation is None: + orientation = LinearRegionItem.Vertical + self.orientation = orientation + self.bounds = QtCore.QRectF() + self.blockLineSignal = False + self.moving = False + + if orientation == LinearRegionItem.Horizontal: + self.lines = [ + InfiniteLine(QtCore.QPointF(0, values[0]), 0, movable=movable, bounds=bounds), + InfiniteLine(QtCore.QPointF(0, values[1]), 0, movable=movable, bounds=bounds)] + elif orientation == LinearRegionItem.Vertical: + self.lines = [ + InfiniteLine(QtCore.QPointF(values[1], 0), 90, movable=movable, bounds=bounds), + InfiniteLine(QtCore.QPointF(values[0], 0), 90, movable=movable, bounds=bounds)] + else: + raise Exception('Orientation must be one of LinearRegionItem.Vertical or LinearRegionItem.Horizontal') + + + for l in self.lines: + l.setParentItem(self) + l.sigPositionChangeFinished.connect(self.lineMoveFinished) + l.sigPositionChanged.connect(self.lineMoved) + + if brush is None: + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) + self.setBrush(brush) + + self.setMovable(movable) + + def getRegion(self): + """Return the values at the edges of the region.""" + #if self.orientation[0] == 'h': + #r = (self.bounds.top(), self.bounds.bottom()) + #else: + #r = (self.bounds.left(), self.bounds.right()) + r = [self.lines[0].value(), self.lines[1].value()] + return (min(r), max(r)) + + def setRegion(self, rgn): + if self.lines[0].value() == rgn[0] and self.lines[1].value() == rgn[1]: + return + self.blockLineSignal = True + self.lines[0].setValue(rgn[0]) + self.blockLineSignal = False + self.lines[1].setValue(rgn[1]) + #self.blockLineSignal = False + self.lineMoved() + self.lineMoveFinished() + + def setBrush(self, br): + self.brush = fn.mkBrush(br) + self.currentBrush = self.brush + + def setBounds(self, bounds): + for l in self.lines: + l.setBounds(bounds) + + def setMovable(self, m): + for l in self.lines: + l.setMovable(m) + self.movable = m + self.setAcceptHoverEvents(m) + + def boundingRect(self): + br = UIGraphicsItem.boundingRect(self) + rng = self.getRegion() + if self.orientation == LinearRegionItem.Vertical: + br.setLeft(rng[0]) + br.setRight(rng[1]) + else: + br.setTop(rng[0]) + br.setBottom(rng[1]) + return br.normalized() + + def paint(self, p, *args): + UIGraphicsItem.paint(self, p, *args) + p.setBrush(self.currentBrush) + p.drawRect(self.boundingRect()) + + def dataBounds(self, axis, frac=1.0): + if axis == self.orientation: + return self.getRegion() + else: + return None + + def lineMoved(self): + if self.blockLineSignal: + return + self.prepareGeometryChange() + #self.emit(QtCore.SIGNAL('regionChanged'), self) + self.sigRegionChanged.emit(self) + + def lineMoveFinished(self): + #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + self.sigRegionChangeFinished.emit(self) + + + #def updateBounds(self): + #vb = self.view().viewRect() + #vals = [self.lines[0].value(), self.lines[1].value()] + #if self.orientation[0] == 'h': + #vb.setTop(min(vals)) + #vb.setBottom(max(vals)) + #else: + #vb.setLeft(min(vals)) + #vb.setRight(max(vals)) + #if vb != self.bounds: + #self.bounds = vb + #self.rect.setRect(vb) + + #def mousePressEvent(self, ev): + #if not self.movable: + #ev.ignore() + #return + #for l in self.lines: + #l.mousePressEvent(ev) ## pass event to both lines so they move together + ##if self.movable and ev.button() == QtCore.Qt.LeftButton: + ##ev.accept() + ##self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) + ##else: + ##ev.ignore() + + #def mouseReleaseEvent(self, ev): + #for l in self.lines: + #l.mouseReleaseEvent(ev) + + #def mouseMoveEvent(self, ev): + ##print "move", ev.pos() + #if not self.movable: + #return + #self.lines[0].blockSignals(True) # only want to update once + #for l in self.lines: + #l.mouseMoveEvent(ev) + #self.lines[0].blockSignals(False) + ##self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) + ##self.emit(QtCore.SIGNAL('dragged'), self) + + def mouseDragEvent(self, ev): + if not self.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0: + return + ev.accept() + + if ev.isStart(): + bdp = ev.buttonDownPos() + self.cursorOffsets = [l.pos() - bdp for l in self.lines] + self.startPositions = [l.pos() for l in self.lines] + self.moving = True + + if not self.moving: + return + + #delta = ev.pos() - ev.lastPos() + self.lines[0].blockSignals(True) # only want to update once + for i, l in enumerate(self.lines): + l.setPos(self.cursorOffsets[i] + ev.pos()) + #l.setPos(l.pos()+delta) + #l.mouseDragEvent(ev) + self.lines[0].blockSignals(False) + self.prepareGeometryChange() + + if ev.isFinish(): + self.moving = False + self.sigRegionChangeFinished.emit(self) + else: + self.sigRegionChanged.emit(self) + + def mouseClickEvent(self, ev): + if self.moving and ev.button() == QtCore.Qt.RightButton: + ev.accept() + for i, l in enumerate(self.lines): + l.setPos(self.startPositions[i]) + self.moving = False + self.sigRegionChanged.emit(self) + self.sigRegionChangeFinished.emit(self) + + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + c = self.brush.color() + c.setAlpha(c.alpha() * 2) + self.currentBrush = fn.mkBrush(c) + else: + self.currentBrush = self.brush + self.update() + + #def hoverEnterEvent(self, ev): + #print "rgn hover enter" + #ev.ignore() + #self.updateHoverBrush() + + #def hoverMoveEvent(self, ev): + #print "rgn hover move" + #ev.ignore() + #self.updateHoverBrush() + + #def hoverLeaveEvent(self, ev): + #print "rgn hover leave" + #ev.ignore() + #self.updateHoverBrush(False) + + #def updateHoverBrush(self, hover=None): + #if hover is None: + #scene = self.scene() + #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) + + #if hover: + #self.currentBrush = fn.mkBrush(255, 0,0,100) + #else: + #self.currentBrush = self.brush + #self.update() + diff --git a/MultiPlotItem.py b/graphicsItems/MultiPlotItem.py similarity index 74% rename from MultiPlotItem.py rename to graphicsItems/MultiPlotItem.py index 2f73c9e5..aa10c525 100644 --- a/MultiPlotItem.py +++ b/graphicsItems/MultiPlotItem.py @@ -6,8 +6,7 @@ Distributed under MIT/X11 license. See license.txt for more infomation. """ from numpy import ndarray -from graphicsItems import * -from PlotItem import * +import GraphicsLayout try: from metaarray import * @@ -16,17 +15,13 @@ except: #raise HAVE_METAARRAY = False - -class MultiPlotItem(QtGui.QGraphicsWidget): - def __init__(self, parent=None): - QtGui.QGraphicsWidget.__init__(self, parent) - self.layout = QtGui.QGraphicsGridLayout() - self.layout.setContentsMargins(1,1,1,1) - self.setLayout(self.layout) - self.layout.setHorizontalSpacing(0) - self.layout.setVerticalSpacing(4) - self.plots = [] +__all__ = ['MultiPlotItem'] +class MultiPlotItem(GraphicsLayout.GraphicsLayout): + """ + Automaticaly generates a grid of plots from a multi-dimensional array + """ + def plot(self, data): #self.layout.clear() self.plots = [] @@ -42,11 +37,12 @@ class MultiPlotItem(QtGui.QGraphicsWidget): break #print "Plotting using axis %d as columns (%d plots)" % (ax, data.shape[ax]) for i in range(data.shape[ax]): - pi = PlotItem() + pi = self.addPlot() + self.nextRow() sl = [slice(None)] * 2 sl[ax] = i pi.plot(data[tuple(sl)]) - self.layout.addItem(pi, i, 0) + #self.layout.addItem(pi, i, 0) self.plots.append((pi, i, 0)) title = None units = None @@ -67,5 +63,7 @@ class MultiPlotItem(QtGui.QGraphicsWidget): for p in self.plots: p[0].close() self.plots = None - for i in range(self.layout.count()): - self.layout.removeAt(i) \ No newline at end of file + self.clear() + + + diff --git a/graphicsItems/PlotCurveItem.py b/graphicsItems/PlotCurveItem.py new file mode 100644 index 00000000..91fb8661 --- /dev/null +++ b/graphicsItems/PlotCurveItem.py @@ -0,0 +1,444 @@ +from pyqtgraph.Qt import QtGui, QtCore +from scipy.fftpack import fft +import numpy as np +import scipy.stats +from GraphicsObject import GraphicsObject +import pyqtgraph.functions as fn +from pyqtgraph import debug +from pyqtgraph.Point import Point +import struct + +__all__ = ['PlotCurveItem'] +class PlotCurveItem(GraphicsObject): + + + """Class representing a single plot curve. Provides: + - Fast data update + - FFT display mode + - shadow pen + - mouse interaction + """ + + sigPlotChanged = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + + def __init__(self, y=None, x=None, fillLevel=None, copy=False, pen=None, shadowPen=None, brush=None, parent=None, color=None, clickable=False): + GraphicsObject.__init__(self, parent) + self.clear() + self.path = None + self.fillPath = None + if pen is None: + if color is None: + self.setPen((200,200,200)) + else: + self.setPen(color) + else: + self.setPen(pen) + + self.shadowPen = shadowPen + if y is not None: + self.updateData(y, x, copy) + + ## this is disastrous for performance. + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + + self.fillLevel = fillLevel + self.brush = brush + + self.metaData = {} + self.opts = { + 'spectrumMode': False, + 'logMode': [False, False], + 'pointMode': False, + 'pointStyle': None, + 'downsample': False, + 'alphaHint': 1.0, + 'alphaMode': False + } + + self.setClickable(clickable) + #self.fps = None + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def setClickable(self, s): + self.clickable = s + + + def getData(self): + if self.xData is None: + return (None, None) + if self.xDisp is None: + nanMask = np.isnan(self.xData) | np.isnan(self.yData) + if any(nanMask): + x = self.xData[~nanMask] + y = self.yData[~nanMask] + else: + x = self.xData + y = self.yData + ds = self.opts['downsample'] + if ds > 1: + x = x[::ds] + #y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing + y = y[::ds] + if self.opts['spectrumMode']: + f = fft(y) / len(y) + y = abs(f[1:len(f)/2]) + dt = x[-1] - x[0] + x = np.linspace(0, 0.5*len(x)/dt, len(y)) + if self.opts['logMode'][0]: + x = np.log10(x) + if self.opts['logMode'][1]: + y = np.log10(y) + self.xDisp = x + self.yDisp = y + #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max() + #print self.xDisp.shape, self.xDisp.min(), self.xDisp.max() + return self.xDisp, self.yDisp + + #def generateSpecData(self): + #f = fft(self.yData) / len(self.yData) + #self.ySpec = abs(f[1:len(f)/2]) + #dt = self.xData[-1] - self.xData[0] + #self.xSpec = linspace(0, 0.5*len(self.xData)/dt, len(self.ySpec)) + + def dataBounds(self, ax, frac=1.0): + (x, y) = self.getData() + if x is None or len(x) == 0: + return (0, 0) + + if ax == 0: + d = x + elif ax == 1: + d = y + + if frac >= 1.0: + return (d.min(), d.max()) + elif frac <= 0.0: + raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) + else: + return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50))) + + def setMeta(self, data): + self.metaData = data + + def meta(self): + return self.metaData + + def setPen(self, pen): + self.pen = fn.mkPen(pen) + self.update() + + def setColor(self, color): + self.pen.setColor(color) + self.update() + + def setAlpha(self, alpha, auto): + self.opts['alphaHint'] = alpha + self.opts['alphaMode'] = auto + self.update() + + def setSpectrumMode(self, mode): + self.opts['spectrumMode'] = mode + self.xDisp = self.yDisp = None + self.path = None + self.update() + + def setLogMode(self, mode): + self.opts['logMode'] = mode + self.xDisp = self.yDisp = None + self.path = None + self.update() + + def setPointMode(self, mode): + self.opts['pointMode'] = mode + self.update() + + def setShadowPen(self, pen): + self.shadowPen = pen + self.update() + + def setDownsampling(self, ds): + if self.opts['downsample'] != ds: + self.opts['downsample'] = ds + self.xDisp = self.yDisp = None + self.path = None + self.update() + + def setData(self, x, y, copy=False): + """For Qwt compatibility""" + self.updateData(y, x, copy) + + def updateData(self, data, x=None, copy=False): + prof = debug.Profiler('PlotCurveItem.updateData', disabled=True) + if isinstance(data, list): + data = np.array(data) + if isinstance(x, list): + x = np.array(x) + if not isinstance(data, np.ndarray) or data.ndim > 2: + raise Exception("Plot data must be 1 or 2D ndarray (data shape is %s)" % str(data.shape)) + if x == None: + if 'complex' in str(data.dtype): + raise Exception("Can not plot complex data types.") + else: + if 'complex' in str(data.dtype)+str(x.dtype): + raise Exception("Can not plot complex data types.") + + if data.ndim == 2: ### If data is 2D array, then assume x and y values are in first two columns or rows. + if x is not None: + raise Exception("Plot data may be 2D only if no x argument is supplied.") + ax = 0 + if data.shape[0] > 2 and data.shape[1] == 2: + ax = 1 + ind = [slice(None), slice(None)] + ind[ax] = 0 + y = data[tuple(ind)] + ind[ax] = 1 + x = data[tuple(ind)] + elif data.ndim == 1: + y = data + prof.mark("data checks") + + self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly + ## Test this bug with test_PlotWidget and zoom in on the animated plot + + self.prepareGeometryChange() + if copy: + self.yData = y.view(np.ndarray).copy() + else: + self.yData = y.view(np.ndarray) + + if x is None: + self.xData = np.arange(0, self.yData.shape[0]) + else: + if copy: + self.xData = x.view(np.ndarray).copy() + else: + self.xData = x.view(np.ndarray) + prof.mark('copy') + + + if self.xData.shape != self.yData.shape: + raise Exception("X and Y arrays must be the same shape--got %s and %s." % (str(x.shape), str(y.shape))) + + self.path = None + self.xDisp = self.yDisp = None + + prof.mark('set') + self.update() + prof.mark('update') + #self.emit(QtCore.SIGNAL('plotChanged'), self) + self.sigPlotChanged.emit(self) + prof.mark('emit') + #prof.finish() + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + prof.mark('set cache mode') + prof.finish() + + def generatePath(self, x, y): + prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) + path = QtGui.QPainterPath() + + ## Create all vertices in path. The method used below creates a binary format so that all + ## vertices can be read in at once. This binary format may change in future versions of Qt, + ## so the original (slower) method is left here for emergencies: + #path.moveTo(x[0], y[0]) + #for i in range(1, y.shape[0]): + # path.lineTo(x[i], y[i]) + + ## Speed this up using >> operator + ## Format is: + ## numVerts(i4) 0(i4) + ## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect + ## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex + ## ... + ## 0(i4) + ## + ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') + + n = x.shape[0] + # create empty array, pad with extra space on either end + arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) + # write first two integers + prof.mark('allocate empty') + arr.data[12:20] = struct.pack('>ii', n, 0) + prof.mark('pack header') + # Fill array with vertex values + arr[1:-1]['x'] = x + arr[1:-1]['y'] = y + arr[1:-1]['c'] = 1 + prof.mark('fill array') + # write last 0 + lastInd = 20*(n+1) + arr.data[lastInd:lastInd+4] = struct.pack('>i', 0) + prof.mark('footer') + # create datastream object and stream into path + buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here + prof.mark('create buffer') + ds = QtCore.QDataStream(buf) + prof.mark('create datastream') + ds >> path + prof.mark('load') + + prof.finish() + return path + + + def shape(self): + if self.path is None: + try: + self.path = self.generatePath(*self.getData()) + except: + return QtGui.QPainterPath() + return self.path + + def boundingRect(self): + (x, y) = self.getData() + if x is None or y is None or len(x) == 0 or len(y) == 0: + return QtCore.QRectF() + + + if self.shadowPen is not None: + lineWidth = (max(self.pen.width(), self.shadowPen.width()) + 1) + else: + lineWidth = (self.pen.width()+1) + + + pixels = self.pixelVectors() + if pixels is None: + pixels = [Point(0,0), Point(0,0)] + xmin = x.min() - pixels[0].x() * lineWidth + xmax = x.max() + pixels[0].x() * lineWidth + ymin = y.min() - abs(pixels[1].y()) * lineWidth + ymax = y.max() + abs(pixels[1].y()) * lineWidth + + + return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) + + def paint(self, p, opt, widget): + prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True) + if self.xData is None: + return + #if self.opts['spectrumMode']: + #if self.specPath is None: + + #self.specPath = self.generatePath(*self.getData()) + #path = self.specPath + #else: + x = None + y = None + if self.path is None: + x,y = self.getData() + if x is None or len(x) == 0 or y is None or len(y) == 0: + return + self.path = self.generatePath(x,y) + self.fillPath = None + + + path = self.path + prof.mark('generate path') + + if self.brush is not None: + if self.fillPath is None: + if x is None: + x,y = self.getData() + p2 = QtGui.QPainterPath(self.path) + p2.lineTo(x[-1], self.fillLevel) + p2.lineTo(x[0], self.fillLevel) + p2.closeSubpath() + self.fillPath = p2 + + p.fillPath(self.fillPath, fn.mkBrush(self.brush)) + + if self.shadowPen is not None: + sp = QtGui.QPen(self.shadowPen) + else: + sp = None + + ## Copy pens and apply alpha adjustment + cp = QtGui.QPen(self.pen) + for pen in [sp, cp]: + if pen is None: + continue + c = pen.color() + c.setAlpha(c.alpha() * self.opts['alphaHint']) + pen.setColor(c) + #pen.setCosmetic(True) + + if self.shadowPen is not None: + p.setPen(sp) + p.drawPath(path) + p.setPen(cp) + p.drawPath(path) + prof.mark('drawPath') + + #print "Render hints:", int(p.renderHints()) + prof.finish() + #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + #p.drawRect(self.boundingRect()) + + + def clear(self): + self.xData = None ## raw values + self.yData = None + self.xDisp = None ## display values (after log / fft) + self.yDisp = None + self.path = None + #del self.xData, self.yData, self.xDisp, self.yDisp, self.path + + #def mousePressEvent(self, ev): + ##GraphicsObject.mousePressEvent(self, ev) + #if not self.clickable: + #ev.ignore() + #if ev.button() != QtCore.Qt.LeftButton: + #ev.ignore() + #self.mousePressPos = ev.pos() + #self.mouseMoved = False + + #def mouseMoveEvent(self, ev): + ##GraphicsObject.mouseMoveEvent(self, ev) + #self.mouseMoved = True + ##print "move" + + #def mouseReleaseEvent(self, ev): + ##GraphicsObject.mouseReleaseEvent(self, ev) + #if not self.mouseMoved: + #self.sigClicked.emit(self) + + def mouseClickEvent(self, ev): + if not self.clickable or ev.button() != QtCore.Qt.LeftButton: + return + ev.accept() + self.sigClicked.emit(self) + + + +class ROIPlotItem(PlotCurveItem): + """Plot curve that monitors an ROI and image for changes to automatically replot.""" + def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None): + self.roi = roi + self.roiData = data + self.roiImg = img + self.axes = axes + self.xVals = xVals + PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color) + #roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) + roi.sigRegionChanged.connect(self.roiChangedEvent) + #self.roiChangedEvent() + + def getRoiData(self): + d = self.roi.getArrayRegion(self.roiData, self.roiImg, axes=self.axes) + if d is None: + return + while d.ndim > 1: + d = d.mean(axis=1) + return d + + def roiChangedEvent(self): + d = self.getRoiData() + self.updateData(d, self.xVals) + diff --git a/graphicsItems/PlotDataItem.py b/graphicsItems/PlotDataItem.py new file mode 100644 index 00000000..91ea77a7 --- /dev/null +++ b/graphicsItems/PlotDataItem.py @@ -0,0 +1,534 @@ +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + +from pyqtgraph.Qt import QtCore +from GraphicsObject import GraphicsObject +from PlotCurveItem import PlotCurveItem +from ScatterPlotItem import ScatterPlotItem +import numpy as np +import scipy +import pyqtgraph.functions as fn + +class PlotDataItem(GraphicsObject): + """GraphicsItem for displaying plot curves, scatter plots, or both.""" + + sigPlotChanged = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + + def __init__(self, *args, **kargs): + """ + There are many different ways to create a PlotDataItem: + + Data initialization: (x,y data only) + + =================================== ====================================== + PlotDataItem(xValues, yValues) x and y values may be any sequence (including ndarray) of real numbers + PlotDataItem(yValues) y values only -- x will be automatically set to range(len(y)) + PlotDataItem(x=xValues, y=yValues) x and y given by keyword arguments + PlotDataItem(ndarray(Nx2)) numpy array with shape (N, 2) where x=data[:,0] and y=data[:,1] + =================================== ====================================== + + Data initialization: (x,y data AND may include spot style) + + =========================== ========================================= + PlotDataItem(recarray) numpy array with dtype=[('x', float), ('y', float), ...] + PlotDataItem(list-of-dicts) [{'x': x, 'y': y, ...}, ...] + PlotDataItem(dict-of-lists) {'x': [...], 'y': [...], ...} + PlotDataItem(MetaArray) 1D array of Y values with X sepecified as axis values + OR 2D array with a column 'y' and extra columns as needed. + =========================== ========================================= + + Line style keyword + ========== ================================================ + pen pen to use for drawing line between points. Default is solid grey, 1px width. Use None to disable line drawing. + shadowPen pen for secondary line to draw behind the primary line. disabled by default. + fillLevel fill the area between the curve and fillLevel + fillBrush fill to use when fillLevel is specified + ========== ================================================ + + Point style keyword arguments: + + ============ ================================================ + symbol symbol to use for drawing points OR list of symbols, one per point. Default is no symbol. + options are o, s, t, d, + + symbolPen outline pen for drawing points OR list of pens, one per point + symbolBrush brush for filling points OR list of brushes, one per point + symbolSize diameter of symbols OR list of diameters + pxMode (bool) If True, then symbolSize is specified in pixels. If False, then symbolSize is + specified in data coordinates. + ============ ================================================ + + Optimization keyword arguments: + + ========== ================================================ + identical spots are all identical. The spot image will be rendered only once and repeated for every point + decimate (int) decimate data + ========== ================================================ + + Meta-info keyword arguments: + + ========== ================================================ + name name of dataset. This would appear in a legend + ========== ================================================ + """ + GraphicsObject.__init__(self) + self.setFlag(self.ItemHasNoContents) + self.xData = None + self.yData = None + self.curves = [] + self.scatters = [] + self.clear() + self.opts = { + 'fftMode': False, + 'logMode': [False, False], + 'downsample': False, + 'alphaHint': 1.0, + 'alphaMode': False, + + 'pen': (200,200,200), + 'shadowPen': None, + 'fillLevel': None, + 'brush': None, + + 'symbol': None, + 'symbolSize': 10, + 'symbolPen': (200,200,200), + 'symbolBrush': (50, 50, 150), + 'identical': False, + } + self.setData(*args, **kargs) + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def boundingRect(self): + return QtCore.QRectF() ## let child items handle this + + def setAlpha(self, alpha, auto): + self.opts['alphaHint'] = alpha + self.opts['alphaMode'] = auto + self.setOpacity(alpha) + #self.update() + + def setFftMode(self, mode): + self.opts['fftMode'] = mode + self.xDisp = self.yDisp = None + self.updateItems() + + def setLogMode(self, mode): + self.opts['logMode'] = mode + self.xDisp = self.yDisp = None + self.updateItems() + + def setPointMode(self, mode): + self.opts['pointMode'] = mode + self.update() + + def setPen(self, pen): + """ + | Sets the pen used to draw lines between points. + | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() <pyqtgraph.mkPen>` + """ + self.opts['pen'] = fn.mkPen(pen) + for c in self.curves: + c.setPen(pen) + self.update() + + def setShadowPen(self, pen): + """ + | Sets the shadow pen used to draw lines between points (this is for enhancing contrast or + emphacizing data). + | This line is drawn behind the primary pen (see :func:`setPen() <pyqtgraph.PlotDataItem.setPen>`) + and should generally be assigned greater width than the primary pen. + | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() <pyqtgraph.mkPen>` + """ + self.opts['shadowPen'] = pen + for c in self.curves: + c.setPen(pen) + self.update() + + def setDownsampling(self, ds): + if self.opts['downsample'] != ds: + self.opts['downsample'] = ds + self.xDisp = self.yDisp = None + self.updateItems() + + def setData(self, *args, **kargs): + """ + Clear any data displayed by this item and display new data. + See :func:`__init__() <pyqtgraph.PlotDataItem.__init__>` for details; it accepts the same arguments. + """ + + self.clear() + + y = None + x = None + if len(args) == 1: + data = args[0] + dt = dataType(data) + if dt == 'empty': + return + elif dt == 'listOfValues': + y = np.array(data) + elif dt == 'Nx2array': + x = data[:,0] + y = data[:,1] + elif dt == 'recarray' or dt == 'dictOfLists': + if 'x' in data: + x = np.array(data['x']) + if 'y' in data: + y = np.array(data['y']) + elif dt == 'listOfDicts': + if 'x' in data[0]: + x = np.array([d.get('x',None) for d in data]) + if 'y' in data[0]: + y = np.array([d.get('y',None) for d in data]) + elif dt == 'MetaArray': + y = data.view(np.ndarray) + x = data.xvals(0).view(np.ndarray) + else: + raise Exception('Invalid data type %s' % type(data)) + + elif len(args) == 2: + seq = ('listOfValues', 'MetaArray') + if dataType(args[0]) not in seq or dataType(args[1]) not in seq: + raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1])))) + if not isinstance(args[0], np.ndarray): + x = np.array(args[0]) + else: + x = args[0].view(np.ndarray) + if not isinstance(args[1], np.ndarray): + y = np.array(args[1]) + else: + y = args[1].view(np.ndarray) + + if 'x' in kargs: + x = kargs['x'] + if 'y' in kargs: + y = kargs['y'] + + + ## pull in all style arguments. + ## Use self.opts to fill in anything not present in kargs. + + + ## if symbol pen/brush are given with no symbol, then assume symbol is 'o' + if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs): + kargs['symbol'] = 'o' + + for k in self.opts.keys(): + if k in kargs: + self.opts[k] = kargs[k] + + #curveArgs = {} + #for k in ['pen', 'shadowPen', 'fillLevel', 'brush']: + #if k in kargs: + #self.opts[k] = kargs[k] + #curveArgs[k] = self.opts[k] + + #scatterArgs = {} + #for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol')]: + #if k in kargs: + #self.opts[k] = kargs[k] + #scatterArgs[v] = self.opts[k] + + + if y is None: + return + if y is not None and x is None: + x = np.arange(len(y)) + + if isinstance(x, list): + x = np.array(x) + if isinstance(y, list): + y = np.array(y) + + self.xData = x.view(np.ndarray) ## one last check to make sure there are no MetaArrays getting by + self.yData = y.view(np.ndarray) + + self.updateItems() + view = self.getViewBox() + if view is not None: + view.itemBoundsChanged(self) ## inform view so it can update its range if it wants + self.sigPlotChanged.emit(self) + + + def updateItems(self): + for c in self.curves+self.scatters: + if c.scene() is not None: + c.scene().removeItem(c) + + curveArgs = {} + for k in ['pen', 'shadowPen', 'fillLevel', 'brush']: + curveArgs[k] = self.opts[k] + + scatterArgs = {} + for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol')]: + scatterArgs[v] = self.opts[k] + + x,y = self.getData() + + if curveArgs['pen'] is not None or curveArgs['brush'] is not None: + curve = PlotCurveItem(x=x, y=y, **curveArgs) + curve.setParentItem(self) + self.curves.append(curve) + + if scatterArgs['symbol'] is not None: + sp = ScatterPlotItem(x=x, y=y, **scatterArgs) + sp.setParentItem(self) + self.scatters.append(sp) + + + def getData(self): + if self.xData is None: + return (None, None) + if self.xDisp is None: + nanMask = np.isnan(self.xData) | np.isnan(self.yData) + if any(nanMask): + x = self.xData[~nanMask] + y = self.yData[~nanMask] + else: + x = self.xData + y = self.yData + ds = self.opts['downsample'] + if ds > 1: + x = x[::ds] + #y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing + y = y[::ds] + if self.opts['fftMode']: + f = np.fft.fft(y) / len(y) + y = abs(f[1:len(f)/2]) + dt = x[-1] - x[0] + x = np.linspace(0, 0.5*len(x)/dt, len(y)) + if self.opts['logMode'][0]: + x = np.log10(x) + if self.opts['logMode'][1]: + y = np.log10(y) + self.xDisp = x + self.yDisp = y + #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max() + #print self.xDisp.shape, self.xDisp.min(), self.xDisp.max() + return self.xDisp, self.yDisp + + def dataBounds(self, ax, frac=1.0): + (x, y) = self.getData() + if x is None or len(x) == 0: + return (0, 0) + + if ax == 0: + d = x + elif ax == 1: + d = y + + if frac >= 1.0: + return (np.min(d), np.max(d)) + elif frac <= 0.0: + raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) + else: + return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50))) + + + def clear(self): + for i in self.curves+self.scatters: + if i.scene() is not None: + i.scene().removeItem(i) + self.curves = [] + self.scatters = [] + self.xData = None + self.yData = None + self.xDisp = None + self.yDisp = None + + def appendData(self, *args, **kargs): + pass + + +def dataType(obj): + if hasattr(obj, '__len__') and len(obj) == 0: + return 'empty' + if isSequence(obj): + first = obj[0] + if isinstance(obj, np.ndarray): + if HAVE_METAARRAY and isinstance(obj, metaarray.MetaArray): + return 'MetaArray' + if obj.ndim == 1: + if obj.dtype.names is None: + return 'listOfValues' + else: + return 'recarray' + elif obj.ndim == 2 and obj.dtype.names is None and obj.shape[1] == 2: + return 'Nx2array' + else: + raise Exception('array shape must be (N,) or (N,2); got %s instead' % str(obj.shape)) + elif isinstance(first, dict): + return 'listOfDict' + else: + return 'listOfValues' + elif isinstance(obj, dict): + return 'dict' + + +def isSequence(obj): + return isinstance(obj, list) or isinstance(obj, np.ndarray) + + + +#class TableData: + #""" + #Class for presenting multiple forms of tabular data through a consistent interface. + #May contain: + #- numpy record array + #- list-of-dicts (all dicts are _not_ required to have the same keys) + #- dict-of-lists + #- dict (single record) + #Note: if all the values in this record are lists, it will be interpreted as multiple records + + #Data can be accessed and modified by column, by row, or by value + #data[columnName] + #data[rowId] + #data[columnName, rowId] = value + #data[columnName] = [value, value, ...] + #data[rowId] = {columnName: value, ...} + #""" + + #def __init__(self, data): + #self.data = data + #if isinstance(data, np.ndarray): + #self.mode = 'array' + #elif isinstance(data, list): + #self.mode = 'list' + #elif isinstance(data, dict): + #types = set(map(type, data.values())) + ### dict may be a dict-of-lists or a single record + #types -= set([list, np.ndarray]) ## if dict contains any non-sequence values, it is probably a single record. + #if len(types) != 0: + #self.data = [self.data] + #self.mode = 'list' + #else: + #self.mode = 'dict' + #elif isinstance(data, TableData): + #self.data = data.data + #self.mode = data.mode + #else: + #raise TypeError(type(data)) + + #for fn in ['__getitem__', '__setitem__']: + #setattr(self, fn, getattr(self, '_TableData'+fn+self.mode)) + + #def originalData(self): + #return self.data + + #def toArray(self): + #if self.mode == 'array': + #return self.data + #if len(self) < 1: + ##return np.array([]) ## need to return empty array *with correct columns*, but this is very difficult, so just return None + #return None + #rec1 = self[0] + #dtype = functions.suggestRecordDType(rec1) + ##print rec1, dtype + #arr = np.empty(len(self), dtype=dtype) + #arr[0] = tuple(rec1.values()) + #for i in xrange(1, len(self)): + #arr[i] = tuple(self[i].values()) + #return arr + + #def __getitem__array(self, arg): + #if isinstance(arg, tuple): + #return self.data[arg[0]][arg[1]] + #else: + #return self.data[arg] + + #def __getitem__list(self, arg): + #if isinstance(arg, basestring): + #return [d.get(arg, None) for d in self.data] + #elif isinstance(arg, int): + #return self.data[arg] + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #return self.data[arg[0]][arg[1]] + #else: + #raise TypeError(type(arg)) + + #def __getitem__dict(self, arg): + #if isinstance(arg, basestring): + #return self.data[arg] + #elif isinstance(arg, int): + #return dict([(k, v[arg]) for k, v in self.data.iteritems()]) + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #return self.data[arg[1]][arg[0]] + #else: + #raise TypeError(type(arg)) + + #def __setitem__array(self, arg, val): + #if isinstance(arg, tuple): + #self.data[arg[0]][arg[1]] = val + #else: + #self.data[arg] = val + + #def __setitem__list(self, arg, val): + #if isinstance(arg, basestring): + #if len(val) != len(self.data): + #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data))) + #for i, rec in enumerate(self.data): + #rec[arg] = val[i] + #elif isinstance(arg, int): + #self.data[arg] = val + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #self.data[arg[0]][arg[1]] = val + #else: + #raise TypeError(type(arg)) + + #def __setitem__dict(self, arg, val): + #if isinstance(arg, basestring): + #if len(val) != len(self.data[arg]): + #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data[arg]))) + #self.data[arg] = val + #elif isinstance(arg, int): + #for k in self.data: + #self.data[k][arg] = val[k] + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #self.data[arg[1]][arg[0]] = val + #else: + #raise TypeError(type(arg)) + + #def _orderArgs(self, args): + ### return args in (int, str) order + #if isinstance(args[0], basestring): + #return (args[1], args[0]) + #else: + #return args + + #def __iter__(self): + #for i in xrange(len(self)): + #yield self[i] + + #def __len__(self): + #if self.mode == 'array' or self.mode == 'list': + #return len(self.data) + #else: + #return max(map(len, self.data.values())) + + #def columnNames(self): + #"""returns column names in no particular order""" + #if self.mode == 'array': + #return self.data.dtype.names + #elif self.mode == 'list': + #names = set() + #for row in self.data: + #names.update(row.keys()) + #return list(names) + #elif self.mode == 'dict': + #return self.data.keys() + + #def keys(self): + #return self.columnNames() \ No newline at end of file diff --git a/graphicsItems/PlotItem/PlotItem.py b/graphicsItems/PlotItem/PlotItem.py new file mode 100644 index 00000000..2e15f016 --- /dev/null +++ b/graphicsItems/PlotItem/PlotItem.py @@ -0,0 +1,1389 @@ +# -*- coding: utf-8 -*- +""" +PlotItem.py - Graphics item implementing a scalable ViewBox with plotting powers. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +This class is one of the workhorses of pyqtgraph. It implements a graphics item with +plots, labels, and scales which can be viewed inside a QGraphicsScene. If you want +a widget that can be added to your GUI, see PlotWidget instead. + +This class is very heavily featured: + - Automatically creates and manages PlotCurveItems + - Fast display and update of plots + - Manages zoom/pan ViewBox, scale, and label elements + - Automatic scaling when data changes + - Control panel with a huge feature set including averaging, decimation, + display, power spectrum, svg/png export, plot linking, and more. +""" +#from graphicsItems import * +from plotConfigTemplate import * +from pyqtgraph.Qt import QtGui, QtCore, QtSvg +import pyqtgraph.functions as fn +from pyqtgraph.widgets.FileDialog import FileDialog +import weakref +#from types import * +import numpy as np +#from .. PlotCurveItem import PlotCurveItem +#from .. ScatterPlotItem import ScatterPlotItem +from .. PlotDataItem import PlotDataItem +from .. ViewBox import ViewBox +from .. AxisItem import AxisItem +from .. LabelItem import LabelItem +from .. GraphicsWidget import GraphicsWidget +from .. ButtonItem import ButtonItem +from pyqtgraph.WidgetGroup import WidgetGroup +import collections + +__all__ = ['PlotItem'] + +#try: + #from WidgetGroup import * + #HAVE_WIDGETGROUP = True +#except: + #HAVE_WIDGETGROUP = False + +try: + from metaarray import * + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + + + + +class PlotItem(GraphicsWidget): + + sigYRangeChanged = QtCore.Signal(object, object) + sigXRangeChanged = QtCore.Signal(object, object) + sigRangeChanged = QtCore.Signal(object, object) + + """Plot graphics item that can be added to any graphics scene. Implements axis titles, scales, interactive viewbox.""" + lastFileDir = None + managers = {} + + def __init__(self, parent=None, name=None, labels=None, title=None, **kargs): + GraphicsWidget.__init__(self, parent) + + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + + ## Set up control buttons + path = os.path.dirname(__file__) + #self.ctrlBtn = ButtonItem(os.path.join(path, 'ctrl.png'), 14, self) + #self.ctrlBtn.clicked.connect(self.ctrlBtnClicked) + self.autoImageFile = os.path.join(path, 'auto.png') + self.lockImageFile = os.path.join(path, 'lock.png') + self.autoBtn = ButtonItem(self.autoImageFile, 14, self) + self.autoBtn.mode = 'auto' + self.autoBtn.clicked.connect(self.autoBtnClicked) + + self.layout = QtGui.QGraphicsGridLayout() + self.layout.setContentsMargins(1,1,1,1) + self.setLayout(self.layout) + self.layout.setHorizontalSpacing(0) + self.layout.setVerticalSpacing(0) + + self.vb = ViewBox(name=name) + #self.vb.sigXRangeChanged.connect(self.xRangeChanged) + #self.vb.sigYRangeChanged.connect(self.yRangeChanged) + #self.vb.sigRangeChangedManually.connect(self.enableManualScale) + #self.vb.sigRangeChanged.connect(self.viewRangeChanged) + + self.layout.addItem(self.vb, 2, 1) + self.alpha = 1.0 + self.autoAlpha = True + self.spectrumMode = False + + #self.autoScale = [True, True] + + ## Create and place scale items + self.scales = { + 'top': {'item': AxisItem(orientation='top', linkView=self.vb), 'pos': (1, 1)}, + 'bottom': {'item': AxisItem(orientation='bottom', linkView=self.vb), 'pos': (3, 1)}, + 'left': {'item': AxisItem(orientation='left', linkView=self.vb), 'pos': (2, 0)}, + 'right': {'item': AxisItem(orientation='right', linkView=self.vb), 'pos': (2, 2)} + } + for k in self.scales: + item = self.scales[k]['item'] + self.layout.addItem(item, *self.scales[k]['pos']) + item.setZValue(-1000) + item.setFlag(item.ItemNegativeZStacksBehindParent) + + self.titleLabel = LabelItem('', size='11pt') + self.layout.addItem(self.titleLabel, 0, 1) + self.setTitle(None) ## hide + + + for i in range(4): + self.layout.setRowPreferredHeight(i, 0) + self.layout.setRowMinimumHeight(i, 0) + self.layout.setRowSpacing(i, 0) + self.layout.setRowStretchFactor(i, 1) + + for i in range(3): + self.layout.setColumnPreferredWidth(i, 0) + self.layout.setColumnMinimumWidth(i, 0) + self.layout.setColumnSpacing(i, 0) + self.layout.setColumnStretchFactor(i, 1) + self.layout.setRowStretchFactor(2, 100) + self.layout.setColumnStretchFactor(1, 100) + + + ## Wrap a few methods from viewBox + for m in [ + 'setXRange', 'setYRange', 'setXLink', 'setYLink', + 'setRange', 'autoRange', 'viewRect', 'setMouseEnabled', + 'enableAutoRange', 'disableAutoRange']: + setattr(self, m, getattr(self.vb, m)) + + self.items = [] + self.curves = [] + self.itemMeta = weakref.WeakKeyDictionary() + self.dataItems = [] + self.paramList = {} + self.avgCurves = {} + + ### Set up context menu + + w = QtGui.QWidget() + self.ctrl = c = Ui_Form() + c.setupUi(w) + dv = QtGui.QDoubleValidator(self) + + menuItems = [ + ('Fourier Transform', c.powerSpectrumGroup), + ('Downsample', c.decimateGroup), + ('Average', c.averageGroup), + ('Alpha', c.alphaGroup), + ('Grid', c.gridGroup), + ('Points', c.pointsGroup), + ] + + + self.ctrlMenu = QtGui.QMenu() + + self.ctrlMenu.setTitle('Plot Options') + self.subMenus = [] + for name, grp in menuItems: + sm = QtGui.QMenu(name) + act = QtGui.QWidgetAction(self) + act.setDefaultWidget(grp) + sm.addAction(act) + self.subMenus.append(sm) + self.ctrlMenu.addMenu(sm) + + + exportOpts = collections.OrderedDict([ + ('SVG - Full Plot', self.saveSvgClicked), + ('SVG - Curves Only', self.saveSvgCurvesClicked), + ('Image', self.saveImgClicked), + ('CSV', self.saveCsvClicked), + ]) + + self.vb.menu.setExportMethods(exportOpts) + + #self.menuAction = QtGui.QWidgetAction(self) + #self.menuAction.setDefaultWidget(w) + #self.ctrlMenu.addAction(self.menuAction) + + #if HAVE_WIDGETGROUP: + self.stateGroup = WidgetGroup() + for name, w in menuItems: + self.stateGroup.autoAdd(w) + + self.fileDialog = None + + #self.xLinkPlot = None + #self.yLinkPlot = None + #self.linksBlocked = False + + #self.setAcceptHoverEvents(True) + + ## Connect control widgets + #c.xMinText.editingFinished.connect(self.setManualXScale) + #c.xMaxText.editingFinished.connect(self.setManualXScale) + #c.yMinText.editingFinished.connect(self.setManualYScale) + #c.yMaxText.editingFinished.connect(self.setManualYScale) + + #c.xManualRadio.clicked.connect(lambda: self.updateXScale()) + #c.yManualRadio.clicked.connect(lambda: self.updateYScale()) + + #c.xAutoRadio.clicked.connect(self.updateXScale) + #c.yAutoRadio.clicked.connect(self.updateYScale) + + #c.xAutoPercentSpin.valueChanged.connect(self.replot) + #c.yAutoPercentSpin.valueChanged.connect(self.replot) + + c.alphaGroup.toggled.connect(self.updateAlpha) + c.alphaSlider.valueChanged.connect(self.updateAlpha) + c.autoAlphaCheck.toggled.connect(self.updateAlpha) + + c.gridGroup.toggled.connect(self.updateGrid) + c.gridAlphaSlider.valueChanged.connect(self.updateGrid) + + c.powerSpectrumGroup.toggled.connect(self.updateSpectrumMode) + #c.saveSvgBtn.clicked.connect(self.saveSvgClicked) + #c.saveSvgCurvesBtn.clicked.connect(self.saveSvgCurvesClicked) + #c.saveImgBtn.clicked.connect(self.saveImgClicked) + #c.saveCsvBtn.clicked.connect(self.saveCsvClicked) + + #self.ctrl.xLinkCombo.currentIndexChanged.connect(self.xLinkComboChanged) + #self.ctrl.yLinkCombo.currentIndexChanged.connect(self.yLinkComboChanged) + + c.downsampleSpin.valueChanged.connect(self.updateDownsampling) + + self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked) + self.ctrl.averageGroup.toggled.connect(self.avgToggled) + + self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation) + self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation) + #c.xMouseCheck.toggled.connect(self.mouseCheckChanged) + #c.yMouseCheck.toggled.connect(self.mouseCheckChanged) + + #self.xLinkPlot = None + #self.yLinkPlot = None + #self.linksBlocked = False + self.manager = None + + self.hideAxis('right') + self.hideAxis('top') + self.showAxis('left') + self.showAxis('bottom') + + #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 title is not None: + self.setTitle(title) + + if len(kargs) > 0: + self.plot(**kargs) + + self.enableAutoRange() + + def implements(self, interface=None): + return interface in ['ViewBoxWrapper'] + + def getViewBox(self): + return self.vb + + + #def paint(self, *args): + #prof = debug.Profiler('PlotItem.paint', disabled=True) + #QtGui.QGraphicsWidget.paint(self, *args) + #prof.finish() + + + def close(self): + #print "delete", self + ## Most of this crap is needed to avoid PySide trouble. + ## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets) + ## the solution is to manually remove all widgets before scene.clear() is called + if self.ctrlMenu is None: ## already shut down + return + self.ctrlMenu.setParent(None) + self.ctrlMenu = None + + #self.ctrlBtn.setParent(None) + #self.ctrlBtn = None + #self.autoBtn.setParent(None) + #self.autoBtn = None + + for k in self.scales: + i = self.scales[k]['item'] + i.close() + + self.scales = None + self.scene().removeItem(self.vb) + self.vb = None + + ## causes invalid index errors: + #for i in range(self.layout.count()): + #self.layout.removeAt(i) + + #for p in self.proxies: + #try: + #p.setWidget(None) + #except RuntimeError: + #break + #self.scene().removeItem(p) + #self.proxies = [] + + #self.menuAction.releaseWidget(self.menuAction.defaultWidget()) + #self.menuAction.setParent(None) + #self.menuAction = None + + #if self.manager is not None: + #self.manager.sigWidgetListChanged.disconnect(self.updatePlotList) + #self.manager.removeWidget(self.name) + #else: + #print "no manager" + + def registerPlot(self, name): + self.vb.register(name) + #self.name = name + #win = str(self.window()) + ##print "register", name, win + #if win not in PlotItem.managers: + #PlotItem.managers[win] = PlotWidgetManager() + #self.manager = PlotItem.managers[win] + #self.manager.addWidget(self, name) + ##QtCore.QObject.connect(self.manager, QtCore.SIGNAL('widgetListChanged'), self.updatePlotList) + #self.manager.sigWidgetListChanged.connect(self.updatePlotList) + #self.updatePlotList() + + #def updatePlotList(self): + #"""Update the list of all plotWidgets in the "link" combos""" + ##print "update plot list", self + #try: + #for sc in [self.ctrl.xLinkCombo, self.ctrl.yLinkCombo]: + #current = unicode(sc.currentText()) + #sc.blockSignals(True) + #try: + #sc.clear() + #sc.addItem("") + #if self.manager is not None: + #for w in self.manager.listWidgets(): + ##print w + #if w == self.name: + #continue + #sc.addItem(w) + #if w == current: + #sc.setCurrentIndex(sc.count()-1) + #finally: + #sc.blockSignals(False) + #if unicode(sc.currentText()) != current: + #sc.currentItemChanged.emit() + #except: + #import gc + #refs= gc.get_referrers(self) + #print " error during update of", self + #print " Referrers are:", refs + #raise + + def updateGrid(self, *args): + g = self.ctrl.gridGroup.isChecked() + if g: + g = self.ctrl.gridAlphaSlider.value() + for k in self.scales: + self.scales[k]['item'].setGrid(g) + + def viewGeometry(self): + """return the screen geometry of the viewbox""" + v = self.scene().views()[0] + b = self.vb.mapRectToScene(self.vb.boundingRect()) + wr = v.mapFromScene(b).boundingRect() + pos = v.mapToGlobal(v.pos()) + wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) + return wr + + + + + + #def viewRangeChanged(self, vb, range): + ##self.emit(QtCore.SIGNAL('viewChanged'), *args) + #self.sigRangeChanged.emit(self, range) + + #def blockLink(self, b): + #self.linksBlocked = b + + #def xLinkComboChanged(self): + #self.setXLink(str(self.ctrl.xLinkCombo.currentText())) + + #def yLinkComboChanged(self): + #self.setYLink(str(self.ctrl.yLinkCombo.currentText())) + + #def setXLink(self, plot=None): + #"""Link this plot's X axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)""" + #if isinstance(plot, basestring): + #if self.manager is None: + #return + #if self.xLinkPlot is not None: + #self.manager.unlinkX(self, self.xLinkPlot) + #plot = self.manager.getWidget(plot) + #if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'): + #plot = plot.getPlotItem() + #self.xLinkPlot = plot + #if plot is not None: + #self.setManualXScale() + #self.manager.linkX(self, plot) + + + + #def setYLink(self, plot=None): + #"""Link this plot's Y axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)""" + #if isinstance(plot, basestring): + #if self.manager is None: + #return + #if self.yLinkPlot is not None: + #self.manager.unlinkY(self, self.yLinkPlot) + #plot = self.manager.getWidget(plot) + #if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'): + #plot = plot.getPlotItem() + #self.yLinkPlot = plot + #if plot is not None: + #self.setManualYScale() + #self.manager.linkY(self, plot) + + #def linkXChanged(self, plot): + #"""Called when a linked plot has changed its X scale""" + ##print "update from", plot + #if self.linksBlocked: + #return + #pr = plot.vb.viewRect() + #pg = plot.viewGeometry() + #if pg is None: + ##print " return early" + #return + #sg = self.viewGeometry() + #upp = float(pr.width()) / pg.width() + #x1 = pr.left() + (sg.x()-pg.x()) * upp + #x2 = x1 + sg.width() * upp + #plot.blockLink(True) + #self.setManualXScale() + #self.setXRange(x1, x2, padding=0) + #plot.blockLink(False) + #self.replot() + + #def linkYChanged(self, plot): + #"""Called when a linked plot has changed its Y scale""" + #if self.linksBlocked: + #return + #pr = plot.vb.viewRect() + #pg = plot.vb.boundingRect() + #sg = self.vb.boundingRect() + #upp = float(pr.height()) / pg.height() + #y1 = pr.bottom() + (sg.y()-pg.y()) * upp + #y2 = y1 + sg.height() * upp + #plot.blockLink(True) + #self.setManualYScale() + #self.setYRange(y1, y2, padding=0) + #plot.blockLink(False) + #self.replot() + + + def avgToggled(self, b): + if b: + self.recomputeAverages() + for k in self.avgCurves: + self.avgCurves[k][1].setVisible(b) + + def avgParamListClicked(self, item): + name = str(item.text()) + self.paramList[name] = (item.checkState() == QtCore.Qt.Checked) + self.recomputeAverages() + + def recomputeAverages(self): + if not self.ctrl.averageGroup.isChecked(): + return + for k in self.avgCurves: + self.removeItem(self.avgCurves[k][1]) + self.avgCurves = {} + for c in self.curves: + self.addAvgCurve(c) + self.replot() + + def addAvgCurve(self, curve): + """Add a single curve into the pool of curves averaged together""" + + ## If there are plot parameters, then we need to determine which to average together. + remKeys = [] + addKeys = [] + if self.ctrl.avgParamList.count() > 0: + + ### First determine the key of the curve to which this new data should be averaged + for i in range(self.ctrl.avgParamList.count()): + item = self.ctrl.avgParamList.item(i) + if item.checkState() == QtCore.Qt.Checked: + remKeys.append(str(item.text())) + else: + addKeys.append(str(item.text())) + + if len(remKeys) < 1: ## In this case, there would be 1 average plot for each data plot; not useful. + return + + p = self.itemMeta.get(curve,{}).copy() + for k in p: + if type(k) is tuple: + p['.'.join(k)] = p[k] + del p[k] + for rk in remKeys: + if rk in p: + del p[rk] + for ak in addKeys: + if ak not in p: + p[ak] = None + key = tuple(p.items()) + + ### Create a new curve if needed + if key not in self.avgCurves: + plot = PlotDataItem() + plot.setPen(fn.mkPen([0, 200, 0])) + plot.setShadowPen(fn.mkPen([0, 0, 0, 100], width=3)) + plot.setAlpha(1.0, False) + plot.setZValue(100) + self.addItem(plot, skipAverage=True) + self.avgCurves[key] = [0, plot] + self.avgCurves[key][0] += 1 + (n, plot) = self.avgCurves[key] + + ### Average data together + (x, y) = curve.getData() + if plot.yData is not None: + newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n) + plot.setData(plot.xData, newData) + else: + plot.setData(x, y) + + + #def mouseCheckChanged(self): + #state = [self.ctrl.xMouseCheck.isChecked(), self.ctrl.yMouseCheck.isChecked()] + #self.vb.setMouseEnabled(*state) + + #def xRangeChanged(self, _, range): + #if any(np.isnan(range)) or any(np.isinf(range)): + #raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender()))) + #self.ctrl.xMinText.setText('%0.5g' % range[0]) + #self.ctrl.xMaxText.setText('%0.5g' % range[1]) + + ### automatically change unit scale + #maxVal = max(abs(range[0]), abs(range[1])) + #(scale, prefix) = fn.siScale(maxVal) + ##for l in ['top', 'bottom']: + ##if self.getLabel(l).isVisible(): + ##self.setLabel(l, unitPrefix=prefix) + ##self.getScale(l).setScale(scale) + ##else: + ##self.setLabel(l, unitPrefix='') + ##self.getScale(l).setScale(1.0) + + ##self.emit(QtCore.SIGNAL('xRangeChanged'), self, range) + #self.sigXRangeChanged.emit(self, range) + + #def yRangeChanged(self, _, range): + #if any(np.isnan(range)) or any(np.isinf(range)): + #raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender()))) + #self.ctrl.yMinText.setText('%0.5g' % range[0]) + #self.ctrl.yMaxText.setText('%0.5g' % range[1]) + + ### automatically change unit scale + #maxVal = max(abs(range[0]), abs(range[1])) + #(scale, prefix) = fn.siScale(maxVal) + ##for l in ['left', 'right']: + ##if self.getLabel(l).isVisible(): + ##self.setLabel(l, unitPrefix=prefix) + ##self.getScale(l).setScale(scale) + ##else: + ##self.setLabel(l, unitPrefix='') + ##self.getScale(l).setScale(1.0) + ##self.emit(QtCore.SIGNAL('yRangeChanged'), self, range) + #self.sigYRangeChanged.emit(self, range) + + def autoBtnClicked(self): + if self.autoBtn.mode == 'auto': + self.enableAutoRange() + else: + self.disableAutoRange() + + def enableAutoScale(self): + """ + Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data. + """ + print "Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead." + self.vb.enableAutoRange(self.vb.XYAxes) + #self.ctrl.xAutoRadio.setChecked(True) + #self.ctrl.yAutoRadio.setChecked(True) + + #self.autoBtn.setImageFile(self.lockImageFile) + #self.autoBtn.mode = 'lock' + #self.updateXScale() + #self.updateYScale() + #self.replot() + + #def updateXScale(self): + #"""Set plot to autoscale or not depending on state of radio buttons""" + #if self.ctrl.xManualRadio.isChecked(): + #self.setManualXScale() + #else: + #self.setAutoXScale() + #self.replot() + + #def updateYScale(self, b=False): + #"""Set plot to autoscale or not depending on state of radio buttons""" + #if self.ctrl.yManualRadio.isChecked(): + #self.setManualYScale() + #else: + #self.setAutoYScale() + #self.replot() + + #def enableManualScale(self, v=[True, True]): + #if v[0]: + #self.autoScale[0] = False + #self.ctrl.xManualRadio.setChecked(True) + ##self.setManualXScale() + #if v[1]: + #self.autoScale[1] = False + #self.ctrl.yManualRadio.setChecked(True) + ##self.setManualYScale() + ##self.autoBtn.enable() + #self.autoBtn.setImageFile(self.autoImageFile) + #self.autoBtn.mode = 'auto' + ##self.replot() + + #def setManualXScale(self): + #self.autoScale[0] = False + #x1 = float(self.ctrl.xMinText.text()) + #x2 = float(self.ctrl.xMaxText.text()) + #self.ctrl.xManualRadio.setChecked(True) + #self.setXRange(x1, x2, padding=0) + #self.autoBtn.show() + ##self.replot() + + #def setManualYScale(self): + #self.autoScale[1] = False + #y1 = float(self.ctrl.yMinText.text()) + #y2 = float(self.ctrl.yMaxText.text()) + #self.ctrl.yManualRadio.setChecked(True) + #self.setYRange(y1, y2, padding=0) + #self.autoBtn.show() + ##self.replot() + + #def setAutoXScale(self): + #self.autoScale[0] = True + #self.ctrl.xAutoRadio.setChecked(True) + ##self.replot() + + #def setAutoYScale(self): + #self.autoScale[1] = True + #self.ctrl.yAutoRadio.setChecked(True) + ##self.replot() + + def addItem(self, item, *args, **kargs): + self.items.append(item) + self.vb.addItem(item, *args) + if hasattr(item, 'implements') and item.implements('plotData'): + self.dataItems.append(item) + #self.plotChanged() + + params = kargs.get('params', {}) + self.itemMeta[item] = params + #item.setMeta(params) + self.curves.append(item) + #self.addItem(c) + + if isinstance(item, PlotDataItem): + ## configure curve for this plot + (alpha, auto) = self.alphaState() + item.setAlpha(alpha, auto) + item.setFftMode(self.ctrl.powerSpectrumGroup.isChecked()) + item.setDownsampling(self.downsampleMode()) + item.setPointMode(self.pointMode()) + + ## Hide older plots if needed + self.updateDecimation() + + ## Add to average if needed + self.updateParamList() + if self.ctrl.averageGroup.isChecked() and 'skipAverage' not in kargs: + self.addAvgCurve(item) + + #c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged) + #item.sigPlotChanged.connect(self.plotChanged) + #self.plotChanged() + + def addDataItem(self, item, *args): + print "PlotItem.addDataItem is deprecated. Use addItem instead." + self.addItem(item, *args) + + def addCurve(self, c, params=None): + print "PlotItem.addCurve is deprecated. Use addItem instead." + self.addItem(item, params) + + + def removeItem(self, item): + if not item in self.items: + return + self.items.remove(item) + if item in self.dataItems: + self.dataItems.remove(item) + + if item.scene() is not None: + self.vb.removeItem(item) + if item in self.curves: + self.curves.remove(item) + self.updateDecimation() + self.updateParamList() + #item.connect(item, QtCore.SIGNAL('plotChanged'), self.plotChanged) + #item.sigPlotChanged.connect(self.plotChanged) + + def clear(self): + for i in self.items[:]: + self.removeItem(i) + self.avgCurves = {} + + def clearPlots(self): + for i in self.curves[:]: + self.removeItem(i) + self.avgCurves = {} + + + def plot(self, *args, **kargs): + """ + Add and return a new plot. + See PlotDataItem.__init__ for data arguments + + Extra allowed arguments are: + clear - clear all plots before displaying new data + params - meta-parameters to associate with this data + """ + + + + #if y is not None: + #data = y + #if data2 is not None: + #x = data + #data = data2 + #if decimate is not None and decimate > 1: + #data = data[::decimate] + #if x is not None: + #x = x[::decimate] + ## print 'plot with decimate = %d' % (decimate) + clear = kargs.get('clear', False) + params = kargs.get('params', None) + + if clear: + self.clear() + + item = PlotDataItem(*args, **kargs) + + if params is None: + params = {} + #if HAVE_METAARRAY and isinstance(data, MetaArray): + #curve = self._plotMetaArray(data, x=x, **kargs) + #elif isinstance(data, np.ndarray): + #curve = self._plotArray(data, x=x, **kargs) + #elif isinstance(data, list): + #if x is not None: + #x = np.array(x) + #curve = self._plotArray(np.array(data), x=x, **kargs) + #elif data is None: + #curve = PlotCurveItem(**kargs) + #else: + #raise Exception('Not sure how to plot object of type %s' % type(data)) + + #print data, curve + self.addItem(item, params=params) + #if pen is not None: + #curve.setPen(fn.mkPen(pen)) + + return item + + def scatterPlot(self, *args, **kargs): + print "PlotItem.scatterPlot is deprecated. Use PlotItem.plot instead." + return self.plot(*args, **kargs) + #sp = ScatterPlotItem(*args, **kargs) + #self.addItem(sp) + #return sp + + + + #def plotChanged(self, curve=None): + ## Recompute auto range if needed + #args = {} + #for ax in [0, 1]: + #print "range", ax + #if self.autoScale[ax]: + #percentScale = [self.ctrl.xAutoPercentSpin.value(), self.ctrl.yAutoPercentSpin.value()][ax] * 0.01 + #mn = None + #mx = None + #for c in self.curves + [c[1] for c in self.avgCurves.values()] + self.dataItems: + #if not c.isVisible(): + #continue + #cmn, cmx = c.getRange(ax, percentScale) + ##print " ", c, cmn, cmx + #if mn is None or cmn < mn: + #mn = cmn + #if mx is None or cmx > mx: + #mx = cmx + #if mn is None or mx is None or any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])): + #continue + #if mn == mx: + #mn -= 1 + #mx += 1 + #if ax == 0: + #args['xRange'] = [mn, mx] + #else: + #args['yRange'] = [mn, mx] + + #if len(args) > 0: + ##print args + #self.setRange(**args) + + def replot(self): + #self.plotChanged() + self.update() + + def updateParamList(self): + self.ctrl.avgParamList.clear() + ## Check to see that each parameter for each curve is present in the list + #print "\nUpdate param list", self + #print "paramList:", self.paramList + for c in self.curves: + #print " curve:", c + for p in self.itemMeta.get(c, {}).keys(): + #print " param:", p + if type(p) is tuple: + p = '.'.join(p) + + ## If the parameter is not in the list, add it. + matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly) + #print " matches:", matches + if len(matches) == 0: + i = QtGui.QListWidgetItem(p) + if p in self.paramList and self.paramList[p] is True: + #print " set checked" + i.setCheckState(QtCore.Qt.Checked) + else: + #print " set unchecked" + i.setCheckState(QtCore.Qt.Unchecked) + self.ctrl.avgParamList.addItem(i) + else: + i = matches[0] + + self.paramList[p] = (i.checkState() == QtCore.Qt.Checked) + #print "paramList:", self.paramList + + + ## This is bullshit. + def writeSvgCurves(self, fileName=None): + if fileName is None: + self.fileDialog = FileDialog() + if PlotItem.lastFileDir is not None: + self.fileDialog.setDirectory(PlotItem.lastFileDir) + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.writeSvg) + return + #if fileName is None: + #fileName = QtGui.QFileDialog.getSaveFileName() + if isinstance(fileName, tuple): + raise Exception("Not implemented yet..") + fileName = str(fileName) + PlotItem.lastFileDir = os.path.dirname(fileName) + + rect = self.vb.viewRect() + xRange = rect.left(), rect.right() + + svg = "" + fh = open(fileName, 'w') + + dx = max(rect.right(),0) - min(rect.left(),0) + ymn = min(rect.top(), rect.bottom()) + ymx = max(rect.top(), rect.bottom()) + dy = max(ymx,0) - min(ymn,0) + sx = 1. + sy = 1. + while dx*sx < 10: + sx *= 1000 + while dy*sy < 10: + sy *= 1000 + sy *= -1 + + #fh.write('<svg viewBox="%f %f %f %f">\n' % (rect.left()*sx, rect.top()*sx, rect.width()*sy, rect.height()*sy)) + fh.write('<svg>\n') + fh.write('<path fill="none" stroke="#000000" stroke-opacity="0.5" stroke-width="1" d="M%f,0 L%f,0"/>\n' % (rect.left()*sx, rect.right()*sx)) + fh.write('<path fill="none" stroke="#000000" stroke-opacity="0.5" stroke-width="1" d="M0,%f L0,%f"/>\n' % (rect.top()*sy, rect.bottom()*sy)) + + + for item in self.curves: + if isinstance(item, PlotCurveItem): + color = fn.colorStr(item.pen.color()) + opacity = item.pen.color().alpha() / 255. + color = color[:6] + x, y = item.getData() + mask = (x > xRange[0]) * (x < xRange[1]) + mask[:-1] += mask[1:] + m2 = mask.copy() + mask[1:] += m2[:-1] + x = x[mask] + y = y[mask] + + x *= sx + y *= sy + + #fh.write('<g fill="none" stroke="#%s" stroke-opacity="1" stroke-width="1">\n' % color) + fh.write('<path fill="none" stroke="#%s" stroke-opacity="%f" stroke-width="1" d="M%f,%f ' % (color, opacity, x[0], y[0])) + for i in xrange(1, len(x)): + fh.write('L%f,%f ' % (x[i], y[i])) + + fh.write('"/>') + #fh.write("</g>") + for item in self.dataItems: + if isinstance(item, ScatterPlotItem): + + pRect = item.boundingRect() + vRect = pRect.intersected(rect) + + for point in item.points(): + pos = point.pos() + if not rect.contains(pos): + continue + color = fn.colorStr(point.brush.color()) + opacity = point.brush.color().alpha() / 255. + color = color[:6] + x = pos.x() * sx + y = pos.y() * sy + + fh.write('<circle cx="%f" cy="%f" r="1" fill="#%s" stroke="none" fill-opacity="%f"/>\n' % (x, y, color, opacity)) + #fh.write('<path fill="none" stroke="#%s" stroke-opacity="%f" stroke-width="1" d="M%f,%f ' % (color, opacity, x[0], y[0])) + #for i in xrange(1, len(x)): + #fh.write('L%f,%f ' % (x[i], y[i])) + + #fh.write('"/>') + + ## get list of curves, scatter plots + + + fh.write("</svg>\n") + + + + def writeSvg(self, fileName=None): + if fileName is None: + fileName = QtGui.QFileDialog.getSaveFileName() + fileName = str(fileName) + PlotItem.lastFileDir = os.path.dirname(fileName) + + self.svg = QtSvg.QSvgGenerator() + self.svg.setFileName(fileName) + res = 120. + view = self.scene().views()[0] + bounds = view.viewport().rect() + bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height()) + + self.svg.setResolution(res) + self.svg.setViewBox(bounds) + + self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height())) + + painter = QtGui.QPainter(self.svg) + view.render(painter, bounds) + + painter.end() + + ## Workaround to set pen widths correctly + import re + data = open(fileName).readlines() + for i in range(len(data)): + line = data[i] + m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line) + if m is not None: + #print "Matched group:", line + g = m.groups() + matrix = map(float, g[2].split(',')) + #print "matrix:", matrix + scale = max(abs(matrix[0]), abs(matrix[3])) + if scale == 0 or scale == 1.0: + continue + data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n' + #print "old line:", line + #print "new line:", data[i] + open(fileName, 'w').write(''.join(data)) + + + def writeImage(self, fileName=None): + if fileName is None: + self.fileDialog = FileDialog() + if PlotItem.lastFileDir is not None: + self.fileDialog.setDirectory(PlotItem.lastFileDir) + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.writeImage) + return + #if fileName is None: + #fileName = QtGui.QFileDialog.getSaveFileName() + if isinstance(fileName, tuple): + raise Exception("Not implemented yet..") + fileName = str(fileName) + PlotItem.lastFileDir = os.path.dirname(fileName) + self.png = QtGui.QImage(int(self.size().width()), int(self.size().height()), QtGui.QImage.Format_ARGB32) + painter = QtGui.QPainter(self.png) + painter.setRenderHints(painter.Antialiasing | painter.TextAntialiasing) + self.scene().render(painter, QtCore.QRectF(), self.mapRectToScene(self.boundingRect())) + painter.end() + self.png.save(fileName) + + def writeCsv(self, fileName=None): + if fileName is None: + self.fileDialog = FileDialog() + if PlotItem.lastFileDir is not None: + self.fileDialog.setDirectory(PlotItem.lastFileDir) + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.writeCsv) + return + #if fileName is None: + #fileName = QtGui.QFileDialog.getSaveFileName() + fileName = str(fileName) + PlotItem.lastFileDir = os.path.dirname(fileName) + + fd = open(fileName, 'w') + data = [c.getData() for c in self.curves] + i = 0 + while True: + done = True + for d in data: + if i < len(d[0]): + fd.write('%g,%g,'%(d[0][i], d[1][i])) + done = False + else: + fd.write(' , ,') + fd.write('\n') + if done: + break + i += 1 + fd.close() + + + def saveState(self): + #if not HAVE_WIDGETGROUP: + #raise Exception("State save/restore requires WidgetGroup class.") + state = self.stateGroup.state() + state['paramList'] = self.paramList.copy() + state['view'] = self.vb.getState() + #print "\nSAVE %s:\n" % str(self.name), state + #print "Saving state. averageGroup.isChecked(): %s state: %s" % (str(self.ctrl.averageGroup.isChecked()), str(state['averageGroup'])) + return state + + def restoreState(self, state): + #if not HAVE_WIDGETGROUP: + #raise Exception("State save/restore requires WidgetGroup class.") + if 'paramList' in state: + self.paramList = state['paramList'].copy() + + self.stateGroup.setState(state) + self.updateSpectrumMode() + self.updateDownsampling() + self.updateAlpha() + self.updateDecimation() + + self.stateGroup.setState(state) + #self.updateXScale() + #self.updateYScale() + self.updateParamList() + + if 'view' not in state: + r = [[float(state['xMinText']), float(state['xMaxText'])], [float(state['yMinText']), float(state['yMaxText'])]] + state['view'] = { + 'autoRange': [state['xAutoRadio'], state['yAutoRadio']], + 'linkedViews': [state['xLinkCombo'], state['yLinkCombo']], + 'targetRange': r, + 'viewRange': r, + } + self.vb.setState(state['view']) + + + #print "\nRESTORE %s:\n" % str(self.name), state + #print "Restoring state. averageGroup.isChecked(): %s state: %s" % (str(self.ctrl.averageGroup.isChecked()), str(state['averageGroup'])) + #avg = self.ctrl.averageGroup.isChecked() + #if avg != state['averageGroup']: + #print " WARNING: avgGroup is %s, should be %s" % (str(avg), str(state['averageGroup'])) + + + def widgetGroupInterface(self): + return (None, PlotItem.saveState, PlotItem.restoreState) + + def updateSpectrumMode(self, b=None): + if b is None: + b = self.ctrl.powerSpectrumGroup.isChecked() + for c in self.curves: + c.setFftMode(b) + self.enableAutoRange() + self.recomputeAverages() + + + def updateDownsampling(self): + ds = self.downsampleMode() + for c in self.curves: + c.setDownsampling(ds) + self.recomputeAverages() + #for c in self.avgCurves.values(): + #c[1].setDownsampling(ds) + + + def downsampleMode(self): + if self.ctrl.decimateGroup.isChecked(): + if self.ctrl.manualDecimateRadio.isChecked(): + ds = self.ctrl.downsampleSpin.value() + else: + ds = True + else: + ds = False + return ds + + def updateDecimation(self): + if self.ctrl.maxTracesCheck.isChecked(): + numCurves = self.ctrl.maxTracesSpin.value() + else: + numCurves = -1 + + curves = self.curves[:] + split = len(curves) - numCurves + for i in range(len(curves)): + if numCurves == -1 or i >= split: + curves[i].show() + else: + if self.ctrl.forgetTracesCheck.isChecked(): + curves[i].clear() + self.removeItem(curves[i]) + else: + curves[i].hide() + + + def updateAlpha(self, *args): + (alpha, auto) = self.alphaState() + for c in self.curves: + c.setAlpha(alpha**2, auto) + + #self.replot(autoRange=False) + + def alphaState(self): + enabled = self.ctrl.alphaGroup.isChecked() + auto = self.ctrl.autoAlphaCheck.isChecked() + alpha = float(self.ctrl.alphaSlider.value()) / self.ctrl.alphaSlider.maximum() + if auto: + alpha = 1.0 ## should be 1/number of overlapping plots + if not enabled: + auto = False + alpha = 1.0 + return (alpha, auto) + + def pointMode(self): + if self.ctrl.pointsGroup.isChecked(): + if self.ctrl.autoPointsCheck.isChecked(): + mode = None + else: + mode = True + else: + mode = False + return mode + + def wheelEvent(self, ev): + # disables default panning the whole scene by mousewheel + ev.accept() + + def resizeEvent(self, ev): + if self.autoBtn is None: ## already closed down + return + btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect()) + y = self.size().height() - btnRect.height() + self.autoBtn.setPos(0, y) + + #def hoverMoveEvent(self, ev): + #self.mousePos = ev.pos() + #self.mouseScreenPos = ev.screenPos() + + + #def ctrlBtnClicked(self): + #self.ctrlMenu.popup(self.mouseScreenPos) + + def getMenu(self): + return self.ctrlMenu + + def getContextMenus(self, event): + ## called when another item is displaying its context menu; we get to add extras to the end of the menu. + return self.ctrlMenu + + + def getLabel(self, key): + pass + + def _checkScaleKey(self, key): + if key not in self.scales: + raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(self.scales.keys()))) + + def getScale(self, key): + self._checkScaleKey(key) + return self.scales[key]['item'] + + def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args): + """ + Set the label for an axis. Basic HTML formatting is allowed. + Arguments: + axis - must be one of 'left', 'bottom', 'right', or 'top' + text - text to display along the axis. HTML allowed. + units - units to display after the title. If units are given, + then an SI prefix will be automatically appended + and the axis values will be scaled accordingly. + (ie, use 'V' instead of 'mV'; 'm' will be added automatically) + """ + self.getScale(axis).setLabel(text=text, units=units, **args) + + def showLabel(self, axis, show=True): + """ + Show or hide one of the plot's axis labels (the axis itself will be unaffected). + axis must be one of 'left', 'bottom', 'right', or 'top' + """ + self.getScale(axis).showLabel(show) + + def setTitle(self, title=None, **args): + """ + Set the title of the plot. Basic HTML formatting is allowed. + If title is None, then the title will be hidden. + """ + if title is None: + self.titleLabel.setVisible(False) + self.layout.setRowFixedHeight(0, 0) + self.titleLabel.setMaximumHeight(0) + else: + self.titleLabel.setMaximumHeight(30) + self.layout.setRowFixedHeight(0, 30) + self.titleLabel.setVisible(True) + self.titleLabel.setText(title, **args) + + def showAxis(self, axis, show=True): + """ + Show or hide one of the plot's axes. + axis must be one of 'left', 'bottom', 'right', or 'top' + """ + s = self.getScale(axis) + p = self.scales[axis]['pos'] + if show: + s.show() + else: + s.hide() + + def hideAxis(self, axis): + self.showAxis(axis, False) + + def showScale(self, *args, **kargs): + print "Deprecated. use showAxis() instead" + return self.showAxis(*args, **kargs) + + def hideButtons(self): + #self.ctrlBtn.hide() + self.autoBtn.hide() + + + def _plotArray(self, arr, x=None, **kargs): + if arr.ndim != 1: + raise Exception("Array must be 1D to plot (shape is %s)" % arr.shape) + if x is None: + x = np.arange(arr.shape[0]) + if x.ndim != 1: + raise Exception("X array must be 1D to plot (shape is %s)" % x.shape) + c = PlotCurveItem(arr, x=x, **kargs) + return c + + + + def _plotMetaArray(self, arr, x=None, autoLabel=True, **kargs): + inf = arr.infoCopy() + if arr.ndim != 1: + raise Exception('can only automatically plot 1 dimensional arrays.') + ## create curve + try: + xv = arr.xvals(0) + #print 'xvals:', xv + except: + if x is None: + xv = np.arange(arr.shape[0]) + else: + xv = x + c = PlotCurveItem(**kargs) + c.setData(x=xv, y=arr.view(np.ndarray)) + + if autoLabel: + name = arr._info[0].get('name', None) + units = arr._info[0].get('units', None) + self.setLabel('bottom', text=name, units=units) + + name = arr._info[1].get('name', None) + units = arr._info[1].get('units', None) + self.setLabel('left', text=name, units=units) + + return c + + def saveSvgClicked(self): + self.writeSvg() + + def saveSvgCurvesClicked(self): + self.writeSvgCurves() + + def saveImgClicked(self): + self.writeImage() + + def saveCsvClicked(self): + self.writeCsv() + + +#class PlotWidgetManager(QtCore.QObject): + + #sigWidgetListChanged = QtCore.Signal(object) + + #"""Used for managing communication between PlotWidgets""" + #def __init__(self): + #QtCore.QObject.__init__(self) + #self.widgets = weakref.WeakValueDictionary() # Don't keep PlotWidgets around just because they are listed here + + #def addWidget(self, w, name): + #self.widgets[name] = w + ##self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys()) + #self.sigWidgetListChanged.emit(self.widgets.keys()) + + #def removeWidget(self, name): + #if name in self.widgets: + #del self.widgets[name] + ##self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys()) + #self.sigWidgetListChanged.emit(self.widgets.keys()) + #else: + #print "plot %s not managed" % name + + + #def listWidgets(self): + #return self.widgets.keys() + + #def getWidget(self, name): + #if name not in self.widgets: + #return None + #else: + #return self.widgets[name] + + #def linkX(self, p1, p2): + ##QtCore.QObject.connect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged) + #p1.sigXRangeChanged.connect(p2.linkXChanged) + ##QtCore.QObject.connect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged) + #p2.sigXRangeChanged.connect(p1.linkXChanged) + #p1.linkXChanged(p2) + ##p2.setManualXScale() + + #def unlinkX(self, p1, p2): + ##QtCore.QObject.disconnect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged) + #p1.sigXRangeChanged.disconnect(p2.linkXChanged) + ##QtCore.QObject.disconnect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged) + #p2.sigXRangeChanged.disconnect(p1.linkXChanged) + + #def linkY(self, p1, p2): + ##QtCore.QObject.connect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged) + #p1.sigYRangeChanged.connect(p2.linkYChanged) + ##QtCore.QObject.connect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged) + #p2.sigYRangeChanged.connect(p1.linkYChanged) + #p1.linkYChanged(p2) + ##p2.setManualYScale() + + #def unlinkY(self, p1, p2): + ##QtCore.QObject.disconnect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged) + #p1.sigYRangeChanged.disconnect(p2.linkYChanged) + ##QtCore.QObject.disconnect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged) + #p2.sigYRangeChanged.disconnect(p1.linkYChanged) diff --git a/graphicsItems/PlotItem/__init__.py b/graphicsItems/PlotItem/__init__.py new file mode 100644 index 00000000..e0db43af --- /dev/null +++ b/graphicsItems/PlotItem/__init__.py @@ -0,0 +1 @@ +from PlotItem import PlotItem diff --git a/graphicsItems/PlotItem/auto.png b/graphicsItems/PlotItem/auto.png new file mode 100644 index 0000000000000000000000000000000000000000..a27ff4f82b6e31f2819061a53ba72d4b322dc26d GIT binary patch literal 1022 zcmV<a0|ESrP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW zd<bNS00009a7bBm000CB000CB0ZV5*6aWAK8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H11A9qCK~z|U&6mGV<46?8ze8e>^J4|8T_gkr2rEb%1y+EsAi5$F z6^a6Zm<{4b{(-IT$`sImw8eJYQn(>RHV8WC_#q^8EE0#dLYAnABJEZo1<0|z1#9Gq zacovOaj)`x-@JLB@te`i5W_HFnj}eY4a4{wz#jlc0K7tTtODRGfHOkKXF(9Yn+{?E zNRsryFpMt%-ZUwxll>rsd=vzs_y|xe7PEwqg0+{XX{c7KP01Jvh2Zn~D2))36$GJ3 zwzjsqYPH%o0Ivby`uZB8C_+&bsH)l&0ES_}aU62F9QykDO!)^P<Q>`E-26-k`O8e6 z&*!0OTEhY3@i>Nuhbis!^b}`jXAR?gJ|9+BSFI4A{DhEEvn7h6YjOZsTU$dU5}~y7 z^K&GV$%b*7ra=@%tgNh<65j&gz3EgG#Wgw4^S0ywFfuZNXf)~?uPBOjlA|s7wbiQC zs;8RI&(Ayh78e&?<5g9)F78(?t`Qashr>vvQjY%V>1hN4f#(c~X91+sX|OE&*sj%T zHo$Z`{oesBEG$s%?d@&by75wm-vWrmVu;0JlqN|Mip3(O4Gj%paB#5s0QIvkl}b1{ zIe{$8wynFgwA5SxmSt_Vm1P-*VW3<t<LKy!@=Z-mAruNV4<MCFAsh}<nj}e&-}Pk( z27{QHnQ0zC{ocR0xS$r_-rgQ6l?v6z>tb{Th(sdbd7jd&vj>3t`+Mx}?owJb8nvAl zzXedg_*7NJ&d!dlUws(>Z-2qx$l2Q3icBU$X@n5$@9(<~3;>gplUQC}ww1>-z{JD^ zIyyS&0KXqSJw2WPSeAt#2v}cVckI)!1dR?JujW<;$3M{9+lzQSPHDHdx465zYdBya z5I`^(d~6R441ni($IFT%z}(y%2qBb~$z*VOdD##kkw{=;V}sIixtt@wlO^!`{m5ps zly-G><(eD-4i67;b8|y!$z&4k?d{$H#>dCe+1W{H^?TpRbX`ZGKwYrW*4BpE*;z+` z3h;1sW*7#5eedhK4q28x0RTXuP=K!Mlx7}K48vH@iAoD0<X_<7#>#OVy1KfMNF=D> z<KttL%Vp01rBVq82M3ha-`|h1u`zHQXMH1_`wheR3gA!RVX@@%d31ModrszLPi<lI sT5hp_UjAY7;!m^x#pBepmw2B41lP7_Luv|AaR2}S07*qoM6N<$f?UVZiU0rr literal 0 HcmV?d00001 diff --git a/graphicsItems/PlotItem/ctrl.png b/graphicsItems/PlotItem/ctrl.png new file mode 100644 index 0000000000000000000000000000000000000000..c8dc96e439da8ef33abf292bd9b808633646c26b GIT binary patch literal 934 zcmV;X16lluP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW zd<bNS00009a7bBm000CB000CB0ZV5*6aWAK8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H110zX9K~z|U&6hn$D_IoBe>Wd~F$C0o$RZJh6k)418x_AmtgMxg zXCY%qyWNsP1VI$676i93_`#s^5(Wz!3mXNE#qQf;A$9{Irg9?;At)qHW{d1(<1@;8 z$%y-}X3jnLp5MLq+?jiXX_{D4RrP~un!f-8z)ye&dz1q{1Aho1ehq~}e_IX00#H>o zY?|h8z;`wUeMwm%#LuBnC|LnWCX*k95TB~O48tIw&)X7Fk|dl?=M$R{;$tWjN{WMn zgYO?7AJaewz}3|iilU%t8pUGK767-~jnC&pmSuW-do6QT2=Rk+YHDgp2r*#=Uszb+ z_V$)isbouVfKsVMHk;+-<b*&VKzn;TKpRkU2qA)&qoODnhEa2nfq?<0r>E)a>Z%*W zFbot$sW$aHfFG?!P19cNJU>6r+S(fD=jY7L&D9N|X<GFpgAV>ZYWaNr)jf|yBBax4 zT3T9IUS4K$a<VRfVzF4A+#bi*cGPv9nVA{V=`;X~i;FZjH`fJGtrr1=5H%t8_Vxhq zdc6dL!Dl|U0QC3wb9i`2XJ_ZLw$ahi%5zgw)7t=acX!j**GD3ecmm;axfmN8d*auq zT(yj6XJ_2s-{bfDNhA{N@9#4*GQ#liFdol;zlWuzB{G=|r>CcN1JqoBySqD9R#pJ; z`~8)yuMQ67E!f)HVtjnuUI2jI-Cd&5DA(85uMW^C#H+{f`1qKKi3!5tFtf9>#9}cX z9v*CksF?%RDijJ_TwGM%Z*Feb+1a7BwY4ThG#dS;ocQVwk)>2BMI;ijZ6&@Lz;1}T z0PKXQ8^A7zh5+n<Xc*unh)gC!EEZ#PbCY;HPDe+_+X2*qSYKZ!kw~z*y2|C{CE;-R zZ2`W7@cDc|c^i6qdMFeMHT$?I7Y!asl5o4-4c~+<i0$od0)YTtua~yAHdIvw$~R<5 zk}UU}LkRH~fYa%$P&cYnD#hU7AV)_>Y;0^WG&Dpmmjl4(^HtXfe>zOl{A|6+viz)o zs8_nK6OYG<$K&X_4wNS;%W}2b@9$oiynku-zbH;Ey+?We4}W%#BJQ5X8UO$Q07*qo IM6N<$f(oslRsaA1 literal 0 HcmV?d00001 diff --git a/graphicsItems/PlotItem/icons.svg b/graphicsItems/PlotItem/icons.svg new file mode 100644 index 00000000..cfdfeba4 --- /dev/null +++ b/graphicsItems/PlotItem/icons.svg @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + version="1.1" + inkscape:version="0.48.1 r9760" + sodipodi:docname="icons.svg"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="290.39972" + inkscape:cy="606.05972" + inkscape:document-units="px" + inkscape:current-layer="layer3" + showgrid="false" + inkscape:window-width="1400" + inkscape:window-height="1030" + inkscape:window-x="-3" + inkscape:window-y="-3" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + style="display:inline" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Layer" + style="display:inline" /> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="Layer#1"> + <g + style="display:inline" + id="g3764" + inkscape:export-filename="/home/luke/work/manis_lab/code/acq4/lib/util/pyqtgraph/graphicsItems/PlotItem/auto.png" + inkscape:export-xdpi="26.181818" + inkscape:export-ydpi="26.181818"> + <rect + ry="18.687822" + y="400.81378" + x="108.08632" + height="100" + width="100" + id="rect2985" + style="fill:#000000;fill-opacity:1;stroke:#a9a9a9;stroke-width:10;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <text + transform="scale(1.0048002,0.99522278)" + sodipodi:linespacing="125%" + id="text3757" + y="490.5354" + x="118.68684" + style="font-size:108.36511993px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#e7e7e7;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial Bold" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial Bold" + y="490.5354" + x="118.68684" + id="tspan3759" + sodipodi:role="line">A</tspan></text> + </g> + <g + id="g3777" + style="display:inline" + transform="translate(140,0)" + inkscape:export-filename="/home/luke/work/manis_lab/code/acq4/lib/util/pyqtgraph/graphicsItems/PlotItem/ctrl.png" + inkscape:export-xdpi="26.181818" + inkscape:export-ydpi="26.181818"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#a9a9a9;stroke-width:10;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" + id="rect3779" + width="100" + height="100" + x="108.08632" + y="400.81378" + ry="18.687822" /> + <path + style="fill:#e3e3e3;fill-opacity:1;stroke:none" + d="m 192.1403,484.47301 c 3.45636,-3.45635 3.45636,-9.02124 0,-12.47759 l -36.39994,-36.39995 c 1.29616,-5.58299 -0.2288,-11.68397 -4.57999,-16.03517 -3.03048,-3.03048 -6.90883,-4.69498 -10.87095,-4.98687 l -2.98378,2.98378 9.49382,9.49382 -2.62905,9.80681 -9.79638,2.61862 -9.49382,-9.49382 -2.97334,2.97334 c 0.29168,3.96216 1.94541,7.85036 4.97644,10.88138 4.35119,4.3512 10.45218,5.87615 16.03517,4.57999 l 36.39995,36.39995 c 3.45635,3.45635 9.02124,3.45635 12.47759,0 l 0.34428,-0.34429 z m -3.38021,-3.04636 c -2.09533,2.09533 -5.4893,2.10575 -7.58463,0.0104 -2.09533,-2.09532 -2.09533,-5.49972 -10e-6,-7.59505 2.09534,-2.09534 5.48931,-2.0849 7.58464,0.0104 2.09532,2.09532 2.09533,5.47887 0,7.5742 z" + id="path3793" + inkscape:connector-curvature="0" /> + </g> + <g + transform="translate(280,0)" + style="display:inline" + id="g3785" + inkscape:export-filename="/home/luke/work/manis_lab/code/acq4/lib/util/pyqtgraph/graphicsItems/PlotItem/lock.png" + inkscape:export-xdpi="26.181818" + inkscape:export-ydpi="26.181818"> + <rect + ry="18.687822" + y="400.81378" + x="108.08632" + height="100" + width="100" + id="rect3787" + style="fill:#000000;fill-opacity:1;stroke:#a9a9a9;stroke-width:10;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> + <path + style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:20;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" + d="m 158.08632,412.60531 c -13.62786,0 -24.7172,11.19602 -24.7172,24.87236 l 0,10.65958 -0.8689,0 c -3.12596,0 -5.63236,2.52191 -5.63236,5.64787 l 0,29.58927 c 0,3.12596 2.5064,5.64787 5.63236,5.64787 l 51.15669,0 c 3.12596,0 5.64787,-2.52191 5.64787,-5.64787 l 0,-29.58927 c 0,-3.12596 -2.52191,-5.64787 -5.64787,-5.64787 l -0.85339,0 0,-10.65958 c 0,-13.67634 -11.08933,-24.87236 -24.7172,-24.87236 z m 0,9.93032 c 8.25237,0 14.78688,6.54986 14.78688,14.94204 l 0,10.65958 -29.57376,0 0,-10.65958 c 0,-8.39218 6.53451,-14.94204 14.78688,-14.94204 z" + id="rect3830" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/graphicsItems/PlotItem/lock.png b/graphicsItems/PlotItem/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..3f85dde05d29a80e9cba8d9d339f88d7f2a960e8 GIT binary patch literal 913 zcmV;C18)3@P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW zd<bNS00009a7bBm000CB000CB0ZV5*6aWAK8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10}e?<K~z|U?U%i4BS#p<f1}+6BUZ8%l)@l{jJFCZf>w!Ln*0d{ zafdMobt(epI8-eN0v0C530PdEPE`<#8{tZYt^V$cKpKk$7d8?&Dy;LOM!Qmq-0sN` zt$aQuPT{AT_hX)UmYLakUon|X@L-ze*OSTQHSj&415p-(55PO%tq|guTrT&g7Z9ER z(==a9CX+Wnd_jxOLSG2+V=k9#_yQV@#!Df@Z_`<>>tfsXLRM5&6-kor`GgQJbGckY zoSdAjI*#)P@D;%2<t6oc9m}#9jYbO*pePEOrjgI*SzBB4+<hU$H`4a@_D@2HAH2m4 z!{FxThVghjmw0Ju30anL949buJRZ~U_c=d5CzHt#i^TxKz&Ip?$a;zOdL7qw1IZGJ z1P2EPv|24XoerH&hgPdarBY#abv1CV>$=qI^=a34A>dmtXjxVu!S3!ZXJ=>R^Lf(g zG_ous%QES7nnIyKyWM7IXD5)uvaIQk%!c@Q)oj}i-19=Az~SK`s;c_FZnsOf+x6{8 zB*M|r5rslwCeLUznm*joP@vq8NG6jQhT+@IW)of4$!4=;vsrXqr`c@!al<f3CX)}L z=0O5Vr4r$A7y#2WDHe<LdObhh>-8uWi<qVfKsX$xR4Oe_fUfHRIF3WPT%MU%E|>iq z;m!GC0nuobR4N6)#l;1K!C+?YU@)N5=>U*QrHDr7|H<bHNF);e`1<;KF5%VH)j!UC zv;ZN*T#_%D`=B%MZ$b_VZf$L`x3}lpo12@Qo}LC0uCK5A<Nf`8j*pLNx7(jDKv5K` z)hhA$r`LITd6`Tm6DVLB8yg!`t5tMepZSxUxdpLU?B3#!MLZt&9-q$>@L%Csz_Wm7 z0U`cw8c<agMN#~~a5!8@RXiGv7!HTNttiTLRrMhxg!mmmk|Z=u^Al~`rdF%Xt$2gr zI1aU1?OuPOY1(v;@Ln_;jbDLR@Tn}Hu8db!R)|C*j}mZqcgOARt#3<`M5R)h?hSr^ n^2X%pTeJT~bL!zK+Vj5vT1`#lyHYUO00000NkvXXu0mjf+uNW{ literal 0 HcmV?d00001 diff --git a/graphicsItems/PlotItem/plotConfigTemplate.py b/graphicsItems/PlotItem/plotConfigTemplate.py new file mode 100644 index 00000000..e8b28bb1 --- /dev/null +++ b/graphicsItems/PlotItem/plotConfigTemplate.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'plotConfigTemplate.ui' +# +# Created: Fri Jan 20 11:24:44 2012 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(258, 605) + self.averageGroup = QtGui.QGroupBox(Form) + self.averageGroup.setGeometry(QtCore.QRect(10, 200, 242, 182)) + self.averageGroup.setCheckable(True) + self.averageGroup.setChecked(False) + self.averageGroup.setObjectName(_fromUtf8("averageGroup")) + self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) + self.gridLayout_5.setMargin(0) + self.gridLayout_5.setSpacing(0) + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.avgParamList = QtGui.QListWidget(self.averageGroup) + self.avgParamList.setObjectName(_fromUtf8("avgParamList")) + self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) + self.decimateGroup = QtGui.QGroupBox(Form) + self.decimateGroup.setGeometry(QtCore.QRect(0, 30, 242, 160)) + self.decimateGroup.setCheckable(True) + self.decimateGroup.setObjectName(_fromUtf8("decimateGroup")) + self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) + self.gridLayout_4.setMargin(0) + self.gridLayout_4.setSpacing(0) + self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) + self.manualDecimateRadio = QtGui.QRadioButton(self.decimateGroup) + self.manualDecimateRadio.setChecked(True) + self.manualDecimateRadio.setObjectName(_fromUtf8("manualDecimateRadio")) + self.gridLayout_4.addWidget(self.manualDecimateRadio, 0, 0, 1, 1) + self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) + self.downsampleSpin.setMinimum(1) + self.downsampleSpin.setMaximum(100000) + self.downsampleSpin.setProperty(_fromUtf8("value"), 1) + self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin")) + self.gridLayout_4.addWidget(self.downsampleSpin, 0, 1, 1, 1) + self.autoDecimateRadio = QtGui.QRadioButton(self.decimateGroup) + self.autoDecimateRadio.setChecked(False) + self.autoDecimateRadio.setObjectName(_fromUtf8("autoDecimateRadio")) + self.gridLayout_4.addWidget(self.autoDecimateRadio, 1, 0, 1, 1) + self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck")) + self.gridLayout_4.addWidget(self.maxTracesCheck, 2, 0, 1, 1) + self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) + self.maxTracesSpin.setObjectName(_fromUtf8("maxTracesSpin")) + self.gridLayout_4.addWidget(self.maxTracesSpin, 2, 1, 1, 1) + self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.forgetTracesCheck.setObjectName(_fromUtf8("forgetTracesCheck")) + self.gridLayout_4.addWidget(self.forgetTracesCheck, 3, 0, 1, 2) + self.powerSpectrumGroup = QtGui.QGroupBox(Form) + self.powerSpectrumGroup.setGeometry(QtCore.QRect(0, 0, 242, 41)) + self.powerSpectrumGroup.setCheckable(True) + self.powerSpectrumGroup.setChecked(False) + self.powerSpectrumGroup.setObjectName(_fromUtf8("powerSpectrumGroup")) + self.pointsGroup = QtGui.QGroupBox(Form) + self.pointsGroup.setGeometry(QtCore.QRect(10, 520, 234, 58)) + self.pointsGroup.setCheckable(True) + self.pointsGroup.setObjectName(_fromUtf8("pointsGroup")) + self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) + self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) + self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) + self.autoPointsCheck.setChecked(True) + self.autoPointsCheck.setObjectName(_fromUtf8("autoPointsCheck")) + self.verticalLayout_5.addWidget(self.autoPointsCheck) + self.gridGroup = QtGui.QGroupBox(Form) + self.gridGroup.setGeometry(QtCore.QRect(10, 460, 234, 60)) + self.gridGroup.setCheckable(True) + self.gridGroup.setChecked(False) + self.gridGroup.setObjectName(_fromUtf8("gridGroup")) + self.verticalLayout_4 = QtGui.QVBoxLayout(self.gridGroup) + self.verticalLayout_4.setObjectName(_fromUtf8("verticalLayout_4")) + self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) + self.gridAlphaSlider.setMaximum(255) + self.gridAlphaSlider.setProperty(_fromUtf8("value"), 70) + self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.gridAlphaSlider.setObjectName(_fromUtf8("gridAlphaSlider")) + self.verticalLayout_4.addWidget(self.gridAlphaSlider) + self.alphaGroup = QtGui.QGroupBox(Form) + self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) + self.alphaGroup.setCheckable(True) + self.alphaGroup.setObjectName(_fromUtf8("alphaGroup")) + self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) + self.autoAlphaCheck.setChecked(False) + self.autoAlphaCheck.setObjectName(_fromUtf8("autoAlphaCheck")) + self.horizontalLayout.addWidget(self.autoAlphaCheck) + self.alphaSlider = QtGui.QSlider(self.alphaGroup) + self.alphaSlider.setMaximum(1000) + self.alphaSlider.setProperty(_fromUtf8("value"), 1000) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setObjectName(_fromUtf8("alphaSlider")) + self.horizontalLayout.addWidget(self.alphaSlider) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) + self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) + self.decimateGroup.setTitle(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) + self.manualDecimateRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) + self.autoDecimateRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) + self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) + self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) + self.powerSpectrumGroup.setTitle(QtGui.QApplication.translate("Form", "Power Spectrum", None, QtGui.QApplication.UnicodeUTF8)) + self.pointsGroup.setTitle(QtGui.QApplication.translate("Form", "Points", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPointsCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.gridGroup.setTitle(QtGui.QApplication.translate("Form", "Grid", None, QtGui.QApplication.UnicodeUTF8)) + self.alphaGroup.setTitle(QtGui.QApplication.translate("Form", "Alpha", None, QtGui.QApplication.UnicodeUTF8)) + self.autoAlphaCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/graphicsItems/PlotItem/plotConfigTemplate.ui b/graphicsItems/PlotItem/plotConfigTemplate.ui new file mode 100644 index 00000000..a9ae33d6 --- /dev/null +++ b/graphicsItems/PlotItem/plotConfigTemplate.ui @@ -0,0 +1,258 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>258</width> + <height>605</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <widget class="QGroupBox" name="averageGroup"> + <property name="geometry"> + <rect> + <x>10</x> + <y>200</y> + <width>242</width> + <height>182</height> + </rect> + </property> + <property name="toolTip"> + <string>Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).</string> + </property> + <property name="title"> + <string>Average</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QListWidget" name="avgParamList"/> + </item> + </layout> + </widget> + <widget class="QGroupBox" name="decimateGroup"> + <property name="geometry"> + <rect> + <x>0</x> + <y>30</y> + <width>242</width> + <height>160</height> + </rect> + </property> + <property name="title"> + <string>Downsample</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QRadioButton" name="manualDecimateRadio"> + <property name="text"> + <string>Manual</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="downsampleSpin"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100000</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="autoDecimateRadio"> + <property name="text"> + <string>Auto</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="maxTracesCheck"> + <property name="toolTip"> + <string>If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.</string> + </property> + <property name="text"> + <string>Max Traces:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="maxTracesSpin"> + <property name="toolTip"> + <string>If multiple curves are displayed in this plot, check "Max Traces" and set this value to limit the number of traces that are displayed.</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QCheckBox" name="forgetTracesCheck"> + <property name="toolTip"> + <string>If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).</string> + </property> + <property name="text"> + <string>Forget hidden traces</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QGroupBox" name="powerSpectrumGroup"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>242</width> + <height>41</height> + </rect> + </property> + <property name="title"> + <string>Power Spectrum</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + <widget class="QGroupBox" name="pointsGroup"> + <property name="geometry"> + <rect> + <x>10</x> + <y>520</y> + <width>234</width> + <height>58</height> + </rect> + </property> + <property name="title"> + <string>Points</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <widget class="QCheckBox" name="autoPointsCheck"> + <property name="text"> + <string>Auto</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QGroupBox" name="gridGroup"> + <property name="geometry"> + <rect> + <x>10</x> + <y>460</y> + <width>234</width> + <height>60</height> + </rect> + </property> + <property name="title"> + <string>Grid</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QSlider" name="gridAlphaSlider"> + <property name="maximum"> + <number>255</number> + </property> + <property name="value"> + <number>70</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QGroupBox" name="alphaGroup"> + <property name="geometry"> + <rect> + <x>10</x> + <y>390</y> + <width>234</width> + <height>60</height> + </rect> + </property> + <property name="title"> + <string>Alpha</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QCheckBox" name="autoAlphaCheck"> + <property name="text"> + <string>Auto</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QSlider" name="alphaSlider"> + <property name="maximum"> + <number>1000</number> + </property> + <property name="value"> + <number>1000</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/widgets.py b/graphicsItems/ROI.py similarity index 67% rename from widgets.py rename to graphicsItems/ROI.py index 8516fefc..f8bb0d72 100644 --- a/widgets.py +++ b/graphicsItems/ROI.py @@ -1,69 +1,69 @@ # -*- coding: utf-8 -*- """ -widgets.py - Interactive graphics items for GraphicsView (ROI widgets) +ROI.py - Interactive graphics items for GraphicsView (ROI widgets) Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. Implements a series of graphics items which display movable/scalable/rotatable shapes for use as region-of-interest markers. ROI class automatically handles extraction of array data from ImageItems. + +The ROI class is meant to serve as the base for more specific types; see several examples +of how to build an ROI at the bottom of the file. """ -from PyQt4 import QtCore, QtGui -if not hasattr(QtCore, 'Signal'): - QtCore.Signal = QtCore.pyqtSignal -#from numpy import array, arccos, dot, pi, zeros, vstack, ubyte, fromfunction, ceil, floor, arctan2 +from pyqtgraph.Qt import QtCore, QtGui +#if not hasattr(QtCore, 'Signal'): + #QtCore.Signal = QtCore.pyqtSignal import numpy as np from numpy.linalg import norm import scipy.ndimage as ndimage -from Point import * -from Transform import Transform +from pyqtgraph.Point import * +from pyqtgraph.Transform import Transform from math import cos, sin -import functions as fn -#from ObjectWorkaround import * +import pyqtgraph.functions as fn +from GraphicsObject import GraphicsObject +from UIGraphicsItem import UIGraphicsItem -def rectStr(r): - return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) +__all__ = [ + 'ROI', + 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', + 'LineROI', 'MultiLineROI', 'LineSegmentROI', 'SpiralROI' +] -# Multiple inheritance not allowed in PyQt. Retarded workaround: -#class QObjectWorkaround: - #def __init__(self): - #self._qObj_ = QtCore.QObject() - #def __getattr__(self, attr): - #if attr == '_qObj_': - #raise Exception("QObjectWorkaround not initialized!") - #return getattr(self._qObj_, attr) - #def connect(self, *args): - #return QtCore.QObject.connect(self._qObj_, *args) +def rectStr(r): + return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) -class ROI(QtGui.QGraphicsObject): +class ROI(GraphicsObject): """Generic region-of-interest widget. Can be used for implementing many types of selection box with rotate/translate/scale handles.""" sigRegionChangeFinished = QtCore.Signal(object) sigRegionChangeStarted = QtCore.Signal(object) sigRegionChanged = QtCore.Signal(object) + sigHoverEvent = QtCore.Signal(object) - def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None): + def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True): #QObjectWorkaround.__init__(self) - QtGui.QGraphicsObject.__init__(self, parent) + GraphicsObject.__init__(self, parent) pos = Point(pos) size = Point(size) self.aspectLocked = False - self.translatable = True + self.translatable = movable + self.rotateAllowed = True if pen is None: - self.pen = QtGui.QPen(QtGui.QColor(255, 255, 255)) - else: - self.pen = fn.mkPen(pen) + pen = (255, 255, 255) + self.setPen(pen) + self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255)) self.handles = [] self.state = {'pos': pos, 'size': size, 'angle': angle} ## angle is in degrees for ease of Qt integration self.lastState = None self.setPos(pos) #self.rotate(-angle * 180. / np.pi) - self.rotate(-angle) + self.rotate(angle) self.setZValue(10) self.isMoving = False @@ -75,10 +75,18 @@ class ROI(QtGui.QGraphicsObject): self.translateSnap = translateSnap self.rotateSnap = rotateSnap self.scaleSnap = scaleSnap - self.setFlag(self.ItemIsSelectable, True) + #self.setFlag(self.ItemIsSelectable, True) def getState(self): return self.state.copy() + + def saveState(self): + """Return the state of the widget in a format suitable for storing to disk.""" + state = {} + state['pos'] = tuple(self.state['pos']) + state['size'] = tuple(self.state['size']) + state['angle'] = self.state['angle'] + return state def setState(self, state): self.setPos(state['pos'], update=False) @@ -97,9 +105,19 @@ class ROI(QtGui.QGraphicsObject): return self.mapToParent(self.boundingRect()).boundingRect() def setPen(self, pen): - self.pen = pen + self.pen = fn.mkPen(pen) + self.currentPen = self.pen self.update() + def size(self): + return self.getState()['size'] + + def pos(self): + return self.getState()['pos'] + + def angle(self): + return self.getState()['angle'] + def setPos(self, pos, update=True): #print "setPos() called." pos = Point(pos) @@ -218,66 +236,138 @@ class ROI(QtGui.QGraphicsObject): for h in self.handles: h['item'].hide() - def mousePressEvent(self, ev): - ## Bug: sometimes we get events we shouldn't. - p = ev.pos() - if not self.isMoving and not self.shape().contains(p): - ev.ignore() - return - if ev.button() == QtCore.Qt.LeftButton: - self.setSelected(True) + #def mousePressEvent(self, ev): + ### Bug: sometimes we get events we shouldn't. + #p = ev.pos() + #if not self.isMoving and not self.shape().contains(p): + #ev.ignore() + #return + #if ev.button() == QtCore.Qt.LeftButton: + #self.setSelected(True) + #if self.translatable: + #self.isMoving = True + #self.preMoveState = self.getState() + #self.cursorOffset = self.scenePos() - ev.scenePos() + ##self.emit(QtCore.SIGNAL('regionChangeStarted'), self) + #self.sigRegionChangeStarted.emit(self) + #ev.accept() + #else: + #ev.ignore() + #elif ev.button() == QtCore.Qt.RightButton: + #if self.isMoving: + #ev.accept() + #self.cancelMove() + #else: + #ev.ignore() + #else: + #ev.ignore() + + #def mouseMoveEvent(self, ev): + ##print "mouse move", ev.pos() + #if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: + #snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None + ##if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier): + ##snap = Point(self.snapSize, self.snapSize) + #newPos = ev.scenePos() + self.cursorOffset + #newPos = self.mapSceneToParent(newPos) + #self.translate(newPos - self.pos(), snap=snap) + + #def mouseReleaseEvent(self, ev): + #if self.translatable: + #self.isMoving = False + ##self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + #self.sigRegionChangeFinished.emit(self) + + def hoverEvent(self, ev): + if self.translatable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + self.currentPen = fn.mkPen(255, 255, 0) + self.sigHoverEvent.emit(self) + else: + self.currentPen = self.pen + self.update() + + def mouseDragEvent(self, ev): + if ev.isStart(): + p = ev.pos() + #if not self.isMoving and not self.shape().contains(p): + #ev.ignore() + #return + if ev.button() == QtCore.Qt.LeftButton: + self.setSelected(True) + if self.translatable: + self.isMoving = True + self.preMoveState = self.getState() + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + #self.emit(QtCore.SIGNAL('regionChangeStarted'), self) + self.sigRegionChangeStarted.emit(self) + ev.accept() + else: + ev.ignore() + + elif ev.isFinish(): if self.translatable: - self.isMoving = True - self.preMoveState = self.getState() - self.cursorOffset = self.scenePos() - ev.scenePos() - #self.emit(QtCore.SIGNAL('regionChangeStarted'), self) - self.sigRegionChangeStarted.emit(self) - ev.accept() - elif ev.button() == QtCore.Qt.RightButton: + self.isMoving = False + #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + self.sigRegionChangeFinished.emit(self) + return + + if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: + snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None + #if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier): + #snap = Point(self.snapSize, self.snapSize) + newPos = self.mapToParent(ev.pos()) + self.cursorOffset + #newPos = self.mapSceneToParent(newPos) + self.translate(newPos - self.pos(), snap=snap) + + + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton: if self.isMoving: ev.accept() self.cancelMove() else: ev.ignore() - else: - ev.ignore() - - def mouseMoveEvent(self, ev): - #print "mouse move", ev.pos() - if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: - snap = None - if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier): - snap = Point(self.snapSize, self.snapSize) - newPos = ev.scenePos() + self.cursorOffset - newPos = self.mapSceneToParent(newPos) - self.translate(newPos - self.pos(), snap=snap) - - def mouseReleaseEvent(self, ev): - if self.translatable: - self.isMoving = False - #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) - self.sigRegionChangeFinished.emit(self) - + + def cancelMove(self): self.isMoving = False self.setState(self.preMoveState) - - def pointPressEvent(self, pt, ev): - #print "press" - self.isMoving = True - self.preMoveState = self.getState() + + + def pointDragEvent(self, pt, ev): + if ev.isStart(): + self.isMoving = True + self.preMoveState = self.getState() + + self.sigRegionChangeStarted.emit(self) + elif ev.isFinish(): + self.isMoving = False + self.sigRegionChangeFinished.emit(self) + return + + #self.movePoint(pt, ev.scenePos(), ev.modifiers()) - #self.emit(QtCore.SIGNAL('regionChangeStarted'), self) - self.sigRegionChangeStarted.emit(self) - #self.pressPos = self.mapFromScene(ev.scenePos()) - #self.pressHandlePos = self.handles[pt]['item'].pos() + + #def pointPressEvent(self, pt, ev): + ##print "press" + #self.isMoving = True + #self.preMoveState = self.getState() + + ##self.emit(QtCore.SIGNAL('regionChangeStarted'), self) + #self.sigRegionChangeStarted.emit(self) + ##self.pressPos = self.mapFromScene(ev.scenePos()) + ##self.pressHandlePos = self.handles[pt]['item'].pos() - def pointReleaseEvent(self, pt, ev): - #print "release" - self.isMoving = False - #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) - self.sigRegionChangeFinished.emit(self) + #def pointReleaseEvent(self, pt, ev): + ##print "release" + #self.isMoving = False + ##self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + #self.sigRegionChangeFinished.emit(self) + #def pointMoveEvent(self, pt, ev): + #self.movePoint(pt, ev.scenePos(), ev.modifiers()) + def stateCopy(self): sc = {} sc['pos'] = Point(self.state['pos']) @@ -291,6 +381,7 @@ class ROI(QtGui.QGraphicsObject): #print " try", h if h['item'] in self.childItems(): p = h['pos'] + #print h['pos'] * self.state['size'] h['item'].setPos(h['pos'] * self.state['size']) #else: #print " Not child!", self.childItems() @@ -299,9 +390,6 @@ class ROI(QtGui.QGraphicsObject): def checkPointMove(self, pt, pos, modifiers): return True - def pointMoveEvent(self, pt, ev): - self.movePoint(pt, ev.scenePos(), ev.modifiers()) - def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()): #print "movePoint() called." @@ -331,9 +419,9 @@ class ROI(QtGui.QGraphicsObject): if h['type'] == 't': #p0 = Point(self.mapToScene(h['item'].pos())) #p1 = Point(pos + self.mapToScene(self.pressHandlePos) - self.mapToScene(self.pressPos)) - snap = None - if self.translateSnap or (modifiers & QtCore.Qt.ControlModifier): - snap = Point(self.snapSize, self.snapSize) + snap = True if (modifiers & QtCore.Qt.ControlModifier) else None + #if self.translateSnap or (): + #snap = Point(self.snapSize, self.snapSize) self.translate(p1-p0, snap=snap, update=False) elif h['type'] == 'f': @@ -404,6 +492,8 @@ class ROI(QtGui.QGraphicsObject): self.updateHandles() elif h['type'] in ['r', 'rf']: + if not self.rotateAllowed: + return ## If the handle is directly over its center point, we can't compute an angle. if lp1.length() == 0 or lp0.length() == 0: return @@ -555,11 +645,14 @@ class ROI(QtGui.QGraphicsObject): def translate(self, *args, **kargs): - """accepts either (x, y, snap) or ([x,y], snap) as arguments""" - if 'snap' not in kargs: - snap = None - else: - snap = kargs['snap'] + """accepts either (x, y, snap) or ([x,y], snap) as arguments + + snap can be: + None (default): use self.translateSnap and self.snapSize to determine whether/how to snap + False: do no snap + Point(w,h) snap to rectangular grid with spacing (w,h) + True: snap using self.snapSize (and ignoring self.translateSnap) + """ if len(args) == 1: pt = args[0] @@ -568,10 +661,16 @@ class ROI(QtGui.QGraphicsObject): newState = self.stateCopy() newState['pos'] = newState['pos'] + pt - if snap != None: - newState['pos'][0] = round(newState['pos'][0] / snap[0]) * snap[0] - newState['pos'][1] = round(newState['pos'][1] / snap[1]) * snap[1] - + + ## snap position + #snap = kargs.get('snap', None) + #if (snap is not False) and not (snap is None and self.translateSnap is False): + + snap = kargs.get('snap', None) + if snap is None: + snap = self.translateSnap + if snap is not False: + newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap) #d = ev.scenePos() - self.mapToScene(self.pressPos) if self.maxBounds is not None: @@ -601,14 +700,31 @@ class ROI(QtGui.QGraphicsObject): r = tr.mapRect(r) return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) + + def getSnapPosition(self, pos, snap=None): + ## Given that pos has been requested, return the nearest snap-to position + ## optionally, snap may be passed in to specify a rectangular snap grid. + ## override this function for more interesting snap functionality.. + + if snap is None or snap is True: + if self.snapSize is None: + return pos + snap = Point(self.snapSize, self.snapSize) + + return Point( + round(pos[0] / snap[0]) * snap[0], + round(pos[1] / snap[1]) * snap[1] + ) + + def boundingRect(self): - return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]) + return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() def paint(self, p, opt, widget): p.save() r = self.boundingRect() p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.pen) + p.setPen(self.currentPen) p.translate(r.left(), r.top()) p.scale(r.width(), r.height()) p.drawRect(0, 0, 1, 1) @@ -661,6 +777,9 @@ class ROI(QtGui.QGraphicsObject): def getArrayRegion(self, data, img, axes=(0,1)): + """Use the position of this ROI relative to an imageItem to pull a slice from an array.""" + + shape = self.state['size'] origin = self.mapToItem(img, QtCore.QPointF(0, 0)) @@ -671,7 +790,7 @@ class ROI(QtGui.QGraphicsObject): lvx = np.sqrt(vx.x()**2 + vx.y()**2) lvy = np.sqrt(vy.x()**2 + vy.y()**2) - pxLen = img.width() / data.shape[axes[0]] + pxLen = img.width() / float(data.shape[axes[0]]) sx = pxLen / lvx sy = pxLen / lvy @@ -831,7 +950,167 @@ class ROI(QtGui.QGraphicsObject): self.setState(st) -class Handle(QtGui.QGraphicsItem): +#class Handle(QtGui.QGraphicsItem): + + #types = { ## defines number of sides, start angle for each handle type + #'t': (4, np.pi/4), + #'f': (4, np.pi/4), + #'s': (4, 0), + #'r': (12, 0), + #'sr': (12, 0), + #'rf': (12, 0), + #} + + #def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None): + ##print " create item with parent", parent + #self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10) + #QtGui.QGraphicsItem.__init__(self, parent) + #self.setFlags(self.flags() | self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges) + #self.setZValue(11) + #self.roi = [] + #self.radius = radius + #self.typ = typ + #self.pen = fn.mkPen(pen) + #self.currentPen = self.pen + #self.pen.setWidth(0) + #self.pen.setCosmetic(True) + #self.isMoving = False + #self.sides, self.startAng = self.types[typ] + #self.buildPath() + #self.updateShape() + + #def connectROI(self, roi, i): + #self.roi.append((roi, i)) + + ##def boundingRect(self): + ##return self.bounds + + + #def hoverEvent(self, ev): + #if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + #self.currentPen = fn.mkPen(255, 0,0) + #else: + #self.currentPen = self.pen + #self.update() + + + + #def mouseClickEvent(self, ev): + ### right-click cancels drag + #if ev.button() == QtCore.Qt.RightButton and self.isMoving: + #self.isMoving = False ## prevents any further motion + #for r in self.roi: + #r[0].cancelMove() + #ev.accept() + + + #def mouseDragEvent(self, ev): + #if ev.button() != QtCore.Qt.LeftButton: + #return + #ev.accept() + + ### Inform ROIs that a drag is happening + ### note: the ROI is informed that the handle has moved using ROI.movePoint + ### this is for other (more nefarious) purposes. + #for r in self.roi: + #r[0].pointDragEvent(r[1], ev) + + #if ev.isFinish(): + #self.isMoving = False + #elif ev.isStart(): + #self.isMoving = True + #self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() + + #if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. + #pos = ev.scenePos() + self.cursorOffset + #self.movePoint(pos, ev.modifiers()) + + + + #def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier()): + #for r in self.roi: + #if not r[0].checkPointMove(r[1], pos, modifiers): + #return + ##print "point moved; inform %d ROIs" % len(self.roi) + ## A handle can be used by multiple ROIs; tell each to update its handle position + #for r in self.roi: + #r[0].movePoint(r[1], pos, modifiers) + + #def buildPath(self): + #size = self.radius + #self.path = QtGui.QPainterPath() + #ang = self.startAng + #dt = 2*np.pi / self.sides + #for i in range(0, self.sides+1): + #x = size * cos(ang) + #y = size * sin(ang) + #ang += dt + #if i == 0: + #self.path.moveTo(x, y) + #else: + #self.path.lineTo(x, y) + + #def paint(self, p, opt, widget): + #### determine rotation of transform + ##m = self.sceneTransform() + ###mi = m.inverted()[0] + ##v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) + ##va = np.arctan2(v.y(), v.x()) + + #### Determine length of unit vector in painter's coords + ###size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) + ###size = (size.x()*size.x() + size.y() * size.y()) ** 0.5 + ##size = self.radius + + ##bounds = QtCore.QRectF(-size, -size, size*2, size*2) + ##if bounds != self.bounds: + ##self.bounds = bounds + ##self.prepareGeometryChange() + #p.setRenderHints(p.Antialiasing, True) + #p.setPen(self.currentPen) + + ##p.rotate(va * 180. / 3.1415926) + ##p.drawPath(self.path) + #p.drawPath(self.shape()) + + ##ang = self.startAng + va + ##dt = 2*np.pi / self.sides + ##for i in range(0, self.sides): + ##x1 = size * cos(ang) + ##y1 = size * sin(ang) + ##x2 = size * cos(ang+dt) + ##y2 = size * sin(ang+dt) + ##ang += dt + ##p.drawLine(Point(x1, y1), Point(x2, y2)) + + #def shape(self): + #return self._shape + + #def boundingRect(self): + #return self.shape().boundingRect() + + #def updateShape(self): + ### determine rotation of transform + #m = self.sceneTransform() + ##mi = m.inverted()[0] + #v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) + #va = np.arctan2(v.y(), v.x()) + + #tr = QtGui.QTransform() + #tr.rotate(va * 180. / 3.1415926) + ##tr.scale(self.radius, self.radius) + #self._shape = tr.map(self.path) + #self.prepareGeometryChange() + + + + #def itemChange(self, change, value): + #ret = QtGui.QGraphicsItem.itemChange(self, change, value) + #if change == self.ItemScenePositionHasChanged: + #self.updateShape() + #return ret + +class Handle(UIGraphicsItem): types = { ## defines number of sides, start angle for each handle type 't': (4, np.pi/4), @@ -842,69 +1121,73 @@ class Handle(QtGui.QGraphicsItem): 'rf': (12, 0), } - def __init__(self, radius, typ=None, pen=QtGui.QPen(QtGui.QColor(200, 200, 220)), parent=None): + def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None): #print " create item with parent", parent - self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10) - QtGui.QGraphicsItem.__init__(self, parent) - self.setFlag(self.ItemIgnoresTransformations) - self.setZValue(11) + #self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10) + #self.setFlags(self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges) self.roi = [] self.radius = radius self.typ = typ - self.prepareGeometryChange() - self.pen = pen + self.pen = fn.mkPen(pen) + self.currentPen = self.pen self.pen.setWidth(0) self.pen.setCosmetic(True) self.isMoving = False self.sides, self.startAng = self.types[typ] self.buildPath() + self._shape = None + + UIGraphicsItem.__init__(self, parent=parent) + #self.updateShape() + self.setZValue(11) def connectROI(self, roi, i): self.roi.append((roi, i)) - def boundingRect(self): - return self.bounds - - def mousePressEvent(self, ev): - # Bug: sometimes we get events not meant for us! - p = ev.pos() - if not self.isMoving and not self.path.contains(p): - ev.ignore() - return - - #print "handle press" - if ev.button() == QtCore.Qt.LeftButton: - self.isMoving = True - self.cursorOffset = self.scenePos() - ev.scenePos() - for r in self.roi: - r[0].pointPressEvent(r[1], ev) - #print " accepted." - ev.accept() - elif ev.button() == QtCore.Qt.RightButton: - if self.isMoving: - self.isMoving = False ## prevents any further motion - for r in self.roi: - r[0].cancelMove() - ev.accept() - else: - ev.ignore() + #def boundingRect(self): + #return self.bounds + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + self.currentPen = fn.mkPen(255, 255,0) else: - ev.ignore() + self.currentPen = self.pen + self.update() + + + def mouseClickEvent(self, ev): + ## right-click cancels drag + if ev.button() == QtCore.Qt.RightButton and self.isMoving: + self.isMoving = False ## prevents any further motion + for r in self.roi: + r[0].cancelMove() + ev.accept() - def mouseReleaseEvent(self, ev): - #print "release" - if ev.button() == QtCore.Qt.LeftButton: + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + return + ev.accept() + + ## Inform ROIs that a drag is happening + ## note: the ROI is informed that the handle has moved using ROI.movePoint + ## this is for other (more nefarious) purposes. + for r in self.roi: + r[0].pointDragEvent(r[1], ev) + + if ev.isFinish(): self.isMoving = False - for r in self.roi: - r[0].pointReleaseEvent(r[1], ev) - - def mouseMoveEvent(self, ev): - #print "handle mouseMove", ev.pos() - if self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: + elif ev.isStart(): + self.isMoving = True + self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() + + if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. pos = ev.scenePos() + self.cursorOffset self.movePoint(pos, ev.modifiers()) - + + + def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier()): for r in self.roi: if not r[0].checkPointMove(r[1], pos, modifiers): @@ -929,27 +1212,27 @@ class Handle(QtGui.QGraphicsItem): self.path.lineTo(x, y) def paint(self, p, opt, widget): - ## determine rotation of transform - m = self.sceneTransform() - #mi = m.inverted()[0] - v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) - va = np.arctan2(v.y(), v.x()) - - ## Determine length of unit vector in painter's coords - #size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) - #size = (size.x()*size.x() + size.y() * size.y()) ** 0.5 - size = self.radius - - bounds = QtCore.QRectF(-size, -size, size*2, size*2) - if bounds != self.bounds: - self.bounds = bounds - self.prepareGeometryChange() + ### determine rotation of transform + #m = self.sceneTransform() + ##mi = m.inverted()[0] + #v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) + #va = np.arctan2(v.y(), v.x()) + + ### Determine length of unit vector in painter's coords + ##size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) + ##size = (size.x()*size.x() + size.y() * size.y()) ** 0.5 + #size = self.radius + + #bounds = QtCore.QRectF(-size, -size, size*2, size*2) + #if bounds != self.bounds: + #self.bounds = bounds + #self.prepareGeometryChange() p.setRenderHints(p.Antialiasing, True) - p.setPen(self.pen) - - p.rotate(va * 180. / 3.1415926) - p.drawPath(self.path) + p.setPen(self.currentPen) + #p.rotate(va * 180. / 3.1415926) + #p.drawPath(self.path) + p.drawPath(self.shape()) #ang = self.startAng + va #dt = 2*np.pi / self.sides #for i in range(0, self.sides): @@ -959,9 +1242,49 @@ class Handle(QtGui.QGraphicsItem): #y2 = size * sin(ang+dt) #ang += dt #p.drawLine(Point(x1, y1), Point(x2, y2)) + + def shape(self): + if self._shape is None: + s = self.generateShape() + if s is None: + return self.shape + self._shape = s + self.prepareGeometryChange() + return self._shape + + def boundingRect(self): + return self.shape().boundingRect() + + def generateShape(self): + ## determine rotation of transform + #m = self.sceneTransform() ## Qt bug: do not access sceneTransform() until we know this object has a scene. + #mi = m.inverted()[0] - - + dt = self.deviceTransform() + + if dt is None: + self._shape = self.path + return None + + v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) + va = np.arctan2(v.y(), v.x()) + + dti = dt.inverted()[0] + devPos = dt.map(QtCore.QPointF(0,0)) + tr = QtGui.QTransform() + tr.translate(devPos.x(), devPos.y()) + tr.rotate(va * 180. / 3.1415926) + + return dti.map(tr.map(self.path)) + + + def viewChangedEvent(self): + self._shape = None ## invalidate shape, recompute later if requested. + #self.updateShape() + + #def itemChange(self, change, value): + #if change == self.ItemScenePositionHasChanged: + #self.updateShape() class TestROI(ROI): @@ -1070,17 +1393,25 @@ class MultiLineROI(QtGui.QGraphicsObject): self.sigRegionChangeFinished.emit(self) - def getArrayRegion(self, arr, img=None): + def getArrayRegion(self, arr, img=None, axes=(0,1)): rgns = [] for l in self.lines: - rgn = l.getArrayRegion(arr, img) + rgn = l.getArrayRegion(arr, img, axes=axes) if rgn is None: continue #return None rgns.append(rgn) #print l.state['size'] - #print [(r.shape) for r in rgns] - return np.vstack(rgns) + + ## make sure orthogonal axis is the same size + ## (sometimes fp errors cause differences) + ms = min([r.shape[axes[1]] for r in rgns]) + sl = [slice(None)] * rgns[0].ndim + sl[axes[1]] = slice(0,ms) + rgns = [r[sl] for r in rgns] + #print [r.shape for r in rgns], axes + + return np.concatenate(rgns, axis=axes[0]) class EllipseROI(ROI): @@ -1093,7 +1424,7 @@ class EllipseROI(ROI): def paint(self, p, opt, widget): r = self.boundingRect() p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.pen) + p.setPen(self.currentPen) p.scale(r.width(), r.height())## workaround for GL bug r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1) @@ -1145,7 +1476,7 @@ class PolygonROI(ROI): def paint(self, p, *args): p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.pen) + p.setPen(self.currentPen) for i in range(len(self.handles)): h1 = self.handles[i]['item'].pos() h2 = self.handles[i-1]['item'].pos() @@ -1174,6 +1505,9 @@ class PolygonROI(ROI): class LineSegmentROI(ROI): + """ + ROI subclass with two or more freely-moving handles connecting lines. + """ def __init__(self, positions, pos=None, **args): if pos is None: pos = [0,0] @@ -1194,10 +1528,10 @@ class LineSegmentROI(ROI): def paint(self, p, *args): p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.pen) + p.setPen(self.currentPen) for i in range(len(self.handles)-1): h1 = self.handles[i]['item'].pos() - h2 = self.handles[i-1]['item'].pos() + h2 = self.handles[i+1]['item'].pos() p.drawLine(h1, h2) def boundingRect(self): @@ -1221,6 +1555,47 @@ class LineSegmentROI(ROI): #sc['handles'] = self.handles return sc + def getArrayRegion(self, data, img, axes=(0,1)): + """ + Use the position of this ROI relative to an imageItem to pull a slice from an array. + Since this pulls 1D data from a 2D coordinate system, the return value will have ndim = data.ndim-1 + """ + + + #shape = self.state['size'] + + #origin = self.mapToItem(img, QtCore.QPointF(0, 0)) + + ## vx and vy point in the directions of the slice axes, but must be scaled properly + #vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin + #vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin + + imgPts = [self.mapToItem(img, h['item'].pos()) for h in self.handles] + rgns = [] + for i in range(len(imgPts)-1): + d = Point(imgPts[i+1] - imgPts[i]) + o = Point(imgPts[i]) + r = fn.affineSlice(data, shape=(int(d.length()),), vectors=[d.norm()], origin=o, axes=axes, order=1) + rgns.append(r) + + return np.concatenate(rgns, axis=axes[0]) + + + #lvx = np.sqrt(vx.x()**2 + vx.y()**2) + #lvy = np.sqrt(vy.x()**2 + vy.y()**2) + #pxLen = img.width() / float(data.shape[axes[0]]) + #sx = pxLen / lvx + #sy = pxLen / lvy + + #vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) + #shape = self.state['size'] + #shape = [abs(shape[0]/sx), abs(shape[1]/sy)] + + #origin = (origin.x(), origin.y()) + + ##print "shape", shape, "vectors", vectors, "origin", origin + + #return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1) class SpiralROI(ROI): @@ -1288,7 +1663,7 @@ class SpiralROI(ROI): def paint(self, p, *args): p.setRenderHint(QtGui.QPainter.Antialiasing) #path = self.shape() - p.setPen(self.pen) + p.setPen(self.currentPen) p.drawPath(self.path) p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) p.drawPath(self.shape()) diff --git a/graphicsItems/ScaleBar.py b/graphicsItems/ScaleBar.py new file mode 100644 index 00000000..be84f822 --- /dev/null +++ b/graphicsItems/ScaleBar.py @@ -0,0 +1,50 @@ +from pyqtgraph.Qt import QtGui, QtCore +from UIGraphicsItem import * +import numpy as np +import pyqtgraph.functions as fn + +__all__ = ['ScaleBar'] +class ScaleBar(UIGraphicsItem): + """ + Displays a rectangular bar with 10 divisions to indicate the relative scale of objects on the view. + """ + def __init__(self, size, width=5, color=(100, 100, 255)): + UIGraphicsItem.__init__(self) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + + self.brush = fn.mkBrush(color) + self.pen = fn.mkPen((0,0,0)) + self._width = width + self.size = size + + def paint(self, p, opt, widget): + UIGraphicsItem.paint(self, p, opt, widget) + + rect = self.boundingRect() + unit = self.pixelSize() + y = rect.bottom() + (rect.top()-rect.bottom()) * 0.02 + y1 = y + unit[1]*self._width + x = rect.right() + (rect.left()-rect.right()) * 0.02 + x1 = x - self.size + + p.setPen(self.pen) + p.setBrush(self.brush) + rect = QtCore.QRectF( + QtCore.QPointF(x1, y1), + QtCore.QPointF(x, y) + ) + p.translate(x1, y1) + p.scale(rect.width(), rect.height()) + p.drawRect(0, 0, 1, 1) + + alpha = np.clip(((self.size/unit[0]) - 40.) * 255. / 80., 0, 255) + p.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, alpha))) + for i in range(1, 10): + #x2 = x + (x1-x) * 0.1 * i + x2 = 0.1 * i + p.drawLine(QtCore.QPointF(x2, 0), QtCore.QPointF(x2, 1)) + + + def setSize(self, s): + self.size = s + diff --git a/graphicsItems/ScatterPlotItem.py b/graphicsItems/ScatterPlotItem.py new file mode 100644 index 00000000..85427eb4 --- /dev/null +++ b/graphicsItems/ScatterPlotItem.py @@ -0,0 +1,377 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +from GraphicsObject import GraphicsObject + +__all__ = ['ScatterPlotItem', 'SpotItem'] +class ScatterPlotItem(GraphicsObject): + + #sigPointClicked = QtCore.Signal(object, object) + sigClicked = QtCore.Signal(object, object) ## self, points + sigPlotChanged = QtCore.Signal(object) + + def __init__(self, spots=None, x=None, y=None, pxMode=True, pen='default', brush='default', size=7, + symbol=None, identical=False, data=None): + + """ + Arguments: + spots: list of dicts. Each dict specifies parameters for a single spot: + {'pos': (x,y), 'size', 'pen', 'brush', 'symbol'} + x,y: array of x,y values. Alternatively, specify spots['pos'] = (x,y) + pxMode: If True, spots are always the same size regardless of scaling, and size is given in px. + Otherwise, size is in scene coordinates and the spots scale with the view. + identical: If True, all spots are forced to look identical. + This can result in performance enhancement. + + symbol can be one of: + 'o' circle + 's' square + 't' triangle + 'd' diamond + '+' plus + """ + + + GraphicsObject.__init__(self) + self.spots = [] + self.range = [[0,0], [0,0]] + self.identical = identical + self._spotPixmap = None + + if brush == 'default': + self.brush = QtGui.QBrush(QtGui.QColor(100, 100, 150)) + else: + self.brush = fn.mkBrush(brush) + + if pen == 'default': + self.pen = QtGui.QPen(QtGui.QColor(200, 200, 200)) + else: + self.pen = fn.mkPen(pen) + + self.symbol = symbol + self.size = size + + self.pxMode = pxMode + if spots is not None or x is not None: + self.setPoints(spots, x, y, data) + + #self.optimize = optimize + #if optimize: + #self.spotImage = QtGui.QImage(size, size, QtGui.QImage.Format_ARGB32_Premultiplied) + #self.spotImage.fill(0) + #p = QtGui.QPainter(self.spotImage) + #p.setRenderHint(p.Antialiasing) + #p.setBrush(brush) + #p.setPen(pen) + #p.drawEllipse(0, 0, size, size) + #p.end() + #self.optimizePixmap = QtGui.QPixmap(self.spotImage) + #self.optimizeFragments = [] + #self.setFlags(self.flags() | self.ItemIgnoresTransformations) + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def setPxMode(self, mode): + self.pxMode = mode + + def clear(self): + for i in self.spots: + i.setParentItem(None) + s = i.scene() + if s is not None: + s.removeItem(i) + self.spots = [] + + + def getRange(self, ax, percent): + return self.range[ax] + + def setPoints(self, spots=None, x=None, y=None, data=None): + """ + Remove all existing points in the scatter plot and add a new set. + Arguments: + spots - list of dicts specifying parameters for each spot + [ {'pos': (x,y), 'pen': 'r', ...}, ...] + x, y - arrays specifying location of spots to add. + all other parameters (pen, symbol, etc.) will be set to the default + values for this scatter plot. + these arguments are IGNORED if 'spots' is specified + data - list of arbitrary objects to be assigned to spot.data for each spot + (this is useful for identifying spots that are clicked on) + """ + self.clear() + self.range = [[0,0],[0,0]] + self.addPoints(spots, x, y, data) + + def addPoints(self, spots=None, x=None, y=None, data=None): + xmn = ymn = xmx = ymx = None + if spots is not None: + n = len(spots) + else: + n = len(x) + + for i in range(n): + if spots is not None: + s = spots[i] + pos = Point(s['pos']) + else: + s = {} + pos = Point(x[i], y[i]) + if data is not None: + s['data'] = data[i] + + size = s.get('size', self.size) + if self.pxMode: + psize = 0 + else: + psize = size + if xmn is None: + xmn = pos[0]-psize + xmx = pos[0]+psize + ymn = pos[1]-psize + ymx = pos[1]+psize + else: + xmn = min(xmn, pos[0]-psize) + xmx = max(xmx, pos[0]+psize) + ymn = min(ymn, pos[1]-psize) + ymx = max(ymx, pos[1]+psize) + #print pos, xmn, xmx, ymn, ymx + brush = s.get('brush', self.brush) + pen = s.get('pen', self.pen) + pen.setCosmetic(True) + symbol = s.get('symbol', self.symbol) + data2 = s.get('data', None) + item = self.mkSpot(pos, size, self.pxMode, brush, pen, data2, symbol=symbol, index=len(self.spots)) + self.spots.append(item) + #if self.optimize: + #item.hide() + #frag = QtGui.QPainter.PixmapFragment.create(pos, QtCore.QRectF(0, 0, size, size)) + #self.optimizeFragments.append(frag) + self.range = [[xmn, xmx], [ymn, ymx]] + + #def setPointSize(self, size): + #for s in self.spots: + #s.size = size + ##self.setPoints([{'size':s.size, 'pos':s.pos(), 'data':s.data} for s in self.spots]) + #self.setPoints() + + #def paint(self, p, *args): + #if not self.optimize: + #return + ##p.setClipRegion(self.boundingRect()) + #p.drawPixmapFragments(self.optimizeFragments, self.optimizePixmap) + + def paint(self, *args): + pass + + def spotPixmap(self): + ## If all spots are identical, return the pixmap to use for all spots + ## Otherwise return None + + if not self.identical: + return None + if self._spotPixmap is None: + #print 'spotPixmap' + spot = SpotItem(size=self.size, pxMode=True, brush=self.brush, pen=self.pen, symbol=self.symbol) + #self._spotPixmap = PixmapSpotItem.makeSpotImage(self.size, self.pen, self.brush, self.symbol) + self._spotPixmap = spot.pixmap + return self._spotPixmap + + def mkSpot(self, pos, size, pxMode, brush, pen, data, symbol=None, index=None): + ## Make and return a SpotItem (or PixmapSpotItem if in pxMode) + + brush = fn.mkBrush(brush) + pen = fn.mkPen(pen) + if pxMode: + img = self.spotPixmap() ## returns None if not using identical mode + #item = PixmapSpotItem(size, brush, pen, data, image=img, symbol=symbol, index=index) + item = SpotItem(size, pxMode, brush, pen, data, symbol=symbol, image=img, index=index) + else: + item = SpotItem(size, pxMode, brush, pen, data, symbol=symbol, index=index) + item.setParentItem(self) + item.setPos(pos) + #item.sigClicked.connect(self.pointClicked) + return item + + def boundingRect(self): + ((xmn, xmx), (ymn, ymx)) = self.range + if xmn is None or xmx is None or ymn is None or ymx is None: + return QtCore.QRectF() + return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn) + #return QtCore.QRectF(xmn-1, ymn-1, xmx-xmn+2, ymx-ymn+2) + + #def pointClicked(self, point): + #self.sigPointClicked.emit(self, point) + + def points(self): + return self.spots[:] + + def pointsAt(self, pos): + x = pos.x() + y = pos.y() + pw = self.pixelWidth() + ph = self.pixelHeight() + pts = [] + for s in self.spots: + sp = s.pos() + ss = s.size + sx = sp.x() + sy = sp.y() + s2x = s2y = ss * 0.5 + if self.pxMode: + s2x *= pw + s2y *= ph + if x > sx-s2x and x < sx+s2x and y > sy-s2y and y < sy+s2y: + pts.append(s) + #print "HIT:", x, y, sx, sy, s2x, s2y + #else: + #print "No hit:", (x, y), (sx, sy) + #print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y) + pts.sort(lambda a,b: cmp(b.zValue(), a.zValue())) + return pts + + + #def mousePressEvent(self, ev): + #QtGui.QGraphicsItem.mousePressEvent(self, ev) + #if ev.button() == QtCore.Qt.LeftButton: + #pts = self.pointsAt(ev.pos()) + #if len(pts) > 0: + #self.mouseMoved = False + #self.ptsClicked = pts + #ev.accept() + #else: + ##print "no spots" + #ev.ignore() + #else: + #ev.ignore() + + #def mouseMoveEvent(self, ev): + #QtGui.QGraphicsItem.mouseMoveEvent(self, ev) + #self.mouseMoved = True + #pass + + #def mouseReleaseEvent(self, ev): + #QtGui.QGraphicsItem.mouseReleaseEvent(self, ev) + #if not self.mouseMoved: + #self.sigClicked.emit(self, self.ptsClicked) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + pts = self.pointsAt(ev.pos()) + if len(pts) > 0: + self.ptsClicked = pts + self.sigClicked.emit(self, self.ptsClicked) + ev.accept() + else: + #print "no spots" + ev.ignore() + else: + ev.ignore() + + + +class SpotItem(GraphicsObject): + #sigClicked = QtCore.Signal(object) + + def __init__(self, size, pxMode, brush, pen, data=None, symbol=None, image=None, index=None): + GraphicsObject.__init__(self) + self.pxMode = pxMode + + if symbol is None: + symbol = 'o' ## circle by default + elif isinstance(symbol, int): ## allow symbols specified by integer for easy iteration + symbol = ['o', 's', 't', 'd', '+'][symbol] + ####print 'SpotItem symbol: ', symbol + self.data = data + self.pen = pen + self.brush = brush + self.size = size + self.index = index + self.symbol = symbol + #s2 = size/2. + self.path = QtGui.QPainterPath() + if symbol == 'o': + self.path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) + elif symbol == 's': + self.path.addRect(QtCore.QRectF(-0.5, -0.5, 1, 1)) + elif symbol is 't' or symbol is '^': + self.path.moveTo(-0.5, -0.5) + self.path.lineTo(0, 0.5) + self.path.lineTo(0.5, -0.5) + self.path.closeSubpath() + #self.path.connectPath(self.path) + elif symbol == 'd': + self.path.moveTo(0., -0.5) + self.path.lineTo(-0.4, 0.) + self.path.lineTo(0, 0.5) + self.path.lineTo(0.4, 0) + self.path.closeSubpath() + #self.path.connectPath(self.path) + elif symbol == '+': + self.path.moveTo(-0.5, -0.01) + self.path.lineTo(-0.5, 0.01) + self.path.lineTo(-0.01, 0.01) + self.path.lineTo(-0.01, 0.5) + self.path.lineTo(0.01, 0.5) + self.path.lineTo(0.01, 0.01) + self.path.lineTo(0.5, 0.01) + self.path.lineTo(0.5, -0.01) + self.path.lineTo(0.01, -0.01) + self.path.lineTo(0.01, -0.5) + self.path.lineTo(-0.01, -0.5) + self.path.lineTo(-0.01, -0.01) + self.path.closeSubpath() + #self.path.connectPath(self.path) + #elif symbol == 'x': + else: + raise Exception("Unknown spot symbol '%s'" % symbol) + #self.path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) + + if pxMode: + ## pre-render an image of the spot and display this rather than redrawing every time. + if image is None: + self.pixmap = self.makeSpotImage(size, pen, brush, symbol) + else: + self.pixmap = image ## image is already provided (probably shared with other spots) + self.setFlags(self.flags() | self.ItemIgnoresTransformations | self.ItemHasNoContents) + self.pi = QtGui.QGraphicsPixmapItem(self.pixmap, self) + self.pi.setPos(-0.5*size, -0.5*size) + else: + self.scale(size, size) + + + def makeSpotImage(self, size, pen, brush, symbol=None): + self.spotImage = QtGui.QImage(size+2, size+2, QtGui.QImage.Format_ARGB32_Premultiplied) + self.spotImage.fill(0) + p = QtGui.QPainter(self.spotImage) + p.setRenderHint(p.Antialiasing) + p.translate(size*0.5+1, size*0.5+1) + p.scale(size, size) + self.paint(p, None, None) + p.end() + return QtGui.QPixmap(self.spotImage) + + + def setBrush(self, brush): + self.brush = fn.mkBrush(brush) + self.update() + + def setPen(self, pen): + self.pen = fn.mkPen(pen) + self.update() + + def boundingRect(self): + return self.path.boundingRect() + + def shape(self): + return self.path + + def paint(self, p, *opts): + p.setPen(self.pen) + p.setBrush(self.brush) + p.drawPath(self.path) + diff --git a/graphicsItems/UIGraphicsItem.py b/graphicsItems/UIGraphicsItem.py new file mode 100644 index 00000000..e1cce7ed --- /dev/null +++ b/graphicsItems/UIGraphicsItem.py @@ -0,0 +1,127 @@ +from pyqtgraph.Qt import QtGui, QtCore +import weakref +from GraphicsObject import GraphicsObject + +__all__ = ['UIGraphicsItem'] +class UIGraphicsItem(GraphicsObject): + """Base class for graphics items with boundaries relative to a GraphicsView or ViewBox. + The purpose of this class is to allow the creation of GraphicsItems which live inside + a scalable view, but whose boundaries will always stay fixed relative to the view's boundaries. + For example: GridItem, InfiniteLine + + The view can be specified on initialization or it can be automatically detected when the item is painted. + + NOTE: Only the item's boundingRect is affected; the item is not transformed in any way. Use viewRangeChanged + to respond to changes in the view. + """ + + #sigViewChanged = QtCore.Signal(object) ## emitted whenever the viewport coords have changed + + def __init__(self, bounds=None, parent=None): + """ + Initialization Arguments: + #view: The view box whose bounds will be used as a reference vor this item's bounds + bounds: QRectF with coordinates relative to view box. The default is QRectF(0,0,1,1), + which means the item will have the same bounds as the view. + """ + GraphicsObject.__init__(self, parent) + self.setFlag(self.ItemSendsScenePositionChanges) + self._connectedView = None + + if bounds is None: + self._bounds = QtCore.QRectF(0, 0, 1, 1) + else: + self._bounds = bounds + + self._boundingRect = None + self.updateView() + + def paint(self, *args): + ## check for a new view object every time we paint. + #self.updateView() + pass + + def itemChange(self, change, value): + ret = GraphicsObject.itemChange(self, change, value) + if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged: + #print "caught parent/scene change:", self.parentItem(), self.scene() + self.updateView() + elif change == self.ItemScenePositionHasChanged: + self.setNewBounds() + return ret + + def updateView(self): + ## called to see whether this item has a new view to connect to + + ## check for this item's current viewbox or view widget + view = self.getViewBox() + if view is None: + #print " no view" + return + + if self._connectedView is not None and view is self._connectedView(): + #print " already have view", view + return + + ## disconnect from previous view + if self._connectedView is not None: + cv = self._connectedView() + if cv is not None: + #print "disconnect:", self + cv.sigRangeChanged.disconnect(self.viewRangeChanged) + + ## connect to new view + #print "connect:", self + view.sigRangeChanged.connect(self.viewRangeChanged) + self._connectedView = weakref.ref(view) + self.setNewBounds() + + def boundingRect(self): + if self._boundingRect is None: + br = self.viewRect() + if br is None: + return QtCore.QRectF() + else: + self._boundingRect = br + return QtCore.QRectF(self._boundingRect) + + def dataBounds(self, axis, frac=1.0): + """Called by ViewBox for determining the auto-range bounds. + By default, UIGraphicsItems are excluded from autoRange.""" + return None + + def viewRangeChanged(self): + """Called when the view widget/viewbox is resized/rescaled""" + self.setNewBounds() + self.update() + + def setNewBounds(self): + """Update the item's bounding rect to match the viewport""" + self._boundingRect = None ## invalidate bounding rect, regenerate later if needed. + self.prepareGeometryChange() + self.viewChangedEvent() + + + def viewChangedEvent(self): + """ + Called whenever the view coordinates have changed. + This is a good method to override if you want to respond to change of coordinates. + """ + pass + + + def setPos(self, *args): + GraphicsObject.setPos(self, *args) + self.setNewBounds() + + def mouseShape(self): + """Return the shape of this item after expanding by 2 pixels""" + shape = self.shape() + ds = self.mapToDevice(shape) + stroker = QtGui.QPainterPathStroker() + stroker.setWidh(2) + ds2 = stroker.createStroke(ds).united(ds) + return self.mapFromDevice(ds2) + + + \ No newline at end of file diff --git a/graphicsItems/VTickGroup.py b/graphicsItems/VTickGroup.py new file mode 100644 index 00000000..214c57b0 --- /dev/null +++ b/graphicsItems/VTickGroup.py @@ -0,0 +1,154 @@ +if __name__ == '__main__': + import os, sys + path = os.path.abspath(os.path.dirname(__file__)) + sys.path.insert(0, os.path.join(path, '..', '..')) + +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +import weakref +from UIGraphicsItem import UIGraphicsItem + +__all__ = ['VTickGroup'] +class VTickGroup(UIGraphicsItem): + """ + Draws a set of tick marks which always occupy the same vertical range of the view, + but have x coordinates relative to the data within the view. + + """ + def __init__(self, xvals=None, yrange=None, pen=None): + + if yrange is None: + yrange = [0, 1] + if xvals is None: + xvals = [] + + #bounds = QtCore.QRectF(0, yrange[0], 1, yrange[1]-yrange[0]) + UIGraphicsItem.__init__(self)#, bounds=bounds) + + if pen is None: + pen = (200, 200, 200) + + self.path = QtGui.QGraphicsPathItem() + + self.ticks = [] + self.xvals = [] + #if view is None: + #self.view = None + #else: + #self.view = weakref.ref(view) + self.yrange = [0,1] + self.setPen(pen) + self.setYRange(yrange) + self.setXVals(xvals) + #self.valid = False + + def setPen(self, pen): + self.pen = fn.mkPen(pen) + + def setXVals(self, vals): + self.xvals = vals + self.rebuildTicks() + #self.valid = False + + def setYRange(self, vals): + self.yrange = vals + #self.relative = relative + #if self.view is not None: + #if relative: + #self.view().sigRangeChanged.connect(self.rescale) + #else: + #try: + #self.view().sigRangeChanged.disconnect(self.rescale) + #except: + #pass + self.rebuildTicks() + #self.valid = False + + #def viewRangeChanged(self): + ### called when the view is scaled + + #UIGraphicsItem.viewRangeChanged(self) + + #self.resetTransform() + ##vb = self.view().viewRect() + ##p1 = vb.bottom() - vb.height() * self.yrange[0] + ##p2 = vb.bottom() - vb.height() * self.yrange[1] + + ##br = self.boundingRect() + ##yr = [p1, p2] + + + + ##self.rebuildTicks() + + ##br = self.boundingRect() + ##print br + ##self.translate(0.0, br.y()) + ##self.scale(1.0, br.height()) + ##self.boundingRect() + #self.update() + + #def boundingRect(self): + #print "--request bounds:" + #b = self.path.boundingRect() + #b2 = UIGraphicsItem.boundingRect(self) + #b2.setY(b.y()) + #b2.setWidth(b.width()) + #print " ", b + #print " ", b2 + #print " ", self.mapRectToScene(b) + #return b2 + + def yRange(self): + #if self.relative: + #height = self.view.size().height() + #p1 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[0])))) + #p2 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[1])))) + #return [p1.y(), p2.y()] + #else: + #return self.yrange + + return self.yrange + + def rebuildTicks(self): + self.path = QtGui.QPainterPath() + yrange = self.yRange() + #print "rebuild ticks:", yrange + for x in self.xvals: + #path.moveTo(x, yrange[0]) + #path.lineTo(x, yrange[1]) + self.path.moveTo(x, 0.) + self.path.lineTo(x, 1.) + #self.setPath(self.path) + #self.valid = True + #self.rescale() + #print " done..", self.boundingRect() + + def paint(self, p, *args): + UIGraphicsItem.paint(self, p, *args) + + br = self.boundingRect() + h = br.height() + br.setY(br.y() + self.yrange[0] * h) + br.setHeight(h - (1.0-self.yrange[1]) * h) + p.translate(0, br.y()) + p.scale(1.0, br.height()) + p.setPen(self.pen) + p.drawPath(self.path) + #QtGui.QGraphicsPathItem.paint(self, *args) + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + import pyqtgraph as pg + vt = VTickGroup([1,3,4,7,9], [0.8, 1.0]) + p = pg.plot() + p.addItem(vt) + + if sys.flags.interactive == 0: + app.exec_() + + + + + \ No newline at end of file diff --git a/graphicsItems/ViewBox.pyc.renamed1 b/graphicsItems/ViewBox.pyc.renamed1 new file mode 100644 index 0000000000000000000000000000000000000000..29807d792d9aafb084c0454aba7c8538d67b04f4 GIT binary patch literal 22064 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 literal 0 HcmV?d00001 diff --git a/graphicsItems/ViewBox/ViewBox.py b/graphicsItems/ViewBox/ViewBox.py new file mode 100644 index 00000000..10d3e347 --- /dev/null +++ b/graphicsItems/ViewBox/ViewBox.py @@ -0,0 +1,978 @@ +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +from .. ItemGroup import ItemGroup +from .. GraphicsWidget import GraphicsWidget +from pyqtgraph.GraphicsScene import GraphicsScene +import pyqtgraph +import weakref +from copy import deepcopy +import collections + +__all__ = ['ViewBox'] + + +class ChildGroup(ItemGroup): + + sigItemsChanged = QtCore.Signal() + + def itemChange(self, change, value): + ret = ItemGroup.itemChange(self, change, value) + if change == self.ItemChildAddedChange or change == self.ItemChildRemovedChange: + self.sigItemsChanged.emit() + + return ret + + +class ViewBox(GraphicsWidget): + """ + Box that allows internal scaling/panning of children by mouse drag. + Not really compatible with GraphicsView having the same functionality. + """ + + sigYRangeChanged = QtCore.Signal(object, object) + sigXRangeChanged = QtCore.Signal(object, object) + sigRangeChangedManually = QtCore.Signal(object) + sigRangeChanged = QtCore.Signal(object, object) + #sigActionPositionChanged = QtCore.Signal(object) + sigStateChanged = QtCore.Signal(object) + + ## mouse modes + PanMode = 3 + RectMode = 1 + + ## axes + XAxis = 0 + YAxis = 1 + XYAxes = 2 + + ## for linking views together + NamedViews = weakref.WeakValueDictionary() # name: ViewBox + AllViews = weakref.WeakKeyDictionary() # ViewBox: None + + + def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, name=None): + GraphicsWidget.__init__(self, parent) + self.name = None + self.linksBlocked = False + self.addedItems = [] + #self.gView = view + #self.showGrid = showGrid + + self.state = { + + ## separating targetRange and viewRange allows the view to be resized + ## while keeping all previously viewed contents visible + 'targetRange': [[0,1], [0,1]], ## child coord. range visible [[xmin, xmax], [ymin, ymax]] + 'viewRange': [[0,1], [0,1]], ## actual range viewed + + 'yInverted': invertY, + 'aspectLocked': False, ## False if aspect is unlocked, otherwise float specifies the locked ratio. + 'autoRange': [True, True], ## False if auto range is disabled, + ## otherwise float gives the fraction of data that is visible + 'linkedViews': [None, None], + + 'mouseEnabled': [enableMouse, enableMouse], + 'mouseMode': ViewBox.PanMode if pyqtgraph.getConfigOption('leftButtonPan') else ViewBox.RectMode, + 'wheelScaleFactor': -1.0 / 8.0, + } + + + self.exportMethods = collections.OrderedDict([ + ('SVG', self.saveSvg), + ('Image', self.saveImage), + ('Print', self.savePrint), + ]) + + self.setFlag(self.ItemClipsChildrenToShape) + self.setFlag(self.ItemIsFocusable, True) ## so we can receive key presses + + ## childGroup is required so that ViewBox has local coordinates similar to device coordinates. + ## this is a workaround for a Qt + OpenGL but that causes improper clipping + ## https://bugreports.qt.nokia.com/browse/QTBUG-23723 + self.childGroup = ChildGroup(self) + self.childGroup.sigItemsChanged.connect(self.itemsChanged) + + #self.useLeftButtonPan = pyqtgraph.getConfigOption('leftButtonPan') # normally use left button to pan + # this also enables capture of keyPressEvents. + + ## Make scale box that is shown when dragging on the view + self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1) + self.rbScaleBox.setPen(fn.mkPen((255,0,0), width=1)) + self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100)) + self.addItem(self.rbScaleBox) + self.rbScaleBox.hide() + + self.axHistory = [] # maintain a history of zoom locations + self.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo" + + self.setZValue(-100) + self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) + + self.setAspectLocked(lockAspect) + + self.border = border + self.menu = ViewBoxMenu(self) + + self.register(name) + if name is None: + self.updateViewLists() + + def register(self, name): + """ + Add this ViewBox to the registered list of views. + *name* will appear in the drop-down lists for axis linking in all other views. + The same can be accomplished by initializing the ViewBox with the *name* attribute. + """ + ViewBox.AllViews[self] = None + if self.name is not None: + del ViewBox.NamedViews[self.name] + self.name = name + if name is not None: + ViewBox.NamedViews[name] = self + ViewBox.updateAllViewLists() + + def unregister(self): + del ViewBox.AllViews[self] + if self.name is not None: + del ViewBox.NamedViews[self.name] + + def close(self): + self.unregister() + + def implements(self, interface): + return interface == 'ViewBox' + + + def getState(self, copy=True): + state = self.state.copy() + state['linkedViews'] = [(None if v is None else v.name) for v in state['linkedViews']] + if copy: + return deepcopy(self.state) + else: + return self.state + + def setState(self, state): + state = state.copy() + self.setXLink(state['linkedViews'][0]) + self.setYLink(state['linkedViews'][1]) + del state['linkedViews'] + + self.state.update(state) + self.updateMatrix() + self.sigStateChanged.emit(self) + + + def setMouseMode(self, mode): + if mode not in [ViewBox.PanMode, ViewBox.RectMode]: + raise Exception("Mode must be ViewBox.PanMode or ViewBox.RectMode") + self.state['mouseMode'] = mode + self.sigStateChanged.emit(self) + + #def toggleLeftAction(self, act): ## for backward compatibility + #if act.text() is 'pan': + #self.setLeftButtonAction('pan') + #elif act.text() is 'zoom': + #self.setLeftButtonAction('rect') + + def setLeftButtonAction(self, mode='rect'): ## for backward compatibility + if mode.lower() == 'rect': + self.setMouseMode(ViewBox.RectMode) + elif mode.lower() == 'pan': + self.setMouseMode(ViewBox.PanMode) + else: + raise Exception('graphicsItems:ViewBox:setLeftButtonAction: unknown mode = %s (Options are "pan" and "rect")' % mode) + + def innerSceneItem(self): + return self.childGroup + + def setMouseEnabled(self, x=None, y=None): + if x is not None: + self.state['mouseEnabled'][0] = x + if y is not None: + self.state['mouseEnabled'][1] = y + self.sigStateChanged.emit(self) + + def mouseEnabled(self): + return self.state['mouseEnabled'][:] + + def addItem(self, item): + if item.zValue() < self.zValue(): + item.setZValue(self.zValue()+1) + item.setParentItem(self.childGroup) + self.addedItems.append(item) + self.updateAutoRange() + #print "addItem:", item, item.boundingRect() + + def removeItem(self, item): + self.addedItems.remove(item) + self.scene().removeItem(item) + self.updateAutoRange() + + def resizeEvent(self, ev): + #self.setRange(self.range, padding=0) + self.updateAutoRange() + self.updateMatrix() + self.sigStateChanged.emit(self) + + def viewRange(self): + return [x[:] for x in self.state['viewRange']] ## return copy + + def viewRect(self): + """Return a QRectF bounding the region visible within the ViewBox""" + try: + vr0 = self.state['viewRange'][0] + vr1 = self.state['viewRange'][1] + return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) + except: + print "make qrectf failed:", self.state['viewRange'] + raise + + #def viewportTransform(self): + ##return self.itemTransform(self.childGroup)[0] + #return self.childGroup.itemTransform(self)[0] + + def targetRange(self): + return [x[:] for x in self.state['targetRange']] ## return copy + + def targetRect(self): + """ + Return the region which has been requested to be visible. + (this is not necessarily the same as the region that is *actually* visible-- + resizing and aspect ratio constraints can cause targetRect() and viewRect() to differ) + """ + try: + tr0 = self.state['targetRange'][0] + tr1 = self.state['targetRange'][1] + return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) + except: + print "make qrectf failed:", self.state['targetRange'] + raise + + def setRange(self, rect=None, xRange=None, yRange=None, padding=0.02, update=True, disableAutoRange=True): + """ + Set the visible range of the ViewBox. + Must specify at least one of *range*, *xRange*, or *yRange*. + + Arguments: + *rect* (QRectF) - The full range that should be visible in the view box. + *xRange* (min,max) - The range that should be visible along the x-axis. + *yRange* (min,max) - The range that should be visible along the y-axis. + *padding* (float) - Expand the view by a fraction of the requested range + By default, this value is 0.02 (2%) + + """ + changes = {} + + if rect is not None: + changes = {0: [rect.left(), rect.right()], 1: [rect.top(), rect.bottom()]} + if xRange is not None: + changes[0] = xRange + if yRange is not None: + changes[1] = yRange + + if len(changes) == 0: + raise Exception("Must specify at least one of rect, xRange, or yRange.") + + changed = [False, False] + for ax, range in changes.iteritems(): + mn = min(range) + mx = max(range) + if mn == mx: ## If we requested 0 range, try to preserve previous scale. Otherwise just pick an arbitrary scale. + dy = self.state['viewRange'][ax][1] - self.state['viewRange'][ax][0] + if dy == 0: + dy = 1 + mn -= dy*0.5 + mx += dy*0.5 + padding = 0.0 + if any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])): + raise Exception("Not setting range [%s, %s]" % (str(mn), str(mx))) + + p = (mx-mn) * padding + mn -= p + mx += p + + if self.state['targetRange'][ax] != [mn, mx]: + self.state['targetRange'][ax] = [mn, mx] + changed[ax] = True + + if any(changed) and disableAutoRange: + if all(changed): + ax = ViewBox.XYAxes + elif changed[0]: + ax = ViewBox.XAxis + elif changed[1]: + ax = ViewBox.YAxis + self.enableAutoRange(ax, False) + + + self.sigStateChanged.emit(self) + + if update: + self.updateMatrix(changed) + + for ax, range in changes.iteritems(): + link = self.state['linkedViews'][ax] + if link is not None: + link.linkedViewChanged(self, ax) + + + + def setYRange(self, min, max, padding=0.02, update=True): + self.setRange(yRange=[min, max], update=update, padding=padding) + + def setXRange(self, min, max, padding=0.02, update=True): + self.setRange(xRange=[min, max], update=update, padding=padding) + + def autoRange(self, padding=0.02): + """ + Set the range of the view box to make all children visible. + """ + bounds = self.childrenBoundingRect() + if bounds is not None: + self.setRange(bounds, padding=padding) + + + def scaleBy(self, s, center=None): + """ + Scale by *s* around given center point (or center of view). + *s* may be a Point or tuple (x, y) + """ + scale = Point(s) + if self.state['aspectLocked'] is not False: + scale[0] = self.state['aspectLocked'] * scale[1] + + vr = self.targetRect() + if center is None: + center = Point(vr.center()) + else: + center = Point(center) + + tl = center + (vr.topLeft()-center) * scale + br = center + (vr.bottomRight()-center) * scale + + self.setRange(QtCore.QRectF(tl, br), padding=0) + + def translateBy(self, t): + """ + Translate the view by *t*, which may be a Point or tuple (x, y). + """ + t = Point(t) + #if viewCoords: ## scale from pixels + #o = self.mapToView(Point(0,0)) + #t = self.mapToView(t) - o + + vr = self.targetRect() + self.setRange(vr.translated(t), padding=0) + + def enableAutoRange(self, axis=None, enable=True): + """ + Enable (or disable) auto-range for *axis*, which may be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes for both. + When enabled, the axis will automatically rescale when items are added/removed or change their shape. + The argument *enable* may optionally be a float (0.0-1.0) which indicates the fraction of the data that should + be visible (this only works with items implementing a dataRange method, such as PlotDataItem). + """ + #print "autorange:", axis, enable + #if not enable: + #import traceback + #traceback.print_stack() + if enable is True: + enable = 1.0 + + if axis is None: + axis = ViewBox.XYAxes + + if axis == ViewBox.XYAxes or axis == 'xy': + self.state['autoRange'][0] = enable + self.state['autoRange'][1] = enable + elif axis == ViewBox.XAxis or axis == 'x': + self.state['autoRange'][0] = enable + elif axis == ViewBox.YAxis or axis == 'y': + self.state['autoRange'][1] = enable + else: + raise Exception('axis argument must be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes.') + + if enable: + self.updateAutoRange() + self.sigStateChanged.emit(self) + + def disableAutoRange(self, axis=None): + self.enableAutoRange(axis, enable=False) + + def autoRangeEnabled(self): + return self.state['autoRange'][:] + + def updateAutoRange(self): + tr = self.viewRect() + if not any(self.state['autoRange']): + return + + fractionVisible = self.state['autoRange'][:] + for i in [0,1]: + if type(fractionVisible[i]) is bool: + fractionVisible[i] = 1.0 + cr = self.childrenBoundingRect(frac=fractionVisible) + wp = cr.width() * 0.02 + hp = cr.height() * 0.02 + cr = cr.adjusted(-wp, -hp, wp, hp) + + if self.state['autoRange'][0] is not False: + tr.setLeft(cr.left()) + tr.setRight(cr.right()) + if self.state['autoRange'][1] is not False: + tr.setTop(cr.top()) + tr.setBottom(cr.bottom()) + + self.setRange(tr, padding=0, disableAutoRange=False) + + def setXLink(self, view): + self.linkView(self.XAxis, view) + + def setYLink(self, view): + self.linkView(self.YAxis, view) + + + def linkView(self, axis, view): + """ + Link X or Y axes of two views and unlink any previously connected axes. *axis* must be ViewBox.XAxis or ViewBox.YAxis. + If view is None, the axis is left unlinked. + """ + if isinstance(view, basestring): + if view == '': + view = None + else: + view = ViewBox.NamedViews[view] + + if hasattr(view, 'implements') and view.implements('ViewBoxWrapper'): + view = view.getViewBox() + + ## used to connect/disconnect signals between a pair of views + if axis == ViewBox.XAxis: + signal = 'sigXRangeChanged' + slot = self.linkedXChanged + else: + signal = 'sigYRangeChanged' + slot = self.linkedYChanged + + + oldLink = self.state['linkedViews'][axis] + if oldLink is not None: + getattr(oldLink, signal).disconnect(slot) + + self.state['linkedViews'][axis] = view + + if view is not None: + getattr(view, signal).connect(slot) + if view.autoRangeEnabled()[axis] is True: + self.enableAutoRange(axis, False) + slot() + else: + if self.autoRangeEnabled()[axis] is False: + slot() + + self.sigStateChanged.emit(self) + + def blockLink(self, b): + self.linksBlocked = b ## prevents recursive plot-change propagation + + def linkedXChanged(self): + view = self.state['linkedViews'][0] + self.linkedViewChanged(view, ViewBox.XAxis) + + def linkedYChanged(self): + view = self.state['linkedViews'][0] + self.linkedViewChanged(view, ViewBox.YAxis) + + + def linkedViewChanged(self, view, axis): + if self.linksBlocked: + return + + vr = view.viewRect() + vg = view.screenGeometry() + if vg is None: + return + + sg = self.screenGeometry() + + view.blockLink(True) + try: + if axis == ViewBox.XAxis: + upp = float(vr.width()) / vg.width() + x1 = vr.left() + (sg.x()-vg.x()) * upp + x2 = x1 + sg.width() * upp + self.enableAutoRange(ViewBox.XAxis, False) + self.setXRange(x1, x2, padding=0) + else: + upp = float(vr.height()) / vg.height() + x1 = vr.bottom() + (sg.y()-vg.y()) * upp + x2 = x1 + sg.height() * upp + self.enableAutoRange(ViewBox.YAxis, False) + self.setYRange(x1, x2, padding=0) + finally: + view.blockLink(False) + + + def screenGeometry(self): + """return the screen geometry of the viewbox""" + v = self.getViewWidget() + if v is None: + return None + b = self.sceneBoundingRect() + wr = v.mapFromScene(b).boundingRect() + pos = v.mapToGlobal(v.pos()) + wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) + return wr + + + + def itemsChanged(self): + ## called when items are added/removed from self.childGroup + self.updateAutoRange() + + def itemBoundsChanged(self, item): + self.updateAutoRange() + + def invertY(self, b=True): + """ + By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis. + """ + self.state['yInverted'] = b + self.updateMatrix() + self.sigStateChanged.emit(self) + + def setAspectLocked(self, lock=True, ratio=1): + """ + If the aspect ratio is locked, view scaling must always preserve the aspect ratio. + By default, the ratio is set to 1; x and y both have the same scaling. + This ratio can be overridden (width/height), or use None to lock in the current ratio. + """ + if not lock: + self.state['aspectLocked'] = False + else: + vr = self.viewRect() + currentRatio = vr.width() / vr.height() + if ratio is None: + ratio = currentRatio + self.state['aspectLocked'] = ratio + if ratio != currentRatio: ## If this would change the current range, do that now + #self.setRange(0, self.state['viewRange'][0][0], self.state['viewRange'][0][1]) + self.updateMatrix() + self.sigStateChanged.emit(self) + + def childTransform(self): + """ + Return the transform that maps from child(item in the childGroup) coordinates to local coordinates. + (This maps from inside the viewbox to outside) + """ + m = self.childGroup.transform() + #m1 = QtGui.QTransform() + #m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y()) + return m #*m1 + + def mapToView(self, obj): + """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" + m = self.childTransform().inverted()[0] + return m.map(obj) + + def mapFromView(self, obj): + """Maps from the coordinate system displayed inside the ViewBox to the local coordinates of the ViewBox""" + m = self.childTransform() + return m.map(obj) + + def mapSceneToView(self, obj): + """Maps from scene coordinates to the coordinate system displayed inside the ViewBox""" + return self.mapToView(self.mapFromScene(obj)) + + def mapViewToScene(self, obj): + """Maps from the coordinate system displayed inside the ViewBox to scene coordinates""" + return self.mapToScene(self.mapFromView(obj)) + + def mapFromItemToView(self, item, obj): + return self.mapSceneToView(item.mapToScene(obj)) + + def mapFromViewToItem(self, item, obj): + return item.mapFromScene(self.mapViewToScene(obj)) + + def itemBoundingRect(self, item): + """Return the bounding rect of the item in view coordinates""" + return self.mapSceneToView(item.sceneBoundingRect()).boundingRect() + + #def viewScale(self): + #vr = self.viewRect() + ##print "viewScale:", self.range + #xd = vr.width() + #yd = vr.height() + #if xd == 0 or yd == 0: + #print "Warning: 0 range in view:", xd, yd + #return np.array([1,1]) + + ##cs = self.canvas().size() + #cs = self.boundingRect() + #scale = np.array([cs.width() / xd, cs.height() / yd]) + ##print "view scale:", scale + #return scale + + def wheelEvent(self, ev, axis=None): + mask = np.array(self.state['mouseEnabled'], dtype=np.float) + if axis is not None and axis >= 0 and axis < len(mask): + mv = mask[axis] + mask[:] = 0 + mask[axis] = mv + s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor + + center = Point(self.childGroup.transform().inverted()[0].map(ev.pos())) + #center = ev.pos() + + self.scaleBy(s, center) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + ev.accept() + + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton: + ev.accept() + self.raiseContextMenu(ev) + + def raiseContextMenu(self, ev): + #print "viewbox.raiseContextMenu called." + + #menu = self.getMenu(ev) + menu = self.getMenu(ev) + self.scene().addParentContextMenus(self, menu, ev) + #print "2:", [str(a.text()) for a in self.menu.actions()] + pos = ev.screenPos() + #pos2 = ev.scenePos() + #print "3:", [str(a.text()) for a in self.menu.actions()] + #self.sigActionPositionChanged.emit(pos2) + + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + #print "4:", [str(a.text()) for a in self.menu.actions()] + + def getMenu(self, ev): + self._menuCopy = self.menu.copy() ## temporary storage to prevent menu disappearing + return self._menuCopy + + def getContextMenus(self, event): + return self.menu.subMenus() + #return [self.getMenu(event)] + + + def mouseDragEvent(self, ev): + ev.accept() ## we accept all buttons + + pos = ev.pos() + lastPos = ev.lastPos() + dif = pos - lastPos + dif = dif * -1 + + ## Ignore axes if mouse is disabled + mask = np.array(self.state['mouseEnabled'], dtype=np.float) + + ## Scale or translate based on mouse button + if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): + if self.state['mouseMode'] == ViewBox.RectMode: + if ev.isFinish(): ## This is the final move in the drag; change the view scale now + #print "finish" + self.rbScaleBox.hide() + #ax = QtCore.QRectF(Point(self.pressPos), Point(self.mousePos)) + ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos)) + ax = self.childGroup.mapRectFromParent(ax) + self.showAxRect(ax) + self.axHistoryPointer += 1 + self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax] + else: + ## update shape of scale box + self.updateScaleBox(ev.buttonDownPos(), ev.pos()) + else: + tr = dif*mask + tr = self.mapToView(tr) - self.mapToView(Point(0,0)) + self.translateBy(tr) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + elif ev.button() & QtCore.Qt.RightButton: + #print "vb.rightDrag" + if self.state['aspectLocked'] is not False: + mask[0] = 0 + + dif = ev.screenPos() - ev.lastScreenPos() + dif = np.array([dif.x(), dif.y()]) + dif[0] *= -1 + s = ((mask * 0.02) + 1) ** dif + center = Point(self.childGroup.transform().inverted()[0].map(ev.buttonDownPos(QtCore.Qt.RightButton))) + #center = Point(ev.buttonDownPos(QtCore.Qt.RightButton)) + self.scaleBy(s, center) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + + def keyPressEvent(self, ev): + """ + This routine should capture key presses in the current view box. + Key presses are used only when mouse mode is RectMode + The following events are implemented: + ctrl-A : zooms out to the default "full" view of the plot + ctrl-+ : moves forward in the zooming stack (if it exists) + ctrl-- : moves backward in the zooming stack (if it exists) + + """ + #print ev.key() + #print 'I intercepted a key press, but did not accept it' + + ## not implemented yet ? + #self.keypress.sigkeyPressEvent.emit() + + ev.accept() + if ev.text() == '-': + self.scaleHistory(-1) + elif ev.text() in ['+', '=']: + self.scaleHistory(1) + elif ev.key() == QtCore.Qt.Key_Backspace: + self.scaleHistory(len(self.axHistory)) + else: + ev.ignore() + + def scaleHistory(self, d): + ptr = max(0, min(len(self.axHistory)-1, self.axHistoryPointer+d)) + if ptr != self.axHistoryPointer: + self.axHistoryPointer = ptr + self.showAxRect(self.axHistory[ptr]) + + + def updateScaleBox(self, p1, p2): + r = QtCore.QRectF(p1, p2) + r = self.childGroup.mapRectFromParent(r) + self.rbScaleBox.setPos(r.topLeft()) + self.rbScaleBox.resetTransform() + self.rbScaleBox.scale(r.width(), r.height()) + self.rbScaleBox.show() + + def showAxRect(self, ax): + self.setRange(ax.normalized()) # be sure w, h are correct coordinates + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + + #def mouseRect(self): + #vs = self.viewScale() + #vr = self.state['viewRange'] + ## Convert positions from screen (view) pixel coordinates to axis coordinates + #ax = QtCore.QRectF(self.pressPos[0]/vs[0]+vr[0][0], -(self.pressPos[1]/vs[1]-vr[1][1]), + #(self.mousePos[0]-self.pressPos[0])/vs[0], -(self.mousePos[1]-self.pressPos[1])/vs[1]) + #return(ax) + + def allChildren(self, item=None): + """Return a list of all children and grandchildren of this ViewBox""" + if item is None: + item = self.childGroup + + children = [item] + for ch in item.childItems(): + children.extend(self.allChildren(ch)) + return children + + + + def childrenBoundingRect(self, frac=None): + """Return the bounding range of all children. + [[xmin, xmax], [ymin, ymax]] + Values may be None if there are no specific bounds for an axis. + """ + + #items = self.allChildren() + items = self.addedItems + + #if item is None: + ##print "children bounding rect:" + #item = self.childGroup + + range = [None, None] + + for item in items: + if not item.isVisible(): + continue + + #print "=========", item + useX = True + useY = True + if hasattr(item, 'dataBounds'): + if frac is None: + frac = (1.0, 1.0) + xr = item.dataBounds(0, frac=frac[0]) + yr = item.dataBounds(1, frac=frac[1]) + if xr is None: + useX = False + xr = (0,0) + if yr is None: + useY = False + yr = (0,0) + + bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0]) + #print " item real:", bounds + else: + if int(item.flags() & item.ItemHasNoContents) > 0: + continue + #print " empty" + else: + bounds = item.boundingRect() + #bounds = [[item.left(), item.top()], [item.right(), item.bottom()]] + #print " item:", bounds + #bounds = QtCore.QRectF(bounds[0][0], bounds[1][0], bounds[0][1]-bounds[0][0], bounds[1][1]-bounds[1][0]) + bounds = self.mapFromItemToView(item, bounds).boundingRect() + #print " ", bounds + + + if not any([useX, useY]): + continue + + if useX != useY: ## != means xor + ang = item.transformAngle() + if ang == 0 or ang == 180: + pass + elif ang == 90 or ang == 270: + tmp = useX + useY = useX + useX = tmp + else: + continue ## need to check for item rotations and decide how best to apply this boundary. + + + if useY: + if range[1] is not None: + range[1] = [min(bounds.top(), range[1][0]), max(bounds.bottom(), range[1][1])] + #bounds.setTop(min(bounds.top(), chb.top())) + #bounds.setBottom(max(bounds.bottom(), chb.bottom())) + else: + range[1] = [bounds.top(), bounds.bottom()] + #bounds.setTop(chb.top()) + #bounds.setBottom(chb.bottom()) + if useX: + if range[0] is not None: + range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])] + #bounds.setLeft(min(bounds.left(), chb.left())) + #bounds.setRight(max(bounds.right(), chb.right())) + else: + range[0] = [bounds.left(), bounds.right()] + #bounds.setLeft(chb.left()) + #bounds.setRight(chb.right()) + + tr = self.targetRange() + if range[0] is None: + range[0] = tr[0] + if range[1] is None: + range[1] = tr[1] + + bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0]) + return bounds + + + + def updateMatrix(self, changed=None): + if changed is None: + changed = [False, False] + #print "udpateMatrix:" + #print " range:", self.range + tr = self.targetRect() + bounds = self.rect() #boundingRect() + #print bounds + + ## set viewRect, given targetRect and possibly aspect ratio constraint + if self.state['aspectLocked'] is False or bounds.height() == 0: + self.state['viewRange'] = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]] + else: + viewRatio = bounds.width() / bounds.height() + targetRatio = self.state['aspectLocked'] * tr.width() / tr.height() + if targetRatio > viewRatio: + ## target is wider than view + dy = 0.5 * (tr.width() / (self.state['aspectLocked'] * viewRatio) - tr.height()) + if dy != 0: + changed[1] = True + self.state['viewRange'] = [self.state['targetRange'][0][:], [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy]] + else: + dx = 0.5 * (tr.height() * viewRatio * self.state['aspectLocked'] - tr.width()) + if dx != 0: + changed[0] = True + self.state['viewRange'] = [[self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx], self.state['targetRange'][1][:]] + + vr = self.viewRect() + #print " bounds:", bounds + if vr.height() == 0 or vr.width() == 0: + return + scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) + if not self.state['yInverted']: + scale = scale * Point(1, -1) + m = QtGui.QTransform() + + ## First center the viewport at 0 + #self.childGroup.resetTransform() + #self.resetTransform() + #center = self.transform().inverted()[0].map(bounds.center()) + center = bounds.center() + #print " transform to center:", center + #if self.state['yInverted']: + #m.translate(center.x(), -center.y()) + #print " inverted; translate", center.x(), center.y() + #else: + m.translate(center.x(), center.y()) + #print " not inverted; translate", center.x(), -center.y() + + ## Now scale and translate properly + m.scale(scale[0], scale[1]) + st = Point(vr.center()) + #st = translate + m.translate(-st[0], -st[1]) + + self.childGroup.setTransform(m) + #self.setTransform(m) + #self.prepareGeometryChange() + + #self.currentScale = scale + + if changed[0]: + self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0])) + if changed[1]: + self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1])) + if any(changed): + self.sigRangeChanged.emit(self, self.state['viewRange']) + + def paint(self, p, opt, widget): + if self.border is not None: + bounds = self.shape() + p.setPen(self.border) + #p.fillRect(bounds, QtGui.QColor(0, 0, 0)) + p.drawPath(bounds) + + def saveSvg(self): + pass + + def saveImage(self): + pass + + def savePrint(self): + printer = QtGui.QPrinter() + if QtGui.QPrintDialog(printer).exec_() == QtGui.QDialog.Accepted: + p = QtGui.QPainter(printer) + p.setRenderHint(p.Antialiasing) + self.scene().render(p) + p.end() + + def updateViewLists(self): + def cmpViews(a, b): + wins = 100 * cmp(a.window() is self.window(), b.window() is self.window()) + alpha = cmp(a.name, b.name) + return wins + alpha + + ## make a sorted list of all named views + nv = ViewBox.NamedViews.values() + nv.sort(cmpViews) + + if self in nv: + nv.remove(self) + names = [v.name for v in nv] + self.menu.setViewList(names) + + @staticmethod + def updateAllViewLists(): + for v in ViewBox.AllViews: + v.updateViewLists() + + + + +from ViewBoxMenu import ViewBoxMenu diff --git a/graphicsItems/ViewBox/ViewBoxMenu.py b/graphicsItems/ViewBox/ViewBoxMenu.py new file mode 100644 index 00000000..d45d89e9 --- /dev/null +++ b/graphicsItems/ViewBox/ViewBoxMenu.py @@ -0,0 +1,222 @@ +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.WidgetGroup import WidgetGroup +from axisCtrlTemplate import Ui_Form as AxisCtrlTemplate + +class ViewBoxMenu(QtGui.QMenu): + def __init__(self, view): + QtGui.QMenu.__init__(self) + + self.view = view + self.valid = False ## tells us whether the ui needs to be updated + + self.setTitle("ViewBox options") + self.viewAll = QtGui.QAction("View All", self) + self.viewAll.triggered.connect(self.autoRange) + self.addAction(self.viewAll) + + self.axes = [] + self.ctrl = [] + self.widgetGroups = [] + self.dv = QtGui.QDoubleValidator(self) + for axis in 'XY': + m = QtGui.QMenu() + m.setTitle("%s Axis" % axis) + w = QtGui.QWidget() + ui = AxisCtrlTemplate() + ui.setupUi(w) + a = QtGui.QWidgetAction(self) + a.setDefaultWidget(w) + m.addAction(a) + self.addMenu(m) + self.axes.append(m) + self.ctrl.append(ui) + wg = WidgetGroup(w) + self.widgetGroups.append(w) + + connects = [ + (ui.mouseCheck.toggled, 'MouseToggled'), + (ui.manualRadio.clicked, 'ManualClicked'), + (ui.minText.editingFinished, 'MinTextChanged'), + (ui.maxText.editingFinished, 'MaxTextChanged'), + (ui.autoRadio.clicked, 'AutoClicked'), + (ui.autoPercentSpin.valueChanged, 'AutoSpinChanged'), + (ui.linkCombo.currentIndexChanged, 'LinkComboChanged'), + ] + + for sig, fn in connects: + sig.connect(getattr(self, axis.lower()+fn)) + + self.export = QtGui.QMenu("Export") + self.setExportMethods(view.exportMethods) + self.addMenu(self.export) + + self.leftMenu = QtGui.QMenu("Mouse Mode") + group = QtGui.QActionGroup(self) + pan = self.leftMenu.addAction("3 button", self.set3ButtonMode) + zoom = self.leftMenu.addAction("1 button", self.set1ButtonMode) + pan.setCheckable(True) + zoom.setCheckable(True) + pan.setActionGroup(group) + zoom.setActionGroup(group) + self.mouseModes = [pan, zoom] + self.addMenu(self.leftMenu) + + self.view.sigStateChanged.connect(self.viewStateChanged) + + self.updateState() + + def copy(self): + m = QtGui.QMenu() + for sm in self.subMenus(): + if isinstance(sm, QtGui.QMenu): + m.addMenu(sm) + else: + m.addAction(sm) + m.setTitle(self.title()) + return m + + def subMenus(self): + if not self.valid: + self.updateState() + return [self.viewAll] + self.axes + [self.export, self.leftMenu] + + + def setExportMethods(self, methods): + self.exportMethods = methods + self.export.clear() + for opt, fn in methods.iteritems(): + self.export.addAction(opt, self.exportMethod) + + + def viewStateChanged(self): + self.valid = False + if self.ctrl[0].minText.isVisible() or self.ctrl[1].minText.isVisible(): + self.updateState() + + def updateState(self): + state = self.view.getState(copy=False) + if state['mouseMode'] == ViewBox.PanMode: + self.mouseModes[0].setChecked(True) + else: + self.mouseModes[1].setChecked(True) + + + for i in [0,1]: + tr = state['targetRange'][i] + self.ctrl[i].minText.setText("%0.5g" % tr[0]) + self.ctrl[i].maxText.setText("%0.5g" % tr[1]) + if state['autoRange'][i] is not False: + self.ctrl[i].autoRadio.setChecked(True) + else: + self.ctrl[i].manualRadio.setChecked(True) + self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i]) + + c = self.ctrl[i].linkCombo + c.blockSignals(True) + try: + view = state['linkedViews'][i] + if view is None: + view = '' + ind = c.findText(view) + if ind == -1: + ind = 0 + c.setCurrentIndex(ind) + finally: + c.blockSignals(False) + + + self.valid = True + + + def autoRange(self): + self.view.autoRange() ## don't let signal call this directly--it'll add an unwanted argument + + def xMouseToggled(self, b): + self.view.setMouseEnabled(x=b) + + def xManualClicked(self): + self.view.enableAutoRange(ViewBox.XAxis, False) + + def xMinTextChanged(self): + self.ctrl[0].manualRadio.setChecked(True) + self.view.setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) + + def xMaxTextChanged(self): + self.ctrl[0].manualRadio.setChecked(True) + self.view.setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) + + def xAutoClicked(self): + val = self.ctrl[0].autoPercentSpin.value() * 0.01 + self.view.enableAutoRange(ViewBox.XAxis, val) + + def xAutoSpinChanged(self, val): + self.ctrl[0].autoRadio.setChecked(True) + self.view.enableAutoRange(ViewBox.XAxis, val*0.01) + + def xLinkComboChanged(self, ind): + self.view.setXLink(str(self.ctrl[0].linkCombo.currentText())) + + + + def yMouseToggled(self, b): + self.view.setMouseEnabled(y=b) + + def yManualClicked(self): + self.view.enableAutoRange(ViewBox.YAxis, False) + + def yMinTextChanged(self): + self.ctrl[1].manualRadio.setChecked(True) + self.view.setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) + + def yMaxTextChanged(self): + self.ctrl[1].manualRadio.setChecked(True) + self.view.setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) + + def yAutoClicked(self): + val = self.ctrl[1].autoPercentSpin.value() * 0.01 + self.view.enableAutoRange(ViewBox.YAxis, val) + + def yAutoSpinChanged(self, val): + self.ctrl[1].autoRadio.setChecked(True) + self.view.enableAutoRange(ViewBox.YAxis, val*0.01) + + def yLinkComboChanged(self, ind): + self.view.setYLink(str(self.ctrl[1].linkCombo.currentText())) + + + def exportMethod(self): + act = self.sender() + self.exportMethods[str(act.text())]() + + + def set3ButtonMode(self): + self.view.setLeftButtonAction('pan') + + def set1ButtonMode(self): + self.view.setLeftButtonAction('rect') + + + def setViewList(self, views): + views = [''] + views + for i in [0,1]: + c = self.ctrl[i].linkCombo + current = unicode(c.currentText()) + c.blockSignals(True) + changed = True + try: + c.clear() + for v in views: + c.addItem(v) + if v == current: + changed = False + c.setCurrentIndex(c.count()-1) + finally: + c.blockSignals(False) + + if changed: + c.setCurrentIndex(0) + c.currentIndexChanged.emit(c.currentIndex()) + +from ViewBox import ViewBox + + \ No newline at end of file diff --git a/graphicsItems/ViewBox/__init__.py b/graphicsItems/ViewBox/__init__.py new file mode 100644 index 00000000..448283ef --- /dev/null +++ b/graphicsItems/ViewBox/__init__.py @@ -0,0 +1 @@ +from ViewBox import ViewBox diff --git a/graphicsItems/ViewBox/axisCtrlTemplate.py b/graphicsItems/ViewBox/axisCtrlTemplate.py new file mode 100644 index 00000000..b229bf3d --- /dev/null +++ b/graphicsItems/ViewBox/axisCtrlTemplate.py @@ -0,0 +1,73 @@ +# -*- coding: utf-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 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(182, 120) + Form.setMaximumSize(QtCore.QSize(200, 16777215)) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.mouseCheck = QtGui.QCheckBox(Form) + 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.setObjectName(_fromUtf8("manualRadio")) + self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 1) + self.minText = QtGui.QLineEdit(Form) + self.minText.setObjectName(_fromUtf8("minText")) + self.gridLayout.addWidget(self.minText, 1, 1, 1, 1) + self.maxText = QtGui.QLineEdit(Form) + self.maxText.setObjectName(_fromUtf8("maxText")) + self.gridLayout.addWidget(self.maxText, 1, 2, 1, 1) + self.autoRadio = QtGui.QRadioButton(Form) + 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.setMinimum(1) + self.autoPercentSpin.setMaximum(100) + self.autoPercentSpin.setSingleStep(1) + self.autoPercentSpin.setProperty(_fromUtf8("value"), 100) + self.autoPercentSpin.setObjectName(_fromUtf8("autoPercentSpin")) + self.gridLayout.addWidget(self.autoPercentSpin, 2, 1, 1, 2) + self.autoPanCheck = QtGui.QCheckBox(Form) + self.autoPanCheck.setObjectName(_fromUtf8("autoPanCheck")) + self.gridLayout.addWidget(self.autoPanCheck, 3, 1, 1, 2) + self.linkCombo = QtGui.QComboBox(Form) + self.linkCombo.setObjectName(_fromUtf8("linkCombo")) + self.gridLayout.addWidget(self.linkCombo, 4, 1, 1, 2) + self.label = QtGui.QLabel(Form) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 4, 0, 1, 1) + + self.retranslateUi(Form) + 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)) + diff --git a/graphicsItems/ViewBox/axisCtrlTemplate.ui b/graphicsItems/ViewBox/axisCtrlTemplate.ui new file mode 100644 index 00000000..f01a3f80 --- /dev/null +++ b/graphicsItems/ViewBox/axisCtrlTemplate.ui @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>182</width> + <height>120</height> + </rect> + </property> + <property name="maximumSize"> + <size> + <width>200</width> + <height>16777215</height> + </size> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="1" colspan="2"> + <widget class="QCheckBox" name="mouseCheck"> + <property name="text"> + <string>Mouse Enabled</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="manualRadio"> + <property name="text"> + <string>Manual</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="minText"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="maxText"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QRadioButton" name="autoRadio"> + <property name="text"> + <string>Auto</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="1" colspan="2"> + <widget class="QSpinBox" name="autoPercentSpin"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + <item row="3" column="1" colspan="2"> + <widget class="QCheckBox" name="autoPanCheck"> + <property name="text"> + <string>Auto Pan Only</string> + </property> + </widget> + </item> + <item row="4" column="1" colspan="2"> + <widget class="QComboBox" name="linkCombo"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Link Axis:</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/graphicsItems/__init__.py b/graphicsItems/__init__.py new file mode 100644 index 00000000..8e411816 --- /dev/null +++ b/graphicsItems/__init__.py @@ -0,0 +1,21 @@ +### just import everything from sub-modules + +#import os + +#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 != '__init__.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: + ##print modName, k + #globals()[k] = getattr(mod, k) diff --git a/graphicsWindows.py b/graphicsWindows.py index 8b8e8678..f2cf87f4 100644 --- a/graphicsWindows.py +++ b/graphicsWindows.py @@ -5,9 +5,11 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -from PyQt4 import QtCore, QtGui -from PlotWidget import * -from ImageView import * +from Qt import QtCore, QtGui +from widgets.PlotWidget import * +from imageview import * +from widgets.GraphicsLayoutWidget import GraphicsLayoutWidget +from widgets.GraphicsView import GraphicsView QAPP = None def mkQApp(): @@ -15,49 +17,12 @@ def mkQApp(): global QAPP QAPP = QtGui.QApplication([]) -class GraphicsLayoutWidget(GraphicsView): - def __init__(self): - GraphicsView.__init__(self) - self.items = {} - self.currentRow = 0 - self.currentCol = 0 - - def nextRow(self): - """Advance to next row for automatic item placement""" - self.currentRow += 1 - self.currentCol = 0 - - def nextCol(self, colspan=1): - """Advance to next column, while returning the current column number - (generally only for internal use)""" - self.currentCol += colspan - return self.currentCol-colspan - - def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - plot = PlotItem(**kargs) - self.addItem(plot, row, col, rowspan, colspan) - return plot - - def addItem(self, item, row=None, col=None, rowspan=1, colspan=1): - if row not in self.items: - self.items[row] = {} - self.items[row][col] = item - - if row is None: - row = self.currentRow - if col is None: - col = self.nextCol(colspan) - self.centralLayout.addItem(item, row, col, rowspan, colspan) - - def getItem(self, row, col): - return self.items[row][col] - class GraphicsWindow(GraphicsLayoutWidget): - def __init__(self, title=None, size=(800,600)): + def __init__(self, title=None, size=(800,600), **kargs): mkQApp() self.win = QtGui.QMainWindow() - GraphicsLayoutWidget.__init__(self) + GraphicsLayoutWidget.__init__(self, **kargs) self.win.setCentralWidget(self) self.win.resize(*size) if title is not None: @@ -83,19 +48,6 @@ class TabWindow(QtGui.QMainWindow): raise NameError(attr) -#class PlotWindow(QtGui.QMainWindow): - #def __init__(self, title=None, **kargs): - #mkQApp() - #QtGui.QMainWindow.__init__(self) - #self.cw = PlotWidget(**kargs) - #self.setCentralWidget(self.cw) - #for m in ['plot', 'autoRange', 'addItem', 'removeItem', 'setLabel', 'clear', 'viewRect']: - #setattr(self, m, getattr(self.cw, m)) - #if title is not None: - #self.setWindowTitle(title) - #self.show() - - class PlotWindow(PlotWidget): def __init__(self, title=None, **kargs): mkQApp() diff --git a/graphicsWindows.pyc b/graphicsWindows.pyc new file mode 100644 index 0000000000000000000000000000000000000000..415b490152399763e69e5d88fec199297f1cb2fb GIT binary patch literal 4242 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 HD|7z<KroH0 literal 0 HcmV?d00001 diff --git a/ImageView.py b/imageview/ImageView.py similarity index 76% rename from ImageView.py rename to imageview/ImageView.py index 3c293964..2e04c82b 100644 --- a/ImageView.py +++ b/imageview/ImageView.py @@ -14,26 +14,32 @@ Widget used for displaying 2D or 3D data. Features: """ from ImageViewTemplate import * -from graphicsItems import * -from widgets import ROI -from PyQt4 import QtCore, QtGui +from pyqtgraph.graphicsItems.ImageItem import * +from pyqtgraph.graphicsItems.ROI import * +from pyqtgraph.graphicsItems.LinearRegionItem import * +from pyqtgraph.graphicsItems.InfiniteLine import * +from pyqtgraph.graphicsItems.ViewBox import * +#from widgets import ROI +from pyqtgraph.Qt import QtCore, QtGui import sys #from numpy import ndarray import ptime import numpy as np import debug -from SignalProxy import proxyConnect +from pyqtgraph.SignalProxy import SignalProxy class PlotROI(ROI): def __init__(self, size): ROI.__init__(self, pos=[0,0], size=size, scaleSnap=True, translateSnap=True) self.addScaleHandle([1, 1], [0, 0]) + self.addRotateHandle([0, 0], [0.5, 0.5]) class ImageView(QtGui.QWidget): sigTimeChanged = QtCore.Signal(object, object) + sigProcessingChanged = QtCore.Signal(object) def __init__(self, parent=None, name="ImageView", *args): QtGui.QWidget.__init__(self, parent, *args) @@ -41,52 +47,59 @@ class ImageView(QtGui.QWidget): self.levelMin = 0 self.name = name self.image = None + self.axes = {} self.imageDisp = None self.ui = Ui_Form() self.ui.setupUi(self) - self.scene = self.ui.graphicsView.sceneObj + self.scene = self.ui.graphicsView.scene() self.ignoreTimeLine = False - if 'linux' in sys.platform.lower(): ## Stupid GL bug in linux. - self.ui.graphicsView.setViewport(QtGui.QWidget()) + #if 'linux' in sys.platform.lower(): ## Stupid GL bug in linux. + # self.ui.graphicsView.setViewport(QtGui.QWidget()) - self.ui.graphicsView.enableMouse(True) - self.ui.graphicsView.autoPixelRange = False - self.ui.graphicsView.setAspectLocked(True) + #self.ui.graphicsView.enableMouse(True) + #self.ui.graphicsView.autoPixelRange = False + #self.ui.graphicsView.setAspectLocked(True) #self.ui.graphicsView.invertY() - self.ui.graphicsView.enableMouse() - - self.ticks = [t[0] for t in self.ui.gradientWidget.listTicks()] - self.ticks[0].colorChangeAllowed = False - self.ticks[1].colorChangeAllowed = False - self.ui.gradientWidget.allowAdd = False - self.ui.gradientWidget.setTickColor(self.ticks[1], QtGui.QColor(255,255,255)) - self.ui.gradientWidget.setOrientation('right') + #self.ui.graphicsView.enableMouse() + self.view = ViewBox() + self.ui.graphicsView.setCentralItem(self.view) + self.view.setAspectLocked(True) + self.view.invertY() + + #self.ticks = [t[0] for t in self.ui.gradientWidget.listTicks()] + #self.ticks[0].colorChangeAllowed = False + #self.ticks[1].colorChangeAllowed = False + #self.ui.gradientWidget.allowAdd = False + #self.ui.gradientWidget.setTickColor(self.ticks[1], QtGui.QColor(255,255,255)) + #self.ui.gradientWidget.setOrientation('right') self.imageItem = ImageItem() - self.scene.addItem(self.imageItem) + self.view.addItem(self.imageItem) self.currentIndex = 0 + self.ui.histogram.setImageItem(self.imageItem) + self.ui.normGroup.hide() self.roi = PlotROI(10) self.roi.setZValue(20) - self.scene.addItem(self.roi) + self.view.addItem(self.roi) self.roi.hide() self.normRoi = PlotROI(10) self.normRoi.setPen(QtGui.QPen(QtGui.QColor(255,255,0))) self.normRoi.setZValue(20) - self.scene.addItem(self.normRoi) + self.view.addItem(self.normRoi) self.normRoi.hide() #self.ui.roiPlot.hide() self.roiCurve = self.ui.roiPlot.plot() - self.timeLine = InfiniteLine(self.ui.roiPlot, 0, movable=True) + self.timeLine = InfiniteLine(0, movable=True) self.timeLine.setPen(QtGui.QPen(QtGui.QColor(255, 255, 0, 200))) self.timeLine.setZValue(1) self.ui.roiPlot.addItem(self.timeLine) self.ui.splitter.setSizes([self.height()-35, 35]) - self.ui.roiPlot.showScale('left', False) + self.ui.roiPlot.hideAxis('left') self.keysPressed = {} self.playTimer = QtCore.QTimer() @@ -100,70 +113,53 @@ class ImageView(QtGui.QWidget): #self.ui.roiPlot.addItem(l) #self.normLines.append(l) #l.hide() - self.normRgn = LinearRegionItem(self.ui.roiPlot, 'vertical') + self.normRgn = LinearRegionItem() self.normRgn.setZValue(0) self.ui.roiPlot.addItem(self.normRgn) self.normRgn.hide() - ## wrap functions from graphics view + ## wrap functions from view box for fn in ['addItem', 'removeItem']: - setattr(self, fn, getattr(self.ui.graphicsView, fn)) + setattr(self, fn, getattr(self.view, fn)) + + ## wrap functions from histogram + for fn in ['setHistogramRange', 'autoHistogramRange', 'getLookupTable', 'getLevels']: + setattr(self, fn, getattr(self.ui.histogram, fn)) - #QtCore.QObject.connect(self.ui.timeSlider, QtCore.SIGNAL('valueChanged(int)'), self.timeChanged) - #self.timeLine.connect(self.timeLine, QtCore.SIGNAL('positionChanged'), self.timeLineChanged) self.timeLine.sigPositionChanged.connect(self.timeLineChanged) - #QtCore.QObject.connect(self.ui.whiteSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage) - #QtCore.QObject.connect(self.ui.blackSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage) - #QtCore.QObject.connect(self.ui.gradientWidget, QtCore.SIGNAL('gradientChanged'), self.updateImage) - self.ui.gradientWidget.sigGradientChanged.connect(self.updateImage) - #QtCore.QObject.connect(self.ui.roiBtn, QtCore.SIGNAL('clicked()'), self.roiClicked) + #self.ui.gradientWidget.sigGradientChanged.connect(self.updateImage) self.ui.roiBtn.clicked.connect(self.roiClicked) - #self.roi.connect(self.roi, QtCore.SIGNAL('regionChanged'), self.roiChanged) self.roi.sigRegionChanged.connect(self.roiChanged) - #QtCore.QObject.connect(self.ui.normBtn, QtCore.SIGNAL('toggled(bool)'), self.normToggled) self.ui.normBtn.toggled.connect(self.normToggled) - #QtCore.QObject.connect(self.ui.normDivideRadio, QtCore.SIGNAL('clicked()'), self.updateNorm) - self.ui.normDivideRadio.clicked.connect(self.updateNorm) - #QtCore.QObject.connect(self.ui.normSubtractRadio, QtCore.SIGNAL('clicked()'), self.updateNorm) - self.ui.normSubtractRadio.clicked.connect(self.updateNorm) - #QtCore.QObject.connect(self.ui.normOffRadio, QtCore.SIGNAL('clicked()'), self.updateNorm) - self.ui.normOffRadio.clicked.connect(self.updateNorm) - #QtCore.QObject.connect(self.ui.normROICheck, QtCore.SIGNAL('clicked()'), self.updateNorm) + self.ui.normDivideRadio.clicked.connect(self.normRadioChanged) + self.ui.normSubtractRadio.clicked.connect(self.normRadioChanged) + self.ui.normOffRadio.clicked.connect(self.normRadioChanged) self.ui.normROICheck.clicked.connect(self.updateNorm) - #QtCore.QObject.connect(self.ui.normFrameCheck, QtCore.SIGNAL('clicked()'), self.updateNorm) self.ui.normFrameCheck.clicked.connect(self.updateNorm) - #QtCore.QObject.connect(self.ui.normTimeRangeCheck, QtCore.SIGNAL('clicked()'), self.updateNorm) self.ui.normTimeRangeCheck.clicked.connect(self.updateNorm) - #QtCore.QObject.connect(self.playTimer, QtCore.SIGNAL('timeout()'), self.timeout) self.playTimer.timeout.connect(self.timeout) - ##QtCore.QObject.connect(self.ui.normStartSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm) - #QtCore.QObject.connect(self.ui.normStopSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm) - self.normProxy = proxyConnect(None, self.normRgn.sigRegionChanged, self.updateNorm) - #self.normRoi.connect(self.normRoi, QtCore.SIGNAL('regionChangeFinished'), self.updateNorm) + self.normProxy = SignalProxy(self.normRgn.sigRegionChanged, slot=self.updateNorm) self.normRoi.sigRegionChangeFinished.connect(self.updateNorm) self.ui.roiPlot.registerPlot(self.name + '_ROI') self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] - - #def __dtor__(self): - ##print "Called ImageView sip destructor" - #self.quit() - #QtGui.QWidget.__dtor__(self) + + self.roiClicked() ## initialize roi plot to correct shape / visibility + def close(self): self.ui.roiPlot.close() self.ui.graphicsView.close() - self.ui.gradientWidget.sigGradientChanged.disconnect(self.updateImage) + #self.ui.gradientWidget.sigGradientChanged.disconnect(self.updateImage) self.scene.clear() del self.image del self.imageDisp - #self.image = None - #self.imageDisp = None self.setParent(None) def keyPressEvent(self, ev): + #print ev.key() if ev.key() == QtCore.Qt.Key_Space: if self.playRate == 0: fps = (self.getProcessedImage().shape[0]-1) / (self.tVals[-1] - self.tVals[0]) @@ -255,7 +251,7 @@ class ImageView(QtGui.QWidget): self.jumpFrames(n) def setCurrentIndex(self, ind): - self.currentIndex = clip(ind, 0, self.getProcessedImage().shape[0]-1) + self.currentIndex = np.clip(ind, 0, self.getProcessedImage().shape[0]-1) self.updateImage() self.ignoreTimeLine = True self.timeLine.setValue(self.tVals[self.currentIndex]) @@ -266,6 +262,13 @@ class ImageView(QtGui.QWidget): if self.axes['t'] is not None: self.setCurrentIndex(self.currentIndex + n) + def normRadioChanged(self): + self.imageDisp = None + self.updateImage() + self.roiChanged() + self.sigProcessingChanged.emit(self) + + def updateNorm(self): #for l, sl in zip(self.normLines, [self.ui.normStartSlider, self.ui.normStopSlider]): #if self.ui.normTimeRangeCheck.isChecked(): @@ -288,31 +291,52 @@ class ImageView(QtGui.QWidget): else: self.normRoi.hide() - self.imageDisp = None - self.updateImage() - self.roiChanged() + if not self.ui.normOffRadio.isChecked(): + self.imageDisp = None + self.updateImage() + self.roiChanged() + self.sigProcessingChanged.emit(self) def normToggled(self, b): self.ui.normGroup.setVisible(b) self.normRoi.setVisible(b and self.ui.normROICheck.isChecked()) self.normRgn.setVisible(b and self.ui.normTimeRangeCheck.isChecked()) + def hasTimeAxis(self): + return 't' in self.axes and self.axes['t'] is not None + def roiClicked(self): + showRoiPlot = False if self.ui.roiBtn.isChecked(): + showRoiPlot = True self.roi.show() #self.ui.roiPlot.show() self.ui.roiPlot.setMouseEnabled(True, True) self.ui.splitter.setSizes([self.height()*0.6, self.height()*0.4]) self.roiCurve.show() self.roiChanged() - self.ui.roiPlot.showScale('left', True) + self.ui.roiPlot.showAxis('left') else: self.roi.hide() self.ui.roiPlot.setMouseEnabled(False, False) - self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max()) - self.ui.splitter.setSizes([self.height()-35, 35]) self.roiCurve.hide() - self.ui.roiPlot.showScale('left', False) + self.ui.roiPlot.hideAxis('left') + + if self.hasTimeAxis(): + showRoiPlot = True + mn = self.tVals.min() + mx = self.tVals.max() + self.ui.roiPlot.setXRange(mn, mx, padding=0.01) + self.timeLine.show() + self.timeLine.setBounds([mn, mx]) + self.ui.roiPlot.show() + if not self.ui.roiBtn.isChecked(): + self.ui.splitter.setSizes([self.height()-35, 35]) + else: + self.timeLine.hide() + #self.ui.roiPlot.hide() + + self.ui.roiPlot.setVisible(showRoiPlot) def roiChanged(self): if self.image is None: @@ -329,7 +353,11 @@ class ImageView(QtGui.QWidget): if data is not None: while data.ndim > 1: data = data.mean(axis=1) - self.roiCurve.setData(y=data, x=self.tVals) + if image.ndim == 3: + self.roiCurve.setData(y=data, x=self.tVals) + else: + self.roiCurve.setData(y=data, x=range(len(data))) + #self.ui.roiPlot.replot() def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None): @@ -390,15 +418,16 @@ class ImageView(QtGui.QWidget): self.imageDisp = None + prof.mark('3') + + self.currentIndex = 0 + self.updateImage() if levels is None and autoLevels: self.autoLevels() if levels is not None: ## this does nothing since getProcessedImage sets these values again. self.levelMax = levels[1] self.levelMin = levels[0] - prof.mark('3') - self.currentIndex = 0 - self.updateImage() if self.ui.roiBtn.isChecked(): self.roiChanged() prof.mark('4') @@ -436,29 +465,31 @@ class ImageView(QtGui.QWidget): self.roiClicked() prof.mark('7') prof.finish() - + + def autoLevels(self): - image = self.getProcessedImage() + #image = self.getProcessedImage() + self.setLevels(self.levelMin, self.levelMax) - #self.ui.whiteSlider.setValue(self.ui.whiteSlider.maximum()) - #self.ui.blackSlider.setValue(0) - - self.ui.gradientWidget.setTickValue(self.ticks[0], 0.0) - self.ui.gradientWidget.setTickValue(self.ticks[1], 1.0) - self.imageItem.setLevels(white=self.whiteLevel(), black=self.blackLevel()) + #self.ui.histogram.imageChanged(autoLevel=True) + def setLevels(self, min, max): + self.ui.histogram.setLevels(min, max) + def autoRange(self): image = self.getProcessedImage() #self.ui.graphicsView.setRange(QtCore.QRectF(0, 0, image.shape[self.axes['x']], image.shape[self.axes['y']]), padding=0., lockAspect=True) - self.ui.graphicsView.setRange(self.imageItem.sceneBoundingRect(), padding=0., lockAspect=True) + self.view.setRange(self.imageItem.boundingRect(), padding=0.) def getProcessedImage(self): if self.imageDisp is None: image = self.normalize(self.image) self.imageDisp = image self.levelMin, self.levelMax = map(float, ImageView.quickMinMax(self.imageDisp)) + self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) + return self.imageDisp @staticmethod @@ -536,14 +567,14 @@ class ImageView(QtGui.QWidget): #print "update:", image.ndim, image.max(), image.min(), self.blackLevel(), self.whiteLevel() if self.axes['t'] is None: #self.ui.timeSlider.hide() - self.imageItem.updateImage(image, white=self.whiteLevel(), black=self.blackLevel()) - self.ui.roiPlot.hide() - self.ui.roiBtn.hide() + self.imageItem.updateImage(image) + #self.ui.roiPlot.hide() + #self.ui.roiBtn.hide() else: - self.ui.roiBtn.show() + #self.ui.roiBtn.show() self.ui.roiPlot.show() #self.ui.timeSlider.show() - self.imageItem.updateImage(image[self.currentIndex], white=self.whiteLevel(), black=self.blackLevel()) + self.imageItem.updateImage(image[self.currentIndex]) def timeIndex(self, slider): @@ -574,11 +605,12 @@ class ImageView(QtGui.QWidget): #print ind return ind, t - def whiteLevel(self): - return self.levelMin + (self.levelMax-self.levelMin) * self.ui.gradientWidget.tickValue(self.ticks[1]) - #return self.levelMin + (self.levelMax-self.levelMin) * self.ui.whiteSlider.value() / self.ui.whiteSlider.maximum() + #def whiteLevel(self): + #return self.levelMin + (self.levelMax-self.levelMin) * self.ui.gradientWidget.tickValue(self.ticks[1]) + ##return self.levelMin + (self.levelMax-self.levelMin) * self.ui.whiteSlider.value() / self.ui.whiteSlider.maximum() - def blackLevel(self): - return self.levelMin + (self.levelMax-self.levelMin) * self.ui.gradientWidget.tickValue(self.ticks[0]) - #return self.levelMin + ((self.levelMax-self.levelMin) / self.ui.blackSlider.maximum()) * self.ui.blackSlider.value() - \ No newline at end of file + #def blackLevel(self): + #return self.levelMin + (self.levelMax-self.levelMin) * self.ui.gradientWidget.tickValue(self.ticks[0]) + ##return self.levelMin + ((self.levelMax-self.levelMin) / self.ui.blackSlider.maximum()) * self.ui.blackSlider.value() + + \ No newline at end of file diff --git a/ImageViewTemplate.py b/imageview/ImageViewTemplate.py similarity index 80% rename from ImageViewTemplate.py rename to imageview/ImageViewTemplate.py index fe283a74..cf00ed7f 100644 --- a/ImageViewTemplate.py +++ b/imageview/ImageViewTemplate.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file './lib/util/pyqtgraph/ImageViewTemplate.ui' +# Form implementation generated from reading ui file 'ImageViewTemplate.ui' # -# Created: Wed May 18 20:44:20 2011 +# Created: Tue Jan 17 23:09:04 2012 # by: PyQt4 UI code generator 4.8.3 # # WARNING! All changes made in this file will be lost! @@ -18,57 +18,53 @@ class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(726, 588) - self.verticalLayout = QtGui.QVBoxLayout(Form) - self.verticalLayout.setSpacing(0) - self.verticalLayout.setMargin(0) - self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.gridLayout_3 = QtGui.QGridLayout(Form) + self.gridLayout_3.setMargin(0) + self.gridLayout_3.setSpacing(0) + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) self.splitter = QtGui.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter.setObjectName(_fromUtf8("splitter")) self.layoutWidget = QtGui.QWidget(self.splitter) self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) self.gridLayout = QtGui.QGridLayout(self.layoutWidget) - self.gridLayout.setMargin(0) self.gridLayout.setSpacing(0) self.gridLayout.setMargin(0) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.graphicsView = GraphicsView(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(10) - sizePolicy.setVerticalStretch(10) - sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) - self.graphicsView.setSizePolicy(sizePolicy) self.graphicsView.setObjectName(_fromUtf8("graphicsView")) - self.gridLayout.addWidget(self.graphicsView, 1, 0, 3, 1) + self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) + self.histogram = HistogramLUTWidget(self.layoutWidget) + self.histogram.setObjectName(_fromUtf8("histogram")) + self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) self.roiBtn = QtGui.QPushButton(self.layoutWidget) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) self.roiBtn.setSizePolicy(sizePolicy) - self.roiBtn.setMaximumSize(QtCore.QSize(30, 16777215)) self.roiBtn.setCheckable(True) self.roiBtn.setObjectName(_fromUtf8("roiBtn")) - self.gridLayout.addWidget(self.roiBtn, 3, 3, 1, 1) - self.gradientWidget = GradientWidget(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(100) - sizePolicy.setHeightForWidth(self.gradientWidget.sizePolicy().hasHeightForWidth()) - self.gradientWidget.setSizePolicy(sizePolicy) - self.gradientWidget.setObjectName(_fromUtf8("gradientWidget")) - self.gridLayout.addWidget(self.gradientWidget, 1, 3, 1, 1) + self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) self.normBtn = QtGui.QPushButton(self.layoutWidget) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth()) self.normBtn.setSizePolicy(sizePolicy) - self.normBtn.setMaximumSize(QtCore.QSize(30, 16777215)) self.normBtn.setCheckable(True) self.normBtn.setObjectName(_fromUtf8("normBtn")) - self.gridLayout.addWidget(self.normBtn, 2, 3, 1, 1) - self.normGroup = QtGui.QGroupBox(self.layoutWidget) + self.gridLayout.addWidget(self.normBtn, 1, 2, 1, 1) + self.roiPlot = PlotWidget(self.splitter) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) + self.roiPlot.setSizePolicy(sizePolicy) + self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) + self.roiPlot.setObjectName(_fromUtf8("roiPlot")) + self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) + self.normGroup = QtGui.QGroupBox(Form) self.normGroup.setObjectName(_fromUtf8("normGroup")) self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) self.gridLayout_2.setMargin(0) @@ -136,24 +132,15 @@ class Ui_Form(object): self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) self.normTBlurSpin.setObjectName(_fromUtf8("normTBlurSpin")) self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) - self.gridLayout.addWidget(self.normGroup, 0, 0, 1, 4) - self.roiPlot = PlotWidget(self.splitter) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) - self.roiPlot.setSizePolicy(sizePolicy) - self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) - self.roiPlot.setObjectName(_fromUtf8("roiPlot")) - self.verticalLayout.addWidget(self.splitter) + self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.roiBtn.setText(QtGui.QApplication.translate("Form", "R", None, QtGui.QApplication.UnicodeUTF8)) - self.normBtn.setText(QtGui.QApplication.translate("Form", "N", None, QtGui.QApplication.UnicodeUTF8)) + self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) + self.normBtn.setText(QtGui.QApplication.translate("Form", "Norm", None, QtGui.QApplication.UnicodeUTF8)) self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8)) self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8)) @@ -168,6 +155,6 @@ class Ui_Form(object): self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8)) self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8)) -from GraphicsView import GraphicsView -from pyqtgraph.GradientWidget import GradientWidget -from PlotWidget import PlotWidget +from pyqtgraph.widgets.GraphicsView import GraphicsView +from pyqtgraph.widgets.PlotWidget import PlotWidget +from pyqtgraph.widgets.HistogramLUTWidget import HistogramLUTWidget diff --git a/imageview/ImageViewTemplate.ui b/imageview/ImageViewTemplate.ui new file mode 100644 index 00000000..497c0c59 --- /dev/null +++ b/imageview/ImageViewTemplate.ui @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>726</width> + <height>588</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <widget class="QWidget" name="layoutWidget"> + <layout class="QGridLayout" name="gridLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="0" rowspan="2"> + <widget class="GraphicsView" name="graphicsView"/> + </item> + <item row="0" column="1" colspan="2"> + <widget class="HistogramLUTWidget" name="histogram"/> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="roiBtn"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>ROI</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="normBtn"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Norm</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="PlotWidget" name="roiPlot" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>40</height> + </size> + </property> + </widget> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="normGroup"> + <property name="title"> + <string>Normalization</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="2"> + <widget class="QRadioButton" name="normSubtractRadio"> + <property name="text"> + <string>Subtract</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QRadioButton" name="normDivideRadio"> + <property name="text"> + <string>Divide</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_5"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Operation:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Mean:</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_4"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Blur:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="normROICheck"> + <property name="text"> + <string>ROI</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QDoubleSpinBox" name="normXBlurSpin"/> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>X</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Y</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="4"> + <widget class="QDoubleSpinBox" name="normYBlurSpin"/> + </item> + <item row="2" column="5"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>T</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QRadioButton" name="normOffRadio"> + <property name="text"> + <string>Off</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QCheckBox" name="normTimeRangeCheck"> + <property name="text"> + <string>Time range</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QCheckBox" name="normFrameCheck"> + <property name="text"> + <string>Frame</string> + </property> + </widget> + </item> + <item row="2" column="6"> + <widget class="QDoubleSpinBox" name="normTBlurSpin"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>PlotWidget</class> + <extends>QWidget</extends> + <header>pyqtgraph.widgets.PlotWidget</header> + <container>1</container> + </customwidget> + <customwidget> + <class>GraphicsView</class> + <extends>QGraphicsView</extends> + <header>pyqtgraph.widgets.GraphicsView</header> + </customwidget> + <customwidget> + <class>HistogramLUTWidget</class> + <extends>QGraphicsView</extends> + <header>pyqtgraph.widgets.HistogramLUTWidget</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/imageview/__init__.py b/imageview/__init__.py new file mode 100644 index 00000000..7bbbe122 --- /dev/null +++ b/imageview/__init__.py @@ -0,0 +1,6 @@ +""" +Widget used for display and analysis of 2D and 3D image data. +Includes ROI plotting over time and image normalization. +""" + +from ImageView import ImageView diff --git a/parametertree/Parameter.py b/parametertree/Parameter.py new file mode 100644 index 00000000..39edb880 --- /dev/null +++ b/parametertree/Parameter.py @@ -0,0 +1,465 @@ +from pyqtgraph.Qt import QtGui, QtCore +import collections, os, weakref, re +from ParameterItem import ParameterItem + +PARAM_TYPES = {} + + +def registerParameterType(name, cls, override=False): + global PARAM_TYPES + if name in PARAM_TYPES and not override: + raise Exception("Parameter type '%s' already exists (use override=True to replace)" % name) + PARAM_TYPES[name] = cls + + + +class Parameter(QtCore.QObject): + """Tree of name=value pairs (modifiable or not) + - Value may be integer, float, string, bool, color, or list selection + - Optionally, a custom widget may be specified for a property + - Any number of extra columns may be added for other purposes + - Any values may be reset to a default value + - Parameters may be grouped / nested + + For more Parameter types, see ParameterTree.parameterTypes module. + """ + ## name, type, limits, etc. + ## can also carry UI hints (slider vs spinbox, etc.) + + sigValueChanged = QtCore.Signal(object, object) ## self, value emitted when value is finished being edited + sigValueChanging = QtCore.Signal(object, object) ## self, value emitted as value is being edited + + sigChildAdded = QtCore.Signal(object, object, object) ## self, child, index + sigChildRemoved = QtCore.Signal(object, object) ## self, child + sigParentChanged = QtCore.Signal(object, object) ## self, parent + sigLimitsChanged = QtCore.Signal(object, object) ## self, limits + sigDefaultChanged = QtCore.Signal(object, object) ## self, default + sigNameChanged = QtCore.Signal(object, object) ## self, name + sigOptionsChanged = QtCore.Signal(object, object) ## self, {opt:val, ...} + + ## Emitted when anything changes about this parameter at all. + ## The second argument is a string indicating what changed ('value', 'childAdded', etc..) + ## The third argument can be any extra information about the change + sigStateChanged = QtCore.Signal(object, object, object) ## self, change, info + + ## emitted when any child in the tree changes state + ## (but only if monitorChildren() is called) + sigTreeStateChanged = QtCore.Signal(object, object) # self, changes + # changes = [(param, change, info), ...] + + # bad planning. + #def __new__(cls, *args, **opts): + #try: + #cls = PARAM_TYPES[opts['type']] + #except KeyError: + #pass + #return QtCore.QObject.__new__(cls, *args, **opts) + + @staticmethod + def create(**opts): + """ + Create a new Parameter (or subclass) instance using opts['type'] to select the + appropriate class. + + Use registerParameterType() to add new class types. + """ + cls = PARAM_TYPES[opts['type']] + return cls(**opts) + + def __init__(self, **opts): + QtCore.QObject.__init__(self) + + self.opts = { + 'readonly': False, + 'visible': True, + 'enabled': True, + 'renamable': False, + 'removable': False, + 'strictNaming': False, # forces name to be usable as a python variable + } + self.opts.update(opts) + + self.childs = [] + self.names = {} ## map name:child + self.items = weakref.WeakKeyDictionary() ## keeps track of tree items representing this parameter + self._parent = None + self.treeStateChanges = [] ## cache of tree state changes to be delivered on next emit + self.blockTreeChangeEmit = 0 + #self.monitoringChildren = False ## prevent calling monitorChildren more than once + + if 'value' not in self.opts: + self.opts['value'] = None + + if 'name' not in self.opts or not isinstance(self.opts['name'], basestring): + raise Exception("Parameter must have a string name specified in opts.") + self.setName(opts['name']) + + for chOpts in self.opts.get('children', []): + #print self, "Add child:", type(chOpts), id(chOpts) + self.addChild(chOpts) + + if 'value' in self.opts and 'default' not in self.opts: + self.opts['default'] = self.opts['value'] + + ## Connect all state changed signals to the general sigStateChanged + self.sigValueChanged.connect(lambda param, data: self.emitStateChanged('value', data)) + self.sigChildAdded.connect(lambda param, *data: self.emitStateChanged('childAdded', data)) + self.sigChildRemoved.connect(lambda param, data: self.emitStateChanged('childRemoved', data)) + self.sigParentChanged.connect(lambda param, data: self.emitStateChanged('parent', data)) + self.sigLimitsChanged.connect(lambda param, data: self.emitStateChanged('limits', data)) + self.sigDefaultChanged.connect(lambda param, data: self.emitStateChanged('default', data)) + self.sigNameChanged.connect(lambda param, data: self.emitStateChanged('name', data)) + self.sigOptionsChanged.connect(lambda param, data: self.emitStateChanged('options', data)) + + #self.watchParam(self) ## emit treechange signals if our own state changes + + def name(self): + return self.opts['name'] + + def setName(self, name): + """Attempt to change the name of this parameter; return the actual name. + (The parameter may reject the name change or automatically pick a different name)""" + if self.opts['strictNaming']: + if len(name) < 1 or re.search(r'\W', name) or re.match(r'\d', name[0]): + raise Exception("Parameter name '%s' is invalid. (Must contain only alphanumeric and underscore characters and may not start with a number)" % name) + parent = self.parent() + if parent is not None: + name = parent._renameChild(self, name) ## first ask parent if it's ok to rename + if self.opts['name'] != name: + self.opts['name'] = name + self.sigNameChanged.emit(self, name) + return name + + def childPath(self, child): + """Return the path of parameter names from self to child.""" + path = [] + while child is not self: + path.insert(0, child.name()) + child = child.parent() + return path + + def setValue(self, value, blockSignal=None): + ## return the actual value that was set + ## (this may be different from the value that was requested) + #print self, "Set value:", value, self.opts['value'], self.opts['value'] == value + try: + if blockSignal is not None: + self.sigValueChanged.disconnect(blockSignal) + if self.opts['value'] == value: + return value + self.opts['value'] = value + self.sigValueChanged.emit(self, value) + finally: + if blockSignal is not None: + self.sigValueChanged.connect(blockSignal) + + return value + + def value(self): + return self.opts['value'] + + def getValues(self): + """Return a tree of all values that are children of this parameter""" + vals = collections.OrderedDict() + for ch in self: + vals[ch.name()] = (ch.value(), ch.getValues()) + return vals + + def saveState(self): + """Return a structure representing the entire state of the parameter tree.""" + state = self.opts.copy() + state['children'] = {ch.name(): ch.saveState() for ch in self} + return state + + def defaultValue(self): + return self.opts['default'] + + def setDefault(self, val): + self.opts['default'] = val + self.sigDefaultChanged.emit(self, val) + + def setToDefault(self): + if self.hasDefault(): + self.setValue(self.defaultValue()) + + def hasDefault(self): + return 'default' in self.opts + + def valueIsDefault(self): + return self.value() == self.defaultValue() + + def setLimits(self, limits): + if 'limits' in self.opts and self.opts['limits'] == limits: + return + self.opts['limits'] = limits + self.sigLimitsChanged.emit(self, limits) + return limits + + def writable(self): + return not self.opts.get('readonly', False) + + def setOpts(self, **opts): + """For setting any arbitrary options.""" + changed = collections.OrderedDict() + for k in opts: + if k == 'value': + self.setValue(opts[k]) + elif k == 'name': + self.setName(opts[k]) + elif k == 'limits': + self.setLimits(opts[k]) + elif k == 'default': + self.setDefault(opts[k]) + elif k not in self.opts or self.opts[k] != opts[k]: + self.opts[k] = opts[k] + changed[k] = opts[k] + + if len(changed) > 0: + self.sigOptionsChanged.emit(self, changed) + + def emitStateChanged(self, changeDesc, data): + ## Emits stateChanged signal and + ## requests emission of new treeStateChanged signal + self.sigStateChanged.emit(self, changeDesc, data) + #self.treeStateChanged(self, changeDesc, data) + self.treeStateChanges.append((self, changeDesc, data)) + self.emitTreeChanges() + + def makeTreeItem(self, depth): + """Return a TreeWidgetItem suitable for displaying/controlling the content of this parameter. + Most subclasses will want to override this function. + """ + if hasattr(self, 'itemClass'): + #print "Param:", self, "Make item from itemClass:", self.itemClass + return self.itemClass(self, depth) + else: + return ParameterItem(self, depth=depth) + + + def addChild(self, child): + """Add another parameter to the end of this parameter's child list.""" + return self.insertChild(len(self.childs), child) + + def insertChild(self, pos, child): + """Insert a new child at pos. + If pos is a Parameter, then insert at the position of that Parameter. + If child is a dict, then a parameter is constructed as Parameter(**child) + """ + if isinstance(child, dict): + child = Parameter.create(**child) + + name = child.name() + if name in self.names: + if child.opts.get('autoIncrementName', False): + name = self.incrementName(name) + child.setName(name) + else: + raise Exception("Already have child named %s" % str(name)) + if isinstance(pos, Parameter): + pos = self.childs.index(pos) + + if child.parent() is not None: + child.remove() + + self.names[name] = child + self.childs.insert(pos, child) + + child.parentChanged(self) + self.sigChildAdded.emit(self, child, pos) + child.sigTreeStateChanged.connect(self.treeStateChanged) + return child + + def removeChild(self, child): + name = child.name() + if name not in self.names or self.names[name] is not child: + raise Exception("Parameter %s is not my child; can't remove." % str(child)) + + del self.names[name] + self.childs.pop(self.childs.index(child)) + child.parentChanged(None) + self.sigChildRemoved.emit(self, child) + child.sigTreeStateChanged.disconnect(self.treeStateChanged) + + def clearChildren(self): + for ch in self.childs[:]: + self.removeChild(ch) + + def parentChanged(self, parent): + self._parent = parent + self.sigParentChanged.emit(self, parent) + + def parent(self): + return self._parent + + def remove(self): + """Remove self from parent's child list""" + parent = self.parent() + if parent is None: + raise Exception("Cannot remove; no parent.") + parent.removeChild(self) + + def incrementName(self, name): + ## return an unused name by adding a number to the name given + base, num = re.match('(.*)(\d*)', name).groups() + numLen = len(num) + if numLen == 0: + num = 2 + numLen = 1 + else: + num = int(num) + while True: + newName = base + ("%%0%dd"%numLen) % num + if newName not in self.childs: + return newName + num += 1 + + def __iter__(self): + for ch in self.childs: + yield ch + + def __getitem__(self, names): + """Get the value of a child parameter""" + if not isinstance(names, tuple): + names = (names,) + return self.param(*names).value() + + def __setitem__(self, names, value): + """Set the value of a child parameter""" + if isinstance(names, basestring): + names = (names,) + return self.param(*names).setValue(value) + + def param(self, *names): + """Return a child parameter. + Accepts the name of the child or a tuple (path, to, child)""" + try: + param = self.names[names[0]] + except KeyError: + raise Exception("Parameter %s has no child named %s" % (self.name(), names[0])) + + if len(names) > 1: + return param.param(*names[1:]) + else: + return param + + def __repr__(self): + return "<%s '%s' at 0x%x>" % (self.__class__.__name__, self.name(), id(self)) + + def __getattr__(self, attr): + #print type(self), attr + if attr in self.names: + return self.param(attr) + else: + raise AttributeError(attr) + + def _renameChild(self, child, name): + ## Only to be called from Parameter.rename + if name in self.names: + return child.name() + self.names[name] = child + del self.names[child.name()] + return name + + def registerItem(self, item): + self.items[item] = None + + def hide(self): + self.show(False) + + def show(self, s=True): + self.opts['visible'] = s + self.sigOptionsChanged.emit(self, {'visible': s}) + + + #def monitorChildren(self): + #if self.monitoringChildren: + #raise Exception("Already monitoring children.") + #self.watchParam(self) + #self.monitoringChildren = True + + #def watchParam(self, param): + #param.sigChildAdded.connect(self.grandchildAdded) + #param.sigChildRemoved.connect(self.grandchildRemoved) + #param.sigStateChanged.connect(self.grandchildChanged) + #for ch in param: + #self.watchParam(ch) + + #def unwatchParam(self, param): + #param.sigChildAdded.disconnect(self.grandchildAdded) + #param.sigChildRemoved.disconnect(self.grandchildRemoved) + #param.sigStateChanged.disconnect(self.grandchildChanged) + #for ch in param: + #self.unwatchParam(ch) + + #def grandchildAdded(self, parent, child): + #self.watchParam(child) + + #def grandchildRemoved(self, parent, child): + #self.unwatchParam(child) + + #def grandchildChanged(self, param, change, data): + ##self.sigTreeStateChanged.emit(self, param, change, data) + #self.emitTreeChange((param, change, data)) + + def treeChangeBlocker(self): + """ + Return an object that can be used to temporarily block and accumulate + sigTreeStateChanged signals. This is meant to be used when numerous changes are + about to be made to the tree and only one change signal should be + emitted at the end. + + Example: + with param.treeChangeBlocker(): + param.addChild(...) + param.removeChild(...) + param.setValue(...) + """ + return SignalBlocker(self.blockTreeChangeSignal, self.unblockTreeChangeSignal) + + def blockTreeChangeSignal(self): + """ + Used to temporarily block and accumulate tree change signals. + *You must remember to unblock*, so it is advisable to use treeChangeBlocker() instead. + """ + self.blockTreeChangeEmit += 1 + + def unblockTreeChangeSignal(self): + """Unblocks enission of sigTreeStateChanged and flushes the changes out through a single signal.""" + self.blockTreeChangeEmit -= 1 + self.emitTreeChanges() + + + def treeStateChanged(self, param, changes): + """ + Called when the state of any sub-parameter has changed. + Arguments: + param: the immediate child whose tree state has changed. + note that the change may have originated from a grandchild. + changes: list of tuples describing all changes that have been made + in this event: (param, changeDescr, data) + + This function can be extended to react to tree state changes. + """ + self.treeStateChanges.extend(changes) + self.emitTreeChanges() + + def emitTreeChanges(self): + if self.blockTreeChangeEmit == 0: + changes = self.treeStateChanges + self.treeStateChanges = [] + self.sigTreeStateChanged.emit(self, changes) + + +class SignalBlocker: + def __init__(self, enterFn, exitFn): + self.enterFn = enterFn + self.exitFn = exitFn + + def __enter__(self): + self.enterFn() + + def __exit__(self, exc_type, exc_value, tb): + self.exitFn() + + + \ No newline at end of file diff --git a/parametertree/ParameterItem.py b/parametertree/ParameterItem.py new file mode 100644 index 00000000..605e6317 --- /dev/null +++ b/parametertree/ParameterItem.py @@ -0,0 +1,148 @@ +from pyqtgraph.Qt import QtGui, QtCore +import collections, os, weakref, re + +class ParameterItem(QtGui.QTreeWidgetItem): + """ + Abstract ParameterTree item. + Used to represent the state of a Parameter from within a ParameterTree. + - Sets first column of item to name + - generates context menu if item is renamable or removable + - handles child added / removed events + - provides virtual functions for handling changes from parameter + For more ParameterItem types, see ParameterTree.parameterTypes module. + """ + + def __init__(self, param, depth=0): + QtGui.QTreeWidgetItem.__init__(self, [param.name(), '']) + + self.param = param + self.param.registerItem(self) ## let parameter know this item is connected to it (for debugging) + self.depth = depth + + param.sigValueChanged.connect(self.valueChanged) + param.sigChildAdded.connect(self.childAdded) + param.sigChildRemoved.connect(self.childRemoved) + param.sigNameChanged.connect(self.nameChanged) + param.sigLimitsChanged.connect(self.limitsChanged) + param.sigDefaultChanged.connect(self.defaultChanged) + param.sigOptionsChanged.connect(self.optsChanged) + + + opts = param.opts + + ## Generate context menu for renaming/removing parameter + self.contextMenu = QtGui.QMenu() + self.contextMenu.addSeparator() + flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled + if opts.get('renamable', False): + flags |= QtCore.Qt.ItemIsEditable + self.contextMenu.addAction('Rename').triggered.connect(self.editName) + if opts.get('removable', False): + self.contextMenu.addAction("Remove").triggered.connect(self.param.remove) + + ## handle movable / dropEnabled options + if opts.get('movable', False): + flags |= QtCore.Qt.ItemIsDragEnabled + if opts.get('dropEnabled', False): + flags |= QtCore.Qt.ItemIsDropEnabled + self.setFlags(flags) + + ## flag used internally during name editing + self.ignoreNameColumnChange = False + + + def valueChanged(self, param, val): + ## called when the parameter's value has changed + pass + + def isFocusable(self): + """Return True if this item should be included in the tab-focus order""" + return False + + def setFocus(self): + """Give input focus to this item. + Can be reimplemented to display editor widgets, etc. + """ + pass + + def focusNext(self, forward=True): + """Give focus to the next (or previous) focusable item in the parameter tree""" + self.treeWidget().focusNext(self, forward=forward) + + + def treeWidgetChanged(self): + """Called when this item is added or removed from a tree. + Expansion, visibility, and column widgets must all be configured AFTER + the item is added to a tree, not during __init__. + """ + self.setHidden(not self.param.opts.get('visible', True)) + self.setExpanded(self.param.opts.get('expanded', True)) + + def childAdded(self, param, child, pos): + item = child.makeTreeItem(depth=self.depth+1) + self.insertChild(pos, item) + item.treeWidgetChanged() + + for i, ch in enumerate(child): + item.childAdded(child, ch, i) + + def childRemoved(self, param, child): + for i in range(self.childCount()): + item = self.child(i) + if item.param is child: + self.takeChild(i) + break + + def contextMenuEvent(self, ev): + if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False): + return + + self.contextMenu.popup(ev.globalPos()) + + def columnChangedEvent(self, col): + """Called when the text in a column has been edited. + By default, we only use changes to column 0 to rename the parameter. + """ + if col == 0: + if self.ignoreNameColumnChange: + return + try: + newName = self.param.setName(str(self.text(col))) + except: + self.setText(0, self.param.name()) + raise + + try: + self.ignoreNameColumnChange = True + self.nameChanged(self, newName) ## If the parameter rejects the name change, we need to set it back. + finally: + self.ignoreNameColumnChange = False + + def nameChanged(self, param, name): + ## called when the parameter's name has changed. + self.setText(0, name) + + def limitsChanged(self, param, limits): + """Called when the parameter's limits have changed""" + pass + + def defaultChanged(self, param, default): + """Called when the parameter's default value has changed""" + pass + + def optsChanged(self, param, opts): + """Called when any options are changed that are not + name, value, default, or limits""" + #print opts + if 'visible' in opts: + self.setHidden(not opts['visible']) + + def editName(self): + self.treeWidget().editItem(self, 0) + + def selected(self, sel): + """Called when this item has been selected (sel=True) OR deselected (sel=False)""" + pass + + + diff --git a/parametertree/ParameterTree.py b/parametertree/ParameterTree.py new file mode 100644 index 00000000..6f90de07 --- /dev/null +++ b/parametertree/ParameterTree.py @@ -0,0 +1,108 @@ +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.widgets.TreeWidget import TreeWidget +import collections, os, weakref, re +#import functions as fn + + + +class ParameterTree(TreeWidget): + """Widget used to display or control data from a ParameterSet""" + + def __init__(self, parent=None): + TreeWidget.__init__(self, parent) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setHorizontalScrollMode(self.ScrollPerPixel) + self.setAnimated(False) + self.setColumnCount(2) + self.setHeaderLabels(["Parameter", "Value"]) + self.setRootIsDecorated(False) + self.setAlternatingRowColors(True) + self.paramSet = None + self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) + self.itemChanged.connect(self.itemChangedEvent) + self.lastSel = None + self.setRootIsDecorated(False) + + def setParameters(self, param, root=None, depth=0, showTop=True): + item = param.makeTreeItem(depth=depth) + if root is None: + root = self.invisibleRootItem() + ## Hide top-level item + if not showTop: + item.setText(0, '') + item.setSizeHint(0, QtCore.QSize(1,1)) + item.setSizeHint(1, QtCore.QSize(1,1)) + depth -= 1 + root.addChild(item) + item.treeWidgetChanged() + + for ch in param: + self.setParameters(ch, root=item, depth=depth+1) + + def focusNext(self, item, forward=True): + ## Give input focus to the next (or previous) item after 'item' + while True: + parent = item.parent() + if parent is None: + return + nextItem = self.nextFocusableChild(parent, item, forward=forward) + if nextItem is not None: + nextItem.setFocus() + self.setCurrentItem(nextItem) + return + item = parent + + def focusPrevious(self, item): + self.focusNext(item, forward=False) + + def nextFocusableChild(self, root, startItem=None, forward=True): + if startItem is None: + if forward: + index = 0 + else: + index = root.childCount()-1 + else: + if forward: + index = root.indexOfChild(startItem) + 1 + else: + index = root.indexOfChild(startItem) - 1 + + if forward: + inds = range(index, root.childCount()) + else: + inds = range(index, -1, -1) + + for i in inds: + item = root.child(i) + if hasattr(item, 'isFocusable') and item.isFocusable(): + return item + else: + item = self.nextFocusableChild(item, forward=forward) + if item is not None: + return item + return None + + def contextMenuEvent(self, ev): + item = self.currentItem() + if hasattr(item, 'contextMenuEvent'): + item.contextMenuEvent(ev) + + def itemChangedEvent(self, item, col): + if hasattr(item, 'columnChangedEvent'): + item.columnChangedEvent(col) + + def selectionChanged(self, *args): + sel = self.selectedItems() + if len(sel) != 1: + sel = None + if self.lastSel is not None: + self.lastSel.selected(False) + if sel is None: + self.lastSel = None + return + self.lastSel = sel[0] + if hasattr(sel[0], 'selected'): + sel[0].selected(True) + return TreeWidget.selectionChanged(self, *args) + + diff --git a/parametertree/__init__.py b/parametertree/__init__.py new file mode 100644 index 00000000..b5912f57 --- /dev/null +++ b/parametertree/__init__.py @@ -0,0 +1,5 @@ +from Parameter import Parameter, registerParameterType +from ParameterTree import ParameterTree +from ParameterItem import ParameterItem + +import parameterTypes as types \ No newline at end of file diff --git a/parametertree/__main__.py b/parametertree/__main__.py new file mode 100644 index 00000000..a3f0b11a --- /dev/null +++ b/parametertree/__main__.py @@ -0,0 +1,140 @@ +## tests for ParameterTree + +## make sure pyqtgraph is in path +import sys,os +md = os.path.abspath(os.path.dirname(__file__)) +sys.path.append(os.path.join(md, '..', '..')) + +from pyqtgraph.Qt import QtCore, QtGui +import collections, user +app = QtGui.QApplication([]) +import pyqtgraph.parametertree.parameterTypes as pTypes +from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType + + +## test subclassing parameters +## This parameter automatically generates two child parameters which are always reciprocals of each other +class ComplexParameter(Parameter): + def __init__(self, **opts): + opts['type'] = 'bool' + opts['value'] = True + Parameter.__init__(self, **opts) + + self.addChild({'name': 'A = 1/B', 'type': 'float', 'value': 7, 'suffix': 'Hz', 'siPrefix': True}) + self.addChild({'name': 'B = 1/A', 'type': 'float', 'value': 1/7., 'suffix': 's', 'siPrefix': True}) + self.a = self.param('A = 1/B') + self.b = self.param('B = 1/A') + self.a.sigValueChanged.connect(self.aChanged) + self.b.sigValueChanged.connect(self.bChanged) + + def aChanged(self): + try: + self.b.sigValueChanged.disconnect(self.bChanged) + self.b.setValue(1.0 / self.a.value()) + finally: + self.b.sigValueChanged.connect(self.bChanged) + + def bChanged(self): + try: + self.a.sigValueChanged.disconnect(self.aChanged) + self.a.setValue(1.0 / self.b.value()) + finally: + self.a.sigValueChanged.connect(self.aChanged) + + +## test add/remove +## this group includes a menu allowing the user to add new parameters into its child list +class ScalableGroup(pTypes.GroupParameter): + def __init__(self, **opts): + opts['type'] = 'group' + opts['addText'] = "Add" + opts['addList'] = ['str', 'float', 'int'] + pTypes.GroupParameter.__init__(self, **opts) + + def addNew(self, typ): + val = { + 'str': '', + 'float': 0.0, + 'int': 0 + }[typ] + self.addChild(dict(name="ScalableParam %d" % (len(self.childs)+1), type=typ, value=val, removable=True, renamable=True)) + + +## test column spanning (widget sub-item that spans all columns) +class TextParameterItem(pTypes.WidgetParameterItem): + def __init__(self, param, depth): + pTypes.WidgetParameterItem.__init__(self, param, depth) + self.subItem = QtGui.QTreeWidgetItem() + self.addChild(self.subItem) + + def treeWidgetChanged(self): + self.treeWidget().setFirstItemColumnSpanned(self.subItem, True) + self.treeWidget().setItemWidget(self.subItem, 0, self.textBox) + self.setExpanded(True) + + def makeWidget(self): + self.textBox = QtGui.QTextEdit() + self.textBox.setMaximumHeight(100) + self.textBox.value = lambda: str(self.textBox.toPlainText()) + self.textBox.setValue = self.textBox.setPlainText + self.textBox.sigChanged = self.textBox.textChanged + return self.textBox + +class TextParameter(Parameter): + type = 'text' + itemClass = TextParameterItem + +registerParameterType('text', TextParameter) + + + + +params = [ + {'name': 'Group 0', 'type': 'group', 'children': [ + {'name': 'Param 1', 'type': 'int', 'value': 10}, + {'name': 'Param 2', 'type': 'float', 'value': 10}, + ]}, + {'name': 'Group 1', 'type': 'group', 'children': [ + {'name': 'Param 1.1', 'type': 'float', 'value': 1.2e-6, 'dec': True, 'siPrefix': True, 'suffix': 'V'}, + {'name': 'Param 1.2', 'type': 'float', 'value': 1.2e6, 'dec': True, 'siPrefix': True, 'suffix': 'Hz'}, + {'name': 'Group 1.3', 'type': 'group', 'children': [ + {'name': 'Param 1.3.1', 'type': 'int', 'value': 11, 'limits': (-7, 15), 'default': -6}, + {'name': 'Param 1.3.2', 'type': 'float', 'value': 1.2e6, 'dec': True, 'siPrefix': True, 'suffix': 'Hz', 'readonly': True}, + ]}, + {'name': 'Param 1.4', 'type': 'str', 'value': "hi"}, + {'name': 'Param 1.5', 'type': 'list', 'values': [1,2,3], 'value': 2}, + {'name': 'Param 1.6', 'type': 'list', 'values': {"one": 1, "two": 2, "three": 3}, 'value': 2}, + ComplexParameter(name='ComplexParam'), + ScalableGroup(name="ScalableGroup", children=[ + {'name': 'ScalableParam 1', 'type': 'str', 'value': "hi"}, + {'name': 'ScalableParam 2', 'type': 'str', 'value': "hi"}, + + ]) + ]}, + {'name': 'Param 5', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"}, + {'name': 'Param 6', 'type': 'color', 'value': "FF0", 'tip': "This is a color button. It cam be renamed.", 'renamable': True}, + {'name': 'TextParam', 'type': 'text', 'value': 'Some text...'}, +] + +#p = pTypes.ParameterSet("params", params) +p = Parameter(name='params', type='group', children=params) +def change(param, changes): + print "tree changes:" + for param, change, data in changes: + print " [" + '.'.join(p.childPath(param))+ "] ", change, data + +p.sigTreeStateChanged.connect(change) + + +t = ParameterTree() +t.setParameters(p, showTop=False) +t.show() +t.resize(400,600) +t2 = ParameterTree() +t2.setParameters(p, showTop=False) +t2.show() +t2.resize(400,600) + +import sys +if sys.flags.interactive == 0: + app.exec_() diff --git a/parametertree/default.png b/parametertree/default.png new file mode 100644 index 0000000000000000000000000000000000000000..f12394214dadb66bd90e7c671d87a5810c1571a6 GIT binary patch literal 810 zcmV+_1J(SAP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0008<Nkl<Zc-p0s zNla5w6o%jJdo5K4sbU!v1w}BJumLn0qApy(#%Ka0Mhpwa3dX2WNl<7RA{ChhF%~ez zLdz7!0OA6KLR%RvZ3QZzIDjQIrVN%qkGI4&k{DgM_>z<NpZ~ir?_HuQmDq>z1OUPe zo)1aVX}s1#$L?N9pk7vg&d^@u+F#8>SvWgp*|R{@KeW?!3fxz47$#1qCH6_Y)^y5H zFib~-aq$M`CM?fzU}@|U$SOTo#9WkucPXo&vB2K&eIgE|bw0-T(%|)#nK)Sc{AX5X zIWXIK##ob14q*wyDlklNU<++L7^Y&uFc|~-!AO{IKaDvBOR`X$$6{h|77ub^ps^|y z@hyVorLkzxE2BU^9tFCgFqp3K1eL%6WF<}}X}+szD2o9fg+8#Ph}aT;>%R=;NxN6^ zTbv-8ut4|b3g|{6U|~2MCR^ChTgEaKC)ux7-lsJ^PbRA}<CtqtW9Y_N!NK*#p771Z zo-k<0@;8Z6h+?8K-+p<n>nwbcU4XA6=i!6g4>T`XP|J5%#wT3-^=I~qQWyqB2C1H_ z_Me+?4L0-^9XAQ?kmH0TldgX)VE^c=aNbnZcyEq2A2q2uJxpWGE*p|Gavqm`%@Q#W z38dLJis=%!hEL*?QgvCt%%ga6-E0rSv|=_efIkj~u&`hia8X517NcV-o8oKpY%2!R zm>szsnsM8u7~qjeo<Y%bZnMeXpPMgReI}(J&1EJ|@`>x1WMKkXTaknYw+SY5C^YeS z2csdwiC&Q6hH#OEDXr!rU1uJ%@Lh>*$V47J6bmAsBv4|U6>byYGSUbfk$D~#xucHK z{Zv!lu04uMceeV4v#+L{6@Vo{eGhCx%J0&2Sm19dTTu(y$TBP2jv!^5H#OdTc&DoF zfSX$6?4fzaau?mB-@@gq<{k6mUm5H6LvOo$DP@b7ou<~qR$lC4S9y(2GiMgTKheDM oL#k?5npWtHR9tJvD)=vc0s@|Dx>pGq$N&HU07*qoM6N<$f=L-|8vp<R literal 0 HcmV?d00001 diff --git a/parametertree/parameterTypes.py b/parametertree/parameterTypes.py new file mode 100644 index 00000000..2025c601 --- /dev/null +++ b/parametertree/parameterTypes.py @@ -0,0 +1,480 @@ +from pyqtgraph.Qt import QtCore, QtGui +from Parameter import Parameter, registerParameterType +from ParameterItem import ParameterItem +from pyqtgraph.widgets.SpinBox import SpinBox +from pyqtgraph.widgets.ColorButton import ColorButton +import os, collections + +class WidgetParameterItem(ParameterItem): + """ + ParameterTree item with: + - label in second column for displaying value + - simple widget for editing value (displayed instead of label when item is selected) + - button that resets value to default + - provides SpinBox, CheckBox, LineEdit, and ColorButton types + This class can be subclassed by overriding makeWidget() to provide a custom widget. + """ + def __init__(self, param, depth): + ParameterItem.__init__(self, param, depth) + + self.hideWidget = True ## hide edit widget, replace with label when not selected + ## set this to False to keep the editor widget always visible + + + ## build widget into column 1 with a display label and default button. + w = self.makeWidget() + self.widget = w + self.eventProxy = EventProxy(w, self.widgetEventFilter) + + opts = self.param.opts + if 'tip' in opts: + w.setToolTip(opts['tip']) + + self.defaultBtn = QtGui.QPushButton() + self.defaultBtn.setFixedWidth(20) + self.defaultBtn.setFixedHeight(20) + modDir = os.path.dirname(__file__) + self.defaultBtn.setIcon(QtGui.QIcon(os.path.join(modDir, 'default.png'))) + self.defaultBtn.clicked.connect(self.defaultClicked) + + self.displayLabel = QtGui.QLabel() + + layout = QtGui.QHBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(2) + layout.addWidget(w) + layout.addWidget(self.displayLabel) + layout.addWidget(self.defaultBtn) + self.layoutWidget = QtGui.QWidget() + self.layoutWidget.setLayout(layout) + + if w.sigChanged is not None: + w.sigChanged.connect(self.widgetValueChanged) + + if hasattr(w, 'sigChanging'): + w.sigChanging.connect(self.widgetValueChanging) + + ## update value shown in widget. + self.valueChanged(self, opts['value'], force=True) + + + def makeWidget(self): + """ + Return a single widget that should be placed in the second tree column. + The widget must be given three attributes: + sigChanged -- a signal that is emitted when the widget's value is changed + value -- a function that returns the value + setValue -- a function that sets the value + This is a good function to override in subclasses. + """ + opts = self.param.opts + t = opts['type'] + if t == 'int': + defs = { + 'value': 0, 'min': None, 'max': None, 'int': True, + 'step': 1.0, 'minStep': 1.0, 'dec': False, + 'siPrefix': False, 'suffix': '' + } + defs.update(opts) + if 'limits' in opts: + defs['bounds'] = opts['limits'] + w = SpinBox() + w.setOpts(**defs) + w.sigChanged = w.sigValueChanged + w.sigChanging = w.sigValueChanging + elif t == 'float': + defs = { + 'value': 0, 'min': None, 'max': None, + 'step': 1.0, 'dec': False, + 'siPrefix': False, 'suffix': '' + } + defs.update(opts) + if 'limits' in opts: + defs['bounds'] = opts['limits'] + w = SpinBox() + w.setOpts(**defs) + w.sigChanged = w.sigValueChanged + w.sigChanging = w.sigValueChanging + elif t == 'bool': + w = QtGui.QCheckBox() + w.sigChanged = w.toggled + w.value = w.isChecked + w.setValue = w.setChecked + self.hideWidget = False + elif t == 'str': + w = QtGui.QLineEdit() + w.sigChanged = w.editingFinished + w.value = lambda: unicode(w.text()) + w.setValue = lambda v: w.setText(unicode(v)) + w.sigChanging = w.textChanged + elif t == 'color': + w = ColorButton() + w.sigChanged = w.sigColorChanged + w.sigChanging = w.sigColorChanging + w.value = w.color + w.setValue = w.setColor + self.hideWidget = False + w.setFlat(True) + else: + raise Exception("Unknown type '%s'" % unicode(t)) + return w + + def widgetEventFilter(self, obj, ev): + ## filter widget's events + ## catch TAB to change focus + ## catch focusOut to hide editor + if ev.type() == ev.KeyPress: + if ev.key() == QtCore.Qt.Key_Tab: + self.focusNext(forward=True) + return True ## don't let anyone else see this event + elif ev.key() == QtCore.Qt.Key_Backtab: + self.focusNext(forward=False) + return True ## don't let anyone else see this event + + #elif ev.type() == ev.FocusOut: + #self.hideEditor() + return False + + def setFocus(self): + self.showEditor() + + def isFocusable(self): + return self.param.writable() + + def valueChanged(self, param, val, force=False): + ## called when the parameter's value has changed + ParameterItem.valueChanged(self, param, val) + + self.widget.sigChanged.disconnect(self.widgetValueChanged) + try: + if force or val != self.widget.value(): + self.widget.setValue(val) + self.updateDisplayLabel(val) ## always make sure label is updated, even if values match! + finally: + self.widget.sigChanged.connect(self.widgetValueChanged) + self.updateDefaultBtn() + + def updateDefaultBtn(self): + ## enable/disable default btn + self.defaultBtn.setEnabled(not self.param.valueIsDefault() and self.param.writable()) + + def updateDisplayLabel(self, value=None): + """Update the display label to reflect the value of the parameter.""" + if value is None: + value = self.param.value() + opts = self.param.opts + if isinstance(self.widget, QtGui.QAbstractSpinBox): + text = unicode(self.widget.lineEdit().text()) + elif isinstance(self.widget, QtGui.QComboBox): + text = self.widget.currentText() + else: + text = unicode(value) + self.displayLabel.setText(text) + + def widgetValueChanged(self): + ## called when the widget's value has been changed by the user + val = self.widget.value() + newVal = self.param.setValue(val) + + def widgetValueChanging(self): + """ + Called when the widget's value is changing, but not finalized. + For example: editing text before pressing enter or changing focus. + """ + pass + + def selected(self, sel): + """Called when this item has been selected (sel=True) OR deselected (sel=False)""" + ParameterItem.selected(self, sel) + + if self.widget is None: + return + if sel and self.param.writable(): + self.showEditor() + elif self.hideWidget: + self.hideEditor() + + def showEditor(self): + self.widget.show() + self.displayLabel.hide() + self.widget.setFocus(QtCore.Qt.OtherFocusReason) + + def hideEditor(self): + self.widget.hide() + self.displayLabel.show() + + def limitsChanged(self, param, limits): + """Called when the parameter's limits have changed""" + ParameterItem.limitsChanged(self, param, limits) + + t = self.param.opts['type'] + if t == 'int' or t == 'float': + self.widget.setOpts(bounds=limits) + else: + return ## don't know what to do with any other types.. + + def defaultChanged(self, param, value): + self.updateDefaultBtn() + + def treeWidgetChanged(self): + """Called when this item is added or removed from a tree.""" + ParameterItem.treeWidgetChanged(self) + + ## add all widgets for this item into the tree + if self.widget is not None: + tree = self.treeWidget() + if tree is None: + return + tree.setItemWidget(self, 1, self.layoutWidget) + self.displayLabel.hide() + self.selected(False) + + def defaultClicked(self): + self.param.setToDefault() + + def optsChanged(self, param, opts): + """Called when any options are changed that are not + name, value, default, or limits""" + #print "opts changed:", opts + ParameterItem.optsChanged(self, param, opts) + + if 'readonly' in opts: + self.updateDefaultBtn() + + ## If widget is a SpinBox, pass options straight through + if isinstance(self.widget, SpinBox): + if 'units' in opts and 'suffix' not in opts: + opts['suffix'] = opts['units'] + self.widget.setOpts(**opts) + self.updateDisplayLabel() + +class EventProxy(QtCore.QObject): + def __init__(self, qobj, callback): + QtCore.QObject.__init__(self) + self.callback = callback + qobj.installEventFilter(self) + + def eventFilter(self, obj, ev): + return self.callback(obj, ev) + + + + +class SimpleParameter(Parameter): + itemClass = WidgetParameterItem + +registerParameterType('int', SimpleParameter, override=True) +registerParameterType('float', SimpleParameter, override=True) +registerParameterType('bool', SimpleParameter, override=True) +registerParameterType('str', SimpleParameter, override=True) +registerParameterType('color', SimpleParameter, override=True) + + + + +class GroupParameterItem(ParameterItem): + """ + Group parameters are used mainly as a generic parent item that holds (and groups!) a set + of child parameters. It also provides a simple mechanism for displaying a button or combo + that can be used to add new parameters to the group. + """ + def __init__(self, param, depth): + ParameterItem.__init__(self, param, depth) + if depth == 0: + for c in [0,1]: + self.setBackground(c, QtGui.QBrush(QtGui.QColor(100,100,100))) + self.setForeground(c, QtGui.QBrush(QtGui.QColor(220,220,255))) + font = self.font(c) + font.setBold(True) + font.setPointSize(font.pointSize()+1) + self.setFont(c, font) + self.setSizeHint(0, QtCore.QSize(0, 25)) + else: + for c in [0,1]: + self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220))) + font = self.font(c) + font.setBold(True) + #font.setPointSize(font.pointSize()+1) + self.setFont(c, font) + self.setSizeHint(0, QtCore.QSize(0, 20)) + + self.addItem = None + if 'addText' in param.opts: + addText = param.opts['addText'] + if 'addList' in param.opts: + self.addWidget = QtGui.QComboBox() + self.addWidget.addItem(addText) + for t in param.opts['addList']: + self.addWidget.addItem(t) + self.addWidget.currentIndexChanged.connect(self.addChanged) + else: + self.addWidget = QtGui.QPushButton(addText) + self.addWidget.clicked.connect(self.addClicked) + w = QtGui.QWidget() + l = QtGui.QHBoxLayout() + l.setContentsMargins(0,0,0,0) + w.setLayout(l) + l.addWidget(self.addWidget) + l.addItem(QtGui.QSpacerItem(200, 10, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)) + self.addWidgetBox = w + self.addItem = QtGui.QTreeWidgetItem([]) + self.addItem.setFlags(QtCore.Qt.ItemIsEnabled) + ParameterItem.addChild(self, self.addItem) + + def addClicked(self): + """Called when "add new" button is clicked + The parameter MUST have an 'addNew' method defined. + """ + self.param.addNew() + + def addChanged(self): + """Called when "add new" combo is changed + The parameter MUST have an 'addNew' method defined. + """ + if self.addWidget.currentIndex() == 0: + return + typ = unicode(self.addWidget.currentText()) + self.param.addNew(typ) + self.addWidget.setCurrentIndex(0) + + def treeWidgetChanged(self): + ParameterItem.treeWidgetChanged(self) + if self.addItem is not None: + self.treeWidget().setItemWidget(self.addItem, 0, self.addWidgetBox) + self.treeWidget().setFirstItemColumnSpanned(self.addItem, True) + + def addChild(self, child): ## make sure added childs are actually inserted before add btn + if self.addItem is not None: + ParameterItem.insertChild(self, self.childCount()-1, child) + else: + ParameterItem.addChild(self, child) + +class GroupParameter(Parameter): + """ + Group parameters are used mainly as a generic parent item that holds (and groups!) a set + of child parameters. It also provides a simple mechanism for displaying a button or combo + that can be used to add new parameters to the group. + """ + type = 'group' + itemClass = GroupParameterItem + + def addNew(self, typ=None): + raise Exception("Must override this function in subclass.") + +registerParameterType('group', GroupParameter, override=True) + + + + + +class ListParameterItem(WidgetParameterItem): + """ + WidgetParameterItem subclass providing comboBox that lets the user select from a list of options. + + """ + def __init__(self, param, depth): + WidgetParameterItem.__init__(self, param, depth) + + def makeWidget(self): + opts = self.param.opts + t = opts['type'] + w = QtGui.QComboBox() + w.setMaximumHeight(20) ## set to match height of spin box and line edit + w.sigChanged = w.currentIndexChanged + w.value = self.value + w.setValue = self.setValue + self.widget = w ## needs to be set before limits are changed + self.limitsChanged(self.param, self.param.opts['limits']) + if len(self.forward) > 0: + self.setValue(self.param.value()) + return w + + def value(self): + #vals = self.param.opts['limits'] + key = unicode(self.widget.currentText()) + #if isinstance(vals, dict): + #return vals[key] + #else: + #return key + print key, self.forward + return self.forward[key] + + def setValue(self, val): + #vals = self.param.opts['limits'] + #if isinstance(vals, dict): + #key = None + #for k,v in vals.iteritems(): + #if v == val: + #key = k + #if key is None: + #raise Exception("Value '%s' not allowed." % val) + #else: + #key = unicode(val) + if val not in self.reverse: + self.widget.setCurrentIndex(0) + else: + key = self.reverse[val] + ind = self.widget.findText(key) + self.widget.setCurrentIndex(ind) + + def limitsChanged(self, param, limits): + # set up forward / reverse mappings for name:value + self.forward = collections.OrderedDict() ## name: value + self.reverse = collections.OrderedDict() ## value: name + if isinstance(limits, dict): + for k, v in limits.iteritems(): + self.forward[k] = v + self.reverse[v] = k + else: + for v in limits: + n = unicode(v) + self.forward[n] = v + self.reverse[v] = n + + try: + self.widget.blockSignals(True) + val = unicode(self.widget.currentText()) + self.widget.clear() + for k in self.forward: + self.widget.addItem(k) + if k == val: + self.widget.setCurrentIndex(self.widget.count()-1) + + finally: + self.widget.blockSignals(False) + + + +class ListParameter(Parameter): + type = 'list' + itemClass = ListParameterItem + + def __init__(self, **opts): + self.forward = collections.OrderedDict() ## name: value + self.reverse = collections.OrderedDict() ## value: name + if 'values' in opts: + opts['limits'] = opts['values'] + Parameter.__init__(self, **opts) + + def setLimits(self, limits): + self.forward = collections.OrderedDict() ## name: value + self.reverse = collections.OrderedDict() ## value: name + if isinstance(limits, dict): + for k, v in limits.iteritems(): + self.forward[k] = v + self.reverse[v] = k + else: + for v in limits: + n = unicode(v) + self.forward[n] = v + self.reverse[v] = n + + Parameter.setLimits(self, limits) + #print self.name(), self.value(), limits + if self.value() not in self.reverse and len(self.reverse) > 0: + self.setValue(self.reverse.keys()[0]) + + +registerParameterType('list', ListParameter, override=True) + + diff --git a/plotConfigTemplate.py b/plotConfigTemplate.py deleted file mode 100644 index e0063b14..00000000 --- a/plotConfigTemplate.py +++ /dev/null @@ -1,295 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './lib/util/pyqtgraph/plotConfigTemplate.ui' -# -# Created: Wed May 18 20:44:20 2011 -# by: PyQt4 UI code generator 4.8.3 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(250, 340) - Form.setMaximumSize(QtCore.QSize(250, 350)) - self.gridLayout_3 = QtGui.QGridLayout(Form) - self.gridLayout_3.setMargin(0) - self.gridLayout_3.setSpacing(0) - self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - self.tabWidget = QtGui.QTabWidget(Form) - self.tabWidget.setMaximumSize(QtCore.QSize(16777215, 16777215)) - self.tabWidget.setObjectName(_fromUtf8("tabWidget")) - self.tab = QtGui.QWidget() - self.tab.setObjectName(_fromUtf8("tab")) - self.verticalLayout = QtGui.QVBoxLayout(self.tab) - self.verticalLayout.setSpacing(0) - self.verticalLayout.setMargin(0) - self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) - self.groupBox = QtGui.QGroupBox(self.tab) - self.groupBox.setObjectName(_fromUtf8("groupBox")) - self.gridLayout = QtGui.QGridLayout(self.groupBox) - self.gridLayout.setMargin(0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.xManualRadio = QtGui.QRadioButton(self.groupBox) - self.xManualRadio.setObjectName(_fromUtf8("xManualRadio")) - self.gridLayout.addWidget(self.xManualRadio, 0, 0, 1, 1) - self.xMinText = QtGui.QLineEdit(self.groupBox) - self.xMinText.setObjectName(_fromUtf8("xMinText")) - self.gridLayout.addWidget(self.xMinText, 0, 1, 1, 1) - self.xMaxText = QtGui.QLineEdit(self.groupBox) - self.xMaxText.setObjectName(_fromUtf8("xMaxText")) - self.gridLayout.addWidget(self.xMaxText, 0, 2, 1, 1) - self.xAutoRadio = QtGui.QRadioButton(self.groupBox) - self.xAutoRadio.setChecked(True) - self.xAutoRadio.setObjectName(_fromUtf8("xAutoRadio")) - self.gridLayout.addWidget(self.xAutoRadio, 1, 0, 1, 1) - self.xAutoPercentSpin = QtGui.QSpinBox(self.groupBox) - self.xAutoPercentSpin.setEnabled(True) - self.xAutoPercentSpin.setMinimum(1) - self.xAutoPercentSpin.setMaximum(100) - self.xAutoPercentSpin.setSingleStep(1) - self.xAutoPercentSpin.setProperty(_fromUtf8("value"), 100) - self.xAutoPercentSpin.setObjectName(_fromUtf8("xAutoPercentSpin")) - self.gridLayout.addWidget(self.xAutoPercentSpin, 1, 1, 1, 2) - self.xLinkCombo = QtGui.QComboBox(self.groupBox) - self.xLinkCombo.setObjectName(_fromUtf8("xLinkCombo")) - self.gridLayout.addWidget(self.xLinkCombo, 2, 1, 1, 2) - self.xMouseCheck = QtGui.QCheckBox(self.groupBox) - self.xMouseCheck.setChecked(True) - self.xMouseCheck.setObjectName(_fromUtf8("xMouseCheck")) - self.gridLayout.addWidget(self.xMouseCheck, 3, 1, 1, 1) - self.xLogCheck = QtGui.QCheckBox(self.groupBox) - self.xLogCheck.setObjectName(_fromUtf8("xLogCheck")) - self.gridLayout.addWidget(self.xLogCheck, 3, 0, 1, 1) - self.label = QtGui.QLabel(self.groupBox) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout.addWidget(self.label, 2, 0, 1, 1) - self.verticalLayout.addWidget(self.groupBox) - self.groupBox_2 = QtGui.QGroupBox(self.tab) - self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) - self.gridLayout_2 = QtGui.QGridLayout(self.groupBox_2) - self.gridLayout_2.setMargin(0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.yManualRadio = QtGui.QRadioButton(self.groupBox_2) - self.yManualRadio.setObjectName(_fromUtf8("yManualRadio")) - self.gridLayout_2.addWidget(self.yManualRadio, 0, 0, 1, 1) - self.yMinText = QtGui.QLineEdit(self.groupBox_2) - self.yMinText.setObjectName(_fromUtf8("yMinText")) - self.gridLayout_2.addWidget(self.yMinText, 0, 1, 1, 1) - self.yMaxText = QtGui.QLineEdit(self.groupBox_2) - self.yMaxText.setObjectName(_fromUtf8("yMaxText")) - self.gridLayout_2.addWidget(self.yMaxText, 0, 2, 1, 1) - self.yAutoRadio = QtGui.QRadioButton(self.groupBox_2) - self.yAutoRadio.setChecked(True) - self.yAutoRadio.setObjectName(_fromUtf8("yAutoRadio")) - self.gridLayout_2.addWidget(self.yAutoRadio, 1, 0, 1, 1) - self.yAutoPercentSpin = QtGui.QSpinBox(self.groupBox_2) - self.yAutoPercentSpin.setEnabled(True) - self.yAutoPercentSpin.setMinimum(1) - self.yAutoPercentSpin.setMaximum(100) - self.yAutoPercentSpin.setSingleStep(1) - self.yAutoPercentSpin.setProperty(_fromUtf8("value"), 100) - self.yAutoPercentSpin.setObjectName(_fromUtf8("yAutoPercentSpin")) - self.gridLayout_2.addWidget(self.yAutoPercentSpin, 1, 1, 1, 2) - self.yLinkCombo = QtGui.QComboBox(self.groupBox_2) - self.yLinkCombo.setObjectName(_fromUtf8("yLinkCombo")) - self.gridLayout_2.addWidget(self.yLinkCombo, 2, 1, 1, 2) - self.yMouseCheck = QtGui.QCheckBox(self.groupBox_2) - self.yMouseCheck.setChecked(True) - self.yMouseCheck.setObjectName(_fromUtf8("yMouseCheck")) - self.gridLayout_2.addWidget(self.yMouseCheck, 3, 1, 1, 1) - self.yLogCheck = QtGui.QCheckBox(self.groupBox_2) - self.yLogCheck.setObjectName(_fromUtf8("yLogCheck")) - self.gridLayout_2.addWidget(self.yLogCheck, 3, 0, 1, 1) - self.label_2 = QtGui.QLabel(self.groupBox_2) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.gridLayout_2.addWidget(self.label_2, 2, 0, 1, 1) - self.verticalLayout.addWidget(self.groupBox_2) - self.tabWidget.addTab(self.tab, _fromUtf8("")) - self.tab_2 = QtGui.QWidget() - self.tab_2.setObjectName(_fromUtf8("tab_2")) - self.verticalLayout_2 = QtGui.QVBoxLayout(self.tab_2) - self.verticalLayout_2.setSpacing(0) - self.verticalLayout_2.setMargin(0) - self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) - self.powerSpectrumGroup = QtGui.QGroupBox(self.tab_2) - self.powerSpectrumGroup.setCheckable(True) - self.powerSpectrumGroup.setChecked(False) - self.powerSpectrumGroup.setObjectName(_fromUtf8("powerSpectrumGroup")) - self.verticalLayout_2.addWidget(self.powerSpectrumGroup) - self.decimateGroup = QtGui.QGroupBox(self.tab_2) - self.decimateGroup.setCheckable(True) - self.decimateGroup.setObjectName(_fromUtf8("decimateGroup")) - self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) - self.gridLayout_4.setMargin(0) - self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) - self.manualDecimateRadio = QtGui.QRadioButton(self.decimateGroup) - self.manualDecimateRadio.setChecked(True) - self.manualDecimateRadio.setObjectName(_fromUtf8("manualDecimateRadio")) - self.gridLayout_4.addWidget(self.manualDecimateRadio, 0, 0, 1, 1) - self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) - self.downsampleSpin.setMinimum(1) - self.downsampleSpin.setMaximum(100000) - self.downsampleSpin.setProperty(_fromUtf8("value"), 1) - self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin")) - self.gridLayout_4.addWidget(self.downsampleSpin, 0, 1, 1, 1) - self.autoDecimateRadio = QtGui.QRadioButton(self.decimateGroup) - self.autoDecimateRadio.setChecked(False) - self.autoDecimateRadio.setObjectName(_fromUtf8("autoDecimateRadio")) - self.gridLayout_4.addWidget(self.autoDecimateRadio, 1, 0, 1, 1) - self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) - self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck")) - self.gridLayout_4.addWidget(self.maxTracesCheck, 2, 0, 1, 1) - self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) - self.maxTracesSpin.setObjectName(_fromUtf8("maxTracesSpin")) - self.gridLayout_4.addWidget(self.maxTracesSpin, 2, 1, 1, 1) - self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) - self.forgetTracesCheck.setObjectName(_fromUtf8("forgetTracesCheck")) - self.gridLayout_4.addWidget(self.forgetTracesCheck, 3, 0, 1, 2) - self.verticalLayout_2.addWidget(self.decimateGroup) - self.averageGroup = QtGui.QGroupBox(self.tab_2) - self.averageGroup.setCheckable(True) - self.averageGroup.setChecked(False) - self.averageGroup.setObjectName(_fromUtf8("averageGroup")) - self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) - self.gridLayout_5.setMargin(0) - self.gridLayout_5.setSpacing(0) - self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) - self.avgParamList = QtGui.QListWidget(self.averageGroup) - self.avgParamList.setObjectName(_fromUtf8("avgParamList")) - self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) - self.verticalLayout_2.addWidget(self.averageGroup) - self.tabWidget.addTab(self.tab_2, _fromUtf8("")) - self.tab_3 = QtGui.QWidget() - self.tab_3.setObjectName(_fromUtf8("tab_3")) - self.verticalLayout_3 = QtGui.QVBoxLayout(self.tab_3) - self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) - self.alphaGroup = QtGui.QGroupBox(self.tab_3) - self.alphaGroup.setCheckable(True) - self.alphaGroup.setObjectName(_fromUtf8("alphaGroup")) - self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) - self.autoAlphaCheck.setChecked(False) - self.autoAlphaCheck.setObjectName(_fromUtf8("autoAlphaCheck")) - self.horizontalLayout.addWidget(self.autoAlphaCheck) - self.alphaSlider = QtGui.QSlider(self.alphaGroup) - self.alphaSlider.setMaximum(1000) - self.alphaSlider.setProperty(_fromUtf8("value"), 1000) - self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.alphaSlider.setObjectName(_fromUtf8("alphaSlider")) - self.horizontalLayout.addWidget(self.alphaSlider) - self.verticalLayout_3.addWidget(self.alphaGroup) - self.gridGroup = QtGui.QGroupBox(self.tab_3) - self.gridGroup.setCheckable(True) - self.gridGroup.setChecked(False) - self.gridGroup.setObjectName(_fromUtf8("gridGroup")) - self.verticalLayout_4 = QtGui.QVBoxLayout(self.gridGroup) - self.verticalLayout_4.setObjectName(_fromUtf8("verticalLayout_4")) - self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) - self.gridAlphaSlider.setMaximum(255) - self.gridAlphaSlider.setProperty(_fromUtf8("value"), 70) - self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.gridAlphaSlider.setObjectName(_fromUtf8("gridAlphaSlider")) - self.verticalLayout_4.addWidget(self.gridAlphaSlider) - self.verticalLayout_3.addWidget(self.gridGroup) - self.pointsGroup = QtGui.QGroupBox(self.tab_3) - self.pointsGroup.setCheckable(True) - self.pointsGroup.setObjectName(_fromUtf8("pointsGroup")) - self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) - self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) - self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) - self.autoPointsCheck.setChecked(True) - self.autoPointsCheck.setObjectName(_fromUtf8("autoPointsCheck")) - self.verticalLayout_5.addWidget(self.autoPointsCheck) - self.verticalLayout_3.addWidget(self.pointsGroup) - spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.verticalLayout_3.addItem(spacerItem) - self.tabWidget.addTab(self.tab_3, _fromUtf8("")) - self.tab_4 = QtGui.QWidget() - self.tab_4.setObjectName(_fromUtf8("tab_4")) - self.gridLayout_7 = QtGui.QGridLayout(self.tab_4) - self.gridLayout_7.setObjectName(_fromUtf8("gridLayout_7")) - spacerItem1 = QtGui.QSpacerItem(59, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem1, 0, 0, 1, 1) - self.gridLayout_6 = QtGui.QGridLayout() - self.gridLayout_6.setObjectName(_fromUtf8("gridLayout_6")) - self.saveSvgBtn = QtGui.QPushButton(self.tab_4) - self.saveSvgBtn.setObjectName(_fromUtf8("saveSvgBtn")) - self.gridLayout_6.addWidget(self.saveSvgBtn, 0, 0, 1, 1) - self.saveImgBtn = QtGui.QPushButton(self.tab_4) - self.saveImgBtn.setObjectName(_fromUtf8("saveImgBtn")) - self.gridLayout_6.addWidget(self.saveImgBtn, 1, 0, 1, 1) - self.saveMaBtn = QtGui.QPushButton(self.tab_4) - self.saveMaBtn.setObjectName(_fromUtf8("saveMaBtn")) - self.gridLayout_6.addWidget(self.saveMaBtn, 2, 0, 1, 1) - self.saveCsvBtn = QtGui.QPushButton(self.tab_4) - self.saveCsvBtn.setObjectName(_fromUtf8("saveCsvBtn")) - self.gridLayout_6.addWidget(self.saveCsvBtn, 3, 0, 1, 1) - self.gridLayout_7.addLayout(self.gridLayout_6, 0, 1, 1, 1) - spacerItem2 = QtGui.QSpacerItem(59, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem2, 0, 2, 1, 1) - spacerItem3 = QtGui.QSpacerItem(20, 211, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_7.addItem(spacerItem3, 1, 1, 1, 1) - self.tabWidget.addTab(self.tab_4, _fromUtf8("")) - self.gridLayout_3.addWidget(self.tabWidget, 0, 0, 1, 1) - - self.retranslateUi(Form) - self.tabWidget.setCurrentIndex(0) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.groupBox.setTitle(QtGui.QApplication.translate("Form", "X Axis", None, QtGui.QApplication.UnicodeUTF8)) - self.xManualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) - self.xMinText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.xMaxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.xAutoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.xAutoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) - self.xMouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse", None, QtGui.QApplication.UnicodeUTF8)) - self.xLogCheck.setText(QtGui.QApplication.translate("Form", "Log", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "Link with:", None, QtGui.QApplication.UnicodeUTF8)) - self.groupBox_2.setTitle(QtGui.QApplication.translate("Form", "Y Axis", None, QtGui.QApplication.UnicodeUTF8)) - self.yManualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) - self.yMinText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.yMaxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.yAutoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.yAutoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) - self.yMouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse", None, QtGui.QApplication.UnicodeUTF8)) - self.yLogCheck.setText(QtGui.QApplication.translate("Form", "Log", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("Form", "Link with:", None, QtGui.QApplication.UnicodeUTF8)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QtGui.QApplication.translate("Form", "Scale", None, QtGui.QApplication.UnicodeUTF8)) - self.powerSpectrumGroup.setTitle(QtGui.QApplication.translate("Form", "Power Spectrum", None, QtGui.QApplication.UnicodeUTF8)) - self.decimateGroup.setTitle(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) - self.manualDecimateRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) - self.autoDecimateRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) - self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) - self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) - self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) - self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QtGui.QApplication.translate("Form", "Data", None, QtGui.QApplication.UnicodeUTF8)) - self.alphaGroup.setTitle(QtGui.QApplication.translate("Form", "Alpha", None, QtGui.QApplication.UnicodeUTF8)) - self.autoAlphaCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.gridGroup.setTitle(QtGui.QApplication.translate("Form", "Grid", None, QtGui.QApplication.UnicodeUTF8)) - self.pointsGroup.setTitle(QtGui.QApplication.translate("Form", "Points", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPointsCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), QtGui.QApplication.translate("Form", "Display", None, QtGui.QApplication.UnicodeUTF8)) - self.saveSvgBtn.setText(QtGui.QApplication.translate("Form", "SVG", None, QtGui.QApplication.UnicodeUTF8)) - self.saveImgBtn.setText(QtGui.QApplication.translate("Form", "Image", None, QtGui.QApplication.UnicodeUTF8)) - self.saveMaBtn.setText(QtGui.QApplication.translate("Form", "MetaArray", None, QtGui.QApplication.UnicodeUTF8)) - self.saveCsvBtn.setText(QtGui.QApplication.translate("Form", "CSV", None, QtGui.QApplication.UnicodeUTF8)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/plotConfigTemplate.ui b/plotConfigTemplate.ui deleted file mode 100644 index 7baeb337..00000000 --- a/plotConfigTemplate.ui +++ /dev/null @@ -1,563 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>Form</class> - <widget class="QWidget" name="Form"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>250</width> - <height>340</height> - </rect> - </property> - <property name="maximumSize"> - <size> - <width>250</width> - <height>350</height> - </size> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <property name="margin"> - <number>0</number> - </property> - <property name="spacing"> - <number>0</number> - </property> - <item row="0" column="0"> - <widget class="QTabWidget" name="tabWidget"> - <property name="maximumSize"> - <size> - <width>16777215</width> - <height>16777215</height> - </size> - </property> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string>Scale</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="spacing"> - <number>0</number> - </property> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>X Axis</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <property name="margin"> - <number>0</number> - </property> - <property name="spacing"> - <number>0</number> - </property> - <item row="0" column="0"> - <widget class="QRadioButton" name="xManualRadio"> - <property name="text"> - <string>Manual</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="xMinText"> - <property name="text"> - <string>0</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLineEdit" name="xMaxText"> - <property name="text"> - <string>0</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QRadioButton" name="xAutoRadio"> - <property name="text"> - <string>Auto</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="1" colspan="2"> - <widget class="QSpinBox" name="xAutoPercentSpin"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="suffix"> - <string>%</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>100</number> - </property> - <property name="singleStep"> - <number>1</number> - </property> - <property name="value"> - <number>100</number> - </property> - </widget> - </item> - <item row="2" column="1" colspan="2"> - <widget class="QComboBox" name="xLinkCombo"/> - </item> - <item row="3" column="1"> - <widget class="QCheckBox" name="xMouseCheck"> - <property name="text"> - <string>Mouse</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QCheckBox" name="xLogCheck"> - <property name="text"> - <string>Log</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Link with:</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Y Axis</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <property name="margin"> - <number>0</number> - </property> - <property name="spacing"> - <number>0</number> - </property> - <item row="0" column="0"> - <widget class="QRadioButton" name="yManualRadio"> - <property name="text"> - <string>Manual</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="yMinText"> - <property name="text"> - <string>0</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLineEdit" name="yMaxText"> - <property name="text"> - <string>0</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QRadioButton" name="yAutoRadio"> - <property name="text"> - <string>Auto</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="1" colspan="2"> - <widget class="QSpinBox" name="yAutoPercentSpin"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="suffix"> - <string>%</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>100</number> - </property> - <property name="singleStep"> - <number>1</number> - </property> - <property name="value"> - <number>100</number> - </property> - </widget> - </item> - <item row="2" column="1" colspan="2"> - <widget class="QComboBox" name="yLinkCombo"/> - </item> - <item row="3" column="1"> - <widget class="QCheckBox" name="yMouseCheck"> - <property name="text"> - <string>Mouse</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QCheckBox" name="yLogCheck"> - <property name="text"> - <string>Log</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Link with:</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="tab_2"> - <attribute name="title"> - <string>Data</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <property name="spacing"> - <number>0</number> - </property> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QGroupBox" name="powerSpectrumGroup"> - <property name="title"> - <string>Power Spectrum</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <widget class="QGroupBox" name="decimateGroup"> - <property name="title"> - <string>Downsample</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <layout class="QGridLayout" name="gridLayout_4"> - <property name="margin"> - <number>0</number> - </property> - <property name="spacing"> - <number>0</number> - </property> - <item row="0" column="0"> - <widget class="QRadioButton" name="manualDecimateRadio"> - <property name="text"> - <string>Manual</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QSpinBox" name="downsampleSpin"> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>100000</number> - </property> - <property name="value"> - <number>1</number> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QRadioButton" name="autoDecimateRadio"> - <property name="text"> - <string>Auto</string> - </property> - <property name="checked"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QCheckBox" name="maxTracesCheck"> - <property name="toolTip"> - <string>If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.</string> - </property> - <property name="text"> - <string>Max Traces:</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QSpinBox" name="maxTracesSpin"> - <property name="toolTip"> - <string>If multiple curves are displayed in this plot, check "Max Traces" and set this value to limit the number of traces that are displayed.</string> - </property> - </widget> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QCheckBox" name="forgetTracesCheck"> - <property name="toolTip"> - <string>If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).</string> - </property> - <property name="text"> - <string>Forget hidden traces</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="averageGroup"> - <property name="toolTip"> - <string>Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).</string> - </property> - <property name="title"> - <string>Average</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_5"> - <property name="margin"> - <number>0</number> - </property> - <property name="spacing"> - <number>0</number> - </property> - <item row="0" column="0"> - <widget class="QListWidget" name="avgParamList"/> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="tab_3"> - <attribute name="title"> - <string>Display</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QGroupBox" name="alphaGroup"> - <property name="title"> - <string>Alpha</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QCheckBox" name="autoAlphaCheck"> - <property name="text"> - <string>Auto</string> - </property> - <property name="checked"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <widget class="QSlider" name="alphaSlider"> - <property name="maximum"> - <number>1000</number> - </property> - <property name="value"> - <number>1000</number> - </property> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="gridGroup"> - <property name="title"> - <string>Grid</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>false</bool> - </property> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QSlider" name="gridAlphaSlider"> - <property name="maximum"> - <number>255</number> - </property> - <property name="value"> - <number>70</number> - </property> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="pointsGroup"> - <property name="title"> - <string>Points</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <widget class="QCheckBox" name="autoPointsCheck"> - <property name="text"> - <string>Auto</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer_2"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="tab_4"> - <attribute name="title"> - <string>Save</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_7"> - <item row="0" column="0"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>59</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="1"> - <layout class="QGridLayout" name="gridLayout_6"> - <item row="0" column="0"> - <widget class="QPushButton" name="saveSvgBtn"> - <property name="text"> - <string>SVG</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QPushButton" name="saveImgBtn"> - <property name="text"> - <string>Image</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QPushButton" name="saveMaBtn"> - <property name="text"> - <string>MetaArray</string> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QPushButton" name="saveCsvBtn"> - <property name="text"> - <string>CSV</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="2"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>59</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="1"> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>211</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/ptime.pyc b/ptime.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3761c6f2d6f56dfcf0adb2d955187786880931e3 GIT binary patch literal 1273 zcmcJP&u-H|5XRR|<2I)Jk&uAn99@aVE$R^=P(h*+sZf$bl}og-C-FA+uDiQ#8m{no zJO*#T1Hg=F6LAIAvd7=9ch}$icI?j2hjIP!#~>58M@aX#bh$4Sav>%Z1CfWK48(-Q zA03KGhdU?THijbah;KAF68TOmc17M57>LU~v5bVR1$M+`Pkf~@ajQet7>gIQ1V&;K z3LOdDWkKtXl88Ngrx4d(RoJiBa0u|(Vy2uj8n_eYjm|u`Dw!h~cc^rZHB!(X5?7d} z%%PCi2s1>QBbO92X=&^nW^1U|zA*YAJ~j2)szu@9)zR@0z^7)3a4M@>F0?6S{9ZY4 z)vWQDLqn@u7@UpMi{oP`RfgJOA4Z6qhQIMJHx?>GZ&G@0D#<SUvHC^F`CqJ0InPHF zybzZGL-C<_K1u@a=RFvrZ!CE)5VFQv&aBN{X>gbsojX`5UqC_rq2BOGR>Z=r99*Mi zU@xI@2m`6*0&SmHNDjaoa9$FJQhG96k=YHugMQLsTf1yz?RbXolFmw#EqzRCG=4W6 zPseA24+*uzHn<CFriH06Es12hGIp6(QY$wt<t)uij;YM9PSR4%(#ESYt=Ct6VP##U zcthArbZNiO=k~!}8MKBdxiJ}HZjLKfgd3;~u7%1LH&AbZuBh){fi_y*{0~j`IiySY z9S*z$+}eiw#kNQ9;uM(MQ_{EGJa01LOdim^WjmebCYw&%2ek1+cDwa`ZZr<tRCwZ$ zE5h^AcO^7r{+_?yj7rP;ZF;S`{~TXeCT~i7)BZr7k&mdeAM6KF5QV*NZ|5=TCt)xA E4YN#Cb^rhX literal 0 HcmV?d00001 diff --git a/widgets/CheckTable.py b/widgets/CheckTable.py new file mode 100644 index 00000000..5e2fc1e6 --- /dev/null +++ b/widgets/CheckTable.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +import VerticalLabel + +__all__ = ['CheckTable'] + +class CheckTable(QtGui.QWidget): + + sigStateChanged = QtCore.Signal(object, object, object) # (row, col, state) + + def __init__(self, columns): + QtGui.QWidget.__init__(self) + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.setLayout(self.layout) + self.headers = [] + self.columns = columns + col = 1 + for c in columns: + label = VerticalLabel.VerticalLabel(c, orientation='vertical') + self.headers.append(label) + self.layout.addWidget(label, 0, col) + col += 1 + + self.rowNames = [] + self.rowWidgets = [] + + + def updateRows(self, rows): + for r in self.rowNames[:]: + if r not in rows: + self.removeRow(r) + for r in rows: + if r not in self.rowNames: + self.addRow(r) + + def addRow(self, name): + label = QtGui.QLabel(name) + row = len(self.rowNames)+1 + self.layout.addWidget(label, row, 0) + checks = [] + col = 1 + for c in self.columns: + check = QtGui.QCheckBox('') + check.col = c + check.row = name + self.layout.addWidget(check, row, col) + checks.append(check) + col += 1 + #QtCore.QObject.connect(check, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) + check.stateChanged.connect(self.checkChanged) + self.rowNames.append(name) + self.rowWidgets.append([label] + checks) + + def removeRow(self, name): + row = self.rowNames.index(name) + self.rowNames.pop(row) + for w in self.rowWidgets[row]: + w.setParent(None) + #QtCore.QObject.disconnect(w, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) + if isinstance(w, QtGui.QCheckBox): + w.stateChanged.disconnect(self.checkChanged) + self.rowWidgets.pop(row) + for i in range(row, len(self.rowNames)): + widgets = self.rowWidgets[i] + for j in range(len(widgets)): + widgets[j].setParent(None) + self.layout.addWidget(widgets[j], i+1, j) + + def checkChanged(self, state): + check = QtCore.QObject.sender(self) + #self.emit(QtCore.SIGNAL('stateChanged'), check.row, check.col, state) + self.sigStateChanged.emit(check.row, check.col, state) + + def saveState(self): + rows = [] + for i in range(len(self.rowNames)): + row = [self.rowNames[i]] + [c.isChecked() for c in self.rowWidgets[i][1:]] + rows.append(row) + return {'cols': self.columns, 'rows': rows} + + def restoreState(self, state): + rows = [r[0] for r in state['rows']] + self.updateRows(rows) + for r in state['rows']: + rowNum = self.rowNames.index(r[0]) + for i in range(1, len(r)): + self.rowWidgets[rowNum][i].setChecked(r[i]) + \ No newline at end of file diff --git a/ColorButton.py b/widgets/ColorButton.py similarity index 83% rename from ColorButton.py rename to widgets/ColorButton.py index cc967c3b..7e677f73 100644 --- a/ColorButton.py +++ b/widgets/ColorButton.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from PyQt4 import QtGui, QtCore -if not hasattr(QtCore, 'Signal'): - QtCore.Signal = QtCore.pyqtSignal -import functions +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as functions + +__all__ = ['ColorButton'] class ColorButton(QtGui.QPushButton): @@ -27,8 +27,14 @@ class ColorButton(QtGui.QPushButton): def paintEvent(self, ev): QtGui.QPushButton.paintEvent(self, ev) p = QtGui.QPainter(self) + rect = self.rect().adjusted(6, 6, -6, -6) + ## draw white base, then texture for indicating transparency, then actual color + p.setBrush(functions.mkBrush('w')) + p.drawRect(rect) + p.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) + p.drawRect(rect) p.setBrush(functions.mkBrush(self._color)) - p.drawRect(self.rect().adjusted(5, 5, -5, -5)) + p.drawRect(rect) p.end() def setColor(self, color, finished=True): @@ -80,4 +86,8 @@ if __name__ == '__main__': btn.sigColorChanging.connect(change) btn.sigColorChanged.connect(done) - \ No newline at end of file + + ## Start Qt event loop unless running in interactive mode. + import sys + if sys.flags.interactive != 1: + app.exec_() diff --git a/widgets/DataTreeWidget.py b/widgets/DataTreeWidget.py new file mode 100644 index 00000000..8a310af6 --- /dev/null +++ b/widgets/DataTreeWidget.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from collections import OrderedDict +import types, traceback +import numpy as np + +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + +__all__ = ['DataTreeWidget'] + +class DataTreeWidget(QtGui.QTreeWidget): + """ + Widget for displaying hierarchical python data structures + (eg, nested dicts, lists, and arrays) + """ + + + def __init__(self, parent=None, data=None): + QtGui.QTreeWidget.__init__(self, parent) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setData(data) + self.setColumnCount(3) + self.setHeaderLabels(['key / index', 'type', 'value']) + + def setData(self, data, hideRoot=False): + """data should be a dictionary.""" + self.clear() + self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot) + #node = self.mkNode('', data) + #while node.childCount() > 0: + #c = node.child(0) + #node.removeChild(c) + #self.invisibleRootItem().addChild(c) + self.expandToDepth(3) + self.resizeColumnToContents(0) + + def buildTree(self, data, parent, name='', hideRoot=False): + if hideRoot: + node = parent + else: + typeStr = type(data).__name__ + if typeStr == 'instance': + typeStr += ": " + data.__class__.__name__ + node = QtGui.QTreeWidgetItem([name, typeStr, ""]) + parent.addChild(node) + + if isinstance(data, types.TracebackType): ## convert traceback to a list of strings + data = map(str.strip, traceback.format_list(traceback.extract_tb(data))) + elif HAVE_METAARRAY and isinstance(data, metaarray.MetaArray): + data = { + 'data': data.view(np.ndarray), + 'meta': data.infoCopy() + } + + if isinstance(data, dict): + for k in data: + self.buildTree(data[k], node, str(k)) + elif isinstance(data, list) or isinstance(data, tuple): + for i in range(len(data)): + self.buildTree(data[i], node, str(i)) + else: + node.setText(2, str(data)) + + + #def mkNode(self, name, v): + #if type(v) is list and len(v) > 0 and isinstance(v[0], dict): + #inds = map(unicode, range(len(v))) + #v = OrderedDict(zip(inds, v)) + #if isinstance(v, dict): + ##print "\nadd tree", k, v + #node = QtGui.QTreeWidgetItem([name]) + #for k in v: + #newNode = self.mkNode(k, v[k]) + #node.addChild(newNode) + #else: + ##print "\nadd value", k, str(v) + #node = QtGui.QTreeWidgetItem([unicode(name), unicode(v)]) + #return node + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + d = { + 'list1': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"], + 'dict1': { + 'x': 1, + 'y': 2, + 'z': 'three' + }, + 'array1 (20x20)': np.ones((10,10)) + } + + tree = DataTreeWidget(data=d) + tree.show() + tree.resize(600,600) + + + ## Start Qt event loop unless running in interactive mode. + import sys + if sys.flags.interactive != 1: + app.exec_() + \ No newline at end of file diff --git a/widgets/FileDialog.py b/widgets/FileDialog.py new file mode 100644 index 00000000..33b838a2 --- /dev/null +++ b/widgets/FileDialog.py @@ -0,0 +1,14 @@ +from pyqtgraph.Qt import QtGui, QtCore +import sys + +__all__ = ['FileDialog'] + +class FileDialog(QtGui.QFileDialog): + ## Compatibility fix for OSX: + ## For some reason the native dialog doesn't show up when you set AcceptMode to AcceptSave on OS X, so we don't use the native dialog + + def __init__(self, *args): + QtGui.QFileDialog.__init__(self, *args) + + if sys.platform == 'darwin': + self.setOption(QtGui.QFileDialog.DontUseNativeDialog) \ No newline at end of file diff --git a/widgets/GradientWidget.py b/widgets/GradientWidget.py new file mode 100644 index 00000000..47d6ab45 --- /dev/null +++ b/widgets/GradientWidget.py @@ -0,0 +1,620 @@ +# -*- coding: utf-8 -*- +if __name__ == '__main__': + import os, sys + path = os.path.join(os.path.dirname(__file__), '..', '..') + sys.path = [path] + sys.path + + +from pyqtgraph.Qt import QtGui, QtCore +from GraphicsView import GraphicsView +from pyqtgraph.graphicsItems.GradientEditorItem import GradientEditorItem +import weakref +import numpy as np +import collections + +__all__ = ['TickSlider', 'GradientWidget', 'BlackWhiteSlider'] + + +class GradientWidget(GraphicsView): + + sigGradientChanged = QtCore.Signal(object) + + def __init__(self, parent=None, orientation='bottom', *args, **kargs): + GraphicsView.__init__(self, parent, useOpenGL=False, background=None) + self.maxDim = 27 + kargs['tickPen'] = 'k' + self.item = GradientEditorItem(*args, **kargs) + self.item.sigGradientChanged.connect(self.sigGradientChanged) + self.setCentralItem(self.item) + self.setOrientation(orientation) + self.setCacheMode(self.CacheNone) + self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing) + self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) + self.setBackgroundRole(QtGui.QPalette.NoRole) + #self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush)) + #self.setAutoFillBackground(False) + #self.setAttribute(QtCore.Qt.WA_PaintOnScreen, False) + #self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, True) + + def setOrientation(self, ort): + self.item.setOrientation(ort) + self.orientation = ort + self.setMaxDim() + + def setMaxDim(self, mx=None): + if mx is None: + mx = self.maxDim + else: + self.maxDim = mx + + if self.orientation in ['bottom', 'top']: + self.setFixedHeight(mx) + self.setMaximumWidth(16777215) + else: + self.setFixedWidth(mx) + self.setMaximumHeight(16777215) + + def __getattr__(self, attr): + return getattr(self.item, attr) + + + +#Gradients = collections.OrderedDict([ + #('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}), + #('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}), + #('yellowy', {'ticks': [(0.0, (0, 0, 0, 255)), (0.2328863796753704, (32, 0, 129, 255)), (0.8362738179251941, (255, 255, 0, 255)), (0.5257586450247, (115, 15, 255, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'} ), + #('bipolar', {'ticks': [(0.0, (0, 255, 255, 255)), (1.0, (255, 255, 0, 255)), (0.5, (0, 0, 0, 255)), (0.25, (0, 0, 255, 255)), (0.75, (255, 0, 0, 255))], 'mode': 'rgb'}), + #('spectrum', {'ticks': [(1.0, (255, 0, 255, 255)), (0.0, (255, 0, 0, 255))], 'mode': 'hsv'}), + #('cyclic', {'ticks': [(0.0, (255, 0, 4, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}), + #('greyclip', {'ticks': [(0.0, (0, 0, 0, 255)), (0.99, (255, 255, 255, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'rgb'}), +#]) + + +#class TickSlider(GraphicsView): + #def __init__(self, parent=None, orientation='bottom', allowAdd=True, **kargs): + #self.orientation = orientation + #self.length = 100 + #self.tickSize = 15 + #self.ticks = {} + #self.maxDim = 20 + #GraphicsView.__init__(self, parent, useOpenGL=False) + #self.allowAdd = allowAdd + ##self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + ##self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + ##self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor) + ##self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) + #self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing) + #self.orientations = { + #'left': (270, 1, -1), + #'right': (270, 1, 1), + #'top': (0, 1, -1), + #'bottom': (0, 1, 1) + #} + + ##self.scene = QtGui.QGraphicsScene() + ##self.setScene(self.scene) + + #self.setOrientation(orientation) + #self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) + #self.setBackgroundRole(QtGui.QPalette.NoRole) + #self.setMouseTracking(True) + + + #def keyPressEvent(self, ev): + #ev.ignore() + + #def setMaxDim(self, mx=None): + #if mx is None: + #mx = self.maxDim + #else: + #self.maxDim = mx + + #if self.orientation in ['bottom', 'top']: + #self.setFixedHeight(mx) + #self.setMaximumWidth(16777215) + #else: + #self.setFixedWidth(mx) + #self.setMaximumHeight(16777215) + + #def setOrientation(self, ort): + #self.orientation = ort + #self.resetTransform() + #self.rotate(self.orientations[ort][0]) + #self.scale(*self.orientations[ort][1:]) + #self.setMaxDim() + + #def addTick(self, x, color=None, movable=True): + #if color is None: + #color = QtGui.QColor(255,255,255) + #tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize) + #self.ticks[tick] = x + #self.scene.addItem(tick) + #return tick + + #def removeTick(self, tick): + #del self.ticks[tick] + #self.scene.removeItem(tick) + + #def tickMoved(self, tick, pos): + ##print "tick changed" + ### Correct position of tick if it has left bounds. + #newX = min(max(0, pos.x()), self.length) + #pos.setX(newX) + #tick.setPos(pos) + #self.ticks[tick] = float(newX) / self.length + + #def tickClicked(self, tick, ev): + #if ev.button() == QtCore.Qt.RightButton: + #self.removeTick(tick) + + #def widgetLength(self): + #if self.orientation in ['bottom', 'top']: + #return self.width() + #else: + #return self.height() + + #def resizeEvent(self, ev): + #wlen = max(40, self.widgetLength()) + #self.setLength(wlen-self.tickSize) + #bounds = self.scene().itemsBoundingRect() + #bounds.setLeft(min(-self.tickSize*0.5, bounds.left())) + #bounds.setRight(max(self.length + self.tickSize, bounds.right())) + ##bounds.setTop(min(bounds.top(), self.tickSize)) + ##bounds.setBottom(max(0, bounds.bottom())) + #self.setSceneRect(bounds) + #self.fitInView(bounds, QtCore.Qt.KeepAspectRatio) + + #def setLength(self, newLen): + #for t, x in self.ticks.items(): + #t.setPos(x * newLen, t.pos().y()) + #self.length = float(newLen) + + #def mousePressEvent(self, ev): + #QtGui.QGraphicsView.mousePressEvent(self, ev) + #self.ignoreRelease = False + #for i in self.items(ev.pos()): + #if isinstance(i, Tick): + #self.ignoreRelease = True + #break + ##if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks + ##self.ignoreRelease = True + + #def mouseReleaseEvent(self, ev): + #QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + #if self.ignoreRelease: + #return + + #pos = self.mapToScene(ev.pos()) + + #if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: + #if pos.x() < 0 or pos.x() > self.length: + #return + #if pos.y() < 0 or pos.y() > self.tickSize: + #return + #pos.setX(min(max(pos.x(), 0), self.length)) + #self.addTick(pos.x()/self.length) + #elif ev.button() == QtCore.Qt.RightButton: + #self.showMenu(ev) + + + #def showMenu(self, ev): + #pass + + #def setTickColor(self, tick, color): + #tick = self.getTick(tick) + #tick.color = color + #tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color))) + + #def setTickValue(self, tick, val): + #tick = self.getTick(tick) + #val = min(max(0.0, val), 1.0) + #x = val * self.length + #pos = tick.pos() + #pos.setX(x) + #tick.setPos(pos) + #self.ticks[tick] = val + + #def tickValue(self, tick): + #tick = self.getTick(tick) + #return self.ticks[tick] + + #def getTick(self, tick): + #if type(tick) is int: + #tick = self.listTicks()[tick][0] + #return tick + + #def mouseMoveEvent(self, ev): + #QtGui.QGraphicsView.mouseMoveEvent(self, ev) + ##print ev.pos(), ev.buttons() + + #def listTicks(self): + #ticks = self.ticks.items() + #ticks.sort(lambda a,b: cmp(a[1], b[1])) + #return ticks + + +#class GradientWidget(TickSlider): + + #sigGradientChanged = QtCore.Signal(object) + + #def __init__(self, *args, **kargs): + #self.currentTick = None + #self.currentTickColor = None + #self.rectSize = 15 + #self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) + #self.backgroundRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) + #self.backgroundRect.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) + #self.colorMode = 'rgb' + #TickSlider.__init__(self, *args, **kargs) + #self.colorDialog = QtGui.QColorDialog() + #self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) + #self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) + + #self.colorDialog.currentColorChanged.connect(self.currentColorChanged) + #self.colorDialog.rejected.connect(self.currentColorRejected) + + ##self.gradient = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(100,0)) + #self.scene.addItem(self.backgroundRect) + #self.scene.addItem(self.gradRect) + + #self.setMaxDim(self.rectSize + self.tickSize) + + ##self.btn = QtGui.QPushButton('RGB') + ##self.btnProxy = self.scene.addWidget(self.btn) + ##self.btnProxy.setFlag(self.btnProxy.ItemIgnoresTransformations) + ##self.btnProxy.scale(0.7, 0.7) + ##self.btnProxy.translate(-self.btnProxy.sceneBoundingRect().width()+self.tickSize/2., 0) + ##if self.orientation == 'bottom': + ##self.btnProxy.translate(0, -self.rectSize) + #self.rgbAction = QtGui.QAction('RGB', self) + #self.rgbAction.setCheckable(True) + #self.rgbAction.triggered.connect(lambda: self.setColorMode('rgb')) + #self.hsvAction = QtGui.QAction('HSV', self) + #self.hsvAction.setCheckable(True) + #self.hsvAction.triggered.connect(lambda: self.setColorMode('hsv')) + + #self.menu = QtGui.QMenu() + + ### build context menu of gradients + #global Gradients + #for g in Gradients: + #px = QtGui.QPixmap(100, 15) + #p = QtGui.QPainter(px) + #self.restoreState(Gradients[g]) + #grad = self.getGradient() + #brush = QtGui.QBrush(grad) + #p.fillRect(QtCore.QRect(0, 0, 100, 15), brush) + #p.end() + #label = QtGui.QLabel() + #label.setPixmap(px) + #label.setContentsMargins(1, 1, 1, 1) + #act = QtGui.QWidgetAction(self) + #act.setDefaultWidget(label) + #act.triggered.connect(self.contextMenuClicked) + #act.name = g + #self.menu.addAction(act) + + #self.menu.addSeparator() + #self.menu.addAction(self.rgbAction) + #self.menu.addAction(self.hsvAction) + + + #for t in self.ticks.keys(): + #self.removeTick(t) + #self.addTick(0, QtGui.QColor(0,0,0), True) + #self.addTick(1, QtGui.QColor(255,0,0), True) + #self.setColorMode('rgb') + #self.updateGradient() + + #def showMenu(self, ev): + #self.menu.popup(ev.globalPos()) + + #def contextMenuClicked(self, b): + #global Gradients + #act = self.sender() + #self.restoreState(Gradients[act.name]) + + #def setColorMode(self, cm): + #if cm not in ['rgb', 'hsv']: + #raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm)) + + #try: + #self.rgbAction.blockSignals(True) + #self.hsvAction.blockSignals(True) + #self.rgbAction.setChecked(cm == 'rgb') + #self.hsvAction.setChecked(cm == 'hsv') + #finally: + #self.rgbAction.blockSignals(False) + #self.hsvAction.blockSignals(False) + #self.colorMode = cm + #self.updateGradient() + + #def updateGradient(self): + #self.gradient = self.getGradient() + #self.gradRect.setBrush(QtGui.QBrush(self.gradient)) + #self.sigGradientChanged.emit(self) + + #def setLength(self, newLen): + #TickSlider.setLength(self, newLen) + #self.backgroundRect.setRect(0, -self.rectSize, newLen, self.rectSize) + #self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize) + #self.updateGradient() + + #def currentColorChanged(self, color): + #if color.isValid() and self.currentTick is not None: + #self.setTickColor(self.currentTick, color) + #self.updateGradient() + + #def currentColorRejected(self): + #self.setTickColor(self.currentTick, self.currentTickColor) + #self.updateGradient() + + #def tickClicked(self, tick, ev): + #if ev.button() == QtCore.Qt.LeftButton: + #if not tick.colorChangeAllowed: + #return + #self.currentTick = tick + #self.currentTickColor = tick.color + #self.colorDialog.setCurrentColor(tick.color) + #self.colorDialog.open() + ##color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) + ##if color.isValid(): + ##self.setTickColor(tick, color) + ##self.updateGradient() + #elif ev.button() == QtCore.Qt.RightButton: + #if not tick.removeAllowed: + #return + #if len(self.ticks) > 2: + #self.removeTick(tick) + #self.updateGradient() + + #def tickMoved(self, tick, pos): + #TickSlider.tickMoved(self, tick, pos) + #self.updateGradient() + + + #def getGradient(self): + #g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) + #if self.colorMode == 'rgb': + #ticks = self.listTicks() + #g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) + #elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop + #ticks = self.listTicks() + #stops = [] + #stops.append((ticks[0][1], ticks[0][0].color)) + #for i in range(1,len(ticks)): + #x1 = ticks[i-1][1] + #x2 = ticks[i][1] + #dx = (x2-x1) / 10. + #for j in range(1,10): + #x = x1 + dx*j + #stops.append((x, self.getColor(x))) + #stops.append((x2, self.getColor(x2))) + #g.setStops(stops) + #return g + + #def getColor(self, x, toQColor=True): + #ticks = self.listTicks() + #if x <= ticks[0][1]: + #c = ticks[0][0].color + #if toQColor: + #return QtGui.QColor(c) # always copy colors before handing them out + #else: + #return (c.red(), c.green(), c.blue(), c.alpha()) + #if x >= ticks[-1][1]: + #c = ticks[-1][0].color + #if toQColor: + #return QtGui.QColor(c) # always copy colors before handing them out + #else: + #return (c.red(), c.green(), c.blue(), c.alpha()) + + #x2 = ticks[0][1] + #for i in range(1,len(ticks)): + #x1 = x2 + #x2 = ticks[i][1] + #if x1 <= x and x2 >= x: + #break + + #dx = (x2-x1) + #if dx == 0: + #f = 0. + #else: + #f = (x-x1) / dx + #c1 = ticks[i-1][0].color + #c2 = ticks[i][0].color + #if self.colorMode == 'rgb': + #r = c1.red() * (1.-f) + c2.red() * f + #g = c1.green() * (1.-f) + c2.green() * f + #b = c1.blue() * (1.-f) + c2.blue() * f + #a = c1.alpha() * (1.-f) + c2.alpha() * f + #if toQColor: + #return QtGui.QColor(r, g, b,a) + #else: + #return (r,g,b,a) + #elif self.colorMode == 'hsv': + #h1,s1,v1,_ = c1.getHsv() + #h2,s2,v2,_ = c2.getHsv() + #h = h1 * (1.-f) + h2 * f + #s = s1 * (1.-f) + s2 * f + #v = v1 * (1.-f) + v2 * f + #c = QtGui.QColor() + #c.setHsv(h,s,v) + #if toQColor: + #return c + #else: + #return (c.red(), c.green(), c.blue(), c.alpha()) + + #def getLookupTable(self, nPts, alpha=True): + #"""Return an RGB/A lookup table.""" + #if alpha: + #table = np.empty((nPts,4), dtype=np.ubyte) + #else: + #table = np.empty((nPts,3), dtype=np.ubyte) + + #for i in range(nPts): + #x = float(i)/(nPts-1) + #color = self.getColor(x, toQColor=False) + #table[i] = color[:table.shape[1]] + + #return table + + + + #def mouseReleaseEvent(self, ev): + #TickSlider.mouseReleaseEvent(self, ev) + #self.updateGradient() + + #def addTick(self, x, color=None, movable=True): + #if color is None: + #color = self.getColor(x) + #t = TickSlider.addTick(self, x, color=color, movable=movable) + #t.colorChangeAllowed = True + #t.removeAllowed = True + #return t + + #def saveState(self): + #ticks = [] + #for t in self.ticks: + #c = t.color + #ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha()))) + #state = {'mode': self.colorMode, 'ticks': ticks} + #return state + + #def restoreState(self, state): + #self.setColorMode(state['mode']) + #for t in self.ticks.keys(): + #self.removeTick(t) + #for t in state['ticks']: + #c = QtGui.QColor(*t[1]) + #self.addTick(t[0], c) + #self.updateGradient() + + + +#class BlackWhiteSlider(GradientWidget): + #def __init__(self, parent): + #GradientWidget.__init__(self, parent) + #self.getTick(0).colorChangeAllowed = False + #self.getTick(1).colorChangeAllowed = False + #self.allowAdd = False + #self.setTickColor(self.getTick(1), QtGui.QColor(255,255,255)) + #self.setOrientation('right') + + #def getLevels(self): + #return (self.tickValue(0), self.tickValue(1)) + + #def setLevels(self, black, white): + #self.setTickValue(0, black) + #self.setTickValue(1, white) + + + + +#class GammaWidget(TickSlider): + #pass + + +#class Tick(QtGui.QGraphicsPolygonItem): + #def __init__(self, view, pos, color, movable=True, scale=10): + ##QObjectWorkaround.__init__(self) + #self.movable = movable + #self.view = weakref.ref(view) + #self.scale = scale + #self.color = color + ##self.endTick = endTick + #self.pg = QtGui.QPolygonF([QtCore.QPointF(0,0), QtCore.QPointF(-scale/3**0.5,scale), QtCore.QPointF(scale/3**0.5,scale)]) + #QtGui.QGraphicsPolygonItem.__init__(self, self.pg) + #self.setPos(pos[0], pos[1]) + #self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemIsSelectable) + #self.setBrush(QtGui.QBrush(QtGui.QColor(self.color))) + #if self.movable: + #self.setZValue(1) + #else: + #self.setZValue(0) + + ##def x(self): + ##return self.pos().x()/100. + + #def mouseMoveEvent(self, ev): + ##print self, "move", ev.scenePos() + #if not self.movable: + #return + #if not ev.buttons() & QtCore.Qt.LeftButton: + #return + + + #newPos = ev.scenePos() + self.mouseOffset + #newPos.setY(self.pos().y()) + ##newPos.setX(min(max(newPos.x(), 0), 100)) + #self.setPos(newPos) + #self.view().tickMoved(self, newPos) + #self.movedSincePress = True + ##self.emit(QtCore.SIGNAL('tickChanged'), self) + #ev.accept() + + #def mousePressEvent(self, ev): + #self.movedSincePress = False + #if ev.button() == QtCore.Qt.LeftButton: + #ev.accept() + #self.mouseOffset = self.pos() - ev.scenePos() + #self.pressPos = ev.scenePos() + #elif ev.button() == QtCore.Qt.RightButton: + #ev.accept() + ##if self.endTick: + ##return + ##self.view.tickChanged(self, delete=True) + + #def mouseReleaseEvent(self, ev): + ##print self, "release", ev.scenePos() + #if not self.movedSincePress: + #self.view().tickClicked(self, ev) + + ##if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos: + ##color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) + ##if color.isValid(): + ##self.color = color + ##self.setBrush(QtGui.QBrush(QtGui.QColor(self.color))) + ###self.emit(QtCore.SIGNAL('tickChanged'), self) + ##self.view.tickChanged(self) + + + + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + w = QtGui.QMainWindow() + w.show() + w.resize(400,400) + cw = QtGui.QWidget() + w.setCentralWidget(cw) + + l = QtGui.QGridLayout() + l.setSpacing(0) + cw.setLayout(l) + + w1 = GradientWidget(orientation='top') + w2 = GradientWidget(orientation='right', allowAdd=False) + #w2.setTickColor(1, QtGui.QColor(255,255,255)) + w3 = GradientWidget(orientation='bottom') + w4 = GradientWidget(orientation='left') + label = QtGui.QLabel(""" + - Click a triangle to change its color + - Drag triangles to move + - Click in an empty area to add a new color + (adding is disabled for the right-side widget) + - Right click a triangle to remove + """) + + l.addWidget(w1, 0, 1) + l.addWidget(w2, 1, 2) + l.addWidget(w3, 2, 1) + l.addWidget(w4, 1, 0) + l.addWidget(label, 1, 1) + + ## Start Qt event loop unless running in interactive mode. + import sys + if sys.flags.interactive != 1: + app.exec_() + + \ No newline at end of file diff --git a/widgets/GraphicsLayoutWidget.py b/widgets/GraphicsLayoutWidget.py new file mode 100644 index 00000000..937c880a --- /dev/null +++ b/widgets/GraphicsLayoutWidget.py @@ -0,0 +1,12 @@ +from pyqtgraph.Qt import QtGui +from pyqtgraph.graphicsItems.GraphicsLayout import GraphicsLayout +from GraphicsView import GraphicsView + +__all__ = ['GraphicsLayoutWidget'] +class GraphicsLayoutWidget(GraphicsView): + def __init__(self, parent=None, **kargs): + GraphicsView.__init__(self, parent) + self.ci = GraphicsLayout(**kargs) + for n in ['nextRow', 'nextCol', 'addPlot', 'addViewBox', 'addItem', 'getItem']: + setattr(self, n, getattr(self.ci, n)) + self.setCentralItem(self.ci) diff --git a/GraphicsView.py b/widgets/GraphicsView.py similarity index 81% rename from GraphicsView.py rename to widgets/GraphicsView.py index 9846d0c4..899db420 100644 --- a/GraphicsView.py +++ b/widgets/GraphicsView.py @@ -5,23 +5,30 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -from PyQt4 import QtCore, QtGui, QtOpenGL, QtSvg +from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL, QtSvg #from numpy import vstack #import time -from Point import * +from pyqtgraph.Point import Point #from vector import * import sys, os import debug - +from FileDialog import FileDialog +from pyqtgraph.GraphicsScene import GraphicsScene +import numpy as np +import pyqtgraph.functions as fn + +__all__ = ['GraphicsView'] + class GraphicsView(QtGui.QGraphicsView): sigRangeChanged = QtCore.Signal(object, object) sigMouseReleased = QtCore.Signal(object) sigSceneMouseMoved = QtCore.Signal(object) #sigRegionChanged = QtCore.Signal(object) + sigScaleChanged = QtCore.Signal(object) lastFileDir = None - def __init__(self, parent=None, useOpenGL=False): + def __init__(self, parent=None, useOpenGL=None, background='k'): """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the viewed coordinate range. Also automatically creates a QGraphicsScene and a central QGraphicsWidget that is automatically scaled to the full view geometry. @@ -35,14 +42,23 @@ class GraphicsView(QtGui.QGraphicsView): self.closed = False QtGui.QGraphicsView.__init__(self, parent) - if 'linux' in sys.platform: ## linux has bugs in opengl implementation - useOpenGL = False + + ## in general openGL is poorly supported in Qt. + ## we only enable it where the performance benefit is critical. + if useOpenGL is None: + if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation + useOpenGL = False + elif 'darwin' in sys.platform: ## openGL greatly speeds up display on mac + useOpenGL = True + else: + useOpenGL = False self.useOpenGL(useOpenGL) self.setCacheMode(self.CacheBackground) - brush = QtGui.QBrush(QtGui.QColor(0,0,0)) - self.setBackgroundBrush(brush) + if background is not None: + brush = fn.mkBrush(background) + self.setBackgroundBrush(brush) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setFrameShape(QtGui.QFrame.NoFrame) @@ -57,7 +73,7 @@ class GraphicsView(QtGui.QGraphicsView): self.lockedViewports = [] self.lastMousePos = None - #self.setMouseTracking(False) + self.setMouseTracking(True) self.aspectLocked = False #self.yInverted = True self.range = QtCore.QRectF(0, 0, 1, 1) @@ -65,7 +81,7 @@ class GraphicsView(QtGui.QGraphicsView): self.currentItem = None self.clearMouse() self.updateMatrix() - self.sceneObj = QtGui.QGraphicsScene() + self.sceneObj = GraphicsScene() self.setScene(self.sceneObj) ## by default we set up a central widget with a grid layout. @@ -103,9 +119,15 @@ class GraphicsView(QtGui.QGraphicsView): self.setViewport(v) def keyPressEvent(self, ev): - ev.ignore() + #QtGui.QGraphicsView.keyPressEvent(self, ev) + self.scene().keyPressEvent(ev) ## bypass view, hand event directly to scene + ## (view likes to eat arrow key events) + def setCentralItem(self, item): + return self.setCentralWidget(item) + + def setCentralWidget(self, item): """Sets a QGraphicsWidget to automatically fill the entire view.""" if self.centralWidget is not None: self.scene().removeItem(self.centralWidget) @@ -142,49 +164,22 @@ class GraphicsView(QtGui.QGraphicsView): else: self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio) - ##print "udpateMatrix:" - #translate = Point(self.range.center()) - #if self.range.width() == 0 or self.range.height() == 0: - #return - #scale = Point(self.size().width()/self.range.width(), self.size().height()/self.range.height()) - - #m = QtGui.QTransform() - - ### First center the viewport at 0 - #self.resetMatrix() - #center = self.viewportTransform().inverted()[0].map(Point(self.width()/2., self.height()/2.)) - #if self.yInverted: - #m.translate(center.x(), center.y()) - ##print " inverted; translate", center.x(), center.y() - #else: - #m.translate(center.x(), -center.y()) - ##print " not inverted; translate", center.x(), -center.y() - - ### Now scale and translate properly - #if self.aspectLocked: - #scale = Point(scale.min()) - #if not self.yInverted: - #scale = scale * Point(1, -1) - #m.scale(scale[0], scale[1]) - ##print " scale:", scale - #st = translate - #m.translate(-st[0], -st[1]) - ##print " translate:", st - #self.setTransform(m) - #self.currentScale = scale - ##self.emit(QtCore.SIGNAL('viewChanged'), self.range) self.sigRangeChanged.emit(self, self.range) if propagate: for v in self.lockedViewports: v.setXRange(self.range, padding=0) - def visibleRange(self): + def viewRect(self): """Return the boundaries of the view in scene coordinates""" ## easier to just return self.range ? r = QtCore.QRectF(self.rect()) return self.viewportTransform().inverted()[0].mapRect(r) + def visibleRange(self): + ## for backward compatibility + return self.viewRect() + def translate(self, dx, dy): self.range.adjust(dx, dy, dx, dy) self.updateMatrix() @@ -210,6 +205,7 @@ class GraphicsView(QtGui.QGraphicsView): self.updateMatrix() + self.sigScaleChanged.emit(self) def setRange(self, newRect=None, padding=0.05, lockAspect=None, propagate=True, disableAutoPixel=True): if disableAutoPixel: @@ -217,23 +213,36 @@ class GraphicsView(QtGui.QGraphicsView): if newRect is None: newRect = self.visibleRange() padding = 0 + padding = Point(padding) newRect = QtCore.QRectF(newRect) pw = newRect.width() * padding[0] ph = newRect.height() * padding[1] - self.range = newRect.adjusted(-pw, -ph, pw, ph) + newRect = newRect.adjusted(-pw, -ph, pw, ph) + scaleChanged = False + if self.range.width() != newRect.width() or self.range.height() != newRect.height(): + scaleChanged = True + self.range = newRect #print "New Range:", self.range self.centralWidget.setGeometry(self.range) self.updateMatrix(propagate) + if scaleChanged: + self.sigScaleChanged.emit(self) def scaleToImage(self, image): """Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase.""" pxSize = image.pixelSize() + image.setPxMode(True) + try: + self.sigScaleChanged.disconnect(image.setScaledMode) + except TypeError: + pass tl = image.sceneBoundingRect().topLeft() w = self.size().width() * pxSize[0] h = self.size().height() * pxSize[1] range = QtCore.QRectF(tl.x(), tl.y(), w, h) self.setRange(range, padding=0) + self.sigScaleChanged.connect(image.setScaledMode) @@ -294,6 +303,9 @@ class GraphicsView(QtGui.QGraphicsView): #ev1.setButtonDownScreenPos(fev.screenPos()) #return ev1 + def leaveEvent(self, ev): + self.scene().leaveEvent(ev) ## inform scene when mouse leaves + def mousePressEvent(self, ev): QtGui.QGraphicsView.mousePressEvent(self, ev) @@ -364,7 +376,7 @@ class GraphicsView(QtGui.QGraphicsView): return if ev.buttons() == QtCore.Qt.RightButton: - delta = Point(clip(delta[0], -50, 50), clip(-delta[1], -50, 50)) + delta = Point(np.clip(delta[0], -50, 50), np.clip(-delta[1], -50, 50)) scale = 1.01 ** delta #if self.yInverted: #scale[0] = 1. / scale[0] @@ -401,7 +413,7 @@ class GraphicsView(QtGui.QGraphicsView): def writeSvg(self, fileName=None): if fileName is None: - self.fileDialog = QtGui.QFileDialog() + self.fileDialog = FileDialog() self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) if GraphicsView.lastFileDir is not None: @@ -420,13 +432,13 @@ class GraphicsView(QtGui.QGraphicsView): def writeImage(self, fileName=None): if fileName is None: - self.fileDialog = QtGui.QFileDialog() + self.fileDialog = FileDialog() self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) ## this is the line that makes the fileDialog not show on mac if GraphicsView.lastFileDir is not None: self.fileDialog.setDirectory(GraphicsView.lastFileDir) self.fileDialog.show() - self.fileDialog.fileSelected.connect(self.writePng) + self.fileDialog.fileSelected.connect(self.writeImage) return fileName = str(fileName) GraphicsView.lastFileDir = os.path.split(fileName)[0] @@ -440,7 +452,14 @@ class GraphicsView(QtGui.QGraphicsView): def writePs(self, fileName=None): if fileName is None: - fileName = str(QtGui.QFileDialog.getSaveFileName()) + self.fileDialog = FileDialog() + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.writePs) + return + #if fileName is None: + # fileName = str(QtGui.QFileDialog.getSaveFileName()) printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) printer.setOutputFileName(fileName) painter = QtGui.QPainter(printer) @@ -468,54 +487,3 @@ class GraphicsView(QtGui.QGraphicsView): #return fl[-1] -#class GraphicsSceneMouseEvent(QtGui.QGraphicsSceneMouseEvent): - #"""Stand-in class for QGraphicsSceneMouseEvent""" - #def __init__(self): - #QtGui.QGraphicsSceneMouseEvent.__init__(self) - - #def setPos(self, p): - #self.vpos = p - #def setButtons(self, p): - #self.vbuttons = p - #def setButton(self, p): - #self.vbutton = p - #def setModifiers(self, p): - #self.vmodifiers = p - #def setScenePos(self, p): - #self.vscenePos = p - #def setLastPos(self, p): - #self.vlastPos = p - #def setLastScenePos(self, p): - #self.vlastScenePos = p - #def setLastScreenPos(self, p): - #self.vlastScreenPos = p - #def setButtonDownPos(self, p): - #self.vbuttonDownPos = p - #def setButtonDownScenePos(self, p): - #self.vbuttonDownScenePos = p - #def setButtonDownScreenPos(self, p): - #self.vbuttonDownScreenPos = p - - #def pos(self): - #return self.vpos - #def buttons(self): - #return self.vbuttons - #def button(self): - #return self.vbutton - #def modifiers(self): - #return self.vmodifiers - #def scenePos(self): - #return self.vscenePos - #def lastPos(self): - #return self.vlastPos - #def lastScenePos(self): - #return self.vlastScenePos - #def lastScreenPos(self): - #return self.vlastScreenPos - #def buttonDownPos(self): - #return self.vbuttonDownPos - #def buttonDownScenePos(self): - #return self.vbuttonDownScenePos - #def buttonDownScreenPos(self): - #return self.vbuttonDownScreenPos - diff --git a/widgets/HistogramLUTWidget.py b/widgets/HistogramLUTWidget.py new file mode 100644 index 00000000..f05d3f1c --- /dev/null +++ b/widgets/HistogramLUTWidget.py @@ -0,0 +1,33 @@ +""" +Widget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. +This is a wrapper around HistogramLUTItem +""" + +from pyqtgraph.Qt import QtGui, QtCore +from GraphicsView import GraphicsView +from pyqtgraph.graphicsItems.HistogramLUTItem import HistogramLUTItem + +__all__ = ['HistogramLUTWidget'] + + +class HistogramLUTWidget(GraphicsView): + + def __init__(self, parent=None, *args, **kargs): + background = kargs.get('background', 'k') + GraphicsView.__init__(self, parent, useOpenGL=False, background=background) + self.item = HistogramLUTItem(*args, **kargs) + self.setCentralItem(self.item) + self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + self.setMinimumWidth(92) + + + def sizeHint(self): + return QtCore.QSize(115, 200) + + + + def __getattr__(self, attr): + return getattr(self.item, attr) + + + diff --git a/widgets/JoystickButton.py b/widgets/JoystickButton.py new file mode 100644 index 00000000..5320563f --- /dev/null +++ b/widgets/JoystickButton.py @@ -0,0 +1,90 @@ +from pyqtgraph.Qt import QtGui, QtCore + + +__all__ = ['JoystickButton'] + +class JoystickButton(QtGui.QPushButton): + sigStateChanged = QtCore.Signal(object, object) + + def __init__(self, parent=None): + QtGui.QPushButton.__init__(self, parent) + self.radius = 200 + self.setCheckable(True) + self.state = None + self.setState(0,0) + + + def mousePressEvent(self, ev): + self.setChecked(True) + self.pressPos = ev.pos() + ev.accept() + + def mouseMoveEvent(self, ev): + dif = ev.pos()-self.pressPos + self.setState(dif.x(), -dif.y()) + + def mouseReleaseEvent(self, ev): + self.setChecked(False) + self.setState(0,0) + + def wheelEvent(self, ev): + ev.accept() + + + def doubleClickEvent(self, ev): + ev.accept() + + def setState(self, *xy): + xy = list(xy) + d = (xy[0]**2 + xy[1]**2)**0.5 + nxy = [0,0] + for i in [0,1]: + if xy[i] == 0: + nxy[i] = 0 + else: + nxy[i] = xy[i]/d + + if d > self.radius: + d = self.radius + d = (d/self.radius)**2 + xy = [nxy[0]*d, nxy[1]*d] + + w2 = self.width()/2. + h2 = self.height()/2 + self.spotPos = QtCore.QPoint(w2*(1+xy[0]), h2*(1-xy[1])) + self.update() + if self.state == xy: + return + self.state = xy + self.sigStateChanged.emit(self, self.state) + + def paintEvent(self, ev): + QtGui.QPushButton.paintEvent(self, ev) + p = QtGui.QPainter(self) + p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) + p.drawEllipse(self.spotPos.x()-3,self.spotPos.y()-3,6,6) + + def resizeEvent(self, ev): + self.setState(*self.state) + QtGui.QPushButton.resizeEvent(self, ev) + + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + w = QtGui.QMainWindow() + b = JoystickButton() + w.setCentralWidget(b) + w.show() + w.resize(100, 100) + + def fn(b, s): + print "state changed:", s + + b.sigStateChanged.connect(fn) + + ## Start Qt event loop unless running in interactive mode. + import sys + if sys.flags.interactive != 1: + app.exec_() + \ No newline at end of file diff --git a/MultiPlotWidget.py b/widgets/MultiPlotWidget.py similarity index 88% rename from MultiPlotWidget.py rename to widgets/MultiPlotWidget.py index 8071127a..00e2c2d8 100644 --- a/MultiPlotWidget.py +++ b/widgets/MultiPlotWidget.py @@ -5,16 +5,17 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -from GraphicsView import * -from MultiPlotItem import * +from GraphicsView import GraphicsView +import pyqtgraph.graphicsItems.MultiPlotItem as MultiPlotItem import exceptions +__all__ = ['MultiPlotWidget'] class MultiPlotWidget(GraphicsView): """Widget implementing a graphicsView with a single PlotItem inside.""" def __init__(self, parent=None): GraphicsView.__init__(self, parent) self.enableMouse(False) - self.mPlotItem = MultiPlotItem() + self.mPlotItem = MultiPlotItem.MultiPlotItem() self.setCentralItem(self.mPlotItem) ## Explicitly wrap methods from mPlotItem #for m in ['setData']: diff --git a/PlotWidget.py b/widgets/PlotWidget.py similarity index 95% rename from PlotWidget.py rename to widgets/PlotWidget.py index 1254b963..310838c5 100644 --- a/PlotWidget.py +++ b/widgets/PlotWidget.py @@ -5,10 +5,12 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ +from pyqtgraph.Qt import QtCore, QtGui from GraphicsView import * -from PlotItem import * +from pyqtgraph.graphicsItems.PlotItem import * import exceptions +__all__ = ['PlotWidget'] class PlotWidget(GraphicsView): #sigRangeChanged = QtCore.Signal(object, object) ## already defined in GraphicsView diff --git a/widgets/ProgressDialog.py b/widgets/ProgressDialog.py new file mode 100644 index 00000000..d5f8a2ca --- /dev/null +++ b/widgets/ProgressDialog.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore + +__all__ = ['ProgressDialog'] +class ProgressDialog(QtGui.QProgressDialog): + """Extends QProgressDialog for use in 'with' statements. + Arguments: + labelText (required) + cancelText Text to display on cancel button, or None to disable it. + minimum + maximum + parent + wait Length of time (im ms) to wait before displaying dialog + busyCursor If True, show busy cursor until dialog finishes + + + Example: + with ProgressDialog("Processing..", minVal, maxVal) as dlg: + # do stuff + dlg.setValue(i) ## could also use dlg += 1 + if dlg.wasCanceled(): + raise Exception("Processing canceled by user") + """ + def __init__(self, labelText, minimum=0, maximum=100, cancelText='Cancel', parent=None, wait=250, busyCursor=False): + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if not isGuiThread: + self.disabled = True + return + + self.disabled = False + + noCancel = False + if cancelText is None: + cancelText = '' + noCancel = True + + self.busyCursor = busyCursor + + QtGui.QProgressDialog.__init__(self, labelText, cancelText, minimum, maximum, parent) + self.setMinimumDuration(wait) + self.setWindowModality(QtCore.Qt.WindowModal) + self.setValue(self.minimum()) + if noCancel: + self.setCancelButton(None) + + + def __enter__(self): + if self.disabled: + return self + if self.busyCursor: + QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) + return self + + def __exit__(self, exType, exValue, exTrace): + if self.disabled: + return + if self.busyCursor: + QtGui.QApplication.restoreOverrideCursor() + self.setValue(self.maximum()) + + def __iadd__(self, val): + """Use inplace-addition operator for easy incrementing.""" + if self.disabled: + return self + self.setValue(self.value()+val) + return self + + + ## wrap all other functions to make sure they aren't being called from non-gui threads + + def setValue(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setValue(self, val) + + def setLabelText(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setLabelText(self, val) + + def setMaximum(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setMaximum(self, val) + + def setMinimum(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setMinimum(self, val) + + def wasCanceled(self): + if self.disabled: + return False + return QtGui.QProgressDialog.wasCanceled(self) + + def maximum(self): + if self.disabled: + return 0 + return QtGui.QProgressDialog.maximum(self) + + def minimum(self): + if self.disabled: + return 0 + return QtGui.QProgressDialog.minimum(self) + \ No newline at end of file diff --git a/widgets/RawImageWidget.py b/widgets/RawImageWidget.py new file mode 100644 index 00000000..84c061a1 --- /dev/null +++ b/widgets/RawImageWidget.py @@ -0,0 +1,79 @@ +from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL + +import pyqtgraph.functions as fn +import numpy as np + +class RawImageWidget(QtGui.QWidget): + """ + Widget optimized for very fast video display. + Generally using an ImageItem inside GraphicsView is fast enough, + but if you need even more performance, this widget is about as fast as it gets (but only in unscaled mode). + """ + def __init__(self, parent=None, scaled=False): + """ + Setting scaled=True will cause the entire image to be displayed within the boundaries of the widget. This also greatly reduces the speed at which it will draw frames. + """ + QtGui.QWidget.__init__(self, parent=None) + self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)) + self.scaled = scaled + self.opts = None + self.image = None + + def setImage(self, img, *args, **kargs): + """ + img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). + Extra arguments are sent to functions.makeARGB + """ + self.opts = (img, args, kargs) + self.image = None + self.update() + + def paintEvent(self, ev): + if self.opts is None: + return + if self.image is None: + argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2]) + self.image = fn.makeQImage(argb, alpha) + self.opts = () + #if self.pixmap is None: + #self.pixmap = QtGui.QPixmap.fromImage(self.image) + p = QtGui.QPainter(self) + if self.scaled: + rect = self.rect() + ar = rect.width() / float(rect.height()) + imar = self.image.width() / float(self.image.height()) + if ar > imar: + rect.setWidth(int(rect.width() * imar/ar)) + else: + rect.setHeight(int(rect.height() * ar/imar)) + + p.drawImage(rect, self.image) + else: + p.drawImage(QtCore.QPointF(), self.image) + #p.drawPixmap(self.rect(), self.pixmap) + p.end() + + +class RawImageGLWidget(QtOpenGL.QGLWidget): + """ + Similar to RawImageWidget, but uses a GL widget to do all drawing. + Generally this will be about as fast as using GraphicsView + ImageItem, + but performance may vary on some platforms. + """ + def __init__(self, parent=None, scaled=False): + QtOpenGL.QGLWidget.__init__(self, parent=None) + self.scaled = scaled + self.image = None + + def setImage(self, img): + self.image = fn.makeQImage(img) + self.update() + + def paintEvent(self, ev): + if self.image is None: + return + p = QtGui.QPainter(self) + p.drawImage(self.rect(), self.image) + p.end() + + diff --git a/widgets/SpinBox.py b/widgets/SpinBox.py new file mode 100644 index 00000000..b2b166a3 --- /dev/null +++ b/widgets/SpinBox.py @@ -0,0 +1,481 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.SignalProxy import SignalProxy + +import pyqtgraph.functions as fn +from math import log +from decimal import Decimal as D ## Use decimal to avoid accumulating floating-point errors +from decimal import * +import weakref + +__all__ = ['SpinBox'] +class SpinBox(QtGui.QAbstractSpinBox): + """QSpinBox widget on steroids. Allows selection of numerical value, with extra features: + - SI prefix notation + - Float values with linear and decimal stepping (1-9, 10-90, 100-900, etc.) + - Option for unbounded values + - Delayed signals (allows multiple rapid changes with only one change signal) + """ + + ## There's a PyQt bug that leaks a reference to the + ## QLineEdit returned from QAbstractSpinBox.lineEdit() + ## This makes it possible to crash the entire program + ## by making accesses to the LineEdit after the spinBox has been deleted. + ## I have no idea how to get around this.. + + + valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox + sigValueChanged = QtCore.Signal(object) # (self) + sigValueChanging = QtCore.Signal(object, object) # (self, value) sent immediately; no delay. + + def __init__(self, parent=None, value=0.0, **kwargs): + QtGui.QAbstractSpinBox.__init__(self, parent) + self.lastValEmitted = None + self.lastText = '' + self.textValid = True ## If false, we draw a red border + self.setMinimumWidth(0) + self.setMaximumHeight(20) + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + self.opts = { + 'bounds': [None, None], + + ## Log scaling options #### Log mode is no longer supported. + #'step': 0.1, + #'minStep': 0.001, + #'log': True, + #'dec': False, + + ## decimal scaling option - example + #'step': 0.1, + #'minStep': .001, + #'log': False, + #'dec': True, + + ## normal arithmetic step + 'step': D('0.01'), ## if 'dec' is false, the spinBox steps by 'step' every time + ## if 'dec' is True, the step size is relative to the value + ## 'step' needs to be an integral divisor of ten, ie 'step'*n=10 for some integer value of n (but only if dec is True) + 'log': False, + 'dec': False, ## if true, does decimal stepping. ie from 1-10 it steps by 'step', from 10 to 100 it steps by 10*'step', etc. + ## if true, minStep must be set in order to cross zero. + + + 'int': False, ## Set True to force value to be integer + + 'suffix': '', + 'siPrefix': False, ## Set to True to display numbers with SI prefix (ie, 100pA instead of 1e-10A) + + 'delayUntilEditFinished': True, ## do not send signals until text editing has finished + + ## for compatibility with QDoubleSpinBox and QSpinBox + 'decimals': 2 + } + + self.decOpts = ['step', 'minStep'] + + self.val = D(unicode(value)) ## Value is precise decimal. Ordinary math not allowed. + self.updateText() + self.skipValidate = False + self.setCorrectionMode(self.CorrectToPreviousValue) + self.setKeyboardTracking(False) + self.setOpts(**kwargs) + + + self.editingFinished.connect(self.editingFinishedEvent) + self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange) + + ##lots of config options, just gonna stuff 'em all in here rather than do the get/set crap. + def setOpts(self, **opts): + for k in opts: + if k == 'bounds': + #print opts[k] + self.setMinimum(opts[k][0], update=False) + self.setMaximum(opts[k][1], update=False) + #for i in [0,1]: + #if opts[k][i] is None: + #self.opts[k][i] = None + #else: + #self.opts[k][i] = D(unicode(opts[k][i])) + elif k in ['step', 'minStep']: + self.opts[k] = D(unicode(opts[k])) + elif k == 'value': + pass ## don't set value until bounds have been set + else: + self.opts[k] = opts[k] + if 'value' in opts: + self.setValue(opts['value']) + + ## If bounds have changed, update value to match + if 'bounds' in opts and 'value' not in opts: + self.setValue() + + ## sanity checks: + if self.opts['int']: + step = self.opts['step'] + mStep = self.opts['minStep'] + if (int(step) != step) or (self.opts['dec'] and (int(mStep) != mStep)): + raise Exception("Integer SpinBox may only have integer step and minStep.") + + self.updateText() + + + + def setMaximum(self, m, update=True): + if m is not None: + m = D(unicode(m)) + self.opts['bounds'][1] = m + if update: + self.setValue() + + def setMinimum(self, m, update=True): + if m is not None: + m = D(unicode(m)) + self.opts['bounds'][0] = m + if update: + self.setValue() + + def setPrefix(self, p): + self.setOpts(prefix=p) + + def setRange(self, r0, r1): + self.setOpts(bounds = [r0,r1]) + + def setProperty(self, prop, val): + """setProperty is just for compatibility with QSpinBox""" + if prop == 'value': + #if type(val) is QtCore.QVariant: + #val = val.toDouble()[0] + self.setValue(val) + else: + print "Warning: SpinBox.setProperty('%s', ..) not supported." % prop + + def setSuffix(self, suf): + self.setOpts(suffix=suf) + + def setSingleStep(self, step): + self.setOpts(step=step) + + def setDecimals(self, decimals): + self.setOpts(decimals=decimals) + + def value(self): + if self.opts['int']: + return int(self.val) + else: + return float(self.val) + + def setValue(self, value=None, update=True, delaySignal=False): + """ + Set the value of this spin. + If the value is out of bounds, it will be moved to the nearest boundary + If the spin is integer type, the value will be coerced to int + Returns the actual value set. + + If value is None, then the current value is used (this is for resetting + the value after bounds, etc. have changed) + """ + + if value is None: + value = self.value() + + bounds = self.opts['bounds'] + if bounds[0] is not None and value < bounds[0]: + value = bounds[0] + if bounds[1] is not None and value > bounds[1]: + value = bounds[1] + + if self.opts['int']: + value = int(value) + + value = D(unicode(value)) + if value == self.val: + return + prev = self.val + + self.val = value + if update: + self.updateText(prev=prev) + + self.sigValueChanging.emit(self, float(self.val)) ## change will be emitted in 300ms if there are no subsequent changes. + if not delaySignal: + self.emitChanged() + + return value + + + def emitChanged(self): + self.lastValEmitted = self.val + self.valueChanged.emit(float(self.val)) + self.sigValueChanged.emit(self) + + def delayedChange(self): + try: + if self.val != self.lastValEmitted: + self.emitChanged() + except RuntimeError: + pass ## This can happen if we try to handle a delayed signal after someone else has already deleted the underlying C++ object. + + def widgetGroupInterface(self): + return (self.valueChanged, SpinBox.value, SpinBox.setValue) + + def sizeHint(self): + return QtCore.QSize(120, 0) + + + def stepEnabled(self): + return self.StepUpEnabled | self.StepDownEnabled + + #def fixup(self, *args): + #print "fixup:", args + + def stepBy(self, n): + n = D(int(n)) ## n must be integral number of steps. + s = [D(-1), D(1)][n >= 0] ## determine sign of step + val = self.val + + for i in range(abs(n)): + + if self.opts['log']: + raise Exception("Log mode no longer supported.") + # step = abs(val) * self.opts['step'] + # if 'minStep' in self.opts: + # step = max(step, self.opts['minStep']) + # val += step * s + if self.opts['dec']: + if val == 0: + step = self.opts['minStep'] + exp = None + else: + vs = [D(-1), D(1)][val >= 0] + #exp = D(int(abs(val*(D('1.01')**(s*vs))).log10())) + fudge = D('1.01')**(s*vs) ## fudge factor. at some places, the step size depends on the step sign. + exp = abs(val * fudge).log10().quantize(1, ROUND_FLOOR) + step = self.opts['step'] * D(10)**exp + if 'minStep' in self.opts: + step = max(step, self.opts['minStep']) + val += s * step + #print "Exp:", exp, "step", step, "val", val + else: + val += s*self.opts['step'] + + if 'minStep' in self.opts and abs(val) < self.opts['minStep']: + val = D(0) + self.setValue(val, delaySignal=True) ## note all steps (arrow buttons, wheel, up/down keys..) emit delayed signals only. + + + def valueInRange(self, value): + bounds = self.opts['bounds'] + if bounds[0] is not None and value < bounds[0]: + return False + if bounds[1] is not None and value > bounds[1]: + return False + if self.opts.get('int', False): + if int(value) != value: + return False + return True + + + def updateText(self, prev=None): + #print "Update text." + self.skipValidate = True + if self.opts['siPrefix']: + if self.val == 0 and prev is not None: + (s, p) = fn.siScale(prev) + txt = "0.0 %s%s" % (p, self.opts['suffix']) + else: + txt = fn.siFormat(float(self.val), suffix=self.opts['suffix']) + else: + txt = '%g%s' % (self.val , self.opts['suffix']) + self.lineEdit().setText(txt) + self.lastText = txt + self.skipValidate = False + + def validate(self, strn, pos): + if self.skipValidate: + #print "skip validate" + #self.textValid = False + ret = QtGui.QValidator.Acceptable + else: + try: + ## first make sure we didn't mess with the suffix + suff = self.opts.get('suffix', '') + if len(suff) > 0 and unicode(strn)[-len(suff):] != suff: + #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff) + ret = QtGui.QValidator.Invalid + + ## next see if we actually have an interpretable value + else: + val = self.interpret() + if val is False: + #print "can't interpret" + #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') + #self.textValid = False + ret = QtGui.QValidator.Intermediate + else: + if self.valueInRange(val): + if not self.opts['delayUntilEditFinished']: + self.setValue(val, update=False) + #print " OK:", self.val + #self.setStyleSheet('') + #self.textValid = True + + ret = QtGui.QValidator.Acceptable + else: + ret = QtGui.QValidator.Intermediate + + except: + #print " BAD" + #import sys + #sys.excepthook(*sys.exc_info()) + #self.textValid = False + #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') + ret = QtGui.QValidator.Intermediate + + ## draw / clear border + if ret == QtGui.QValidator.Intermediate: + self.textValid = False + elif ret == QtGui.QValidator.Acceptable: + self.textValid = True + ## note: if text is invalid, we don't change the textValid flag + ## since the text will be forced to its previous state anyway + self.update() + + ## support 2 different pyqt APIs. Bleh. + if hasattr(QtCore, 'QString'): + return (ret, pos) + else: + return (ret, strn, pos) + + def paintEvent(self, ev): + QtGui.QAbstractSpinBox.paintEvent(self, ev) + + ## draw red border if text is invalid + if not self.textValid: + p = QtGui.QPainter(self) + p.setRenderHint(p.Antialiasing) + p.setPen(fn.mkPen((200,50,50), width=2)) + p.drawRoundedRect(self.rect().adjusted(2, 2, -2, -2), 4, 4) + p.end() + + + def interpret(self): + """Return value of text. Return False if text is invalid, raise exception if text is intermediate""" + strn = self.lineEdit().text() + suf = self.opts['suffix'] + if len(suf) > 0: + if strn[-len(suf):] != suf: + return False + #raise Exception("Units are invalid.") + strn = strn[:-len(suf)] + try: + val = fn.siEval(strn) + except: + #sys.excepthook(*sys.exc_info()) + #print "invalid" + return False + #print val + return val + + #def interpretText(self, strn=None): + #print "Interpret:", strn + #if strn is None: + #strn = self.lineEdit().text() + #self.setValue(siEval(strn), update=False) + ##QtGui.QAbstractSpinBox.interpretText(self) + + + def editingFinishedEvent(self): + """Edit has finished; set value.""" + #print "Edit finished." + if unicode(self.lineEdit().text()) == self.lastText: + #print "no text change." + return + try: + val = self.interpret() + except: + return + + if val is False: + #print "value invalid:", str(self.lineEdit().text()) + return + if val == self.val: + #print "no value change:", val, self.val + return + self.setValue(val, delaySignal=False) ## allow text update so that values are reformatted pretty-like + + #def textChanged(self): + #print "Text changed." + + +### Drop-in replacement for SpinBox; just for crash-testing +#class SpinBox(QtGui.QDoubleSpinBox): + #valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox + #sigValueChanged = QtCore.Signal(object) # (self) + #sigValueChanging = QtCore.Signal(object) # (value) + #def __init__(self, parent=None, *args, **kargs): + #QtGui.QSpinBox.__init__(self, parent) + + #def __getattr__(self, attr): + #return lambda *args, **kargs: None + + #def widgetGroupInterface(self): + #return (self.valueChanged, SpinBox.value, SpinBox.setValue) + + +if __name__ == '__main__': + import sys + app = QtGui.QApplication([]) + + def valueChanged(sb): + #sb = QtCore.QObject.sender() + print str(sb) + " valueChanged: %s" % str(sb.value()) + + def valueChanging(sb, value): + #sb = QtCore.QObject.sender() + print str(sb) + " valueChanging: %s" % str(sb.value()) + + def mkWin(): + win = QtGui.QMainWindow() + g = QtGui.QFormLayout() + w = QtGui.QWidget() + w.setLayout(g) + win.setCentralWidget(w) + s1 = SpinBox(value=5, step=0.1, bounds=[-1.5, None], suffix='units') + t1 = QtGui.QLineEdit() + g.addRow(s1, t1) + s2 = SpinBox(value=10e-6, dec=True, step=0.1, minStep=1e-6, suffix='A', siPrefix=True) + t2 = QtGui.QLineEdit() + g.addRow(s2, t2) + s3 = SpinBox(value=1000, dec=True, step=0.5, minStep=1e-6, bounds=[1, 1e9], suffix='Hz', siPrefix=True) + t3 = QtGui.QLineEdit() + g.addRow(s3, t3) + s4 = SpinBox(int=True, dec=True, step=1, minStep=1, bounds=[-10, 1000]) + t4 = QtGui.QLineEdit() + g.addRow(s4, t4) + + win.show() + + import sys + for sb in [s1, s2, s3,s4]: + + #QtCore.QObject.connect(sb, QtCore.SIGNAL('valueChanged(double)'), lambda v: sys.stdout.write(str(sb) + " valueChanged\n")) + #QtCore.QObject.connect(sb, QtCore.SIGNAL('editingFinished()'), lambda: sys.stdout.write(str(sb) + " editingFinished\n")) + sb.sigValueChanged.connect(valueChanged) + sb.sigValueChanging.connect(valueChanging) + sb.editingFinished.connect(lambda: sys.stdout.write(str(sb) + " editingFinished\n")) + return win, w, [s1, s2, s3, s4] + a = mkWin() + + + def test(n=100): + for i in range(n): + win, w, sb = mkWin() + for s in sb: + w.setParent(None) + s.setParent(None) + s.valueChanged.disconnect() + s.editingFinished.disconnect() + + ## Start Qt event loop unless running in interactive mode. + if sys.flags.interactive != 1: + app.exec_() diff --git a/widgets/TableWidget.py b/widgets/TableWidget.py new file mode 100644 index 00000000..b1d38a92 --- /dev/null +++ b/widgets/TableWidget.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore + +import numpy as np +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + +__all__ = ['TableWidget'] +class TableWidget(QtGui.QTableWidget): + """Extends QTableWidget with some useful functions for automatic data handling. + Can automatically format and display: + numpy arrays + numpy record arrays + metaarrays + list-of-lists [[1,2,3], [4,5,6]] + dict-of-lists {'x': [1,2,3], 'y': [4,5,6]} + list-of-dicts [ + {'x': 1, 'y': 4}, + {'x': 2, 'y': 5}, + {'x': 3, 'y': 6} + ] + """ + + def __init__(self, *args): + QtGui.QTableWidget.__init__(self, *args) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setSelectionMode(QtGui.QAbstractItemView.ContiguousSelection) + self.clear() + self.contextMenu = QtGui.QMenu() + self.contextMenu.addAction('Copy Selection').triggered.connect(self.copySel) + self.contextMenu.addAction('Copy All').triggered.connect(self.copyAll) + self.contextMenu.addAction('Save Selection').triggered.connect(self.saveSel) + self.contextMenu.addAction('Save All').triggered.connect(self.saveAll) + + def clear(self): + QtGui.QTableWidget.clear(self) + self.verticalHeadersSet = False + self.horizontalHeadersSet = False + self.items = [] + self.setRowCount(0) + self.setColumnCount(0) + + def setData(self, data): + self.clear() + self.appendData(data) + + def appendData(self, data): + """Types allowed: + 1 or 2D numpy array or metaArray + 1D numpy record array + list-of-lists, list-of-dicts or dict-of-lists + """ + fn0, header0 = self.iteratorFn(data) + if fn0 is None: + self.clear() + return + it0 = fn0(data) + try: + first = it0.next() + except StopIteration: + return + #if type(first) == type(np.float64(1)): + # return + fn1, header1 = self.iteratorFn(first) + if fn1 is None: + self.clear() + return + + #print fn0, header0 + #print fn1, header1 + firstVals = [x for x in fn1(first)] + self.setColumnCount(len(firstVals)) + + #print header0, header1 + if not self.verticalHeadersSet and header0 is not None: + #print "set header 0:", header0 + self.setRowCount(len(header0)) + self.setVerticalHeaderLabels(header0) + self.verticalHeadersSet = True + if not self.horizontalHeadersSet and header1 is not None: + #print "set header 1:", header1 + self.setHorizontalHeaderLabels(header1) + self.horizontalHeadersSet = True + + self.setRow(0, firstVals) + i = 1 + for row in it0: + self.setRow(i, [x for x in fn1(row)]) + i += 1 + + def iteratorFn(self, data): + """Return 1) a function that will provide an iterator for data and 2) a list of header strings""" + if isinstance(data, list): + return lambda d: d.__iter__(), None + elif isinstance(data, dict): + return lambda d: d.itervalues(), map(str, data.keys()) + elif HAVE_METAARRAY and isinstance(data, metaarray.MetaArray): + if data.axisHasColumns(0): + header = [str(data.columnName(0, i)) for i in xrange(data.shape[0])] + elif data.axisHasValues(0): + header = map(str, data.xvals(0)) + else: + header = None + return self.iterFirstAxis, header + elif isinstance(data, np.ndarray): + return self.iterFirstAxis, None + elif isinstance(data, np.void): + return self.iterate, map(str, data.dtype.names) + elif data is None: + return (None,None) + else: + raise Exception("Don't know how to iterate over data type: %s" % str(type(data))) + + def iterFirstAxis(self, data): + for i in xrange(data.shape[0]): + yield data[i] + + def iterate(self, data): ## for numpy.void, which can be iterated but mysteriously has no __iter__ (??) + for x in data: + yield x + + def appendRow(self, data): + self.appendData([data]) + + def addRow(self, vals): + #print "add row:", vals + row = self.rowCount() + self.setRowCount(row+1) + self.setRow(row, vals) + + def setRow(self, row, vals): + if row > self.rowCount()-1: + self.setRowCount(row+1) + for col in xrange(self.columnCount()): + val = vals[col] + if isinstance(val, float) or isinstance(val, np.floating): + s = "%0.3g" % val + else: + s = str(val) + item = QtGui.QTableWidgetItem(s) + item.value = val + #print "add item to row %d:"%row, item, item.value + self.items.append(item) + self.setItem(row, col, item) + + def serialize(self, useSelection=False): + """Convert entire table (or just selected area) into tab-separated text values""" + if useSelection: + selection = self.selectedRanges()[0] + rows = range(selection.topRow(), selection.bottomRow()+1) + columns = range(selection.leftColumn(), selection.rightColumn()+1) + else: + rows = range(self.rowCount()) + columns = range(self.columnCount()) + + + data = [] + if self.horizontalHeadersSet: + row = [] + if self.verticalHeadersSet: + row.append(u'') + + for c in columns: + row.append(unicode(self.horizontalHeaderItem(c).text())) + data.append(row) + + for r in rows: + row = [] + if self.verticalHeadersSet: + row.append(unicode(self.verticalHeaderItem(r).text())) + for c in columns: + item = self.item(r, c) + if item is not None: + row.append(unicode(item.value)) + else: + row.append(u'') + data.append(row) + + s = u'' + for row in data: + s += (u'\t'.join(row) + u'\n') + return s + + def copySel(self): + """Copy selected data to clipboard.""" + QtGui.QApplication.clipboard().setText(self.serialize(useSelection=True)) + + def copyAll(self): + """Copy all data to clipboard.""" + QtGui.QApplication.clipboard().setText(self.serialize(useSelection=False)) + + def saveSel(self): + """Save selected data to file.""" + self.save(self.serialize(useSelection=True)) + + def saveAll(self): + """Save all data to file.""" + self.save(self.serialize(useSelection=False)) + + def save(self, data): + fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As..", "", "Tab-separated values (*.tsv)") + if fileName == '': + return + open(fileName, 'w').write(data) + + + def contextMenuEvent(self, ev): + self.contextMenu.popup(ev.globalPos()) + + def keyPressEvent(self, ev): + if ev.text() == 'c' and ev.modifiers() == QtCore.Qt.ControlModifier: + ev.accept() + self.copy() + else: + ev.ignore() + + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + t = TableWidget() + win.setCentralWidget(t) + win.resize(800,600) + win.show() + + ll = [[1,2,3,4,5]] * 20 + ld = [{'x': 1, 'y': 2, 'z': 3}] * 20 + dl = {'x': range(20), 'y': range(20), 'z': range(20)} + + a = np.ones((20, 5)) + ra = np.ones((20,), dtype=[('x', int), ('y', int), ('z', int)]) + + t.setData(ll) + + if HAVE_METAARRAY: + ma = metaarray.MetaArray(np.ones((20, 3)), info=[ + {'values': np.linspace(1, 5, 20)}, + {'cols': [ + {'name': 'x'}, + {'name': 'y'}, + {'name': 'z'}, + ]} + ]) + t.setData(ma) + \ No newline at end of file diff --git a/widgets/TreeWidget.py b/widgets/TreeWidget.py new file mode 100644 index 00000000..43bba487 --- /dev/null +++ b/widgets/TreeWidget.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +if __name__ == '__main__': + import sys, os + sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) +from pyqtgraph.Qt import QtGui, QtCore +from weakref import * + +__all__ = ['TreeWidget'] +class TreeWidget(QtGui.QTreeWidget): + """Extends QTreeWidget to allow internal drag/drop with widgets in the tree. + Also maintains the expanded state of subtrees as they are moved. + This class demonstrates the absurd lengths one must go to to make drag/drop work.""" + + sigItemMoved = QtCore.Signal(object, object, object) # (item, parent, index) + + def __init__(self, parent=None): + QtGui.QTreeWidget.__init__(self, parent) + #self.itemWidgets = WeakKeyDictionary() + self.setAcceptDrops(True) + self.setDragEnabled(True) + self.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed|QtGui.QAbstractItemView.SelectedClicked) + self.placeholders = [] + self.childNestingLimit = None + + def setItemWidget(self, item, col, wid): + w = QtGui.QWidget() ## foster parent / surrogate child widget + l = QtGui.QVBoxLayout() + l.setContentsMargins(0,0,0,0) + w.setLayout(l) + w.setSizePolicy(wid.sizePolicy()) + w.setMinimumHeight(wid.minimumHeight()) + w.setMinimumWidth(wid.minimumWidth()) + l.addWidget(wid) + w.realChild = wid + self.placeholders.append(w) + QtGui.QTreeWidget.setItemWidget(self, item, col, w) + + def itemWidget(self, item, col): + w = QtGui.QTreeWidget.itemWidget(self, item, col) + if w is not None: + w = w.realChild + return w + + def dropMimeData(self, parent, index, data, action): + item = self.currentItem() + p = parent + #print "drop", item, "->", parent, index + while True: + if p is None: + break + if p is item: + return False + #raise Exception("Can not move item into itself.") + p = p.parent() + + if not self.itemMoving(item, parent, index): + return False + + currentParent = item.parent() + if currentParent is None: + currentParent = self.invisibleRootItem() + if parent is None: + parent = self.invisibleRootItem() + + if currentParent is parent and index > parent.indexOfChild(item): + index -= 1 + + self.prepareMove(item) + + currentParent.removeChild(item) + #print " insert child to index", index + parent.insertChild(index, item) ## index will not be correct + self.setCurrentItem(item) + + self.recoverMove(item) + #self.emit(QtCore.SIGNAL('itemMoved'), item, parent, index) + self.sigItemMoved.emit(item, parent, index) + return True + + def itemMoving(self, item, parent, index): + """Called when item has been dropped elsewhere in the tree. + Return True to accept the move, False to reject.""" + return True + + def prepareMove(self, item): + item.__widgets = [] + item.__expanded = item.isExpanded() + for i in range(self.columnCount()): + w = self.itemWidget(item, i) + item.__widgets.append(w) + if w is None: + continue + w.setParent(None) + for i in range(item.childCount()): + self.prepareMove(item.child(i)) + + def recoverMove(self, item): + for i in range(self.columnCount()): + w = item.__widgets[i] + if w is None: + continue + self.setItemWidget(item, i, w) + for i in range(item.childCount()): + self.recoverMove(item.child(i)) + + item.setExpanded(False) ## Items do not re-expand correctly unless they are collapsed first. + QtGui.QApplication.instance().processEvents() + item.setExpanded(item.__expanded) + + def collapseTree(self, item): + item.setExpanded(False) + for i in range(item.childCount()): + self.collapseTree(item.child(i)) + + def removeTopLevelItem(self, item): + for i in range(self.topLevelItemCount()): + if self.topLevelItem(i) is item: + self.takeTopLevelItem(i) + return + raise Exception("Item '%s' not in top-level items." % str(item)) + + def listAllItems(self, item=None): + items = [] + if item != None: + items.append(item) + else: + item = self.invisibleRootItem() + + for cindex in range(item.childCount()): + foundItems = self.listAllItems(item=item.child(cindex)) + for f in foundItems: + items.append(f) + return items + + def dropEvent(self, ev): + QtGui.QTreeWidget.dropEvent(self, ev) + self.updateDropFlags() + + + def updateDropFlags(self): + ### intended to put a limit on how deep nests of children can go. + ### self.childNestingLimit is upheld when moving items without children, but if the item being moved has children/grandchildren, the children/grandchildren + ### can end up over the childNestingLimit. + if self.childNestingLimit == None: + pass # enable drops in all items (but only if there are drops that aren't enabled? for performance...) + else: + items = self.listAllItems() + for item in items: + parentCount = 0 + p = item.parent() + while p is not None: + parentCount += 1 + p = p.parent() + if parentCount >= self.childNestingLimit: + item.setFlags(item.flags() & (~QtCore.Qt.ItemIsDropEnabled)) + else: + item.setFlags(item.flags() | QtCore.Qt.ItemIsDropEnabled) + +if __name__ == '__main__': + app = QtGui.QApplication([]) + + w = TreeWidget() + w.setColumnCount(2) + w.show() + + i1 = QtGui.QTreeWidgetItem(["Item 1"]) + i11 = QtGui.QTreeWidgetItem(["Item 1.1"]) + i12 = QtGui.QTreeWidgetItem(["Item 1.2"]) + i2 = QtGui.QTreeWidgetItem(["Item 2"]) + i21 = QtGui.QTreeWidgetItem(["Item 2.1"]) + i211 = QtGui.QTreeWidgetItem(["Item 2.1.1"]) + i212 = QtGui.QTreeWidgetItem(["Item 2.1.2"]) + i22 = QtGui.QTreeWidgetItem(["Item 2.2"]) + i3 = QtGui.QTreeWidgetItem(["Item 3"]) + i4 = QtGui.QTreeWidgetItem(["Item 4"]) + i5 = QtGui.QTreeWidgetItem(["Item 5"]) + + w.addTopLevelItem(i1) + w.addTopLevelItem(i2) + w.addTopLevelItem(i3) + w.addTopLevelItem(i4) + w.addTopLevelItem(i5) + i1.addChild(i11) + i1.addChild(i12) + i2.addChild(i21) + i21.addChild(i211) + i21.addChild(i212) + i2.addChild(i22) + + b1 = QtGui.QPushButton("B1") + w.setItemWidget(i1, 1, b1) + + app.exec_() + diff --git a/widgets/VerticalLabel.py b/widgets/VerticalLabel.py new file mode 100644 index 00000000..fa45ae5d --- /dev/null +++ b/widgets/VerticalLabel.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore + +__all__ = ['VerticalLabel'] +#class VerticalLabel(QtGui.QLabel): + #def paintEvent(self, ev): + #p = QtGui.QPainter(self) + #p.rotate(-90) + #self.hint = p.drawText(QtCore.QRect(-self.height(), 0, self.height(), self.width()), QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter, self.text()) + #p.end() + #self.setMinimumWidth(self.hint.height()) + #self.setMinimumHeight(self.hint.width()) + + #def sizeHint(self): + #if hasattr(self, 'hint'): + #return QtCore.QSize(self.hint.height(), self.hint.width()) + #else: + #return QtCore.QSize(16, 50) + +class VerticalLabel(QtGui.QLabel): + def __init__(self, text, orientation='vertical', forceWidth=True): + QtGui.QLabel.__init__(self, text) + self.forceWidth = forceWidth + self.orientation = None + self.setOrientation(orientation) + + def setOrientation(self, o): + if self.orientation == o: + return + self.orientation = o + self.update() + self.updateGeometry() + + def paintEvent(self, ev): + p = QtGui.QPainter(self) + #p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) + #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) + #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) + + #p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255))) + + if self.orientation == 'vertical': + p.rotate(-90) + rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width()) + else: + rgn = self.contentsRect() + align = self.alignment() + #align = QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter + + self.hint = p.drawText(rgn, align, self.text()) + p.end() + + if self.orientation == 'vertical': + self.setMaximumWidth(self.hint.height()) + self.setMinimumWidth(0) + self.setMaximumHeight(16777215) + if self.forceWidth: + self.setMinimumHeight(self.hint.width()) + else: + self.setMinimumHeight(0) + else: + self.setMaximumHeight(self.hint.height()) + self.setMinimumHeight(0) + self.setMaximumWidth(16777215) + if self.forceWidth: + self.setMinimumWidth(self.hint.width()) + else: + self.setMinimumWidth(0) + + def sizeHint(self): + if self.orientation == 'vertical': + if hasattr(self, 'hint'): + return QtCore.QSize(self.hint.height(), self.hint.width()) + else: + return QtCore.QSize(19, 50) + else: + if hasattr(self, 'hint'): + return QtCore.QSize(self.hint.width(), self.hint.height()) + else: + return QtCore.QSize(50, 19) + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + w = QtGui.QWidget() + l = QtGui.QGridLayout() + w.setLayout(l) + + l1 = VerticalLabel("text 1", orientation='horizontal') + l2 = VerticalLabel("text 2") + l3 = VerticalLabel("text 3") + l4 = VerticalLabel("text 4", orientation='horizontal') + l.addWidget(l1, 0, 0) + l.addWidget(l2, 1, 1) + l.addWidget(l3, 2, 2) + l.addWidget(l4, 3, 3) + win.setCentralWidget(w) + win.show() \ No newline at end of file diff --git a/widgets/__init__.py b/widgets/__init__.py new file mode 100644 index 00000000..a81fe391 --- /dev/null +++ b/widgets/__init__.py @@ -0,0 +1,21 @@ +## just import everything from sub-modules + +#import os + +#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 != '__init__.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: + #print modName, k + #globals()[k] = getattr(mod, k) -- GitLab