mostly functional example

This commit is contained in:
Nils Nemitz 2021-03-08 02:14:37 +09:00
parent eb6f724767
commit 148a35d430
16 changed files with 536 additions and 174 deletions

View File

@ -0,0 +1,153 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Update a simple plot as rapidly as possible to measure speed.
"""
## Add path to library (just for examples; you do not need this)
import initExample
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import mkQApp, QtGui, QtCore, QtWidgets
from pyqtgraph.ptime import time
from pyqtgraph import functions as fn
class MainWindow(QtWidgets.QMainWindow):
""" example application main window """
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
main_wid = QtWidgets.QWidget()
self.setCentralWidget(main_wid)
self.setWindowTitle('pyqtgraph example: Palette application test')
self.resize(600,600)
# pg.functions.SIGNAL_SOURCE.paletteChangedSignal.connect(self.testSignal) # test link
# pg.palette.get('monogreen').apply()
main_layout = QtWidgets.QGridLayout( main_wid )
gr_wid = pg.GraphicsLayoutWidget(show=True)
main_layout.addWidget( gr_wid, 0,0, 1,4 )
btn = QtWidgets.QPushButton('continuous')
btn.clicked.connect(self.handle_button_timer_on)
main_layout.addWidget(btn, 1,0, 1,1 )
btn = QtWidgets.QPushButton('stop updates')
btn.clicked.connect(self.handle_button_timer_off)
main_layout.addWidget(btn, 2,0, 1,1 )
btn = QtWidgets.QPushButton('apply <legacy>')
btn.clicked.connect(self.handle_button_pal1)
main_layout.addWidget(btn, 1,2, 1,1 )
btn = QtWidgets.QPushButton('apply <mono green>')
btn.clicked.connect(self.handle_button_pal2)
main_layout.addWidget(btn, 1,3, 1,1 )
btn = QtWidgets.QPushButton('apply <relaxed - dark>')
btn.clicked.connect(self.handle_button_pal3)
main_layout.addWidget(btn, 2,2, 1,1 )
btn = QtWidgets.QPushButton('apply <relaxed - light>')
btn.clicked.connect(self.handle_button_pal4)
main_layout.addWidget(btn, 2,3, 1,1 )
# self.manager = pg.fn.NAMED_COLOR_MANAGER
# self.pen1 = pg.NamedPen('p1') #,width=0.5)
self.plt = gr_wid.addPlot()
self.plt.enableAutoRange(False)
self.plt.setYRange( -7,7 )
self.plt.setXRange( 0, 15 ) #500 )
self.plt.setLabel('bottom', 'Index', units='B')
self.data1 = +3 + np.random.normal(size=(15)) #500))
self.data2 = -3 + np.random.normal(size=(15)) #500))
self.curve1 = pg.PlotDataItem(pen='r', symbol='o', symbolSize=10, symbolPen='gr_fg', symbolBrush=('y',127))
self.plt.addItem(self.curve1)
self.curve2 = pg.PlotCurveItem(pen='p3', brush='p4')
self.curve2.setFillLevel(0)
self.plt.addItem(self.curve2)
self.show()
self.pal_1 = pg.palette.get('legacy')
self.pal_2 = pg.palette.get('monogreen')
self.pal_3 = pg.palette.get('relaxed_dark')
self.pal_4 = pg.palette.get('relaxed_light')
self.lastTime = time()
self.fps = None
self.timer = QtCore.QTimer(singleShot=False)
self.timer.timeout.connect( self.timed_update )
self.timed_update()
def testSignal(self, val):
""" demonstrate use of PaletteChanged signal """
print('"Palette changed" signal was received with value', val)
def handle_button_timer_on(self):
""" (re-)activate timer """
self.timer.start(1)
def handle_button_timer_off(self):
""" de-activate timer """
self.timer.stop()
def handle_button_pal1(self):
""" apply palette 1 on request """
print('--> legacy')
self.pal_1.apply()
def handle_button_pal2(self):
""" apply palette 2 on request """
print('--> mono green')
self.pal_2.apply()
def handle_button_pal3(self):
""" apply palette 1 on request """
print('--> relax(light)')
self.pal_3.apply()
def handle_button_pal4(self):
""" apply palette 1 on request """
print('--> relax(light)')
self.pal_4.apply()
def timed_update(self):
""" update loop, called by timer """
self.data1[:-1] = self.data1[1:]
self.data1[-1] = +3 + np.random.normal()
xdata = np.arange( len(self.data1) )
self.curve1.setData( x=xdata, y=self.data1 )
self.data2[:-1] = self.data2[1:]
self.data2[-1] = -3 + np.random.normal()
self.curve2.setData( y=self.data2 )
now = time()
dt = now - self.lastTime
self.lastTime = now
if self.fps is None:
self.fps = 1.0/dt
else:
s = np.clip(dt*3., 0, 1)
self.fps = self.fps * (1-s) + (1.0/dt) * s
self.plt.setTitle('%0.2f fps' % self.fps)
QtWidgets.QApplication.processEvents() ## force complete redraw for every plot
mkQApp("Palette test application")
main_window = MainWindow()
## Start Qt event loop
if __name__ == '__main__':
QtWidgets.QApplication.instance().exec_()

View File

@ -1,7 +1,6 @@
from collections import OrderedDict
from argparse import Namespace
examples = OrderedDict([
('Command-line usage', 'CLIexample.py'),
('Basic Plotting', Namespace(filename='Plotting.py', recommended=True)),
@ -21,6 +20,7 @@ examples = OrderedDict([
('Auto-range', 'PlotAutoRange.py'),
('Remote Plotting', 'RemoteSpeedTest.py'),
('Scrolling plots', 'scrollingPlots.py'),
('Palette adjustment','PaletteApplicationExample.py'),
('HDF5 big data', 'hdf5.py'),
('Demos', OrderedDict([
('Optics', 'optics_demos.py'),

View File

@ -62,12 +62,17 @@ CONFIG_OPTIONS = {
'useCupy': False, # When True, attempt to use cupy ( currently only with ImageItem and related functions )
}
def setConfigOption(opt, value):
if opt not in CONFIG_OPTIONS:
raise KeyError('Unknown configuration option "%s"' % opt)
if opt == 'imageAxisOrder' and value not in ('row-major', 'col-major'):
raise ValueError('imageAxisOrder must be either "row-major" or "col-major"')
# setConfigOption should be relocated to have access to functions.py
# Then background / foreground updates can be intercepted and applied to the palette
# if opt == 'background':
# functions.Colors['gr_bg'] = functions.Colors[value]
# if opt == 'foreground':
# functions.Colors['gr_fg'] = functions.Colors[value]
CONFIG_OPTIONS[opt] = value
def setConfigOptions(**opts):
@ -280,6 +285,7 @@ from .ptime import time
from .Qt import isQObjectAlive
from .ThreadsafeTimer import *
from .namedPen import *
from .namedBrush import *
from .palette import *

View File

@ -22,6 +22,7 @@ from .Qt import QtGui, QtCore, QT_LIB, QtVersion
from . import Qt
from .namedColorManager import NamedColorManager
from .namedPen import NamedPen
from .namedBrush import NamedBrush
from .metaarray import MetaArray
from collections import OrderedDict
@ -219,25 +220,66 @@ class Color(QtGui.QColor):
return (self.red, self.green, self.blue, self.alpha)[ind]()
def parseNamedColorSpecification(*args):
"""
check if args specify a NamedColor, looking for
'name' or ('name', alpha) information.
Returns:
None if invalid
('name', alpha) if a valid name and alpha value is given
('name', None) if no alpha value is available
('', None) if an empty name is given, indicating a blank color
None if the specification does not match a NamedColor
"""
while len(args) <= 1:
if len(args) == 0:
return None
if len(args) == 1:
arg = args[0]
if isinstance(arg, str):
if len(arg) == 0:
return ('', None) # valid, but blank
if arg[0] == '#':
return None # hexadecimal string not handled as NamedColor
if arg in Colors:
return (arg, None) # valid name, no alpha given
if isinstance(arg, tuple) or isinstance(arg, list):
args = arg # promote to top level
else:
return None #numerical values not handled as NamedColor
if len(args) == 2:
if isinstance(arg[0], str):
return (arg[0], arg[1]) # return ('name', alpha) tuple
return None # all other cases not handled as NamedColor
def mkColor(*args):
"""
Convenience function for constructing QColor from a variety of argument types. Accepted arguments are:
================ ================================================
'c' one of: r, g, b, c, m, y, k, w
'name' any color name specifed in palette
R, G, B, [A] integers 0-255
(R, G, B, [A]) tuple of integers 0-255
float greyscale, 0.0-1.0
int see :func:`intColor() <pyqtgraph.intColor>`
(int, hues) see :func:`intColor() <pyqtgraph.intColor>`
"RGB" hexadecimal strings; may begin with '#'
"RGBA"
"RRGGBB"
"RRGGBBAA"
"#RGB" hexadecimal strings; should begin with '#'
"#RGBA"
"#RRGGBB"
"#RRGGBBAA"
QColor QColor instance; makes a copy.
================ ================================================
"""
err = 'Not sure how to make a color from "%s"' % str(args)
result = parseNamedColorSpecification(args) # check if this is a named palette color
if result is not None: # make a return palette color
name, alpha = result
if name == '':
return QtGui.QColor(0,0,0,0) # empty string means "no color"
qcol = Colors[name]
if alpha is not None: qcol.setAlpha( alpha )
return qcol
if len(args) == 1:
if isinstance(args[0], basestring):
c = args[0]
@ -301,116 +343,115 @@ def mkColor(*args):
return QtGui.QColor(*args)
def mkBrush(*args, **kwds):
def mkBrush(*args, **kargs):
"""
| Convenience function for constructing Brush.
| This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() <pyqtgraph.mkColor>`
| Calling mkBrush(None) returns an invisible brush.
"""
print('mkBrush called with',args,kwds)
if 'color' in kwds:
color = kwds['color']
elif len(args) == 1:
arg = args[0]
if arg is None:
return QtGui.QBrush(QtCore.Qt.NoBrush)
elif isinstance(arg, QtGui.QBrush):
return QtGui.QBrush(arg)
while ( # unravel single element sublists
( isinstance(args, tuple) or isinstance(args,list) )
and len(args) == 1
):
args = args[0]
# now args is either a non-list entity, or a multi-element tuple
# short-circuits:
if isinstance(args, NamedBrush):
return args # pass through predefined NamedPen directly
elif isinstance(args, QtGui.QBrush):
return QtGui.QBrush(args) ## return a copy of this brush
elif isinstance(args, dict):
return mkBrush(**args) # retry with kwargs assigned from dictionary
elif args is None:
return QtGui.QBrush( QtCore.Qt.NoBrush ) # explicit None means "no brush"
# no short-circuit, continue parsing to construct QPen or NamedPen
if 'hsv' in kargs: # hsv argument takes precedence
qcol = hsvColor( *kargs['hsv'] )
qbrush = QtGui.QBrush(qcol)
else:
if 'color' in kargs:
args = kargs['color'] # 'color' KW-argument overrides unnamed arguments
if args is None:
return QtGui.QBrush( QtCore.Qt.NoBrush ) # explicit None means "no brush"
if args == () or args == []:
print(' functions: returning default color NamedBrush')
qpen = NamedBrush( 'gr_fg' ) # default foreground color
else:
color = arg
elif len(args) > 1:
color = args
return QtGui.QBrush(mkColor(color))
def mkNamedPen(name, **kargs):
"""
Try to create a named pen.
Currently, this quietly returns None if 'name' does not specify a color.
This causes mkPen to keep trying to parse color information.
In addition to arguments 'style', 'width', 'dash' and 'cosmetic',
'alpha' = 0-255 sets the opacity of the named pen
"""
alpha = kargs.get('alpha', None)
try:
pen = NamedPen( name, alpha=alpha )
except ValueError:
print(' failed to make NamedPen',name,kargs)
return None
if kargs == {}:
print(' prepared NamedPen',name,'(no kargs)')
return pen # default pen of width zero is cosmetic by default
style = kargs.get('style', None) # in many cases, a predefined pen is just passed through
width = kargs.get('width', 1) # collect remaining kargs to define properties
dash = kargs.get('dash', None)
cosmetic = kargs.get('cosmetic', True)
pen.setCosmetic(cosmetic)
if style is not None: pen.setStyle(style)
if dash is not None: pen.setDashPattern(dash)
print(' prepared NamedPen',name,kargs)
return pen
result = parseNamedColorSpecification(args)
if result is not None: # make a NamedBrush
name, alpha = result
if name == '':
return QtGui.QBrush( QtCore.Qt.NoBrush ) # empty string means "no brush"
qbrush = NamedBrush(name, alpha=alpha)
else: # make a QBrush
qcol = mkColor(args)
qbrush = QtGui.QBrush(qcol)
# here we would apply additional style based on KW-arguments
return qbrush
def mkPen(*args, **kargs):
"""
Convenience function for constructing QPen.
Examples::
mkPen(color)
mkPen(color, width=2)
mkPen(cosmetic=False, width=4.5, color='r')
mkPen({'color': "FF0", width: 2})
mkPen(None) # (no pen)
mkPen() # default color
In these examples, *color* may be replaced with any arguments accepted by :func:`mkColor() <pyqtgraph.mkColor>` """
# print('mkPen called:',args,kargs)
color = kargs.get('color', None) # collect only immediately required properties
style = kargs.get('style', None) # in many cases, a predefined pen is just passed through
pen = None
if len(args) == 1:
arg = args[0]
if isinstance(arg, str):
if len(arg) == 0: # empty string sets "no pen"
style = QtCore.Qt.NoPen
elif arg[0] != '#':
pen = mkNamedPen(arg)
elif isinstance(arg, NamedPen):
return arg # pass through predefined NamedPen directly
elif isinstance(arg, QtGui.QPen):
return QtGui.QPen(arg) ## return a copy of this pen
elif isinstance(arg, dict):
return mkPen(**arg)
elif arg is None:
style = QtCore.Qt.NoPen
while ( # unravel single element sublists
( isinstance(args, tuple) or isinstance(args,list) )
and len(args) == 1
):
args = args[0]
# now args is either a non-list entity, or a multi-element tuple
# short-circuits:
if isinstance(args, NamedPen):
return args # pass through predefined NamedPen directly
elif isinstance(args, QtGui.QPen):
return QtGui.QPen(args) ## return a copy of this pen
elif isinstance(args, dict):
return mkPen(**args) # retry with kwargs assigned from dictionary
elif args is None:
return QtGui.QPen( QtCore.Qt.NoPen ) # explicit None means "no pen"
# no short-circuit, continue parsing to construct QPen or NamedPen
width = kargs.get('width', 1) # width 1 unless specified otherwise
if 'hsv' in kargs: # hsv argument takes precedence
qcol = hsvColor( *kargs['hsv'] )
qpen = QtGui.QPen(QtGui.QBrush(qcol), width)
else:
if 'color' in kargs:
args = kargs['color'] # 'color' KW-argument overrides unnamed arguments
if args is None:
return QtGui.QPen( QtCore.Qt.NoPen ) # explicit None means "no pen"
if args == () or args == []:
qpen = NamedPen( 'gr_fg', width=width ) # default foreground color
else:
color = arg
elif len(args) > 1:
color = args
result = parseNamedColorSpecification(args)
if result is not None: # make a NamedPen
name, alpha = result
if name == '':
return QtGui.QPen( QtCore.Qt.NoPen ) # empty string means "no pen"
qpen = NamedPen( name, alpha=alpha, width=width )
else: # make a QPen
qcol = mkColor(args)
qpen = QtGui.QPen(QtGui.QBrush(qcol), width)
# now apply styles according to kw arguments:
style = kargs.get('style', None)
width = kargs.get('width', 1) # collect remaining kargs to define properties
dash = kargs.get('dash', None)
cosmetic = kargs.get('cosmetic', True)
hsv = kargs.get('hsv', None)
if color is None:
color = mkColor('l')
if hsv is not None:
color = hsvColor(*hsv)
else:
color = mkColor(color)
if pen is None:
pen = QtGui.QPen(QtGui.QBrush(color), width)
pen.setCosmetic(cosmetic)
assert qpen is not None
qpen.setCosmetic(cosmetic)
if style is not None:
pen.setStyle(style)
qpen.setStyle(style)
if dash is not None:
pen.setDashPattern(dash)
return pen
qpen.setDashPattern(dash)
return qpen
def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0):

View File

@ -104,7 +104,7 @@ class AxisItem(GraphicsWidget):
self.setPen(pen)
if textPen is None:
self.setTextPen('gr_fg') # default foreground color
self.setTextPen('gr_txt') # default text color
else:
self.setTextPen(pen)
@ -1217,4 +1217,6 @@ class AxisItem(GraphicsWidget):
def styleHasChanged(self):
""" self.picture needs to be invalidated to initiate full redraw """
self.picture = None
self.labelStyle['color'] = self._textPen.color().name()
self._updateLabel()
super().styleHasChanged()

View File

@ -5,6 +5,7 @@ from .GraphicsItem import GraphicsItem
from .. import functions as fn
__all__ = ['GraphicsObject']
DEBUG = False
try: # prepare common definition for slot decorator across PyQt / Pyside:
QT_CORE_SLOT = QtCore.pyqtSlot
@ -58,4 +59,4 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
""" called to trigger redraw after all named colors have been updated """
# self._boundingRect = None
self.update()
print('redraw after style change:', self)
if DEBUG: print(' GrpahicsObject: redraw after style change:', self)

View File

@ -4,6 +4,7 @@ from .GraphicsItem import GraphicsItem
from .. import functions as fn
__all__ = ['GraphicsWidget']
DEBUG = False
try: # prepare common definition for slot decorator across PyQt / Pyside:
QT_CORE_SLOT = QtCore.pyqtSlot
@ -22,7 +23,6 @@ class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget):
"""
QtGui.QGraphicsWidget.__init__(self, *args, **kargs)
GraphicsItem.__init__(self)
# fn.NAMED_COLOR_MANAGER.paletteChangeSignal.connect(self.styleChange)
fn.NAMED_COLOR_MANAGER.paletteHasChangedSignal.connect(self.styleHasChanged)
## done by GraphicsItem init
@ -64,16 +64,9 @@ class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget):
#print "shape:", p.boundingRect()
return p
# @QT_CORE_SLOT(dict)
# # @QtCore.Slot()
# def styleChange(self, color_dict):
# """ stub function called after Palette.apply(), specific ractions to palette redefinitions execute here """
# print('style change request:', self, type(color_dict))
@QT_CORE_SLOT()
# @QtCore.Slot(dict)
def styleHasChanged(self):
""" called to trigger redraw after all named colors have been updated """
# self._boundingRect = None
self.update()
print('redraw after style change:', self)
if DEBUG: print(' GraphicsWidget: redraw after style change:', self)

View File

@ -15,16 +15,19 @@ class LabelItem(GraphicsWidget, GraphicsWidgetAnchor):
Note: To display text inside a scaled view (ViewBox, PlotWidget, etc) use TextItem
"""
def __init__(self, text=' ', parent=None, angle=0, **args):
GraphicsWidget.__init__(self, parent)
GraphicsWidgetAnchor.__init__(self)
self.item = QtGui.QGraphicsTextItem(self)
self.opts = {
'color': None,
'color': 'gr_txt', # default text color. Was: None,
'justify': 'center'
}
self.opts.update(args)
self._brush = fn.mkBrush(self.opts['color']) # make a NamedBrush by default
self._hex_color_override = None
self._optlist = []
self._sizeHint = {}
self.setText(text)
self.setAngle(angle)
@ -32,6 +35,9 @@ class LabelItem(GraphicsWidget, GraphicsWidgetAnchor):
def setAttr(self, attr, value):
"""Set default text properties. See setText() for accepted parameters."""
self.opts[attr] = value
if attr == 'color':
self._brush = fn.mkBrush(value) # make a new NamedBrush/QBrush
self._hex_color_override = None # to use for all normal output
def setText(self, text, **args):
"""Set the text and text properties in the label. Accepts optional arguments for auto-generating
@ -46,25 +52,28 @@ class LabelItem(GraphicsWidget, GraphicsWidgetAnchor):
==================== ==============================
"""
self.text = text
if 'color' in args:
# temporary override for color:
col = fn.mkColor(opts['color'])
self._hex_color_override = col.name()
color_opt = self._hex_color_override
else:
self._hex_color_override = None # return to defined color
color_opt = self._brush.color().name()
opts = self.opts
for k in args:
opts[k] = args[k]
optlist = []
color = self.opts['color']
if color is None:
color = getConfigOption('foreground')
color = fn.mkColor(color)
optlist.append('color: #' + fn.colorStr(color)[:6])
self.optlist = []
# self.optlist.append('color: ' + col.name()
if 'size' in opts:
optlist.append('font-size: ' + opts['size'])
self.optlist.append('font-size: ' + opts['size'])
if 'bold' in opts and opts['bold'] in [True, False]:
optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']])
self.optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']])
if 'italic' in opts and opts['italic'] in [True, False]:
optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']])
full = "<span style='%s'>%s</span>" % ('; '.join(optlist), text)
#print full
self.optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']])
full = "<span style='color: {:s}; {:s}'>{:s}</span>".format(color_opt, '; '.join(self.optlist), self.text)
self.item.setHtml(full)
self.updateMin()
self.resizeEvent(None)
@ -133,10 +142,18 @@ class LabelItem(GraphicsWidget, GraphicsWidgetAnchor):
def itemRect(self):
return self.item.mapRectToParent(self.item.boundingRect())
def styleHasChanged(self):
""" overridden to update color without changing the text """
if self._hex_color_override is not None:
return # nothing to do, overridden text color will not change.
color_opt = self._brush.color().name() # get updated color
full = "<span style='color: {:s}; {:s}'>{:s}</span>".format(color_opt, '; '.join(self.optlist), self.text)
self.item.setHtml(full)
super().styleHasChanged()
#def paint(self, p, *args):
#p.setPen(fn.mkPen('r'))
#p.drawRect(self.rect())
#p.setPen(fn.mkPen('g'))
#p.drawRect(self.itemRect())

View File

@ -20,6 +20,7 @@ from ..python2_3 import basestring
__all__ = ['ScatterPlotItem', 'SpotItem']
DEBUG = False
# When pxMode=True for ScatterPlotItem, QPainter.drawPixmap is used for drawing, which
@ -253,6 +254,7 @@ class SymbolAtlas(object):
images = []
data = []
for key, style in styles.items():
if DEBUG: print('\nrender:', style[2].color().name(), style[3].color().name(), style)
img = renderSymbol(*style)
arr = fn.imageToArray(img, copy=False, transpose=False)
images.append(img) # keep these to delay garbage collection
@ -411,8 +413,8 @@ class ScatterPlotItem(GraphicsObject):
'name': None,
'symbol': 'o',
'size': 7,
'pen': fn.mkPen(getConfigOption('foreground')),
'brush': fn.mkBrush(100, 100, 150),
'pen': fn.mkPen('gr_fg'), # getConfigOption('foreground')),
'brush': fn.mkBrush(('gr_fg',128)), # (100, 100, 150),
'hoverable': False,
'tip': 'x: {x:.3g}\ny: {y:.3g}\ndata={data}'.format,
}
@ -489,7 +491,6 @@ class ScatterPlotItem(GraphicsObject):
Add new points to the scatter plot.
Arguments are the same as setData()
"""
## deal with non-keyword arguments
if len(args) == 1:
kargs['spots'] = args[0]
@ -1235,6 +1236,19 @@ class ScatterPlotItem(GraphicsObject):
def _hasHoverStyle(self):
return any(self.opts['hover' + opt.title()] != _DEFAULT_STYLE[opt]
for opt in ['symbol', 'size', 'pen', 'brush'])
def styleHasChanged(self):
""" overridden to trigger symbol atlas refresh """
if DEBUG:
print(' ScatterPlotItem: style update!')
print(' pens:',self.data['pen'] )
print(' default pen :', self.opts[ 'pen' ].color().name(), self.opts[ 'pen' ] )
print(' default brush:', self.opts['brush'].color().name(), self.opts['brush'] )
self.fragmentAtlas.clear()
self.data['sourceRect'] = (0, 0, 0, 0)
self.updateSpots(self.data)
super().styleHasChanged()
class SpotItem(object):

View File

@ -87,7 +87,8 @@ def test_init_spots():
# check data is correct
spots = s.points()
defPen = pg.mkPen(pg.getConfigOption('foreground'))
# defPen = pg.mkPen(pg.getConfigOption('foreground'))
defPen = pg.mkPen('gr_fg')
assert spots[0].pos().x() == 0
assert spots[0].pos().y() == 1

80
pyqtgraph/namedBrush.py Normal file
View File

@ -0,0 +1,80 @@
# from ..Qt import QtGui
from .Qt import QtGui, QtCore
from . import functions as fn
__all__ = ['NamedBrush']
DEBUG = False
class NamedBrush(QtGui.QBrush):
""" Extends QPen to retain a functional color description """
def __init__(self, name, alpha=None ):
"""
Creates a new NamedBrush object.
'name' should be in 'functions.Colors'
'alpha' controls opacity which persists over palette changes
"""
if DEBUG: print(' NamedBrush created as',name,alpha)
super().__init__(QtCore.Qt.SolidPattern) # Initialize QBrush superclass
self._identifier = (name, alpha)
self._updateQColor(self._identifier)
fn.NAMED_COLOR_MANAGER.register( self ) # manually register for callbacks
def __eq__(self, other): # make this a hashable object
# return other is self
if isinstance(other, self.__class__):
return self._identifier == other._identifier
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return id(self)
def setColor(self, name=None, alpha=None):
""" update color name. This does not trigger a global redraw. """
if name is None:
name = self._identifier[0]
elif isinstance(name, QtGui.QColor):
# Replicates alpha adjustment workaround in NamedPen, allowing only alpha to be adjusted retroactively
if alpha is None:
alpha = name.alpha() # extract from given QColor
name = self._identifier[0]
if DEBUG: print(' NamedBrush: setColor(QColor) call: set alpha to', alpha)
self._identifier = (name, alpha)
self._updateQColor(self._identifier)
def setAlpha(self, alpha):
""" update opacity value """
self._identifier = (self._identifier[0], alpha)
self._updateQColor(self._identifier)
def _updateQColor(self, identifier, color_dict=None):
""" update super-class QColor """
name, alpha = identifier
if color_dict is None:
color_dict = fn.NAMED_COLOR_MANAGER.colors()
try:
qcol = fn.Colors[name]
except ValueError as exc:
raise ValueError("Color '{:s}' is not in list of defined colors".format(str(name)) ) from exc
if alpha is not None:
qcol.setAlpha( alpha )
if DEBUG: print(' NamedBrush '+name+' updated to QColor ('+str(qcol.name())+', alpha='+str(alpha)+')')
super().setColor( qcol )
def identifier(self):
""" return current color identifier """
return self._identifier
def paletteChange(self, color_dict):
""" refresh QColor according to lookup of identifier in functions.Colors """
if DEBUG: print(' NamedBrush: style change request:', self, type(color_dict))
self._updateQColor(self._identifier, color_dict=color_dict)
if DEBUG:
qcol = super().color()
name, alpha = self._identifier
print(' NamedBrush: retrieved new QColor ('+str(qcol.name())+') '
+ 'for name '+str(name)+' ('+str(alpha)+')' )

View File

@ -34,7 +34,7 @@ for idx, col in enumerate( ( # twelve predefined plot colors
) ):
key = 'p{:X}'.format(idx)
DEFAULT_COLORS[key] = DEFAULT_COLORS[col]
# define and instantiate a SignalSource object to pass signals to all pyqtgraph elements
class NamedColorManager(QtCore.QObject): # this needs to emit QEvents
"""
@ -64,6 +64,7 @@ class NamedColorManager(QtCore.QObject): # this needs to emit QEvents
def register(self, obj):
""" register a function for paletteChange callback """
self.registered_objects.add( obj )
# if DEBUG: print(' NamedColorManager: New list', self.registered_objects )
def redefinePalette(self, color_dic):
""" update list of named colors, emitsignals to color objects and widgets """

View File

@ -11,42 +11,31 @@ class NamedPen(QtGui.QPen):
def __init__(self, name, width=1, alpha=None ):
"""
Creates a new NamedPen object.
'identifier' should be a 'name' included in 'functions.Colors' or
'(name, alpha)' to include transparency
'name' should be included in 'functions.Colors'
'width' specifies linewidth and defaults to 1
'alpha' controls opacity which persists over palette changes
"""
try:
qcol = fn.Colors[name]
# print('QColor alpha is', qcol.alpha() )
except ValueError as exc:
raise ValueError("Color {:s} is not in list of defined colors".format(str(name)) ) from exc
if alpha is not None:
if DEBUG: print(' NamedPen: setting alpha to',alpha)
qcol.setAlpha( alpha )
super().__init__( QtGui.QBrush(qcol), width) # Initialize QPen superclass
if DEBUG: print(' NamedBrush created as',name,alpha)
super().__init__(QtCore.Qt.SolidLine) # Initialize QPen superclass
super().setWidth(width)
super().setCosmetic(True)
self._identifier = (name, alpha)
self._updateQColor(self._identifier)
fn.NAMED_COLOR_MANAGER.register( self ) # manually register for callbacks
def __eq__(self, other): # make this a hashable object
return other is self
# return other is self
if isinstance(other, self.__class__):
return self._identifier == other._identifier
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return id(self)
# def _parse_identifier(self, identifier):
# """ parse identifier parameter, which can be 'name' or '(name, alpha)' """
# alpha = None
# if isinstance(identifier, str):
# name = identifier
# else:
# try:
# name, alpha = identifier
# except ValueError as exc:
# raise ValueError("Invalid argument. 'identifier' should be 'name' or ('name', 'alpha'), but is {:s}".format(str(color)) ) from exc
# if name[0] == '#':
# raise TypeError("NamedPen should not be used for fixed colors ('name' = {:s} was given)".format(str(name)) )
# return name, alpha
def setColor(self, name=None, alpha=None):
""" update color name. This does not trigger a global redraw. """
if name is None:
@ -54,23 +43,33 @@ class NamedPen(QtGui.QPen):
elif isinstance(name, QtGui.QColor):
# this is a workaround for the alpha adjustements in AxisItem:
# While the color will not change, the alpha value can be adjusted as needed.
alpha = name.alpha() # extract
self._identifier = self._identifier[0], alpha
# print(' NamedColor setColor(QColor) call: set alpha to', name.alpha())
if alpha is None:
alpha = name.alpha() # extract from given QColor
name = self._identifier[0]
if DEBUG: print(' NamedPen: setColor(QColor) call: set alpha to', alpha)
self._identifier = (name, alpha)
self._updateQColor(self._identifier)
def setAlpha(self, alpha):
""" update opacity value """
self._identifier = (self._identifier[0], alpha)
self._updateQColor(self._identifier)
def _updateQColor(self, identifier, color_dict=None):
""" update super-class QColor """
name, alpha = identifier
if color_dict is None:
color_dict = fn.NAMED_COLOR_MANAGER.colors()
try:
qcol = fn.Colors[name]
except ValueError as exc:
raise ValueError("Color {:s} is not in list of defined colors".format(str(name)) ) from exc
if alpha is not None:
raise ValueError("Color '{:s}' is not in list of defined colors".format(str(name)) ) from exc
if alpha is not None:
qcol.setAlpha( alpha )
if DEBUG: print(' NamedPen updated to QColor ('+str(qcol.name())+')')
super().setColor( qcol )
self._identifier = (name, alpha)
# def setBrush(self):
# """ disabled """
# return None
def paletteChange(self, color_dict):
""" refresh QColor according to lookup of identifier in functions.Colors """

View File

@ -30,12 +30,12 @@ LEGACY_PLOT = [ # plot / accent colors:
]
MONOGREEN_RAW = {
'col_g0':'#000000', 'col_g1':'#014801', 'col_g2':'#077110', 'col_g3':'#159326',
'col_g0':'#001000', 'col_g1':'#014801', 'col_g2':'#077110', 'col_g3':'#159326',
'col_g4':'#2DB143', 'col_g5':'#50CD65', 'col_g6':'#7FE7A0', 'col_g7':'#BFFFD4'
}
MONOGREEN_FUNC = {
'gr_fg' : 'col_g5',
'gr_bg' : 'col_g1', # for distinction in testing, should be col_g0
'gr_bg' : 'col_g0', # for distinction in testing, should be col_g0
'gr_txt' : 'col_g5',
'gr_acc' : 'col_g5',
'gr_hov' : 'col_g7',
@ -64,11 +64,11 @@ RELAXED_RAW = { # "fresh" raw colors:
'col_grass' :'#7AA621', 'col_l_grass' :'#BCD982', 'col_d_grass' :'#50730B',
'col_yellow':'#BFB226', 'col_l_yellow':'#F2E985', 'col_d_yellow':'#80760D',
'col_gold' :'#A67A21', 'col_l_gold' :'#D9B46C', 'col_d_gold' :'#73500B',
'col_black' :'#000000', 'col_gr1' :'#242429', 'col_gr2' :'#44444D',
'col_gr3' :'#575763', 'col_gr4' :'#7B7B8C', 'col_gr5' :'#B4B4CC',
# 'col_black' :'#000000', 'col_gr1' :'#242429', 'col_gr2' :'#44444D',
'col_black' :'#000000', 'col_gr1' :'#161619', 'col_gr2' :'#43434D',
'col_gr3' :'#70707F', 'col_gr4' :'#9D9DB2', 'col_gr5' :'#C9C9E5',
'col_white' :'#FFFFFF'
}
# 'col_gray' :'#666666', 'col_l_gray' :'#B6B6B6', 'col_d_gray' :'#3D3D3D',
RELAXED_DARK_FUNC= { # functional colors:
'gr_fg' : 'col_gr5',
'gr_bg' : 'col_gr1',
@ -97,6 +97,36 @@ RELAXED_DARK_PLOT = [ # plot / accent colors:
'col_l_green'
]
RELAXED_LIGHT_FUNC= { # functional colors:
'gr_fg' : 'col_gr1',
'gr_bg' : 'col_gr5',
'gr_txt' : 'col_black',
'gr_acc' : 'col_orange',
'gr_hov' : 'col_black',
'gr_reg' : ('col_blue', 30),
# legacy colors:
'b': 'col_blue' , 'c': 'col_cyan', 'g': 'col_green',
'y': 'col_yellow', 'r': 'col_red' , 'm': 'col_violet',
'k': 'col_black' , 'w': 'col_white',
'd': 'col_gr2' , 'l': 'col_gr4' , 's': 'col_sky'
}
RELAXED_LIGHT_PLOT = [ # plot / accent colors:
'col_sky' ,
'col_indigo',
'col_purple',
'col_red' ,
'col_gold' ,
'col_grass' ,
'col_cyan' ,
'col_blue' ,
'col_violet',
'col_orange',
'col_yellow',
'col_green'
]
def block_to_QColor( block, dic=None ):
""" convert color information to a QColor """
# allowed formats:
@ -152,8 +182,8 @@ DEFAULT_PALETTE = assemble_palette( LEGACY_RAW, LEGACY_FUNC, LEGACY_PLOT )
def get(name):
if name == 'relaxed_dark':
pal = assemble_palette( RELAXED_RAW, RELAXED_DARK_FUNC, RELAXED_DARK_PLOT )
# elif name == 'relaxed_light':
# pal = assemble_palette( RELAXED_RAW, RELAXED_LIGHT_FUNC, RELAXED_LIGHT_PLOT )
elif name == 'relaxed_light':
pal = assemble_palette( RELAXED_RAW, RELAXED_LIGHT_FUNC, RELAXED_LIGHT_PLOT )
elif name == 'monogreen':
pal = assemble_palette( MONOGREEN_RAW, MONOGREEN_FUNC, MONOGREEN_PLOT )
else:

View File

@ -15,8 +15,15 @@ from .. import functions as fn
from .. import debug as debug
from .. import getConfigOption
try: # prepare common definition for slot decorator across PyQt / Pyside:
QT_CORE_SLOT = QtCore.pyqtSlot
except AttributeError:
QT_CORE_SLOT = QtCore.Slot
__all__ = ['GraphicsView']
DEBUG = False
class GraphicsView(QtGui.QGraphicsView):
"""Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the
@ -93,7 +100,6 @@ class GraphicsView(QtGui.QGraphicsView):
self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate)
self.lockedViewports = []
self.lastMousePos = None
self.setMouseTracking(True)
@ -123,6 +129,10 @@ class GraphicsView(QtGui.QGraphicsView):
self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
self.clickAccepted = False
# connect to style update signals from NamedColorManager:
fn.NAMED_COLOR_MANAGER.paletteHasChangedSignal.connect(self.styleHasChanged)
def setAntialiasing(self, aa):
"""Enable or disable default antialiasing.
Note that this will only affect items that do not specify their own antialiasing options."""
@ -137,12 +147,17 @@ class GraphicsView(QtGui.QGraphicsView):
To use the defaults specified py pyqtgraph.setConfigOption, use background='default'.
To make the background transparent, use background=None.
"""
self._background = background
if background == 'default':
# background = getConfigOption('background')
background = 'gr_bg' # default graphics background color
brush = fn.mkBrush(background)
self.setBackgroundBrush(brush)
self._background = background # maintained for compatibility
if DEBUG: print(' GraphicsView: Generating BG brush for', self._background)
self._bgBrush = fn.mkBrush(self._background)
if DEBUG: print(' GraphicsView: Background color: ',self._bgBrush.color().name(), self._bgBrush.color().alpha())
self.setBackgroundBrush( self._bgBrush )
# testBrush = QtGui.QBrush( QtGui.QColor('#000000') )
# print(' test brush style:',testBrush.style() )
# self.setBackgroundBrush( testBrush )
def paintEvent(self, ev):
self.scene().prepareForPaint()
@ -402,3 +417,10 @@ class GraphicsView(QtGui.QGraphicsView):
def dragEnterEvent(self, ev):
ev.ignore() ## not sure why, but for some reason this class likes to consume drag events
@QT_CORE_SLOT()
def styleHasChanged(self):
""" called to trigger redraw after all named colors have been updated """
self.setBackgroundBrush( self._bgBrush )
# self.update()
if DEBUG: print(' Background update and redraw after style change:', self)

View File

@ -27,13 +27,15 @@ def test_basics_graphics_view():
assert view.scaleCenter is False
assert view.clickAccepted is False
assert view.centralWidget is not None
assert view._background == "default"
# assert view._background == "default"
assert view._background == "gr_bg"
# Set background color
# --------------------------------------
view.setBackground("w")
assert view._background == "w"
assert view.backgroundBrush().color() == QtCore.Qt.white
# assert view.backgroundBrush().color() == QtCore.Qt.white
assert view.backgroundBrush().color().name() == '#ffffff' #QtCore.Qt.white
# Set anti aliasing
# --------------------------------------