avoid calling setLabel repeatedly for AxisItem
This commit is contained in:
parent
2e69b9c5e6
commit
b575b56edf
@ -14,10 +14,10 @@ class AxisItem(GraphicsWidget):
|
||||
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 draw a grid.
|
||||
If maxTickLength is negative, ticks point into the plot.
|
||||
If maxTickLength is negative, ticks point into the plot.
|
||||
"""
|
||||
|
||||
def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True):
|
||||
|
||||
def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True, text='', units='', unitPrefix='', **args):
|
||||
"""
|
||||
============== ===============================================================
|
||||
**Arguments:**
|
||||
@ -26,11 +26,19 @@ class AxisItem(GraphicsWidget):
|
||||
into the plot, positive values draw outward.
|
||||
linkView (ViewBox) causes the range of values displayed in the axis
|
||||
to be linked to the visible range of a ViewBox.
|
||||
showValues (bool) Whether to display values adjacent to ticks
|
||||
showValues (bool) Whether to display values adjacent to ticks
|
||||
pen (QPen) Pen used when drawing ticks.
|
||||
text The text (excluding units) to display on the label for this
|
||||
axis.
|
||||
units The units for this axis. Units should generally be given
|
||||
without any scaling prefix (eg, 'V' instead of 'mV'). The
|
||||
scaling prefix will be automatically prepended based on the
|
||||
range of data displayed.
|
||||
**args All extra keyword arguments become CSS style options for
|
||||
the <span> tag which will surround the axis label and units.
|
||||
============== ===============================================================
|
||||
"""
|
||||
|
||||
|
||||
GraphicsWidget.__init__(self, parent)
|
||||
self.label = QtGui.QGraphicsTextItem(self)
|
||||
self.picture = None
|
||||
@ -39,15 +47,15 @@ class AxisItem(GraphicsWidget):
|
||||
raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.")
|
||||
if orientation in ['left', 'right']:
|
||||
self.label.rotate(-90)
|
||||
|
||||
|
||||
self.style = {
|
||||
'tickTextOffset': [5, 2], ## (horizontal, vertical) spacing between text and axis
|
||||
'tickTextOffset': [5, 2], ## (horizontal, vertical) spacing between text and axis
|
||||
'tickTextWidth': 30, ## space reserved for tick text
|
||||
'tickTextHeight': 18,
|
||||
'tickTextHeight': 18,
|
||||
'autoExpandTextSpace': True, ## automatically expand text space if needed
|
||||
'tickFont': None,
|
||||
'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick
|
||||
'textFillLimits': [ ## how much of the axis to fill up with tick text, maximally.
|
||||
'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick
|
||||
'textFillLimits': [ ## how much of the axis to fill up with tick text, maximally.
|
||||
(0, 0.8), ## never fill more than 80% of the axis
|
||||
(2, 0.6), ## If we already have 2 ticks with text, fill no more than 60% of the axis
|
||||
(4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis
|
||||
@ -58,93 +66,93 @@ class AxisItem(GraphicsWidget):
|
||||
'maxTickLevel': 2,
|
||||
'maxTextLevel': 2,
|
||||
}
|
||||
|
||||
self.textWidth = 30 ## Keeps track of maximum width / height of tick text
|
||||
|
||||
self.textWidth = 30 ## Keeps track of maximum width / height of tick text
|
||||
self.textHeight = 18
|
||||
|
||||
|
||||
# If the user specifies a width / height, remember that setting
|
||||
# indefinitely.
|
||||
self.fixedWidth = None
|
||||
self.fixedHeight = None
|
||||
|
||||
self.labelText = ''
|
||||
self.labelUnits = ''
|
||||
self.labelUnitPrefix=''
|
||||
self.labelStyle = {}
|
||||
|
||||
self.labelText = text
|
||||
self.labelUnits = units
|
||||
self.labelUnitPrefix = unitPrefix
|
||||
self.labelStyle = args
|
||||
self.logMode = False
|
||||
self.tickFont = None
|
||||
|
||||
|
||||
self._tickLevels = None ## used to override the automatic ticking system with explicit ticks
|
||||
self._tickSpacing = None # used to override default tickSpacing method
|
||||
self.scale = 1.0
|
||||
self.autoSIPrefix = True
|
||||
self.autoSIPrefixScale = 1.0
|
||||
|
||||
|
||||
self.setRange(0, 1)
|
||||
|
||||
|
||||
if pen is None:
|
||||
self.setPen()
|
||||
else:
|
||||
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 setStyle(self, **kwds):
|
||||
"""
|
||||
Set various style options.
|
||||
|
||||
|
||||
=================== =======================================================
|
||||
Keyword Arguments:
|
||||
tickLength (int) The maximum length of ticks in pixels.
|
||||
Positive values point toward the text; negative
|
||||
tickLength (int) The maximum length of ticks in pixels.
|
||||
Positive values point toward the text; negative
|
||||
values point away.
|
||||
tickTextOffset (int) reserved spacing between text and axis in px
|
||||
tickTextWidth (int) Horizontal space reserved for tick text in px
|
||||
tickTextHeight (int) Vertical space reserved for tick text in px
|
||||
autoExpandTextSpace (bool) Automatically expand text space if the tick
|
||||
strings become too long.
|
||||
tickFont (QFont or None) Determines the font used for tick
|
||||
tickFont (QFont or None) Determines the font used for tick
|
||||
values. Use None for the default font.
|
||||
stopAxisAtTick (tuple: (bool min, bool max)) If True, the axis
|
||||
line is drawn only as far as the last tick.
|
||||
Otherwise, the line is drawn to the edge of the
|
||||
stopAxisAtTick (tuple: (bool min, bool max)) If True, the axis
|
||||
line is drawn only as far as the last tick.
|
||||
Otherwise, the line is drawn to the edge of the
|
||||
AxisItem boundary.
|
||||
textFillLimits (list of (tick #, % fill) tuples). This structure
|
||||
determines how the AxisItem decides how many ticks
|
||||
determines how the AxisItem decides how many ticks
|
||||
should have text appear next to them. Each tuple in
|
||||
the list specifies what fraction of the axis length
|
||||
may be occupied by text, given the number of ticks
|
||||
that already have text displayed. For example::
|
||||
|
||||
|
||||
[(0, 0.8), # Never fill more than 80% of the axis
|
||||
(2, 0.6), # If we already have 2 ticks with text,
|
||||
(2, 0.6), # If we already have 2 ticks with text,
|
||||
# fill no more than 60% of the axis
|
||||
(4, 0.4), # If we already have 4 ticks with text,
|
||||
(4, 0.4), # If we already have 4 ticks with text,
|
||||
# fill no more than 40% of the axis
|
||||
(6, 0.2)] # If we already have 6 ticks with text,
|
||||
(6, 0.2)] # If we already have 6 ticks with text,
|
||||
# fill no more than 20% of the axis
|
||||
|
||||
|
||||
showValues (bool) indicates whether text is displayed adjacent
|
||||
to ticks.
|
||||
=================== =======================================================
|
||||
|
||||
|
||||
Added in version 0.9.9
|
||||
"""
|
||||
for kwd,value in kwds.items():
|
||||
if kwd not in self.style:
|
||||
raise NameError("%s is not a valid style argument." % kwd)
|
||||
|
||||
|
||||
if kwd in ('tickLength', 'tickTextOffset', 'tickTextWidth', 'tickTextHeight'):
|
||||
if not isinstance(value, int):
|
||||
raise ValueError("Argument '%s' must be int" % kwd)
|
||||
|
||||
|
||||
if kwd == 'tickTextOffset':
|
||||
if self.orientation in ('left', 'right'):
|
||||
self.style['tickTextOffset'][0] = value
|
||||
@ -158,19 +166,19 @@ class AxisItem(GraphicsWidget):
|
||||
self.style[kwd] = value
|
||||
else:
|
||||
self.style[kwd] = value
|
||||
|
||||
|
||||
self.picture = None
|
||||
self._adjustSize()
|
||||
self.update()
|
||||
|
||||
|
||||
def close(self):
|
||||
self.scene().removeItem(self.label)
|
||||
self.label = None
|
||||
self.scene().removeItem(self)
|
||||
|
||||
|
||||
def setGrid(self, grid):
|
||||
"""Set the alpha value (0-255) for the grid, or False to disable.
|
||||
|
||||
|
||||
When grid lines are enabled, the axis tick lines are extended to cover
|
||||
the extent of the linked ViewBox, if any.
|
||||
"""
|
||||
@ -178,28 +186,28 @@ class AxisItem(GraphicsWidget):
|
||||
self.picture = None
|
||||
self.prepareGeometryChange()
|
||||
self.update()
|
||||
|
||||
|
||||
def setLogMode(self, log):
|
||||
"""
|
||||
If *log* is True, then ticks are displayed on a logarithmic scale and values
|
||||
are adjusted accordingly. (This is usually accessed by changing the log mode
|
||||
are adjusted accordingly. (This is usually accessed by changing the log mode
|
||||
of a :func:`PlotItem <pyqtgraph.PlotItem.setLogMode>`)
|
||||
"""
|
||||
self.logMode = log
|
||||
self.picture = None
|
||||
self.update()
|
||||
|
||||
|
||||
def setTickFont(self, font):
|
||||
self.tickFont = font
|
||||
self.picture = None
|
||||
self.prepareGeometryChange()
|
||||
## Need to re-allocate space depending on font size?
|
||||
|
||||
|
||||
self.update()
|
||||
|
||||
|
||||
def resizeEvent(self, ev=None):
|
||||
#s = self.size()
|
||||
|
||||
|
||||
## Set the position of the label
|
||||
nudge = 5
|
||||
br = self.label.boundingRect()
|
||||
@ -218,7 +226,7 @@ class AxisItem(GraphicsWidget):
|
||||
p.setY(int(self.size().height()-br.height()+nudge))
|
||||
self.label.setPos(p)
|
||||
self.picture = None
|
||||
|
||||
|
||||
def showLabel(self, show=True):
|
||||
"""Show/hide the label text for this axis."""
|
||||
#self.drawLabel = show
|
||||
@ -229,10 +237,10 @@ class AxisItem(GraphicsWidget):
|
||||
self._updateHeight()
|
||||
if self.autoSIPrefix:
|
||||
self.updateAutoSIPrefix()
|
||||
|
||||
|
||||
def setLabel(self, text=None, units=None, unitPrefix=None, **args):
|
||||
"""Set the text displayed adjacent to the axis.
|
||||
|
||||
|
||||
============== =============================================================
|
||||
**Arguments:**
|
||||
text The text (excluding units) to display on the label for this
|
||||
@ -244,23 +252,26 @@ class AxisItem(GraphicsWidget):
|
||||
**args All extra keyword arguments become CSS style options for
|
||||
the <span> tag which will surround the axis label and units.
|
||||
============== =============================================================
|
||||
|
||||
|
||||
The final text generated for the label will look like::
|
||||
|
||||
|
||||
<span style="...options...">{text} (prefix{units})</span>
|
||||
|
||||
Each extra keyword argument will become a CSS option in the above template.
|
||||
|
||||
Each extra keyword argument will become a CSS option in the above template.
|
||||
For example, you can set the font size and color of the label::
|
||||
|
||||
|
||||
labelStyle = {'color': '#FFF', 'font-size': '14pt'}
|
||||
axis.setLabel('label text', units='V', **labelStyle)
|
||||
|
||||
|
||||
"""
|
||||
show_label = False
|
||||
if text is not None:
|
||||
self.labelText = text
|
||||
self.showLabel()
|
||||
show_label = True
|
||||
if units is not None:
|
||||
self.labelUnits = units
|
||||
show_label = True
|
||||
if show_label:
|
||||
self.showLabel()
|
||||
if unitPrefix is not None:
|
||||
self.labelUnitPrefix = unitPrefix
|
||||
@ -270,7 +281,7 @@ class AxisItem(GraphicsWidget):
|
||||
self._adjustSize()
|
||||
self.picture = None
|
||||
self.update()
|
||||
|
||||
|
||||
def labelString(self):
|
||||
if self.labelUnits == '':
|
||||
if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0:
|
||||
@ -280,13 +291,13 @@ class AxisItem(GraphicsWidget):
|
||||
else:
|
||||
#print repr(self.labelUnitPrefix), repr(self.labelUnits)
|
||||
units = asUnicode('(%s%s)') % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits))
|
||||
|
||||
|
||||
s = asUnicode('%s %s') % (asUnicode(self.labelText), asUnicode(units))
|
||||
|
||||
|
||||
style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle])
|
||||
|
||||
|
||||
return asUnicode("<span style='%s'>%s</span>") % (style, asUnicode(s))
|
||||
|
||||
|
||||
def _updateMaxTextSize(self, x):
|
||||
## Informs that the maximum tick size orthogonal to the axis has
|
||||
## changed; we use this to decide whether the item needs to be resized
|
||||
@ -305,22 +316,22 @@ class AxisItem(GraphicsWidget):
|
||||
if self.style['autoExpandTextSpace'] is True:
|
||||
self._updateHeight()
|
||||
#return True ## size has changed
|
||||
|
||||
|
||||
def _adjustSize(self):
|
||||
if self.orientation in ['left', 'right']:
|
||||
self._updateWidth()
|
||||
else:
|
||||
self._updateHeight()
|
||||
|
||||
|
||||
def setHeight(self, h=None):
|
||||
"""Set the height of this axis reserved for ticks and tick labels.
|
||||
The height of the axis label is automatically added.
|
||||
|
||||
|
||||
If *height* is None, then the value will be determined automatically
|
||||
based on the size of the tick text."""
|
||||
self.fixedHeight = h
|
||||
self._updateHeight()
|
||||
|
||||
|
||||
def _updateHeight(self):
|
||||
if not self.isVisible():
|
||||
h = 0
|
||||
@ -338,20 +349,20 @@ class AxisItem(GraphicsWidget):
|
||||
h += self.label.boundingRect().height() * 0.8
|
||||
else:
|
||||
h = self.fixedHeight
|
||||
|
||||
|
||||
self.setMaximumHeight(h)
|
||||
self.setMinimumHeight(h)
|
||||
self.picture = None
|
||||
|
||||
|
||||
def setWidth(self, w=None):
|
||||
"""Set the width of this axis reserved for ticks and tick labels.
|
||||
The width of the axis label is automatically added.
|
||||
|
||||
|
||||
If *width* is None, then the value will be determined automatically
|
||||
based on the size of the tick text."""
|
||||
self.fixedWidth = w
|
||||
self._updateWidth()
|
||||
|
||||
|
||||
def _updateWidth(self):
|
||||
if not self.isVisible():
|
||||
w = 0
|
||||
@ -369,20 +380,20 @@ class AxisItem(GraphicsWidget):
|
||||
w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate
|
||||
else:
|
||||
w = self.fixedWidth
|
||||
|
||||
|
||||
self.setMaximumWidth(w)
|
||||
self.setMinimumWidth(w)
|
||||
self.picture = None
|
||||
|
||||
|
||||
def pen(self):
|
||||
if self._pen is None:
|
||||
return fn.mkPen(getConfigOption('foreground'))
|
||||
return fn.mkPen(self._pen)
|
||||
|
||||
|
||||
def setPen(self, *args, **kwargs):
|
||||
"""
|
||||
Set the pen used for drawing text, axes, ticks, and grid lines.
|
||||
If no arguments are given, the default foreground color will be used
|
||||
If no arguments are given, the default foreground color will be used
|
||||
(see :func:`setConfigOption <pyqtgraph.setConfigOption>`).
|
||||
"""
|
||||
self.picture = None
|
||||
@ -393,44 +404,44 @@ class AxisItem(GraphicsWidget):
|
||||
self.labelStyle['color'] = '#' + fn.colorStr(self._pen.color())[:6]
|
||||
self.setLabel()
|
||||
self.update()
|
||||
|
||||
|
||||
def setScale(self, scale=None):
|
||||
"""
|
||||
Set the value scaling for this axis.
|
||||
|
||||
Set the value scaling for this axis.
|
||||
|
||||
Setting this value causes the axis to draw ticks and tick labels as if
|
||||
the view coordinate system were scaled. By default, the axis scaling is
|
||||
the view coordinate system were scaled. By default, the axis scaling is
|
||||
1.0.
|
||||
"""
|
||||
# Deprecated usage, kept for backward compatibility
|
||||
if scale is None:
|
||||
if scale is None:
|
||||
scale = 1.0
|
||||
self.enableAutoSIPrefix(True)
|
||||
|
||||
|
||||
if scale != self.scale:
|
||||
self.scale = scale
|
||||
self.setLabel()
|
||||
self.picture = None
|
||||
self.update()
|
||||
|
||||
|
||||
def enableAutoSIPrefix(self, enable=True):
|
||||
"""
|
||||
Enable (or disable) automatic SI prefix scaling on this axis.
|
||||
|
||||
When enabled, this feature automatically determines the best SI prefix
|
||||
Enable (or disable) automatic SI prefix scaling on this axis.
|
||||
|
||||
When enabled, this feature automatically determines the best SI prefix
|
||||
to prepend to the label units, while ensuring that axis values are scaled
|
||||
accordingly.
|
||||
|
||||
For example, if the axis spans values from -0.1 to 0.1 and has units set
|
||||
accordingly.
|
||||
|
||||
For example, if the axis spans values from -0.1 to 0.1 and has units set
|
||||
to 'V' then the axis would display values -100 to 100
|
||||
and the units would appear as 'mV'
|
||||
|
||||
|
||||
This feature is enabled by default, and is only available when a suffix
|
||||
(unit string) is provided to display on the label.
|
||||
"""
|
||||
self.autoSIPrefix = enable
|
||||
self.updateAutoSIPrefix()
|
||||
|
||||
|
||||
def updateAutoSIPrefix(self):
|
||||
if self.label.isVisible():
|
||||
(scale, prefix) = fn.siScale(max(abs(self.range[0]*self.scale), abs(self.range[1]*self.scale)))
|
||||
@ -440,12 +451,12 @@ class AxisItem(GraphicsWidget):
|
||||
self.setLabel(unitPrefix=prefix)
|
||||
else:
|
||||
scale = 1.0
|
||||
|
||||
|
||||
self.autoSIPrefixScale = scale
|
||||
self.picture = None
|
||||
self.update()
|
||||
|
||||
|
||||
|
||||
|
||||
def setRange(self, mn, mx):
|
||||
"""Set the range of values displayed by the axis.
|
||||
Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView <pyqtgraph.AxisItem.linkToView>`"""
|
||||
@ -456,14 +467,14 @@ class AxisItem(GraphicsWidget):
|
||||
self.updateAutoSIPrefix()
|
||||
self.picture = None
|
||||
self.update()
|
||||
|
||||
|
||||
def linkedView(self):
|
||||
"""Return the ViewBox this axis is linked to"""
|
||||
if self._linkedView is None:
|
||||
return None
|
||||
else:
|
||||
return self._linkedView()
|
||||
|
||||
|
||||
def linkToView(self, view):
|
||||
"""Link this axis to a ViewBox, causing its displayed range to match the visible range of the view."""
|
||||
oldView = self.linkedView()
|
||||
@ -476,11 +487,11 @@ class AxisItem(GraphicsWidget):
|
||||
if oldView is not None:
|
||||
oldView.sigXRangeChanged.disconnect(self.linkedViewChanged)
|
||||
view.sigXRangeChanged.connect(self.linkedViewChanged)
|
||||
|
||||
|
||||
if oldView is not None:
|
||||
oldView.sigResized.disconnect(self.linkedViewChanged)
|
||||
view.sigResized.connect(self.linkedViewChanged)
|
||||
|
||||
|
||||
def linkedViewChanged(self, view, newRange=None):
|
||||
if self.orientation in ['right', 'left']:
|
||||
if newRange is None:
|
||||
@ -496,7 +507,7 @@ class AxisItem(GraphicsWidget):
|
||||
self.setRange(*newRange[::-1])
|
||||
else:
|
||||
self.setRange(*newRange)
|
||||
|
||||
|
||||
def boundingRect(self):
|
||||
linkedView = self.linkedView()
|
||||
if linkedView is None or self.grid is False:
|
||||
@ -515,7 +526,7 @@ class AxisItem(GraphicsWidget):
|
||||
return rect
|
||||
else:
|
||||
return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect())
|
||||
|
||||
|
||||
def paint(self, p, opt, widget):
|
||||
profiler = debug.Profiler()
|
||||
if self.picture is None:
|
||||
@ -544,26 +555,26 @@ class AxisItem(GraphicsWidget):
|
||||
[ (minorTickValue1, minorTickString1), (minorTickValue2, minorTickString2), ... ],
|
||||
...
|
||||
]
|
||||
|
||||
|
||||
If *ticks* is None, then the default tick system will be used instead.
|
||||
"""
|
||||
self._tickLevels = ticks
|
||||
self.picture = None
|
||||
self.update()
|
||||
|
||||
|
||||
def setTickSpacing(self, major=None, minor=None, levels=None):
|
||||
"""
|
||||
Explicitly determine the spacing of major and minor ticks. This
|
||||
Explicitly determine the spacing of major and minor ticks. This
|
||||
overrides the default behavior of the tickSpacing method, and disables
|
||||
the effect of setTicks(). Arguments may be either *major* and *minor*,
|
||||
or *levels* which is a list of (spacing, offset) tuples for each
|
||||
the effect of setTicks(). Arguments may be either *major* and *minor*,
|
||||
or *levels* which is a list of (spacing, offset) tuples for each
|
||||
tick level desired.
|
||||
|
||||
|
||||
If no arguments are given, then the default behavior of tickSpacing
|
||||
is enabled.
|
||||
|
||||
|
||||
Examples::
|
||||
|
||||
|
||||
# two levels, all offsets = 0
|
||||
axis.setTickSpacing(5, 1)
|
||||
# three levels, all offsets = 0
|
||||
@ -571,7 +582,7 @@ class AxisItem(GraphicsWidget):
|
||||
# reset to default
|
||||
axis.setTickSpacing()
|
||||
"""
|
||||
|
||||
|
||||
if levels is None:
|
||||
if major is None:
|
||||
levels = None
|
||||
@ -580,16 +591,16 @@ class AxisItem(GraphicsWidget):
|
||||
self._tickSpacing = levels
|
||||
self.picture = None
|
||||
self.update()
|
||||
|
||||
|
||||
|
||||
def tickSpacing(self, minVal, maxVal, size):
|
||||
"""Return values describing the desired spacing and offset of ticks.
|
||||
|
||||
This method is called whenever the axis needs to be redrawn and is a
|
||||
|
||||
This method is called whenever the axis needs to be redrawn and is a
|
||||
good method to override in subclasses that require control over tick locations.
|
||||
|
||||
|
||||
The return value must be a list of tuples, one for each set of ticks::
|
||||
|
||||
|
||||
[
|
||||
(major tick spacing, offset),
|
||||
(minor tick spacing, offset),
|
||||
@ -600,32 +611,32 @@ class AxisItem(GraphicsWidget):
|
||||
# First check for override tick spacing
|
||||
if self._tickSpacing is not None:
|
||||
return self._tickSpacing
|
||||
|
||||
|
||||
dif = abs(maxVal - minVal)
|
||||
if dif == 0:
|
||||
return []
|
||||
|
||||
|
||||
## decide optimal minor tick spacing in pixels (this is just aesthetics)
|
||||
optimalTickCount = max(2., np.log(size))
|
||||
|
||||
## optimal minor tick spacing
|
||||
|
||||
## optimal minor tick spacing
|
||||
optimalSpacing = dif / optimalTickCount
|
||||
|
||||
|
||||
## the largest power-of-10 spacing which is smaller than optimal
|
||||
p10unit = 10 ** np.floor(np.log10(optimalSpacing))
|
||||
|
||||
|
||||
## Determine major/minor tick spacings which flank the optimal spacing.
|
||||
intervals = np.array([1., 2., 10., 20., 100.]) * p10unit
|
||||
minorIndex = 0
|
||||
while intervals[minorIndex+1] <= optimalSpacing:
|
||||
minorIndex += 1
|
||||
|
||||
|
||||
levels = [
|
||||
(intervals[minorIndex+2], 0),
|
||||
(intervals[minorIndex+1], 0),
|
||||
#(intervals[minorIndex], 0) ## Pretty, but eats up CPU
|
||||
]
|
||||
|
||||
|
||||
if self.style['maxTickLevel'] >= 2:
|
||||
## decide whether to include the last level of ticks
|
||||
minSpacing = min(size / 20., 30.)
|
||||
@ -633,16 +644,16 @@ class AxisItem(GraphicsWidget):
|
||||
if dif / intervals[minorIndex] <= maxTickCount:
|
||||
levels.append((intervals[minorIndex], 0))
|
||||
return levels
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##### This does not work -- switching between 2/5 confuses the automatic text-level-selection
|
||||
### Determine major/minor tick spacings which flank the optimal spacing.
|
||||
#intervals = np.array([1., 2., 5., 10., 20., 50., 100.]) * p10unit
|
||||
#minorIndex = 0
|
||||
#while intervals[minorIndex+1] <= optimalSpacing:
|
||||
#minorIndex += 1
|
||||
|
||||
|
||||
### make sure we never see 5 and 2 at the same time
|
||||
#intIndexes = [
|
||||
#[0,1,3],
|
||||
@ -651,42 +662,42 @@ class AxisItem(GraphicsWidget):
|
||||
#[3,4,6],
|
||||
#[3,5,6],
|
||||
#][minorIndex]
|
||||
|
||||
|
||||
#return [
|
||||
#(intervals[intIndexes[2]], 0),
|
||||
#(intervals[intIndexes[1]], 0),
|
||||
#(intervals[intIndexes[0]], 0)
|
||||
#]
|
||||
|
||||
|
||||
def tickValues(self, minVal, maxVal, size):
|
||||
"""
|
||||
Return the values and spacing of ticks to draw::
|
||||
|
||||
[
|
||||
(spacing, [major ticks]),
|
||||
(spacing, [minor ticks]),
|
||||
...
|
||||
|
||||
[
|
||||
(spacing, [major ticks]),
|
||||
(spacing, [minor ticks]),
|
||||
...
|
||||
]
|
||||
|
||||
|
||||
By default, this method calls tickSpacing to determine the correct tick locations.
|
||||
This is a good method to override in subclasses.
|
||||
"""
|
||||
minVal, maxVal = sorted((minVal, maxVal))
|
||||
|
||||
|
||||
minVal *= self.scale
|
||||
|
||||
minVal *= self.scale
|
||||
maxVal *= self.scale
|
||||
#size *= self.scale
|
||||
|
||||
|
||||
ticks = []
|
||||
tickLevels = self.tickSpacing(minVal, maxVal, size)
|
||||
allValues = np.array([])
|
||||
for i in range(len(tickLevels)):
|
||||
spacing, offset = tickLevels[i]
|
||||
|
||||
|
||||
## determine starting tick
|
||||
start = (np.ceil((minVal-offset) / spacing) * spacing) + offset
|
||||
|
||||
|
||||
## determine number of ticks
|
||||
num = int((maxVal-start) / spacing) + 1
|
||||
values = (np.arange(num) * spacing + start) / self.scale
|
||||
@ -696,11 +707,11 @@ class AxisItem(GraphicsWidget):
|
||||
values = list(filter(lambda x: all(np.abs(allValues-x) > spacing/self.scale*0.01), values))
|
||||
allValues = np.concatenate([allValues, values])
|
||||
ticks.append((spacing/self.scale, values))
|
||||
|
||||
|
||||
if self.logMode:
|
||||
return self.logTickValues(minVal, maxVal, size, ticks)
|
||||
|
||||
|
||||
|
||||
|
||||
#nticks = []
|
||||
#for t in ticks:
|
||||
#nvals = []
|
||||
@ -708,24 +719,24 @@ class AxisItem(GraphicsWidget):
|
||||
#nvals.append(v/self.scale)
|
||||
#nticks.append((t[0]/self.scale,nvals))
|
||||
#ticks = nticks
|
||||
|
||||
|
||||
return ticks
|
||||
|
||||
|
||||
def logTickValues(self, minVal, maxVal, size, stdTicks):
|
||||
|
||||
|
||||
## start with the tick spacing given by tickValues().
|
||||
## Any level whose spacing is < 1 needs to be converted to log scale
|
||||
|
||||
|
||||
ticks = []
|
||||
for (spacing, t) in stdTicks:
|
||||
if spacing >= 1.0:
|
||||
ticks.append((spacing, t))
|
||||
|
||||
|
||||
if len(ticks) < 3:
|
||||
v1 = int(np.floor(minVal))
|
||||
v2 = int(np.ceil(maxVal))
|
||||
#major = list(range(v1+1, v2))
|
||||
|
||||
|
||||
minor = []
|
||||
for v in range(v1, v2):
|
||||
minor.extend(v + np.log10(np.arange(1, 10)))
|
||||
@ -734,21 +745,21 @@ class AxisItem(GraphicsWidget):
|
||||
return ticks
|
||||
|
||||
def tickStrings(self, values, scale, spacing):
|
||||
"""Return the strings that should be placed next to ticks. This method is called
|
||||
"""Return the strings that should be placed next to ticks. This method is called
|
||||
when redrawing the axis and is a good method to override in subclasses.
|
||||
The method is called with a list of tick values, a scaling factor (see below), and the
|
||||
spacing between ticks (this is required since, in some instances, there may be only
|
||||
The method is called with a list of tick values, a scaling factor (see below), and the
|
||||
spacing between ticks (this is required since, in some instances, there may be only
|
||||
one tick and thus no other way to determine the tick spacing)
|
||||
|
||||
|
||||
The scale argument is used when the axis label is displaying units which may have an SI scaling prefix.
|
||||
When determining the text to display, use value*scale to correctly account for this prefix.
|
||||
For example, if the axis label's units are set to 'V', then a tick value of 0.001 might
|
||||
be accompanied by a scale value of 1000. This indicates that the label is displaying 'mV', and
|
||||
be accompanied by a scale value of 1000. This indicates that the label is displaying 'mV', and
|
||||
thus the tick should display 0.001 * 1000 = 1.
|
||||
"""
|
||||
if self.logMode:
|
||||
return self.logTickStrings(values, scale, spacing)
|
||||
|
||||
|
||||
places = max(0, np.ceil(-np.log10(spacing*scale)))
|
||||
strings = []
|
||||
for v in values:
|
||||
@ -759,27 +770,27 @@ class AxisItem(GraphicsWidget):
|
||||
vstr = ("%%0.%df" % places) % vs
|
||||
strings.append(vstr)
|
||||
return strings
|
||||
|
||||
|
||||
def logTickStrings(self, values, scale, spacing):
|
||||
return ["%0.1g"%x for x in 10 ** np.array(values).astype(float)]
|
||||
|
||||
|
||||
def generateDrawSpecs(self, p):
|
||||
"""
|
||||
Calls tickValues() and tickStrings() to determine where and how ticks should
|
||||
be drawn, then generates from this a set of drawing commands to be
|
||||
be drawn, then generates from this a set of drawing commands to be
|
||||
interpreted by drawPicture().
|
||||
"""
|
||||
profiler = debug.Profiler()
|
||||
|
||||
#bounds = self.boundingRect()
|
||||
bounds = self.mapRectFromParent(self.geometry())
|
||||
|
||||
|
||||
linkedView = self.linkedView()
|
||||
if linkedView is None or self.grid is False:
|
||||
tickBounds = bounds
|
||||
else:
|
||||
tickBounds = linkedView.mapRectToItem(self, linkedView.boundingRect())
|
||||
|
||||
|
||||
if self.orientation == 'left':
|
||||
span = (bounds.topRight(), bounds.bottomRight())
|
||||
tickStart = tickBounds.right()
|
||||
@ -805,7 +816,7 @@ class AxisItem(GraphicsWidget):
|
||||
tickDir = 1
|
||||
axis = 1
|
||||
#print tickStart, tickStop, span
|
||||
|
||||
|
||||
## determine size of this item in pixels
|
||||
points = list(map(self.mapToDevice, span))
|
||||
if None in points:
|
||||
@ -830,7 +841,7 @@ class AxisItem(GraphicsWidget):
|
||||
for val, strn in level:
|
||||
values.append(val)
|
||||
strings.append(strn)
|
||||
|
||||
|
||||
## determine mapping between tick values and local coordinates
|
||||
dif = self.range[1] - self.range[0]
|
||||
if dif == 0:
|
||||
@ -843,29 +854,29 @@ class AxisItem(GraphicsWidget):
|
||||
else:
|
||||
xScale = bounds.width() / dif
|
||||
offset = self.range[0] * xScale
|
||||
|
||||
|
||||
xRange = [x * xScale - offset for x in self.range]
|
||||
xMin = min(xRange)
|
||||
xMax = max(xRange)
|
||||
|
||||
|
||||
profiler('init')
|
||||
|
||||
|
||||
tickPositions = [] # remembers positions of previously drawn ticks
|
||||
|
||||
|
||||
## compute coordinates to draw ticks
|
||||
## draw three different intervals, long ticks first
|
||||
tickSpecs = []
|
||||
for i in range(len(tickLevels)):
|
||||
tickPositions.append([])
|
||||
ticks = tickLevels[i][1]
|
||||
|
||||
|
||||
## length of tick
|
||||
tickLength = self.style['tickLength'] / ((i*0.5)+1.0)
|
||||
|
||||
|
||||
lineAlpha = 255 / (i+1)
|
||||
if self.grid is not False:
|
||||
lineAlpha *= self.grid/255. * np.clip((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.)
|
||||
|
||||
|
||||
for v in ticks:
|
||||
## determine actual position to draw this tick
|
||||
x = (v * xScale) - offset
|
||||
@ -873,7 +884,7 @@ class AxisItem(GraphicsWidget):
|
||||
tickPositions[i].append(None)
|
||||
continue
|
||||
tickPositions[i].append(x)
|
||||
|
||||
|
||||
p1 = [x, x]
|
||||
p2 = [x, x]
|
||||
p1[axis] = tickStart
|
||||
@ -887,7 +898,7 @@ class AxisItem(GraphicsWidget):
|
||||
tickSpecs.append((tickPen, Point(p1), Point(p2)))
|
||||
profiler('compute ticks')
|
||||
|
||||
|
||||
|
||||
if self.style['stopAxisAtTick'][0] is True:
|
||||
stop = max(span[0].y(), min(map(min, tickPositions)))
|
||||
if axis == 0:
|
||||
@ -902,7 +913,7 @@ class AxisItem(GraphicsWidget):
|
||||
span[1].setX(stop)
|
||||
axisSpec = (self.pen(), span[0], span[1])
|
||||
|
||||
|
||||
|
||||
textOffset = self.style['tickTextOffset'][axis] ## spacing between axis and text
|
||||
#if self.style['autoExpandTextSpace'] is True:
|
||||
#textWidth = self.textWidth
|
||||
@ -910,15 +921,15 @@ class AxisItem(GraphicsWidget):
|
||||
#else:
|
||||
#textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text
|
||||
#textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text
|
||||
|
||||
|
||||
textSize2 = 0
|
||||
textRects = []
|
||||
textSpecs = [] ## list of draw
|
||||
|
||||
|
||||
# If values are hidden, return early
|
||||
if not self.style['showValues']:
|
||||
return (axisSpec, tickSpecs, textSpecs)
|
||||
|
||||
|
||||
for i in range(min(len(tickLevels), self.style['maxTextLevel']+1)):
|
||||
## Get the list of strings to display for this level
|
||||
if tickStrings is None:
|
||||
@ -926,10 +937,10 @@ class AxisItem(GraphicsWidget):
|
||||
strings = self.tickStrings(values, self.autoSIPrefixScale * self.scale, spacing)
|
||||
else:
|
||||
strings = tickStrings[i]
|
||||
|
||||
|
||||
if len(strings) == 0:
|
||||
continue
|
||||
|
||||
|
||||
## ignore strings belonging to ticks that were previously ignored
|
||||
for j in range(len(strings)):
|
||||
if tickPositions[i][j] is None:
|
||||
@ -945,10 +956,10 @@ class AxisItem(GraphicsWidget):
|
||||
## boundingRect is usually just a bit too large
|
||||
## (but this probably depends on per-font metrics?)
|
||||
br.setHeight(br.height() * 0.8)
|
||||
|
||||
|
||||
rects.append(br)
|
||||
textRects.append(rects[-1])
|
||||
|
||||
|
||||
if len(textRects) > 0:
|
||||
## measure all text, make sure there's enough room
|
||||
if axis == 0:
|
||||
@ -973,7 +984,7 @@ class AxisItem(GraphicsWidget):
|
||||
break
|
||||
if finished:
|
||||
break
|
||||
|
||||
|
||||
#spacing, values = tickLevels[best]
|
||||
#strings = self.tickStrings(values, self.scale, spacing)
|
||||
# Determine exactly where tick text should be drawn
|
||||
@ -1006,24 +1017,24 @@ class AxisItem(GraphicsWidget):
|
||||
#p.drawText(rect, textFlags, vstr)
|
||||
textSpecs.append((rect, textFlags, vstr))
|
||||
profiler('compute text')
|
||||
|
||||
|
||||
## update max text size if needed.
|
||||
self._updateMaxTextSize(textSize2)
|
||||
|
||||
|
||||
return (axisSpec, tickSpecs, textSpecs)
|
||||
|
||||
|
||||
def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
|
||||
profiler = debug.Profiler()
|
||||
|
||||
p.setRenderHint(p.Antialiasing, False)
|
||||
p.setRenderHint(p.TextAntialiasing, True)
|
||||
|
||||
|
||||
## draw long line along axis
|
||||
pen, p1, p2 = axisSpec
|
||||
p.setPen(pen)
|
||||
p.drawLine(p1, p2)
|
||||
p.translate(0.5,0) ## resolves some damn pixel ambiguity
|
||||
|
||||
|
||||
## draw ticks
|
||||
for pen, p1, p2 in tickSpecs:
|
||||
p.setPen(pen)
|
||||
@ -1045,7 +1056,7 @@ class AxisItem(GraphicsWidget):
|
||||
self._updateWidth()
|
||||
else:
|
||||
self._updateHeight()
|
||||
|
||||
|
||||
def hide(self):
|
||||
GraphicsWidget.hide(self)
|
||||
if self.orientation in ['left', 'right']:
|
||||
@ -1054,23 +1065,23 @@ class AxisItem(GraphicsWidget):
|
||||
self._updateHeight()
|
||||
|
||||
def wheelEvent(self, ev):
|
||||
if self.linkedView() is None:
|
||||
if 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()
|
||||
|
||||
|
||||
def mouseDragEvent(self, event):
|
||||
if self.linkedView() is None:
|
||||
if self.linkedView() is None:
|
||||
return
|
||||
if self.orientation in ['left', 'right']:
|
||||
return self.linkedView().mouseDragEvent(event, axis=1)
|
||||
else:
|
||||
return self.linkedView().mouseDragEvent(event, axis=0)
|
||||
|
||||
|
||||
def mouseClickEvent(self, event):
|
||||
if self.linkedView() is None:
|
||||
if self.linkedView() is None:
|
||||
return
|
||||
return self.linkedView().mouseClickEvent(event)
|
||||
|
Loading…
Reference in New Issue
Block a user