2012-03-02 02:55:32 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2021-02-19 19:33:06 +00:00
|
|
|
import importlib
|
|
|
|
import os
|
2020-06-09 04:36:09 +00:00
|
|
|
import warnings
|
2012-03-02 02:55:32 +00:00
|
|
|
import weakref
|
2021-06-07 14:44:19 +00:00
|
|
|
import collections.abc
|
2021-02-19 19:33:06 +00:00
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
import numpy as np
|
2021-02-19 19:33:06 +00:00
|
|
|
|
|
|
|
from ..AxisItem import AxisItem
|
|
|
|
from ..ButtonItem import ButtonItem
|
|
|
|
from ..GraphicsWidget import GraphicsWidget
|
|
|
|
from ..InfiniteLine import InfiniteLine
|
|
|
|
from ..LabelItem import LabelItem
|
|
|
|
from ..LegendItem import LegendItem
|
|
|
|
from ..PlotDataItem import PlotDataItem
|
|
|
|
from ..ViewBox import ViewBox
|
2015-05-19 13:29:55 +00:00
|
|
|
from ... import functions as fn
|
2021-02-19 19:33:06 +00:00
|
|
|
from ... import icons, PlotCurveItem, ScatterPlotItem
|
|
|
|
from ...Qt import QtGui, QtCore, QT_LIB
|
2013-12-23 12:51:33 +00:00
|
|
|
from ...WidgetGroup import WidgetGroup
|
2015-05-19 13:29:55 +00:00
|
|
|
from ...python2_3 import basestring
|
2021-02-19 19:33:06 +00:00
|
|
|
from ...widgets.FileDialog import FileDialog
|
2015-05-19 13:29:55 +00:00
|
|
|
|
2021-01-27 19:34:32 +00:00
|
|
|
translate = QtCore.QCoreApplication.translate
|
|
|
|
|
2021-01-14 03:15:34 +00:00
|
|
|
ui_template = importlib.import_module(
|
|
|
|
f'.plotConfigTemplate_{QT_LIB.lower()}', package=__package__)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
__all__ = ['PlotItem']
|
|
|
|
|
|
|
|
|
|
|
|
class PlotItem(GraphicsWidget):
|
2018-06-08 15:43:46 +00:00
|
|
|
"""GraphicsWidget implementing a standard 2D plotting area with axes.
|
|
|
|
|
2012-04-16 20:45:55 +00:00
|
|
|
**Bases:** :class:`GraphicsWidget <pyqtgraph.GraphicsWidget>`
|
2012-04-15 14:20:07 +00:00
|
|
|
|
2018-06-08 15:43:46 +00:00
|
|
|
This class provides the ViewBox-plus-axes that appear when using
|
|
|
|
:func:`pg.plot() <pyqtgraph.plot>`, :class:`PlotWidget <pyqtgraph.PlotWidget>`,
|
|
|
|
and :func:`GraphicsLayoutWidget.addPlot() <pyqtgraph.GraphicsLayoutWidget.addPlot>`.
|
|
|
|
|
|
|
|
It's main functionality is:
|
|
|
|
|
|
|
|
- Manage placement of ViewBox, AxisItems, and LabelItems
|
|
|
|
- Create and manage a list of PlotDataItems displayed inside the ViewBox
|
|
|
|
- Implement a context menu with commonly used display and analysis options
|
|
|
|
|
|
|
|
Use :func:`plot() <pyqtgraph.PlotItem.plot>` to create a new PlotDataItem and
|
|
|
|
add it to the view. Use :func:`addItem() <pyqtgraph.PlotItem.addItem>` to
|
|
|
|
add any QGraphicsItem to the view.
|
2012-04-15 14:20:07 +00:00
|
|
|
|
|
|
|
This class wraps several methods from its internal ViewBox:
|
2012-04-15 16:32:20 +00:00
|
|
|
:func:`setXRange <pyqtgraph.ViewBox.setXRange>`,
|
|
|
|
:func:`setYRange <pyqtgraph.ViewBox.setYRange>`,
|
|
|
|
:func:`setRange <pyqtgraph.ViewBox.setRange>`,
|
|
|
|
:func:`autoRange <pyqtgraph.ViewBox.autoRange>`,
|
2021-06-07 14:44:19 +00:00
|
|
|
:func:`setDefaultPadding <pyqtgraph.ViewBox.setDefaultPadding>`,
|
2012-04-15 16:32:20 +00:00
|
|
|
:func:`setXLink <pyqtgraph.ViewBox.setXLink>`,
|
|
|
|
:func:`setYLink <pyqtgraph.ViewBox.setYLink>`,
|
2012-05-08 21:56:55 +00:00
|
|
|
:func:`setAutoPan <pyqtgraph.ViewBox.setAutoPan>`,
|
|
|
|
:func:`setAutoVisible <pyqtgraph.ViewBox.setAutoVisible>`,
|
2014-02-05 01:28:23 +00:00
|
|
|
:func:`setLimits <pyqtgraph.ViewBox.setLimits>`,
|
2012-04-15 16:32:20 +00:00
|
|
|
:func:`viewRect <pyqtgraph.ViewBox.viewRect>`,
|
2012-04-28 20:00:42 +00:00
|
|
|
:func:`viewRange <pyqtgraph.ViewBox.viewRange>`,
|
2012-04-15 16:32:20 +00:00
|
|
|
:func:`setMouseEnabled <pyqtgraph.ViewBox.setMouseEnabled>`,
|
|
|
|
:func:`enableAutoRange <pyqtgraph.ViewBox.enableAutoRange>`,
|
|
|
|
:func:`disableAutoRange <pyqtgraph.ViewBox.disableAutoRange>`,
|
|
|
|
:func:`setAspectLocked <pyqtgraph.ViewBox.setAspectLocked>`,
|
2012-08-03 04:29:05 +00:00
|
|
|
:func:`invertY <pyqtgraph.ViewBox.invertY>`,
|
2014-04-28 11:36:59 +00:00
|
|
|
:func:`invertX <pyqtgraph.ViewBox.invertX>`,
|
2012-04-15 16:32:20 +00:00
|
|
|
:func:`register <pyqtgraph.ViewBox.register>`,
|
|
|
|
:func:`unregister <pyqtgraph.ViewBox.unregister>`
|
2012-04-15 14:20:07 +00:00
|
|
|
|
2012-04-15 16:32:20 +00:00
|
|
|
The ViewBox itself can be accessed by calling :func:`getViewBox() <pyqtgraph.PlotItem.getViewBox>`
|
2012-04-16 20:45:55 +00:00
|
|
|
|
|
|
|
==================== =======================================================================
|
2014-02-12 20:06:54 +00:00
|
|
|
**Signals:**
|
2012-04-16 20:45:55 +00:00
|
|
|
sigYRangeChanged wrapped from :class:`ViewBox <pyqtgraph.ViewBox>`
|
|
|
|
sigXRangeChanged wrapped from :class:`ViewBox <pyqtgraph.ViewBox>`
|
|
|
|
sigRangeChanged wrapped from :class:`ViewBox <pyqtgraph.ViewBox>`
|
|
|
|
==================== =======================================================================
|
2012-04-15 14:20:07 +00:00
|
|
|
"""
|
2012-04-15 16:32:20 +00:00
|
|
|
|
|
|
|
sigRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox range has changed
|
|
|
|
sigYRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox Y range has changed
|
|
|
|
sigXRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox X range has changed
|
2018-06-08 15:43:46 +00:00
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
lastFileDir = None
|
|
|
|
|
2012-07-09 12:38:30 +00:00
|
|
|
def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs):
|
2012-03-23 06:41:10 +00:00
|
|
|
"""
|
|
|
|
Create a new PlotItem. All arguments are optional.
|
Date axis item (#1154)
* Add DateAxisItem
* Change style to camelCase
* Fix missing first tick for negative timestamps
* Add ms precision, auto skipping
Auto skipping allows a zoom level to skip ticks automatically if the
maximum number of ticks/pt is exceeded
* fixes suggested by @goetzc
* workaround for negative argument to utcfromtimestamp on windows
* attachToPlotItem method
* default date axis orientation
* Use new DateAxisItem in Plot Customization example
* attachToPlotItem bugfix
* examples of DateAxisItem
* modified description of customPlot example
* added descriptions to the new examples, reformatted their code, included the first one into utils.py
* typo
* Refactored code for setting axis items into new function
Replaces "DateAxisItem.attachToPlotItem"
* Fix string comparison with ==
* Doc: Slightly more text for DateAxisItem, small improvement for PlotItem
* Make PlotWidget.setAxisItems official
* Fix typo in docstring
* renamed an example
* merge bug fix
* Revert "merge bug fix"
This reverts commit 876b5a7cdb50cd824b4a5218427081b3ce5c2fe4.
* Real bug fix
* support for dates upto -1e13..1e13
* Automatically limit DateAxisItem to a range from -1e12 to 1e12 years
Very large years (|y|>1e13) cause infinite loop, and since nobody
needs time 100 times larger than the age of the universe anyways,
this constrains it to 1e12.
Following suggestion by @axil:
https://github.com/pyqtgraph/pyqtgraph/pull/1154#issuecomment-612662168
* Also catch ValueErrors occuring on Linux before OverfloeErrors
While zooming out, before hitting OverflowErrors, utctimestamp
produces ValueErrors (at least on my Linux machine), so they
are also catched.
* Fix: Timestamp 0 corresponds to year 1970
For large years, x axis labels jump by 1970 years if it is not
accounted for timestamp 0 to be equal to year 1970.
* Fix: When zooming into extreme dates, OSError occurs
This commit catches the OSError like the other observed errors
* Disable stepping below years for dates outside *_REGULAR_TIMESTAMP
2 reasons: First: At least on my Linux machine, zooming into
those dates creates infinite loops. Second: Nobody needs
sub-year-precision for those extreme years anyways.
* Adapt zoom level sizes based on current font size and screen resolution
This is somewhat experimental. With this commit, no longer 60 px are
assumed as width for all zoom levels, but the current font and
display resolution are considered to calculate the width of ticks in
each zoom level. See the new function `updateZoomLevels` for
details.
Before calling this function, overridden functions `paint` and
`generateDrawSpecs` provide information over the current display
and font via `self.fontScaleFactor` and `self.fontMetrics`.
* Meaningful error meassage when adding axis to multiple PlotItems
As @axil noted in the DateAxisItem PR, currently users get a
segmentation fault when one tries to add an axis to multiple
PlotItems. This commit adds a meaningful RuntimeError message
for that case.
* setZoomLevelForDensity: Refactoring and calculating optimal spacing on the fly
* DateTimeAxis Fix: 1970 shows when zooming far out
* Refactoring: Make zoomLevels a customizable dict again
* updated the dateaxisitem example
* Fix: Get screen resolution in a way that also works for Qt 4
This is both a simplification in code and an improvement in backwards compatibility with Qt 4.
* DateAxisItem Fix: Also resolve time below 0.5 seconds
* unix line endings in examples
* DateTimeAxis Fix: For years < 1 and > 9999, stepping broke
Stepping was off by 1970 years for years < 1 and > 9999,
resulting in a gap in ticks visible when zooming out. Fixed by
subtracting the usual 1970 years.
* DateTimeAxis Fix: Zooming out too far causes infinite loop
Fixed by setting default limits to +/- 1e10 years. Should still
be enough.
* improved second dateaxisitem example
* 1..9999 years limit
* DateTimeAxis: Use OrderedDict to stay compatible with Python < 3-6
* DateAxisItem: Use font height to determine spacing for vertical axes
* window title
* added dateaxisitem.rst
* updated index.rst
Co-authored-by: Lukas Heiniger <lukas.heiniger@sed.ethz.ch>
Co-authored-by: Lev Maximov <lev.maximov@gmail.com>
Co-authored-by: 2xB <2xB@users.noreply.github.com>
2020-04-27 18:43:22 +00:00
|
|
|
Any extra keyword arguments are passed to :func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`.
|
2012-03-23 06:41:10 +00:00
|
|
|
|
2012-07-09 12:38:30 +00:00
|
|
|
============== ==========================================================================================
|
2014-02-03 20:13:10 +00:00
|
|
|
**Arguments:**
|
2012-07-09 12:38:30 +00:00
|
|
|
*title* Title to display at the top of the item. Html is allowed.
|
|
|
|
*labels* A dictionary specifying the axis labels to display::
|
2012-04-15 16:32:20 +00:00
|
|
|
|
2012-07-09 12:38:30 +00:00
|
|
|
{'left': (args), 'bottom': (args), ...}
|
2012-04-15 16:32:20 +00:00
|
|
|
|
2012-07-09 12:38:30 +00:00
|
|
|
The name of each axis and the corresponding arguments are passed to
|
|
|
|
:func:`PlotItem.setLabel() <pyqtgraph.PlotItem.setLabel>`
|
|
|
|
Optionally, PlotItem my also be initialized with the keyword arguments left,
|
|
|
|
right, top, or bottom to achieve the same effect.
|
|
|
|
*name* Registers a name for this view so that others may link to it
|
|
|
|
*viewBox* If specified, the PlotItem will be constructed with this as its ViewBox.
|
|
|
|
*axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items
|
|
|
|
for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top')
|
|
|
|
and the values must be instances of AxisItem (or at least compatible with AxisItem).
|
|
|
|
============== ==========================================================================================
|
2012-03-23 06:41:10 +00:00
|
|
|
"""
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
GraphicsWidget.__init__(self, parent)
|
|
|
|
|
2021-06-06 01:17:43 +00:00
|
|
|
self.setSizePolicy(QtGui.QSizePolicy.Policy.Expanding, QtGui.QSizePolicy.Policy.Expanding)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
## Set up control buttons
|
|
|
|
path = os.path.dirname(__file__)
|
2021-02-06 05:09:59 +00:00
|
|
|
self.autoBtn = ButtonItem(icons.getGraphPixmap('auto'), 14, self)
|
2012-03-02 02:55:32 +00:00
|
|
|
self.autoBtn.mode = 'auto'
|
|
|
|
self.autoBtn.clicked.connect(self.autoBtnClicked)
|
2012-12-23 05:51:28 +00:00
|
|
|
self.buttonsHidden = False ## whether the user has requested buttons to be hidden
|
|
|
|
self.mouseHovering = False
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
self.layout = QtGui.QGraphicsGridLayout()
|
|
|
|
self.layout.setContentsMargins(1,1,1,1)
|
|
|
|
self.setLayout(self.layout)
|
|
|
|
self.layout.setHorizontalSpacing(0)
|
|
|
|
self.layout.setVerticalSpacing(0)
|
2021-01-13 04:34:14 +00:00
|
|
|
|
2012-07-09 12:38:30 +00:00
|
|
|
if viewBox is None:
|
2021-01-13 04:34:14 +00:00
|
|
|
viewBox = ViewBox(parent=self, enableMenu=enableMenu)
|
2012-07-09 12:38:30 +00:00
|
|
|
self.vb = viewBox
|
2012-12-23 05:51:28 +00:00
|
|
|
self.vb.sigStateChanged.connect(self.viewStateChanged)
|
2021-01-13 04:34:14 +00:00
|
|
|
|
|
|
|
# Enable or disable plotItem menu
|
|
|
|
self.setMenuEnabled(enableMenu, None)
|
2012-07-09 12:38:30 +00:00
|
|
|
|
|
|
|
if name is not None:
|
|
|
|
self.vb.register(name)
|
2012-03-23 06:46:59 +00:00
|
|
|
self.vb.sigRangeChanged.connect(self.sigRangeChanged)
|
|
|
|
self.vb.sigXRangeChanged.connect(self.sigXRangeChanged)
|
|
|
|
self.vb.sigYRangeChanged.connect(self.sigYRangeChanged)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
self.layout.addItem(self.vb, 2, 1)
|
|
|
|
self.alpha = 1.0
|
|
|
|
self.autoAlpha = True
|
|
|
|
self.spectrumMode = False
|
|
|
|
|
2012-12-27 01:12:49 +00:00
|
|
|
self.legend = None
|
|
|
|
|
2020-06-08 03:29:28 +00:00
|
|
|
# Initialize axis items
|
2012-07-09 12:38:30 +00:00
|
|
|
self.axes = {}
|
2020-06-08 03:29:28 +00:00
|
|
|
self.setAxisItems(axisItems)
|
|
|
|
|
2014-11-27 02:25:17 +00:00
|
|
|
self.titleLabel = LabelItem('', size='11pt', parent=self)
|
2012-03-02 02:55:32 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
self.items = []
|
|
|
|
self.curves = []
|
|
|
|
self.itemMeta = weakref.WeakKeyDictionary()
|
|
|
|
self.dataItems = []
|
|
|
|
self.paramList = {}
|
|
|
|
self.avgCurves = {}
|
|
|
|
|
|
|
|
### Set up context menu
|
|
|
|
|
|
|
|
w = QtGui.QWidget()
|
2021-01-14 03:15:34 +00:00
|
|
|
self.ctrl = c = ui_template.Ui_Form()
|
2012-03-02 02:55:32 +00:00
|
|
|
c.setupUi(w)
|
|
|
|
dv = QtGui.QDoubleValidator(self)
|
|
|
|
|
|
|
|
menuItems = [
|
2021-01-27 19:34:32 +00:00
|
|
|
(translate("PlotItem", 'Transforms'), c.transformGroup),
|
|
|
|
(translate("PlotItem", 'Downsample'), c.decimateGroup),
|
|
|
|
(translate("PlotItem", 'Average'), c.averageGroup),
|
|
|
|
(translate("PlotItem", 'Alpha'), c.alphaGroup),
|
|
|
|
(translate("PlotItem", 'Grid'), c.gridGroup),
|
|
|
|
(translate("PlotItem", 'Points'), c.pointsGroup),
|
2012-03-02 02:55:32 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
self.ctrlMenu = QtGui.QMenu()
|
|
|
|
|
2021-01-27 19:34:32 +00:00
|
|
|
self.ctrlMenu.setTitle(translate("PlotItem", 'Plot Options'))
|
2012-03-02 02:55:32 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
self.stateGroup = WidgetGroup()
|
|
|
|
for name, w in menuItems:
|
|
|
|
self.stateGroup.autoAdd(w)
|
|
|
|
|
|
|
|
self.fileDialog = None
|
|
|
|
|
|
|
|
c.alphaGroup.toggled.connect(self.updateAlpha)
|
|
|
|
c.alphaSlider.valueChanged.connect(self.updateAlpha)
|
|
|
|
c.autoAlphaCheck.toggled.connect(self.updateAlpha)
|
|
|
|
|
2012-04-21 19:57:47 +00:00
|
|
|
c.xGridCheck.toggled.connect(self.updateGrid)
|
|
|
|
c.yGridCheck.toggled.connect(self.updateGrid)
|
2012-03-02 02:55:32 +00:00
|
|
|
c.gridAlphaSlider.valueChanged.connect(self.updateGrid)
|
|
|
|
|
2012-04-21 19:57:47 +00:00
|
|
|
c.fftCheck.toggled.connect(self.updateSpectrumMode)
|
|
|
|
c.logXCheck.toggled.connect(self.updateLogMode)
|
|
|
|
c.logYCheck.toggled.connect(self.updateLogMode)
|
2017-03-27 20:00:38 +00:00
|
|
|
c.derivativeCheck.toggled.connect(self.updateDerivativeMode)
|
|
|
|
c.phasemapCheck.toggled.connect(self.updatePhasemapMode)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
c.downsampleSpin.valueChanged.connect(self.updateDownsampling)
|
2013-07-03 15:20:49 +00:00
|
|
|
c.downsampleCheck.toggled.connect(self.updateDownsampling)
|
|
|
|
c.autoDownsampleCheck.toggled.connect(self.updateDownsampling)
|
|
|
|
c.subsampleRadio.toggled.connect(self.updateDownsampling)
|
|
|
|
c.meanRadio.toggled.connect(self.updateDownsampling)
|
|
|
|
c.clipToViewCheck.toggled.connect(self.updateDownsampling)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked)
|
|
|
|
self.ctrl.averageGroup.toggled.connect(self.avgToggled)
|
|
|
|
|
|
|
|
self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation)
|
2021-05-06 01:07:34 +00:00
|
|
|
self.ctrl.forgetTracesCheck.toggled.connect(self.updateDecimation)
|
2012-03-02 02:55:32 +00:00
|
|
|
self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation)
|
2020-06-08 03:29:28 +00:00
|
|
|
|
2012-03-23 06:41:10 +00:00
|
|
|
if labels is None:
|
|
|
|
labels = {}
|
2012-07-09 12:38:30 +00:00
|
|
|
for label in list(self.axes.keys()):
|
2012-03-23 06:41:10 +00:00
|
|
|
if label in kargs:
|
|
|
|
labels[label] = kargs[label]
|
|
|
|
del kargs[label]
|
|
|
|
for k in labels:
|
|
|
|
if isinstance(labels[k], basestring):
|
|
|
|
labels[k] = (labels[k],)
|
|
|
|
self.setLabel(k, *labels[k])
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
if title is not None:
|
|
|
|
self.setTitle(title)
|
|
|
|
|
|
|
|
if len(kargs) > 0:
|
2020-06-08 03:29:28 +00:00
|
|
|
self.plot(**kargs)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def implements(self, interface=None):
|
|
|
|
return interface in ['ViewBoxWrapper']
|
|
|
|
|
|
|
|
def getViewBox(self):
|
2012-07-09 12:38:30 +00:00
|
|
|
"""Return the :class:`ViewBox <pyqtgraph.ViewBox>` contained within."""
|
2012-03-02 02:55:32 +00:00
|
|
|
return self.vb
|
|
|
|
|
2014-03-13 20:38:50 +00:00
|
|
|
## Wrap a few methods from viewBox.
|
|
|
|
#Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive
|
|
|
|
#because we had a reference to an instance method (creating wrapper methods at runtime instead).
|
2014-03-30 06:51:32 +00:00
|
|
|
for m in ['setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', # NOTE:
|
2021-06-07 14:44:19 +00:00
|
|
|
'setAutoVisible', 'setDefaultPadding', 'setRange', 'autoRange', 'viewRect', 'viewRange', # If you update this list, please
|
2014-03-30 06:51:32 +00:00
|
|
|
'setMouseEnabled', 'setLimits', 'enableAutoRange', 'disableAutoRange', # update the class docstring
|
2014-04-28 11:36:59 +00:00
|
|
|
'setAspectLocked', 'invertY', 'invertX', 'register', 'unregister']: # as well.
|
2014-03-30 06:51:32 +00:00
|
|
|
|
2014-03-24 19:47:32 +00:00
|
|
|
def _create_method(name):
|
2014-03-13 20:38:50 +00:00
|
|
|
def method(self, *args, **kwargs):
|
|
|
|
return getattr(self.vb, name)(*args, **kwargs)
|
|
|
|
method.__name__ = name
|
|
|
|
return method
|
|
|
|
|
2014-03-24 19:47:32 +00:00
|
|
|
locals()[m] = _create_method(m)
|
2014-03-13 20:38:50 +00:00
|
|
|
|
|
|
|
del _create_method
|
Date axis item (#1154)
* Add DateAxisItem
* Change style to camelCase
* Fix missing first tick for negative timestamps
* Add ms precision, auto skipping
Auto skipping allows a zoom level to skip ticks automatically if the
maximum number of ticks/pt is exceeded
* fixes suggested by @goetzc
* workaround for negative argument to utcfromtimestamp on windows
* attachToPlotItem method
* default date axis orientation
* Use new DateAxisItem in Plot Customization example
* attachToPlotItem bugfix
* examples of DateAxisItem
* modified description of customPlot example
* added descriptions to the new examples, reformatted their code, included the first one into utils.py
* typo
* Refactored code for setting axis items into new function
Replaces "DateAxisItem.attachToPlotItem"
* Fix string comparison with ==
* Doc: Slightly more text for DateAxisItem, small improvement for PlotItem
* Make PlotWidget.setAxisItems official
* Fix typo in docstring
* renamed an example
* merge bug fix
* Revert "merge bug fix"
This reverts commit 876b5a7cdb50cd824b4a5218427081b3ce5c2fe4.
* Real bug fix
* support for dates upto -1e13..1e13
* Automatically limit DateAxisItem to a range from -1e12 to 1e12 years
Very large years (|y|>1e13) cause infinite loop, and since nobody
needs time 100 times larger than the age of the universe anyways,
this constrains it to 1e12.
Following suggestion by @axil:
https://github.com/pyqtgraph/pyqtgraph/pull/1154#issuecomment-612662168
* Also catch ValueErrors occuring on Linux before OverfloeErrors
While zooming out, before hitting OverflowErrors, utctimestamp
produces ValueErrors (at least on my Linux machine), so they
are also catched.
* Fix: Timestamp 0 corresponds to year 1970
For large years, x axis labels jump by 1970 years if it is not
accounted for timestamp 0 to be equal to year 1970.
* Fix: When zooming into extreme dates, OSError occurs
This commit catches the OSError like the other observed errors
* Disable stepping below years for dates outside *_REGULAR_TIMESTAMP
2 reasons: First: At least on my Linux machine, zooming into
those dates creates infinite loops. Second: Nobody needs
sub-year-precision for those extreme years anyways.
* Adapt zoom level sizes based on current font size and screen resolution
This is somewhat experimental. With this commit, no longer 60 px are
assumed as width for all zoom levels, but the current font and
display resolution are considered to calculate the width of ticks in
each zoom level. See the new function `updateZoomLevels` for
details.
Before calling this function, overridden functions `paint` and
`generateDrawSpecs` provide information over the current display
and font via `self.fontScaleFactor` and `self.fontMetrics`.
* Meaningful error meassage when adding axis to multiple PlotItems
As @axil noted in the DateAxisItem PR, currently users get a
segmentation fault when one tries to add an axis to multiple
PlotItems. This commit adds a meaningful RuntimeError message
for that case.
* setZoomLevelForDensity: Refactoring and calculating optimal spacing on the fly
* DateTimeAxis Fix: 1970 shows when zooming far out
* Refactoring: Make zoomLevels a customizable dict again
* updated the dateaxisitem example
* Fix: Get screen resolution in a way that also works for Qt 4
This is both a simplification in code and an improvement in backwards compatibility with Qt 4.
* DateAxisItem Fix: Also resolve time below 0.5 seconds
* unix line endings in examples
* DateTimeAxis Fix: For years < 1 and > 9999, stepping broke
Stepping was off by 1970 years for years < 1 and > 9999,
resulting in a gap in ticks visible when zooming out. Fixed by
subtracting the usual 1970 years.
* DateTimeAxis Fix: Zooming out too far causes infinite loop
Fixed by setting default limits to +/- 1e10 years. Should still
be enough.
* improved second dateaxisitem example
* 1..9999 years limit
* DateTimeAxis: Use OrderedDict to stay compatible with Python < 3-6
* DateAxisItem: Use font height to determine spacing for vertical axes
* window title
* added dateaxisitem.rst
* updated index.rst
Co-authored-by: Lukas Heiniger <lukas.heiniger@sed.ethz.ch>
Co-authored-by: Lev Maximov <lev.maximov@gmail.com>
Co-authored-by: 2xB <2xB@users.noreply.github.com>
2020-04-27 18:43:22 +00:00
|
|
|
|
|
|
|
def setAxisItems(self, axisItems=None):
|
|
|
|
"""
|
|
|
|
Place axis items as given by `axisItems`. Initializes non-existing axis items.
|
|
|
|
|
|
|
|
============== ==========================================================================================
|
2020-05-12 04:17:57 +00:00
|
|
|
**Arguments:**
|
Date axis item (#1154)
* Add DateAxisItem
* Change style to camelCase
* Fix missing first tick for negative timestamps
* Add ms precision, auto skipping
Auto skipping allows a zoom level to skip ticks automatically if the
maximum number of ticks/pt is exceeded
* fixes suggested by @goetzc
* workaround for negative argument to utcfromtimestamp on windows
* attachToPlotItem method
* default date axis orientation
* Use new DateAxisItem in Plot Customization example
* attachToPlotItem bugfix
* examples of DateAxisItem
* modified description of customPlot example
* added descriptions to the new examples, reformatted their code, included the first one into utils.py
* typo
* Refactored code for setting axis items into new function
Replaces "DateAxisItem.attachToPlotItem"
* Fix string comparison with ==
* Doc: Slightly more text for DateAxisItem, small improvement for PlotItem
* Make PlotWidget.setAxisItems official
* Fix typo in docstring
* renamed an example
* merge bug fix
* Revert "merge bug fix"
This reverts commit 876b5a7cdb50cd824b4a5218427081b3ce5c2fe4.
* Real bug fix
* support for dates upto -1e13..1e13
* Automatically limit DateAxisItem to a range from -1e12 to 1e12 years
Very large years (|y|>1e13) cause infinite loop, and since nobody
needs time 100 times larger than the age of the universe anyways,
this constrains it to 1e12.
Following suggestion by @axil:
https://github.com/pyqtgraph/pyqtgraph/pull/1154#issuecomment-612662168
* Also catch ValueErrors occuring on Linux before OverfloeErrors
While zooming out, before hitting OverflowErrors, utctimestamp
produces ValueErrors (at least on my Linux machine), so they
are also catched.
* Fix: Timestamp 0 corresponds to year 1970
For large years, x axis labels jump by 1970 years if it is not
accounted for timestamp 0 to be equal to year 1970.
* Fix: When zooming into extreme dates, OSError occurs
This commit catches the OSError like the other observed errors
* Disable stepping below years for dates outside *_REGULAR_TIMESTAMP
2 reasons: First: At least on my Linux machine, zooming into
those dates creates infinite loops. Second: Nobody needs
sub-year-precision for those extreme years anyways.
* Adapt zoom level sizes based on current font size and screen resolution
This is somewhat experimental. With this commit, no longer 60 px are
assumed as width for all zoom levels, but the current font and
display resolution are considered to calculate the width of ticks in
each zoom level. See the new function `updateZoomLevels` for
details.
Before calling this function, overridden functions `paint` and
`generateDrawSpecs` provide information over the current display
and font via `self.fontScaleFactor` and `self.fontMetrics`.
* Meaningful error meassage when adding axis to multiple PlotItems
As @axil noted in the DateAxisItem PR, currently users get a
segmentation fault when one tries to add an axis to multiple
PlotItems. This commit adds a meaningful RuntimeError message
for that case.
* setZoomLevelForDensity: Refactoring and calculating optimal spacing on the fly
* DateTimeAxis Fix: 1970 shows when zooming far out
* Refactoring: Make zoomLevels a customizable dict again
* updated the dateaxisitem example
* Fix: Get screen resolution in a way that also works for Qt 4
This is both a simplification in code and an improvement in backwards compatibility with Qt 4.
* DateAxisItem Fix: Also resolve time below 0.5 seconds
* unix line endings in examples
* DateTimeAxis Fix: For years < 1 and > 9999, stepping broke
Stepping was off by 1970 years for years < 1 and > 9999,
resulting in a gap in ticks visible when zooming out. Fixed by
subtracting the usual 1970 years.
* DateTimeAxis Fix: Zooming out too far causes infinite loop
Fixed by setting default limits to +/- 1e10 years. Should still
be enough.
* improved second dateaxisitem example
* 1..9999 years limit
* DateTimeAxis: Use OrderedDict to stay compatible with Python < 3-6
* DateAxisItem: Use font height to determine spacing for vertical axes
* window title
* added dateaxisitem.rst
* updated index.rst
Co-authored-by: Lukas Heiniger <lukas.heiniger@sed.ethz.ch>
Co-authored-by: Lev Maximov <lev.maximov@gmail.com>
Co-authored-by: 2xB <2xB@users.noreply.github.com>
2020-04-27 18:43:22 +00:00
|
|
|
*axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items
|
|
|
|
for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top')
|
|
|
|
and the values must be instances of AxisItem (or at least compatible with AxisItem).
|
|
|
|
============== ==========================================================================================
|
|
|
|
"""
|
|
|
|
|
|
|
|
if axisItems is None:
|
|
|
|
axisItems = {}
|
|
|
|
|
|
|
|
# Array containing visible axis items
|
|
|
|
# Also containing potentially hidden axes, but they are not touched so it does not matter
|
|
|
|
visibleAxes = ['left', 'bottom']
|
2020-09-23 03:25:02 +00:00
|
|
|
visibleAxes.extend(axisItems.keys()) # Note that it does not matter that this adds
|
Date axis item (#1154)
* Add DateAxisItem
* Change style to camelCase
* Fix missing first tick for negative timestamps
* Add ms precision, auto skipping
Auto skipping allows a zoom level to skip ticks automatically if the
maximum number of ticks/pt is exceeded
* fixes suggested by @goetzc
* workaround for negative argument to utcfromtimestamp on windows
* attachToPlotItem method
* default date axis orientation
* Use new DateAxisItem in Plot Customization example
* attachToPlotItem bugfix
* examples of DateAxisItem
* modified description of customPlot example
* added descriptions to the new examples, reformatted their code, included the first one into utils.py
* typo
* Refactored code for setting axis items into new function
Replaces "DateAxisItem.attachToPlotItem"
* Fix string comparison with ==
* Doc: Slightly more text for DateAxisItem, small improvement for PlotItem
* Make PlotWidget.setAxisItems official
* Fix typo in docstring
* renamed an example
* merge bug fix
* Revert "merge bug fix"
This reverts commit 876b5a7cdb50cd824b4a5218427081b3ce5c2fe4.
* Real bug fix
* support for dates upto -1e13..1e13
* Automatically limit DateAxisItem to a range from -1e12 to 1e12 years
Very large years (|y|>1e13) cause infinite loop, and since nobody
needs time 100 times larger than the age of the universe anyways,
this constrains it to 1e12.
Following suggestion by @axil:
https://github.com/pyqtgraph/pyqtgraph/pull/1154#issuecomment-612662168
* Also catch ValueErrors occuring on Linux before OverfloeErrors
While zooming out, before hitting OverflowErrors, utctimestamp
produces ValueErrors (at least on my Linux machine), so they
are also catched.
* Fix: Timestamp 0 corresponds to year 1970
For large years, x axis labels jump by 1970 years if it is not
accounted for timestamp 0 to be equal to year 1970.
* Fix: When zooming into extreme dates, OSError occurs
This commit catches the OSError like the other observed errors
* Disable stepping below years for dates outside *_REGULAR_TIMESTAMP
2 reasons: First: At least on my Linux machine, zooming into
those dates creates infinite loops. Second: Nobody needs
sub-year-precision for those extreme years anyways.
* Adapt zoom level sizes based on current font size and screen resolution
This is somewhat experimental. With this commit, no longer 60 px are
assumed as width for all zoom levels, but the current font and
display resolution are considered to calculate the width of ticks in
each zoom level. See the new function `updateZoomLevels` for
details.
Before calling this function, overridden functions `paint` and
`generateDrawSpecs` provide information over the current display
and font via `self.fontScaleFactor` and `self.fontMetrics`.
* Meaningful error meassage when adding axis to multiple PlotItems
As @axil noted in the DateAxisItem PR, currently users get a
segmentation fault when one tries to add an axis to multiple
PlotItems. This commit adds a meaningful RuntimeError message
for that case.
* setZoomLevelForDensity: Refactoring and calculating optimal spacing on the fly
* DateTimeAxis Fix: 1970 shows when zooming far out
* Refactoring: Make zoomLevels a customizable dict again
* updated the dateaxisitem example
* Fix: Get screen resolution in a way that also works for Qt 4
This is both a simplification in code and an improvement in backwards compatibility with Qt 4.
* DateAxisItem Fix: Also resolve time below 0.5 seconds
* unix line endings in examples
* DateTimeAxis Fix: For years < 1 and > 9999, stepping broke
Stepping was off by 1970 years for years < 1 and > 9999,
resulting in a gap in ticks visible when zooming out. Fixed by
subtracting the usual 1970 years.
* DateTimeAxis Fix: Zooming out too far causes infinite loop
Fixed by setting default limits to +/- 1e10 years. Should still
be enough.
* improved second dateaxisitem example
* 1..9999 years limit
* DateTimeAxis: Use OrderedDict to stay compatible with Python < 3-6
* DateAxisItem: Use font height to determine spacing for vertical axes
* window title
* added dateaxisitem.rst
* updated index.rst
Co-authored-by: Lukas Heiniger <lukas.heiniger@sed.ethz.ch>
Co-authored-by: Lev Maximov <lev.maximov@gmail.com>
Co-authored-by: 2xB <2xB@users.noreply.github.com>
2020-04-27 18:43:22 +00:00
|
|
|
# some values to visibleAxes a second time
|
|
|
|
|
|
|
|
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
|
|
|
|
if k in self.axes:
|
|
|
|
if k not in axisItems:
|
|
|
|
continue # Nothing to do here
|
|
|
|
|
|
|
|
# Remove old axis
|
|
|
|
oldAxis = self.axes[k]['item']
|
|
|
|
self.layout.removeItem(oldAxis)
|
|
|
|
oldAxis.scene().removeItem(oldAxis)
|
|
|
|
oldAxis.unlinkFromView()
|
|
|
|
|
|
|
|
# Create new axis
|
|
|
|
if k in axisItems:
|
|
|
|
axis = axisItems[k]
|
|
|
|
if axis.scene() is not None:
|
2020-09-23 03:25:02 +00:00
|
|
|
if k not in self.axes or axis != self.axes[k]["item"]:
|
|
|
|
raise RuntimeError(
|
|
|
|
"Can't add an axis to multiple plots. Shared axes"
|
|
|
|
" can be achieved with multiple AxisItem instances"
|
|
|
|
" and set[X/Y]Link.")
|
Date axis item (#1154)
* Add DateAxisItem
* Change style to camelCase
* Fix missing first tick for negative timestamps
* Add ms precision, auto skipping
Auto skipping allows a zoom level to skip ticks automatically if the
maximum number of ticks/pt is exceeded
* fixes suggested by @goetzc
* workaround for negative argument to utcfromtimestamp on windows
* attachToPlotItem method
* default date axis orientation
* Use new DateAxisItem in Plot Customization example
* attachToPlotItem bugfix
* examples of DateAxisItem
* modified description of customPlot example
* added descriptions to the new examples, reformatted their code, included the first one into utils.py
* typo
* Refactored code for setting axis items into new function
Replaces "DateAxisItem.attachToPlotItem"
* Fix string comparison with ==
* Doc: Slightly more text for DateAxisItem, small improvement for PlotItem
* Make PlotWidget.setAxisItems official
* Fix typo in docstring
* renamed an example
* merge bug fix
* Revert "merge bug fix"
This reverts commit 876b5a7cdb50cd824b4a5218427081b3ce5c2fe4.
* Real bug fix
* support for dates upto -1e13..1e13
* Automatically limit DateAxisItem to a range from -1e12 to 1e12 years
Very large years (|y|>1e13) cause infinite loop, and since nobody
needs time 100 times larger than the age of the universe anyways,
this constrains it to 1e12.
Following suggestion by @axil:
https://github.com/pyqtgraph/pyqtgraph/pull/1154#issuecomment-612662168
* Also catch ValueErrors occuring on Linux before OverfloeErrors
While zooming out, before hitting OverflowErrors, utctimestamp
produces ValueErrors (at least on my Linux machine), so they
are also catched.
* Fix: Timestamp 0 corresponds to year 1970
For large years, x axis labels jump by 1970 years if it is not
accounted for timestamp 0 to be equal to year 1970.
* Fix: When zooming into extreme dates, OSError occurs
This commit catches the OSError like the other observed errors
* Disable stepping below years for dates outside *_REGULAR_TIMESTAMP
2 reasons: First: At least on my Linux machine, zooming into
those dates creates infinite loops. Second: Nobody needs
sub-year-precision for those extreme years anyways.
* Adapt zoom level sizes based on current font size and screen resolution
This is somewhat experimental. With this commit, no longer 60 px are
assumed as width for all zoom levels, but the current font and
display resolution are considered to calculate the width of ticks in
each zoom level. See the new function `updateZoomLevels` for
details.
Before calling this function, overridden functions `paint` and
`generateDrawSpecs` provide information over the current display
and font via `self.fontScaleFactor` and `self.fontMetrics`.
* Meaningful error meassage when adding axis to multiple PlotItems
As @axil noted in the DateAxisItem PR, currently users get a
segmentation fault when one tries to add an axis to multiple
PlotItems. This commit adds a meaningful RuntimeError message
for that case.
* setZoomLevelForDensity: Refactoring and calculating optimal spacing on the fly
* DateTimeAxis Fix: 1970 shows when zooming far out
* Refactoring: Make zoomLevels a customizable dict again
* updated the dateaxisitem example
* Fix: Get screen resolution in a way that also works for Qt 4
This is both a simplification in code and an improvement in backwards compatibility with Qt 4.
* DateAxisItem Fix: Also resolve time below 0.5 seconds
* unix line endings in examples
* DateTimeAxis Fix: For years < 1 and > 9999, stepping broke
Stepping was off by 1970 years for years < 1 and > 9999,
resulting in a gap in ticks visible when zooming out. Fixed by
subtracting the usual 1970 years.
* DateTimeAxis Fix: Zooming out too far causes infinite loop
Fixed by setting default limits to +/- 1e10 years. Should still
be enough.
* improved second dateaxisitem example
* 1..9999 years limit
* DateTimeAxis: Use OrderedDict to stay compatible with Python < 3-6
* DateAxisItem: Use font height to determine spacing for vertical axes
* window title
* added dateaxisitem.rst
* updated index.rst
Co-authored-by: Lukas Heiniger <lukas.heiniger@sed.ethz.ch>
Co-authored-by: Lev Maximov <lev.maximov@gmail.com>
Co-authored-by: 2xB <2xB@users.noreply.github.com>
2020-04-27 18:43:22 +00:00
|
|
|
else:
|
|
|
|
axis = AxisItem(orientation=k, parent=self)
|
|
|
|
|
|
|
|
# Set up new axis
|
|
|
|
axis.linkToView(self.vb)
|
|
|
|
self.axes[k] = {'item': axis, 'pos': pos}
|
|
|
|
self.layout.addItem(axis, *pos)
|
2021-06-07 14:44:19 +00:00
|
|
|
# place axis above images at z=0, items that want to draw over the axes should be placed at z>=1:
|
|
|
|
axis.setZValue(0.5)
|
2021-06-06 01:17:43 +00:00
|
|
|
axis.setFlag(axis.GraphicsItemFlag.ItemNegativeZStacksBehindParent)
|
Date axis item (#1154)
* Add DateAxisItem
* Change style to camelCase
* Fix missing first tick for negative timestamps
* Add ms precision, auto skipping
Auto skipping allows a zoom level to skip ticks automatically if the
maximum number of ticks/pt is exceeded
* fixes suggested by @goetzc
* workaround for negative argument to utcfromtimestamp on windows
* attachToPlotItem method
* default date axis orientation
* Use new DateAxisItem in Plot Customization example
* attachToPlotItem bugfix
* examples of DateAxisItem
* modified description of customPlot example
* added descriptions to the new examples, reformatted their code, included the first one into utils.py
* typo
* Refactored code for setting axis items into new function
Replaces "DateAxisItem.attachToPlotItem"
* Fix string comparison with ==
* Doc: Slightly more text for DateAxisItem, small improvement for PlotItem
* Make PlotWidget.setAxisItems official
* Fix typo in docstring
* renamed an example
* merge bug fix
* Revert "merge bug fix"
This reverts commit 876b5a7cdb50cd824b4a5218427081b3ce5c2fe4.
* Real bug fix
* support for dates upto -1e13..1e13
* Automatically limit DateAxisItem to a range from -1e12 to 1e12 years
Very large years (|y|>1e13) cause infinite loop, and since nobody
needs time 100 times larger than the age of the universe anyways,
this constrains it to 1e12.
Following suggestion by @axil:
https://github.com/pyqtgraph/pyqtgraph/pull/1154#issuecomment-612662168
* Also catch ValueErrors occuring on Linux before OverfloeErrors
While zooming out, before hitting OverflowErrors, utctimestamp
produces ValueErrors (at least on my Linux machine), so they
are also catched.
* Fix: Timestamp 0 corresponds to year 1970
For large years, x axis labels jump by 1970 years if it is not
accounted for timestamp 0 to be equal to year 1970.
* Fix: When zooming into extreme dates, OSError occurs
This commit catches the OSError like the other observed errors
* Disable stepping below years for dates outside *_REGULAR_TIMESTAMP
2 reasons: First: At least on my Linux machine, zooming into
those dates creates infinite loops. Second: Nobody needs
sub-year-precision for those extreme years anyways.
* Adapt zoom level sizes based on current font size and screen resolution
This is somewhat experimental. With this commit, no longer 60 px are
assumed as width for all zoom levels, but the current font and
display resolution are considered to calculate the width of ticks in
each zoom level. See the new function `updateZoomLevels` for
details.
Before calling this function, overridden functions `paint` and
`generateDrawSpecs` provide information over the current display
and font via `self.fontScaleFactor` and `self.fontMetrics`.
* Meaningful error meassage when adding axis to multiple PlotItems
As @axil noted in the DateAxisItem PR, currently users get a
segmentation fault when one tries to add an axis to multiple
PlotItems. This commit adds a meaningful RuntimeError message
for that case.
* setZoomLevelForDensity: Refactoring and calculating optimal spacing on the fly
* DateTimeAxis Fix: 1970 shows when zooming far out
* Refactoring: Make zoomLevels a customizable dict again
* updated the dateaxisitem example
* Fix: Get screen resolution in a way that also works for Qt 4
This is both a simplification in code and an improvement in backwards compatibility with Qt 4.
* DateAxisItem Fix: Also resolve time below 0.5 seconds
* unix line endings in examples
* DateTimeAxis Fix: For years < 1 and > 9999, stepping broke
Stepping was off by 1970 years for years < 1 and > 9999,
resulting in a gap in ticks visible when zooming out. Fixed by
subtracting the usual 1970 years.
* DateTimeAxis Fix: Zooming out too far causes infinite loop
Fixed by setting default limits to +/- 1e10 years. Should still
be enough.
* improved second dateaxisitem example
* 1..9999 years limit
* DateTimeAxis: Use OrderedDict to stay compatible with Python < 3-6
* DateAxisItem: Use font height to determine spacing for vertical axes
* window title
* added dateaxisitem.rst
* updated index.rst
Co-authored-by: Lukas Heiniger <lukas.heiniger@sed.ethz.ch>
Co-authored-by: Lev Maximov <lev.maximov@gmail.com>
Co-authored-by: 2xB <2xB@users.noreply.github.com>
2020-04-27 18:43:22 +00:00
|
|
|
axisVisible = k in visibleAxes
|
|
|
|
self.showAxis(k, axisVisible)
|
2018-06-08 15:43:46 +00:00
|
|
|
|
2013-05-10 03:02:14 +00:00
|
|
|
def setLogMode(self, x=None, y=None):
|
2012-04-21 19:57:47 +00:00
|
|
|
"""
|
2013-05-10 03:02:14 +00:00
|
|
|
Set log scaling for x and/or y axes.
|
2012-04-21 19:57:47 +00:00
|
|
|
This informs PlotDataItems to transform logarithmically and switches
|
|
|
|
the axes to use log ticking.
|
|
|
|
|
|
|
|
Note that *no other items* in the scene will be affected by
|
2013-05-10 03:02:14 +00:00
|
|
|
this; there is (currently) no generic way to redisplay a GraphicsItem
|
2012-04-21 19:57:47 +00:00
|
|
|
with log coordinates.
|
|
|
|
|
|
|
|
"""
|
2013-05-10 03:02:14 +00:00
|
|
|
if x is not None:
|
|
|
|
self.ctrl.logXCheck.setChecked(x)
|
|
|
|
if y is not None:
|
|
|
|
self.ctrl.logYCheck.setChecked(y)
|
2012-04-21 19:57:47 +00:00
|
|
|
|
|
|
|
def showGrid(self, x=None, y=None, alpha=None):
|
|
|
|
"""
|
|
|
|
Show or hide the grid for either axis.
|
|
|
|
|
|
|
|
============== =====================================
|
|
|
|
**Arguments:**
|
|
|
|
x (bool) Whether to show the X grid
|
|
|
|
y (bool) Whether to show the Y grid
|
|
|
|
alpha (0.0-1.0) Opacity of the grid
|
2012-04-21 20:11:15 +00:00
|
|
|
============== =====================================
|
2012-04-21 19:57:47 +00:00
|
|
|
"""
|
|
|
|
if x is None and y is None and alpha is None:
|
|
|
|
raise Exception("Must specify at least one of x, y, or alpha.") ## prevent people getting confused if they just call showGrid()
|
|
|
|
|
|
|
|
if x is not None:
|
|
|
|
self.ctrl.xGridCheck.setChecked(x)
|
|
|
|
if y is not None:
|
|
|
|
self.ctrl.yGridCheck.setChecked(y)
|
|
|
|
if alpha is not None:
|
2021-06-02 05:43:58 +00:00
|
|
|
v = fn.clip_scalar(alpha, 0, 1) * self.ctrl.gridAlphaSlider.maximum() # slider range 0 to 255
|
|
|
|
self.ctrl.gridAlphaSlider.setValue( int(v) )
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def close(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
|
|
|
|
|
2014-03-13 20:38:50 +00:00
|
|
|
self.autoBtn.setParent(None)
|
|
|
|
self.autoBtn = None
|
2012-03-02 02:55:32 +00:00
|
|
|
|
2012-07-09 12:38:30 +00:00
|
|
|
for k in self.axes:
|
|
|
|
i = self.axes[k]['item']
|
2012-03-02 02:55:32 +00:00
|
|
|
i.close()
|
|
|
|
|
2012-07-09 12:38:30 +00:00
|
|
|
self.axes = None
|
2012-03-02 02:55:32 +00:00
|
|
|
self.scene().removeItem(self.vb)
|
|
|
|
self.vb = None
|
|
|
|
|
2012-04-15 14:20:07 +00:00
|
|
|
def registerPlot(self, name): ## for backward compatibility
|
2012-03-02 02:55:32 +00:00
|
|
|
self.vb.register(name)
|
2012-03-23 06:41:10 +00:00
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def updateGrid(self, *args):
|
2012-04-21 19:57:47 +00:00
|
|
|
alpha = self.ctrl.gridAlphaSlider.value()
|
|
|
|
x = alpha if self.ctrl.xGridCheck.isChecked() else False
|
2012-04-22 17:08:38 +00:00
|
|
|
y = alpha if self.ctrl.yGridCheck.isChecked() else False
|
2012-04-21 19:57:47 +00:00
|
|
|
self.getAxis('top').setGrid(x)
|
|
|
|
self.getAxis('bottom').setGrid(x)
|
|
|
|
self.getAxis('left').setGrid(y)
|
|
|
|
self.getAxis('right').setGrid(y)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def viewGeometry(self):
|
2012-04-15 14:20:07 +00:00
|
|
|
"""Return the screen geometry of the viewbox"""
|
2012-03-02 02:55:32 +00:00
|
|
|
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 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())
|
2021-06-06 01:17:43 +00:00
|
|
|
self.paramList[name] = (item.checkState() == QtCore.Qt.CheckState.Checked)
|
2012-03-02 02:55:32 +00:00
|
|
|
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):
|
2012-04-15 14:20:07 +00:00
|
|
|
## Add a single curve into the pool of curves averaged together
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
## 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)
|
2021-06-06 01:17:43 +00:00
|
|
|
if item.checkState() == QtCore.Qt.CheckState.Checked:
|
2012-03-02 02:55:32 +00:00
|
|
|
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()
|
2015-01-06 21:21:29 +00:00
|
|
|
stepMode = curve.opts['stepMode']
|
2014-08-07 13:03:26 +00:00
|
|
|
if plot.yData is not None and y.shape == plot.yData.shape:
|
|
|
|
# note that if shapes do not match, then the average resets.
|
2012-03-02 02:55:32 +00:00
|
|
|
newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n)
|
2015-01-06 21:21:29 +00:00
|
|
|
plot.setData(plot.xData, newData, stepMode=stepMode)
|
2012-03-02 02:55:32 +00:00
|
|
|
else:
|
2015-01-06 21:21:29 +00:00
|
|
|
plot.setData(x, y, stepMode=stepMode)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def autoBtnClicked(self):
|
|
|
|
if self.autoBtn.mode == 'auto':
|
|
|
|
self.enableAutoRange()
|
2012-12-23 05:51:28 +00:00
|
|
|
self.autoBtn.hide()
|
2012-03-02 02:55:32 +00:00
|
|
|
else:
|
|
|
|
self.disableAutoRange()
|
|
|
|
|
2012-12-23 05:51:28 +00:00
|
|
|
def viewStateChanged(self):
|
|
|
|
self.updateButtons()
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def enableAutoScale(self):
|
|
|
|
"""
|
|
|
|
Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data.
|
|
|
|
"""
|
2021-02-12 05:34:02 +00:00
|
|
|
warnings.warn(
|
|
|
|
'PlotItem.enableAutoScale is deprecated, and will be removed in 0.13'
|
|
|
|
'Use PlotItem.enableAutoRange(axis, enable) instead',
|
|
|
|
DeprecationWarning, stacklevel=2
|
|
|
|
)
|
2012-03-02 02:55:32 +00:00
|
|
|
self.vb.enableAutoRange(self.vb.XYAxes)
|
|
|
|
|
|
|
|
def addItem(self, item, *args, **kargs):
|
2012-04-16 20:45:55 +00:00
|
|
|
"""
|
|
|
|
Add a graphics item to the view box.
|
|
|
|
If the item has plot data (PlotDataItem, PlotCurveItem, ScatterPlotItem), it may
|
|
|
|
be included in analysis performed by the PlotItem.
|
|
|
|
"""
|
2020-06-09 04:36:09 +00:00
|
|
|
if item in self.items:
|
|
|
|
warnings.warn('Item already added to PlotItem, ignoring.')
|
|
|
|
return
|
2012-03-02 02:55:32 +00:00
|
|
|
self.items.append(item)
|
2012-04-03 05:01:33 +00:00
|
|
|
vbargs = {}
|
|
|
|
if 'ignoreBounds' in kargs:
|
|
|
|
vbargs['ignoreBounds'] = kargs['ignoreBounds']
|
|
|
|
self.vb.addItem(item, *args, **vbargs)
|
2013-12-19 17:30:00 +00:00
|
|
|
name = None
|
2012-03-02 02:55:32 +00:00
|
|
|
if hasattr(item, 'implements') and item.implements('plotData'):
|
2013-12-19 17:30:00 +00:00
|
|
|
name = item.name()
|
2012-03-02 02:55:32 +00:00
|
|
|
self.dataItems.append(item)
|
|
|
|
#self.plotChanged()
|
|
|
|
|
|
|
|
params = kargs.get('params', {})
|
|
|
|
self.itemMeta[item] = params
|
|
|
|
#item.setMeta(params)
|
|
|
|
self.curves.append(item)
|
|
|
|
#self.addItem(c)
|
|
|
|
|
2012-10-31 05:53:16 +00:00
|
|
|
if hasattr(item, 'setLogMode'):
|
|
|
|
item.setLogMode(self.ctrl.logXCheck.isChecked(), self.ctrl.logYCheck.isChecked())
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
if isinstance(item, PlotDataItem):
|
|
|
|
## configure curve for this plot
|
|
|
|
(alpha, auto) = self.alphaState()
|
|
|
|
item.setAlpha(alpha, auto)
|
2012-04-21 19:57:47 +00:00
|
|
|
item.setFftMode(self.ctrl.fftCheck.isChecked())
|
2013-07-03 15:20:49 +00:00
|
|
|
item.setDownsampling(*self.downsampleMode())
|
|
|
|
item.setClipToView(self.clipToViewMode())
|
2012-03-02 02:55:32 +00:00
|
|
|
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()
|
2013-12-19 17:30:00 +00:00
|
|
|
#name = kargs.get('name', getattr(item, 'opts', {}).get('name', None))
|
2012-12-27 01:12:49 +00:00
|
|
|
if name is not None and hasattr(self, 'legend') and self.legend is not None:
|
2018-06-08 15:43:46 +00:00
|
|
|
self.legend.addItem(item, name=name)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def addDataItem(self, item, *args):
|
2021-02-12 05:34:02 +00:00
|
|
|
warnings.warn(
|
|
|
|
'PlotItem.addDataItem is deprecated and will be removed in 0.13. '
|
|
|
|
'Use PlotItem.addItem instead',
|
|
|
|
DeprecationWarning, stacklevel=2
|
|
|
|
)
|
2012-03-02 02:55:32 +00:00
|
|
|
self.addItem(item, *args)
|
|
|
|
|
2013-02-10 19:10:30 +00:00
|
|
|
def listDataItems(self):
|
|
|
|
"""Return a list of all data items (PlotDataItem, PlotCurveItem, ScatterPlotItem, etc)
|
|
|
|
contained in this PlotItem."""
|
|
|
|
return self.dataItems[:]
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def addCurve(self, c, params=None):
|
2021-02-12 05:34:02 +00:00
|
|
|
warnings.warn(
|
|
|
|
'PlotItem.addCurve is deprecated and will be removed in 0.13. '
|
|
|
|
'Use PlotItem.addItem instead.',
|
|
|
|
DeprecationWarning, stacklevel=2
|
|
|
|
)
|
|
|
|
|
2012-04-16 21:15:25 +00:00
|
|
|
self.addItem(c, params)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
2013-02-10 19:10:30 +00:00
|
|
|
def addLine(self, x=None, y=None, z=None, **kwds):
|
|
|
|
"""
|
|
|
|
Create an InfiniteLine and add to the plot.
|
|
|
|
|
|
|
|
If *x* is specified,
|
|
|
|
the line will be vertical. If *y* is specified, the line will be
|
|
|
|
horizontal. All extra keyword arguments are passed to
|
|
|
|
:func:`InfiniteLine.__init__() <pyqtgraph.InfiniteLine.__init__>`.
|
|
|
|
Returns the item created.
|
|
|
|
"""
|
2019-06-20 02:37:09 +00:00
|
|
|
kwds['pos'] = kwds.get('pos', x if x is not None else y)
|
|
|
|
kwds['angle'] = kwds.get('angle', 0 if x is None else 90)
|
|
|
|
line = InfiniteLine(**kwds)
|
2013-02-10 19:10:30 +00:00
|
|
|
self.addItem(line)
|
|
|
|
if z is not None:
|
|
|
|
line.setZValue(z)
|
2018-06-08 15:43:46 +00:00
|
|
|
return line
|
2013-02-10 19:10:30 +00:00
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def removeItem(self, item):
|
2012-04-18 04:02:15 +00:00
|
|
|
"""
|
|
|
|
Remove an item from the internal ViewBox.
|
|
|
|
"""
|
2012-03-02 02:55:32 +00:00
|
|
|
if not item in self.items:
|
|
|
|
return
|
|
|
|
self.items.remove(item)
|
|
|
|
if item in self.dataItems:
|
|
|
|
self.dataItems.remove(item)
|
|
|
|
|
2019-09-13 04:00:38 +00:00
|
|
|
self.vb.removeItem(item)
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
if item in self.curves:
|
|
|
|
self.curves.remove(item)
|
|
|
|
self.updateDecimation()
|
|
|
|
self.updateParamList()
|
|
|
|
|
2017-09-15 16:05:24 +00:00
|
|
|
if self.legend is not None:
|
|
|
|
self.legend.removeItem(item)
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def clear(self):
|
2012-04-18 04:02:15 +00:00
|
|
|
"""
|
|
|
|
Remove all items from the ViewBox.
|
|
|
|
"""
|
2012-03-02 02:55:32 +00:00
|
|
|
for i in self.items[:]:
|
|
|
|
self.removeItem(i)
|
|
|
|
self.avgCurves = {}
|
|
|
|
|
|
|
|
def clearPlots(self):
|
|
|
|
for i in self.curves[:]:
|
|
|
|
self.removeItem(i)
|
2018-06-08 15:43:46 +00:00
|
|
|
self.avgCurves = {}
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def plot(self, *args, **kargs):
|
|
|
|
"""
|
|
|
|
Add and return a new plot.
|
2012-04-15 16:32:20 +00:00
|
|
|
See :func:`PlotDataItem.__init__ <pyqtgraph.PlotDataItem.__init__>` for data arguments
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
Extra allowed arguments are:
|
|
|
|
clear - clear all plots before displaying new data
|
|
|
|
params - meta-parameters to associate with this data
|
|
|
|
"""
|
|
|
|
clear = kargs.get('clear', False)
|
|
|
|
params = kargs.get('params', None)
|
|
|
|
|
|
|
|
if clear:
|
|
|
|
self.clear()
|
|
|
|
|
|
|
|
item = PlotDataItem(*args, **kargs)
|
|
|
|
|
|
|
|
if params is None:
|
|
|
|
params = {}
|
|
|
|
self.addItem(item, params=params)
|
|
|
|
|
|
|
|
return item
|
|
|
|
|
2020-06-06 22:56:01 +00:00
|
|
|
def addLegend(self, offset=(30, 30), **kwargs):
|
2012-11-23 21:05:14 +00:00
|
|
|
"""
|
2020-06-06 22:56:01 +00:00
|
|
|
Create a new :class:`~pyqtgraph.LegendItem` and anchor it over the
|
|
|
|
internal ViewBox. Plots will be automatically displayed in the legend
|
|
|
|
if they are created with the 'name' argument.
|
2017-09-15 16:05:24 +00:00
|
|
|
|
|
|
|
If a LegendItem has already been created using this method, that
|
|
|
|
item will be returned rather than creating a new one.
|
2020-06-06 22:56:01 +00:00
|
|
|
|
|
|
|
Accepts the same arguments as :meth:`~pyqtgraph.LegendItem`.
|
2012-11-23 21:05:14 +00:00
|
|
|
"""
|
2020-06-13 05:40:20 +00:00
|
|
|
|
2017-09-15 16:05:24 +00:00
|
|
|
if self.legend is None:
|
2020-06-06 22:56:01 +00:00
|
|
|
self.legend = LegendItem(offset=offset, **kwargs)
|
2017-09-15 16:05:24 +00:00
|
|
|
self.legend.setParentItem(self.vb)
|
2012-11-23 21:05:14 +00:00
|
|
|
return self.legend
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def scatterPlot(self, *args, **kargs):
|
2012-04-21 19:57:47 +00:00
|
|
|
if 'pen' in kargs:
|
|
|
|
kargs['symbolPen'] = kargs['pen']
|
|
|
|
kargs['pen'] = None
|
|
|
|
|
|
|
|
if 'brush' in kargs:
|
|
|
|
kargs['symbolBrush'] = kargs['brush']
|
|
|
|
del kargs['brush']
|
|
|
|
|
|
|
|
if 'size' in kargs:
|
|
|
|
kargs['symbolSize'] = kargs['size']
|
|
|
|
del kargs['size']
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
return self.plot(*args, **kargs)
|
|
|
|
|
|
|
|
def replot(self):
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
def updateParamList(self):
|
|
|
|
self.ctrl.avgParamList.clear()
|
|
|
|
## Check to see that each parameter for each curve is present in the list
|
|
|
|
for c in self.curves:
|
2012-05-11 22:05:41 +00:00
|
|
|
for p in list(self.itemMeta.get(c, {}).keys()):
|
2012-03-02 02:55:32 +00:00
|
|
|
if type(p) is tuple:
|
|
|
|
p = '.'.join(p)
|
|
|
|
|
|
|
|
## If the parameter is not in the list, add it.
|
2021-06-06 01:17:43 +00:00
|
|
|
matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchFlag.MatchExactly)
|
2012-03-02 02:55:32 +00:00
|
|
|
if len(matches) == 0:
|
|
|
|
i = QtGui.QListWidgetItem(p)
|
|
|
|
if p in self.paramList and self.paramList[p] is True:
|
2021-06-06 01:17:43 +00:00
|
|
|
i.setCheckState(QtCore.Qt.CheckState.Checked)
|
2012-03-02 02:55:32 +00:00
|
|
|
else:
|
2021-06-06 01:17:43 +00:00
|
|
|
i.setCheckState(QtCore.Qt.CheckState.Unchecked)
|
2012-03-02 02:55:32 +00:00
|
|
|
self.ctrl.avgParamList.addItem(i)
|
|
|
|
else:
|
|
|
|
i = matches[0]
|
|
|
|
|
2021-06-06 01:17:43 +00:00
|
|
|
self.paramList[p] = (i.checkState() == QtCore.Qt.CheckState.Checked)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def writeSvgCurves(self, fileName=None):
|
|
|
|
if fileName is None:
|
2018-06-08 15:43:46 +00:00
|
|
|
self._chooseFilenameDialog(handler=self.writeSvg)
|
2012-03-02 02:55:32 +00:00
|
|
|
return
|
2018-04-04 16:25:36 +00:00
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
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 = ""
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2019-11-12 16:45:42 +00:00
|
|
|
with open(fileName, 'w') as fh:
|
|
|
|
# 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.
|
2012-03-02 02:55:32 +00:00
|
|
|
color = color[:6]
|
2019-11-12 16:45:42 +00:00
|
|
|
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 range(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("</svg>\n")
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def writeSvg(self, fileName=None):
|
|
|
|
if fileName is None:
|
2018-06-08 15:43:46 +00:00
|
|
|
self._chooseFilenameDialog(handler=self.writeSvg)
|
2018-04-04 16:25:36 +00:00
|
|
|
return
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
fileName = str(fileName)
|
|
|
|
PlotItem.lastFileDir = os.path.dirname(fileName)
|
|
|
|
|
2015-02-28 15:32:34 +00:00
|
|
|
from ...exporters import SVGExporter
|
|
|
|
ex = SVGExporter(self)
|
|
|
|
ex.export(fileName)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def writeImage(self, fileName=None):
|
|
|
|
if fileName is None:
|
2018-06-08 15:43:46 +00:00
|
|
|
self._chooseFilenameDialog(handler=self.writeImage)
|
2012-03-02 02:55:32 +00:00
|
|
|
return
|
2018-04-04 16:25:36 +00:00
|
|
|
|
2018-06-08 15:43:46 +00:00
|
|
|
from ...exporters import ImageExporter
|
|
|
|
ex = ImageExporter(self)
|
|
|
|
ex.export(fileName)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def writeCsv(self, fileName=None):
|
|
|
|
if fileName is None:
|
2018-06-08 15:43:46 +00:00
|
|
|
self._chooseFilenameDialog(handler=self.writeCsv)
|
2012-03-02 02:55:32 +00:00
|
|
|
return
|
2018-04-04 16:25:36 +00:00
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
fileName = str(fileName)
|
|
|
|
PlotItem.lastFileDir = os.path.dirname(fileName)
|
|
|
|
|
|
|
|
data = [c.getData() for c in self.curves]
|
2019-11-12 16:45:42 +00:00
|
|
|
with open(fileName, 'w') as fd:
|
|
|
|
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
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def saveState(self):
|
|
|
|
state = self.stateGroup.state()
|
|
|
|
state['paramList'] = self.paramList.copy()
|
|
|
|
state['view'] = self.vb.getState()
|
|
|
|
return state
|
|
|
|
|
|
|
|
def restoreState(self, state):
|
|
|
|
if 'paramList' in state:
|
|
|
|
self.paramList = state['paramList'].copy()
|
|
|
|
|
|
|
|
self.stateGroup.setState(state)
|
|
|
|
self.updateSpectrumMode()
|
|
|
|
self.updateDownsampling()
|
|
|
|
self.updateAlpha()
|
|
|
|
self.updateDecimation()
|
|
|
|
|
2012-04-21 19:57:47 +00:00
|
|
|
if 'powerSpectrumGroup' in state:
|
|
|
|
state['fftCheck'] = state['powerSpectrumGroup']
|
|
|
|
if 'gridGroup' in state:
|
|
|
|
state['xGridCheck'] = state['gridGroup']
|
|
|
|
state['yGridCheck'] = state['gridGroup']
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
self.stateGroup.setState(state)
|
|
|
|
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'])
|
|
|
|
|
|
|
|
def widgetGroupInterface(self):
|
|
|
|
return (None, PlotItem.saveState, PlotItem.restoreState)
|
|
|
|
|
|
|
|
def updateSpectrumMode(self, b=None):
|
|
|
|
if b is None:
|
2012-04-21 19:57:47 +00:00
|
|
|
b = self.ctrl.fftCheck.isChecked()
|
2012-03-02 02:55:32 +00:00
|
|
|
for c in self.curves:
|
|
|
|
c.setFftMode(b)
|
|
|
|
self.enableAutoRange()
|
|
|
|
self.recomputeAverages()
|
|
|
|
|
2012-04-21 19:57:47 +00:00
|
|
|
def updateLogMode(self):
|
|
|
|
x = self.ctrl.logXCheck.isChecked()
|
|
|
|
y = self.ctrl.logYCheck.isChecked()
|
2012-10-31 05:53:16 +00:00
|
|
|
for i in self.items:
|
|
|
|
if hasattr(i, 'setLogMode'):
|
|
|
|
i.setLogMode(x,y)
|
2012-04-21 19:57:47 +00:00
|
|
|
self.getAxis('bottom').setLogMode(x)
|
|
|
|
self.getAxis('top').setLogMode(x)
|
|
|
|
self.getAxis('left').setLogMode(y)
|
|
|
|
self.getAxis('right').setLogMode(y)
|
|
|
|
self.enableAutoRange()
|
|
|
|
self.recomputeAverages()
|
2017-03-27 20:00:38 +00:00
|
|
|
|
|
|
|
def updateDerivativeMode(self):
|
|
|
|
d = self.ctrl.derivativeCheck.isChecked()
|
|
|
|
for i in self.items:
|
|
|
|
if hasattr(i, 'setDerivativeMode'):
|
|
|
|
i.setDerivativeMode(d)
|
|
|
|
self.enableAutoRange()
|
|
|
|
self.recomputeAverages()
|
|
|
|
|
|
|
|
def updatePhasemapMode(self):
|
|
|
|
d = self.ctrl.phasemapCheck.isChecked()
|
|
|
|
for i in self.items:
|
|
|
|
if hasattr(i, 'setPhasemapMode'):
|
|
|
|
i.setPhasemapMode(d)
|
|
|
|
self.enableAutoRange()
|
|
|
|
self.recomputeAverages()
|
|
|
|
|
2012-04-21 19:57:47 +00:00
|
|
|
|
2013-07-03 15:20:49 +00:00
|
|
|
def setDownsampling(self, ds=None, auto=None, mode=None):
|
|
|
|
"""Change the default downsampling mode for all PlotDataItems managed by this plot.
|
|
|
|
|
2014-02-12 20:18:36 +00:00
|
|
|
=============== =================================================================
|
2014-02-05 20:04:33 +00:00
|
|
|
**Arguments:**
|
|
|
|
ds (int) Reduce visible plot samples by this factor, or
|
|
|
|
(bool) To enable/disable downsampling without changing the value.
|
|
|
|
auto (bool) If True, automatically pick *ds* based on visible range
|
|
|
|
mode 'subsample': Downsample by taking the first of N samples.
|
2014-03-01 02:07:43 +00:00
|
|
|
This method is fastest and least accurate.
|
2014-02-05 20:04:33 +00:00
|
|
|
'mean': Downsample by taking the mean of N samples.
|
|
|
|
'peak': Downsample by drawing a saw wave that follows the min
|
2014-03-01 02:07:43 +00:00
|
|
|
and max of the original data. This method produces the best
|
|
|
|
visual representation of the data but is slower.
|
2014-02-12 20:18:36 +00:00
|
|
|
=============== =================================================================
|
2013-07-03 15:20:49 +00:00
|
|
|
"""
|
|
|
|
if ds is not None:
|
|
|
|
if ds is False:
|
|
|
|
self.ctrl.downsampleCheck.setChecked(False)
|
|
|
|
elif ds is True:
|
|
|
|
self.ctrl.downsampleCheck.setChecked(True)
|
|
|
|
else:
|
|
|
|
self.ctrl.downsampleCheck.setChecked(True)
|
|
|
|
self.ctrl.downsampleSpin.setValue(ds)
|
|
|
|
|
|
|
|
if auto is not None:
|
|
|
|
if auto and ds is not False:
|
|
|
|
self.ctrl.downsampleCheck.setChecked(True)
|
|
|
|
self.ctrl.autoDownsampleCheck.setChecked(auto)
|
|
|
|
|
|
|
|
if mode is not None:
|
|
|
|
if mode == 'subsample':
|
|
|
|
self.ctrl.subsampleRadio.setChecked(True)
|
|
|
|
elif mode == 'mean':
|
|
|
|
self.ctrl.meanRadio.setChecked(True)
|
|
|
|
elif mode == 'peak':
|
|
|
|
self.ctrl.peakRadio.setChecked(True)
|
|
|
|
else:
|
|
|
|
raise ValueError("mode argument must be 'subsample', 'mean', or 'peak'.")
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def updateDownsampling(self):
|
2013-07-03 15:20:49 +00:00
|
|
|
ds, auto, method = self.downsampleMode()
|
|
|
|
clip = self.ctrl.clipToViewCheck.isChecked()
|
2012-03-02 02:55:32 +00:00
|
|
|
for c in self.curves:
|
2013-07-03 15:20:49 +00:00
|
|
|
c.setDownsampling(ds, auto, method)
|
|
|
|
c.setClipToView(clip)
|
2012-03-02 02:55:32 +00:00
|
|
|
self.recomputeAverages()
|
|
|
|
|
|
|
|
def downsampleMode(self):
|
2013-07-03 15:20:49 +00:00
|
|
|
if self.ctrl.downsampleCheck.isChecked():
|
|
|
|
ds = self.ctrl.downsampleSpin.value()
|
2012-03-02 02:55:32 +00:00
|
|
|
else:
|
2013-07-03 15:20:49 +00:00
|
|
|
ds = 1
|
|
|
|
|
|
|
|
auto = self.ctrl.downsampleCheck.isChecked() and self.ctrl.autoDownsampleCheck.isChecked()
|
|
|
|
|
|
|
|
if self.ctrl.subsampleRadio.isChecked():
|
|
|
|
method = 'subsample'
|
|
|
|
elif self.ctrl.meanRadio.isChecked():
|
|
|
|
method = 'mean'
|
|
|
|
elif self.ctrl.peakRadio.isChecked():
|
|
|
|
method = 'peak'
|
|
|
|
|
|
|
|
return ds, auto, method
|
|
|
|
|
|
|
|
def setClipToView(self, clip):
|
|
|
|
"""Set the default clip-to-view mode for all PlotDataItems managed by this plot.
|
|
|
|
If *clip* is True, then PlotDataItems will attempt to draw only points within the visible
|
|
|
|
range of the ViewBox."""
|
|
|
|
self.ctrl.clipToViewCheck.setChecked(clip)
|
|
|
|
|
|
|
|
def clipToViewMode(self):
|
|
|
|
return self.ctrl.clipToViewCheck.isChecked()
|
2018-06-08 15:43:46 +00:00
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def updateDecimation(self):
|
2021-05-06 00:08:04 +00:00
|
|
|
if not self.ctrl.maxTracesCheck.isChecked():
|
|
|
|
numCurves = len(self.curves)
|
2012-03-02 02:55:32 +00:00
|
|
|
else:
|
2021-05-06 00:08:04 +00:00
|
|
|
numCurves = self.ctrl.maxTracesSpin.value()
|
|
|
|
|
2021-05-06 01:07:34 +00:00
|
|
|
if self.ctrl.forgetTracesCheck.isChecked():
|
|
|
|
for curve in self.curves[:-numCurves]:
|
2021-05-06 00:08:04 +00:00
|
|
|
curve.clear()
|
|
|
|
self.removeItem(curve)
|
2021-05-06 01:07:34 +00:00
|
|
|
|
|
|
|
for i, curve in enumerate(reversed(self.curves)):
|
|
|
|
if i < numCurves:
|
|
|
|
curve.show()
|
2021-05-06 00:08:04 +00:00
|
|
|
else:
|
|
|
|
curve.hide()
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def updateAlpha(self, *args):
|
|
|
|
(alpha, auto) = self.alphaState()
|
|
|
|
for c in self.curves:
|
|
|
|
c.setAlpha(alpha**2, auto)
|
|
|
|
|
|
|
|
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 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)
|
2012-07-09 12:38:30 +00:00
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
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.
|
2012-07-09 12:38:30 +00:00
|
|
|
if self.menuEnabled():
|
|
|
|
return self.ctrlMenu
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def setMenuEnabled(self, enableMenu=True, enableViewBoxMenu='same'):
|
|
|
|
"""
|
|
|
|
Enable or disable the context menu for this PlotItem.
|
|
|
|
By default, the ViewBox's context menu will also be affected.
|
|
|
|
(use enableViewBoxMenu=None to leave the ViewBox unchanged)
|
|
|
|
"""
|
|
|
|
self._menuEnabled = enableMenu
|
|
|
|
if enableViewBoxMenu is None:
|
|
|
|
return
|
2019-02-14 21:36:02 +00:00
|
|
|
if enableViewBoxMenu == 'same':
|
|
|
|
enableViewBoxMenu = enableMenu
|
2012-07-09 12:38:30 +00:00
|
|
|
self.vb.setMenuEnabled(enableViewBoxMenu)
|
|
|
|
|
|
|
|
def menuEnabled(self):
|
|
|
|
return self._menuEnabled
|
|
|
|
|
2012-12-23 05:51:28 +00:00
|
|
|
def hoverEvent(self, ev):
|
|
|
|
if ev.enter:
|
|
|
|
self.mouseHovering = True
|
|
|
|
if ev.exit:
|
|
|
|
self.mouseHovering = False
|
|
|
|
|
|
|
|
self.updateButtons()
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def getLabel(self, key):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def _checkScaleKey(self, key):
|
2012-07-09 12:38:30 +00:00
|
|
|
if key not in self.axes:
|
|
|
|
raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys()))))
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def getScale(self, key):
|
2012-04-21 19:57:47 +00:00
|
|
|
return self.getAxis(key)
|
|
|
|
|
|
|
|
def getAxis(self, name):
|
|
|
|
"""Return the specified AxisItem.
|
|
|
|
*name* should be 'left', 'bottom', 'top', or 'right'."""
|
|
|
|
self._checkScaleKey(name)
|
2012-07-09 12:38:30 +00:00
|
|
|
return self.axes[name]['item']
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args):
|
|
|
|
"""
|
|
|
|
Set the label for an axis. Basic HTML formatting is allowed.
|
2012-04-15 16:32:20 +00:00
|
|
|
|
2014-02-05 20:04:33 +00:00
|
|
|
============== =================================================================
|
2014-02-03 20:13:10 +00:00
|
|
|
**Arguments:**
|
2014-02-05 20:04:33 +00:00
|
|
|
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)
|
|
|
|
============== =================================================================
|
2012-03-02 02:55:32 +00:00
|
|
|
"""
|
2012-08-31 21:18:06 +00:00
|
|
|
self.getAxis(axis).setLabel(text=text, units=units, **args)
|
2013-03-28 00:24:01 +00:00
|
|
|
self.showAxis(axis)
|
2012-03-02 02:55:32 +00:00
|
|
|
|
2013-01-30 20:56:08 +00:00
|
|
|
def setLabels(self, **kwds):
|
|
|
|
"""
|
|
|
|
Convenience function allowing multiple labels and/or title to be set in one call.
|
|
|
|
Keyword arguments can be 'title', 'left', 'bottom', 'right', or 'top'.
|
|
|
|
Values may be strings or a tuple of arguments to pass to setLabel.
|
|
|
|
"""
|
|
|
|
for k,v in kwds.items():
|
|
|
|
if k == 'title':
|
|
|
|
self.setTitle(v)
|
|
|
|
else:
|
|
|
|
if isinstance(v, basestring):
|
|
|
|
v = (v,)
|
|
|
|
self.setLabel(k, *v)
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
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'
|
|
|
|
"""
|
2020-06-08 03:29:28 +00:00
|
|
|
s = self.getScale(axis)
|
|
|
|
p = self.axes[axis]['pos']
|
2012-03-02 02:55:32 +00:00
|
|
|
if show:
|
|
|
|
s.show()
|
|
|
|
else:
|
|
|
|
s.hide()
|
|
|
|
|
|
|
|
def hideAxis(self, axis):
|
2012-07-12 19:35:58 +00:00
|
|
|
"""Hide one of the PlotItem's axes. ('left', 'bottom', 'right', or 'top')"""
|
2012-03-02 02:55:32 +00:00
|
|
|
self.showAxis(axis, False)
|
2021-06-07 14:44:19 +00:00
|
|
|
|
|
|
|
def showAxes(self, selection, showValues=True, size=False):
|
|
|
|
"""
|
|
|
|
Convenience method for quickly configuring axis settings.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
selection: boolean or tuple of booleans (left, top, right, bottom)
|
|
|
|
Determines which AxisItems will be displayed.
|
|
|
|
A single boolean value will set all axes,
|
|
|
|
so that ``showAxes(True)`` configures the axes to draw a frame.
|
|
|
|
showValues: optional, boolean or tuple of booleans (left, top, right, bottom)
|
|
|
|
Determines if values will be displayed for the ticks of each axis.
|
|
|
|
True value shows values for left and bottom axis (default).
|
|
|
|
False shows no values.
|
|
|
|
None leaves settings unchanged.
|
|
|
|
If not specified, left and bottom axes will be drawn with values.
|
|
|
|
size: optional, float or tuple of floats (width, height)
|
|
|
|
Reserves as fixed amount of space (width for vertical axis, height for horizontal axis)
|
|
|
|
for each axis where tick values are enabled. If only a single float value is given, it
|
|
|
|
will be applied for both width and height. If `None` is given instead of a float value,
|
|
|
|
the axis reverts to automatic allocation of space.
|
|
|
|
"""
|
|
|
|
if selection is True: # shortcut: enable all axes, creating a frame
|
|
|
|
selection = (True, True, True, True)
|
|
|
|
elif selection is False: # shortcut: disable all axes
|
|
|
|
selection = (False, False, False, False)
|
|
|
|
if showValues is True: # shortcut: defaults arrangement with labels at left and bottom
|
|
|
|
showValues = (True, False, False, True)
|
|
|
|
elif showValues is False: # shortcut: disable all labels
|
|
|
|
showValues = (False, False, False, False)
|
|
|
|
elif showValues is None: # leave labelling untouched
|
|
|
|
showValues = (None, None, None, None)
|
|
|
|
if size is not False and not isinstance(size, collections.abc.Sized):
|
|
|
|
size = (size, size) # make sure that size is either False or a full set of (width, height)
|
|
|
|
|
|
|
|
all_axes = ('left','top','right','bottom')
|
|
|
|
for show_axis, show_value, axis_key in zip(selection, showValues, all_axes):
|
|
|
|
if show_axis is None:
|
|
|
|
pass # leave axis display as it is.
|
|
|
|
else:
|
|
|
|
if show_axis: self.showAxis(axis_key)
|
|
|
|
else : self.hideAxis(axis_key)
|
|
|
|
|
|
|
|
if show_value is None:
|
|
|
|
pass # leave value display as it is.
|
|
|
|
else:
|
|
|
|
ax = self.getAxis(axis_key)
|
|
|
|
ax.setStyle(showValues=show_value)
|
|
|
|
if size is not False: # size adjustment is requested
|
|
|
|
if axis_key in ('left','right'):
|
|
|
|
if show_value: ax.setWidth(size[0])
|
|
|
|
else : ax.setWidth( None )
|
|
|
|
elif axis_key in ('top', 'bottom'):
|
|
|
|
if show_value: ax.setHeight(size[1])
|
|
|
|
else : ax.setHeight( None )
|
|
|
|
|
2012-03-02 02:55:32 +00:00
|
|
|
def showScale(self, *args, **kargs):
|
2021-02-12 05:34:02 +00:00
|
|
|
warnings.warn(
|
|
|
|
'PlotItem.showScale has been deprecated and will be removed in 0.13. '
|
|
|
|
'Use PlotItem.showAxis() instead',
|
|
|
|
DeprecationWarning, stacklevel=2
|
|
|
|
)
|
2012-03-02 02:55:32 +00:00
|
|
|
return self.showAxis(*args, **kargs)
|
|
|
|
|
|
|
|
def hideButtons(self):
|
2012-07-12 19:35:58 +00:00
|
|
|
"""Causes auto-scale button ('A' in lower-left corner) to be hidden for this PlotItem"""
|
2012-03-02 02:55:32 +00:00
|
|
|
#self.ctrlBtn.hide()
|
2012-12-23 05:51:28 +00:00
|
|
|
self.buttonsHidden = True
|
|
|
|
self.updateButtons()
|
2012-03-02 02:55:32 +00:00
|
|
|
|
2012-12-23 05:51:28 +00:00
|
|
|
def showButtons(self):
|
|
|
|
"""Causes auto-scale button ('A' in lower-left corner) to be visible for this PlotItem"""
|
|
|
|
#self.ctrlBtn.hide()
|
|
|
|
self.buttonsHidden = False
|
|
|
|
self.updateButtons()
|
|
|
|
|
|
|
|
def updateButtons(self):
|
2014-08-07 13:03:26 +00:00
|
|
|
try:
|
|
|
|
if self._exportOpts is False and self.mouseHovering and not self.buttonsHidden and not all(self.vb.autoRangeEnabled()):
|
|
|
|
self.autoBtn.show()
|
|
|
|
else:
|
|
|
|
self.autoBtn.hide()
|
|
|
|
except RuntimeError:
|
|
|
|
pass # this can happen if the plot has been deleted.
|
2012-03-02 02:55:32 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
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
|
|
|
|
|
2012-12-25 05:43:31 +00:00
|
|
|
def setExportMode(self, export, opts=None):
|
|
|
|
GraphicsWidget.setExportMode(self, export, opts)
|
|
|
|
self.updateButtons()
|
2012-03-23 06:41:10 +00:00
|
|
|
|
2018-06-08 15:43:46 +00:00
|
|
|
def _chooseFilenameDialog(self, handler):
|
2018-04-04 16:25:36 +00:00
|
|
|
self.fileDialog = FileDialog()
|
|
|
|
if PlotItem.lastFileDir is not None:
|
|
|
|
self.fileDialog.setDirectory(PlotItem.lastFileDir)
|
2021-06-06 01:17:43 +00:00
|
|
|
self.fileDialog.setFileMode(QtGui.QFileDialog.FileMode.AnyFile)
|
|
|
|
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptMode.AcceptSave)
|
2018-04-04 16:25:36 +00:00
|
|
|
self.fileDialog.show()
|
|
|
|
self.fileDialog.fileSelected.connect(handler)
|