implemented some suggested improvements

This commit is contained in:
Nils Nemitz 2021-05-17 00:37:47 +09:00
parent 9cb8986d90
commit 1f8c82ff30
12 changed files with 80 additions and 70 deletions

View File

@ -317,7 +317,7 @@ if QT_LIB in [PYQT5, PYQT6]:
loadUiType = uic.loadUiType
QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
# QtCore.Slot = QtCore.pyqtSlot # The current policy is to avoid slot decorators.
if QT_LIB == PYSIDE6:
# PySide6 6.0 has a missing binding

View File

@ -78,7 +78,7 @@ class ColorRegistry(QtCore.QObject):
Typically instantiated by functions.py as COLOR_REGISTRY
Instantiated by 'functions.py' and retrievable as functions.COLOR_REGISTRY
"""
paletteHasChangedSignal = QtCore.Signal() # equated to pyqtSignal in qt.py for PyQt
graphStyleChanged = QtCore.Signal() # equated to pyqtSignal in qt.py for PyQt
_registrationGenerator = itertools.count()
def __init__(self, color_dic):
@ -168,7 +168,7 @@ class ColorRegistry(QtCore.QObject):
skipCache = True
register = False
elif name not in self.color_dic:
warnings.warn('Unknown color identifier '+str(name)+' enocuntered.')
warnings.warn(f"Unknown color identifier '{name}' encountered.")
return None # unknown color identifier
if not skipCache and desc in self.color_cache:
return self.color_cache[desc]
@ -202,7 +202,7 @@ class ColorRegistry(QtCore.QObject):
skipCache = True
register = False
elif name not in self.color_dic:
warnings.warn('Unknown color identifier '+str(name)+' enocuntered in pen descriptor.')
warnings.warn(f"Unknown color identifier '{name}' encountered in pen descriptor.")
return None # unknown color identifier
if not skipCache and desc in self.pen_cache:
return self.pen_cache[desc]
@ -235,7 +235,7 @@ class ColorRegistry(QtCore.QObject):
skipCache = True
register = False
elif name not in self.color_dic:
warnings.warn('Unknown color identifier '+str(name)+' enocuntered in brush descriptor.')
warnings.warn(f"Unknown color identifier '{name}' encountered in brush descriptor.")
return None # unknown color identifier
if not skipCache and desc in self.brush_cache:
return self.brush_cache[desc]
@ -311,10 +311,10 @@ class ColorRegistry(QtCore.QObject):
"""
Removes obj (QColor, QPen or QBrush) from the registry, usually called by finalize on deletion
"""
obj, desc = self.registered_objects[registration]
# obj, desc = self.registered_objects[registration]
# print('unregistering', registration, '(',str(obj),'):',str(desc))
# del obj, desc
del self.registered_objects[registration]
del obj, desc
def colors(self):
""" return current list of colors """
@ -326,24 +326,23 @@ class ColorRegistry(QtCore.QObject):
def redefinePalette(self, colors=None):
"""
Update list of named colors if 'colors' dictionary is given
Update list of registered colors if 'colors' dictionary is given
Emits paletteHasChanged signals to color objects and widgets, even if color_dict is None
"""
if colors is not None:
for key in DEFAULT_COLORS:
if key not in colors:
raise ValueError("Palette definition is missing '"+str(key)+"'")
raise ValueError(f"Palette definition is missing '{key}'")
self.color_dic.clear()
self.color_dic.update(colors)
# notifies named color objects of new assignments:
# for key in self.registered_objects:
# notifies registerd color objects of new assignments:
for ref, desc in self.registered_objects.values():
# ref, desc = self.registered_objects[key]
obj = ref()
# print('updating', obj)
if obj is None:
warnings.warn('Expired object with descriptor '+str(desc)+' remains in color registry.', RuntimeWarning)
warnings.warn(f"Expired object with descriptor '{desc})' remains in color registry.", RuntimeWarning)
elif isinstance(obj, QtGui.QColor):
self._update_QColor(obj, desc)
elif isinstance(obj, QtGui.QPen):
@ -351,4 +350,4 @@ class ColorRegistry(QtCore.QObject):
elif isinstance(obj, QtGui.QBrush):
self._update_QBrush(obj, desc)
# notify all graphics widgets that redraw is required:
self.paletteHasChangedSignal.emit()
self.graphStyleChanged.emit()

View File

@ -189,7 +189,7 @@ def getFromColorcet(name):
for hex_str in color_strings:
if hex_str[0] != '#': continue
if len(hex_str) != 7:
raise ValueError('Invalid color string '+str(hex_str)+' in colorcet import.')
raise ValueError(f"Invalid color string '{hex_str}' in colorcet import.")
color_tuple = tuple( bytes.fromhex( hex_str[1:] ) )
color_list.append( color_tuple )
if len(color_list) == 0:
@ -202,36 +202,45 @@ def getFromColorcet(name):
def makeMonochrome(color='green'):
"""
Returns a ColorMap object imitating a monochrome computer screen
=============== =================================================================
**Arguments:**
color Primary color description. Can be one of predefined identifiers
'green', 'amber', 'blue', 'red', 'lavender', 'pink'
or a tuple of relative R,G,B contributions in range 0.0 to 1.0
=============== =================================================================
Returns a ColorMap object imitating a monochrome computer screen.
Parameters
----------
color: str of tuple of floats
Primary color description. Can be one of predefined identifiers
'green', 'amber', 'blue', 'red', 'lavender', 'pink'
or a tuple of relative ``(R,G,B)`` contributions in range 0.0 to 1.0
"""
name = 'monochrome-'+str(color)
name = f'monochrome-{color}' # if needed, this automatically stringifies numerical tuples
stops = np.array([0.000, 0.167, 0.247, 0.320, 0.411, 0.539, 0.747, 1.000])
active = np.array([ 16, 72, 113, 147, 177, 205, 231, 255])
leakage = np.array([ 0, 1, 7, 21, 45, 80, 127, 191])
delta_arr = active - leakage
predefined = {
'green': (0.00, 1.00, 0.33), 'amber' : (1.00, 0.50, 0.00),
'blue' : (0.00, 0.50, 1.00), 'red' : (1.00, 0.10, 0.00),
'blue' : (0.00, 0.50, 1.00), 'red' : (1.00, 0.10, 0.00),
'pink' : (1.00, 0.10, 0.50), 'lavender': (0.67, 0.33, 1.00)
}
if color in predefined: color = predefined[color]
if not isinstance(color, tuple):
definitions = ["'"+key+"'" for key in predefined]
definitions = [f"'{key}'" for key in predefined]
raise ValueError("'color' needs to be an (R,G,B) tuple of floats or one of "+definitions.join(', ') )
r, g, b = color
color_list = []
for leak, delta in zip(leakage, delta_arr):
color_tuple = (
color_list = [
(
r * delta + leak,
g * delta + leak,
b * delta + leak )
color_list.append(color_tuple)
b * delta + leak
) for leak, delta in zip(leakage, delta_arr)
]
# color_list = []
# for leak, delta in zip(leakage, delta_arr):
# color_tuple = (
# r * delta + leak,
# g * delta + leak,
# b * delta + leak )
# color_list.append(color_tuple)
cmap = ColorMap(name=name, pos=stops, color=color_list )
return cmap
@ -330,7 +339,7 @@ class ColorMap(object):
if mapping in [self.CLIP, self.REPEAT, self.DIVERGING, self.MIRROR]:
self.mapping_mode = mapping # only allow defined values
else:
raise ValueError("Undefined mapping type '{:s}'".format(str(mapping)) )
raise ValueError(f"Undefined mapping type '{mapping}'")
self.stopsCache = {}
def __str__(self):

View File

@ -32,8 +32,6 @@ from .python2_3 import asUnicode
# legacy color definitions:
# ColorRegistry now maintains the primary list of palette colors,
# accessible through functions.COLOR_REGISTRY.colors().
# # NamedColorManager now maintains the primary list of palette colors,
# # accessible through functions.NAMED_COLOR_MANAGER.colors().
# For backwards compatibility, this dictionary is updated to contain the same information.
#
# For the user, colors and color palettes are most conveniently accessed through a Palette object.
@ -229,7 +227,7 @@ def mkColor(*args):
types. Accepted arguments are:
================ ===========================================================
'name' any color name specifed in palette
'name' any color name specifed in active palette
('name', alpha) color name from palette with specified opacity 0-255
R, G, B, [A] integers 0-255
(R, G, B, [A]) tuple of integers 0-255
@ -258,9 +256,10 @@ def mkColor(*args):
return args # pass through registered pen directly
return QtGui.QColor(args) ## return a copy of this color
err = 'Could not create a color from {:s}(type: {:s})'.format(str(args), str(type(args)))
# no short-circuit, continue parsing to construct a QPen and register it if appropriate
err = f'Could not create a color from {args} (type: {type(args)})'
result = COLOR_REGISTRY.getRegisteredColor(args)
if result is not None: # make a NamedPen
if result is not None: # return this color if we got one.
return result
# print('trying extra methods on',args)
@ -359,7 +358,7 @@ def mkBrush(*args, **kargs):
# if args is None:
# return QtGui.QBrush( QtCore.Qt.NoBrush ) # explicit None means "no brush"
# no short-circuit, continue parsing to construct QPen or NamedPen
# no short-circuit, continue parsing to construct a QBrush and register it if appropriate
if 'hsv' in kargs: # hsv argument takes precedence
qcol = hsvColor( *kargs['hsv'] )
return QtGui.QBrush(qcol)
@ -373,9 +372,7 @@ def mkBrush(*args, **kargs):
if args == () or args == []:
# print(' functions: returning default color registered brush')
return COLOR_REGISTRY.getRegisteredBrush('gr_fg')
# return NamedBrush( 'gr_fg', manager=NAMED_COLOR_MANAGER ) # default foreground color
# result = parseNamedColorSpecification(args)
# Do the the arguments make a suitable brush descriptor?
result = COLOR_REGISTRY.getRegisteredBrush(args)
if result is not None:
@ -421,7 +418,7 @@ def mkPen(*args, **kargs):
# if args is None:
# return QtGui.QPen( QtCore.Qt.NoPen ) # explicit None means "no pen"
# no short-circuit, continue parsing to construct QPen or NamedPen
# no short-circuit, continue parsing to construct a QPen and register it if appropriate
width = kargs.get('width', 1) # width 1 unless specified otherwise
if 'hsv' in kargs: # hsv argument takes precedence
qcol = hsvColor( *kargs['hsv'] )
@ -436,9 +433,9 @@ def mkPen(*args, **kargs):
qpen = COLOR_REGISTRY.getRegisteredPen( ('gr_fg', width) ) # default foreground color
else:
result = COLOR_REGISTRY.getRegisteredPen(args)
if result is not None: # make a NamedPen
if result is not None: # return this pen if we got one
qpen = result
else: # make a QPen
else: # make a regular QPen
qcol = mkColor(args)
qpen = QtGui.QPen(QtGui.QBrush(qcol), width)
# now apply styles according to kw arguments:
@ -446,7 +443,7 @@ def mkPen(*args, **kargs):
dash = kargs.get('dash', None)
cosmetic = kargs.get('cosmetic', True)
if qpen is None:
raise ValueError('Failed to construct QPen from arguments '+str(args)+','+str(kargs) )
raise ValueError("Failed to construct QPen from arguments '{args}','{kargs}'." )
qpen.setCosmetic(cosmetic)
if style is not None:
qpen.setStyle(style)

View File

@ -1212,9 +1212,9 @@ class AxisItem(GraphicsWidget):
return
return lv.mouseClickEvent(event)
def styleHasChanged(self):
def updateGraphStyle(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()
super().updateGraphStyle()

View File

@ -19,7 +19,7 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
QtGui.QGraphicsObject.__init__(self, *args)
self.setFlag(self.ItemSendsGeometryChanges)
GraphicsItem.__init__(self)
fn.COLOR_REGISTRY.paletteHasChangedSignal.connect(self.styleHasChanged)
fn.COLOR_REGISTRY.graphStyleChanged.connect(self.updateGraphStyle)
def itemChange(self, change, value):
ret = super().itemChange(change, value)
@ -42,9 +42,9 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
return ret
@QtCore.Slot() # qt.py equates this to pyqtSlot for PyQt
def styleHasChanged(self):
""" called to trigger redraw after all named colors have been updated """
# Slot for graphStyleChanged signal emitted by ColorRegistry, omitted decorator: @QtCore.Slot()
def updateGraphStyle(self):
""" called to trigger redraw after all registered colors have been updated """
# self._boundingRect = None
self.update()
if DEBUG_REDRAW: print(' GraphicsObject: redraw after style change:', self)

View File

@ -17,7 +17,7 @@ class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget):
"""
QtGui.QGraphicsWidget.__init__(self, *args, **kargs)
GraphicsItem.__init__(self)
fn.COLOR_REGISTRY.paletteHasChangedSignal.connect(self.styleHasChanged)
fn.COLOR_REGISTRY.graphStyleChanged.connect(self.updateGraphStyle)
## done by GraphicsItem init
#GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items()
@ -58,9 +58,9 @@ class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget):
#print "shape:", p.boundingRect()
return p
@QtCore.Slot() # qt.py equates this to pyqtSlot for PyQt
def styleHasChanged(self):
""" called to trigger redraw after all named colors have been updated """
# Slot for graphStyleChanged signal emitted by ColorRegistry, omitted decorator: @QtCore.Slot()
def updateGraphStyle(self):
""" called to trigger redraw after all registered colors have been updated """
# self._boundingRect = None
self.update()
if DEBUG_REDRAW: print(' GraphicsWidget: redraw after style change:', self)

View File

@ -143,14 +143,14 @@ class LabelItem(GraphicsWidget, GraphicsWidgetAnchor):
def itemRect(self):
return self.item.mapRectToParent(self.item.boundingRect())
def styleHasChanged(self):
def updateGraphStyle(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()
super().updateGraphStyle()
#def paint(self, p, *args):
#p.setPen(fn.mkPen('r'))

View File

@ -1269,12 +1269,12 @@ class ScatterPlotItem(GraphicsObject):
return any(self.opts['hover' + opt.title()] != _DEFAULT_STYLE[opt]
for opt in ['symbol', 'size', 'pen', 'brush'])
def styleHasChanged(self):
def updateGraphStyle(self):
""" overridden to trigger symbol atlas refresh """
self.fragmentAtlas.clear()
self.data['sourceRect'] = (0, 0, 0, 0)
self.updateSpots(self.data)
super().styleHasChanged()
super().updateGraphStyle()

View File

@ -221,8 +221,8 @@ class TextItem(GraphicsObject):
self._lastTransform = pt
self.updateTextPos()
def styleHasChanged(self):
def updateGraphStyle(self):
""" overridden to mnanually refresh color """
if self._color is not None:
self.textItem.setDefaultTextColor(self._color)
super().styleHasChanged()
super().updateGraphStyle()

View File

@ -184,7 +184,8 @@ class Palette(object):
Primary colors:
'b', 'c', 'g', 'y', 'r', 'm'
Gray scale:
'k', 'd', 'l', 'w' ranging from black to white
'm0' to 'm9' ranging from black to white
'k', 'd', 'l', 'w' black, dark gray, light gray and white, typically parts of the 'm0' to 'm9' range
's' slate gray
System colors:
'gr_bg', 'gr_fg', 'gr_txt' graph background, foreground and text colors
@ -219,6 +220,7 @@ class Palette(object):
if colors is not None:
# print('color dictionary:', colors)
self.add(colors) # override specified colors
# todo: add a monochrome ramp and sample 'mono' colors from that
def __getitem__(self, key):
""" Convenient shorthand access to palette colors """
@ -273,7 +275,7 @@ class Palette(object):
if cmap is None:
raise ValueError("Please specify 'cmap' parameter when no default colormap is available.")
if not isinstance( cmap, colormap.ColorMap ):
raise ValueError("Failed to obtain ColorMap object for 'cmap' = '+str(cmap).")
raise ValueError(f"Failed to obtain ColorMap object for 'cmap' = '{cmap}'.")
if prefix == 'p':
self.cmap = cmap # replace default color map
@ -307,7 +309,9 @@ class Palette(object):
Adds a default ramp of grays m0 to m9,
linearized according to CIElab lightness value
"""
pass
# todo: define based on start, intermediate, end colors
# to give grays with different warmth and range of contrast
def emulateSystem(self):
"""
@ -394,16 +398,17 @@ class Palette(object):
# project legacy colors onto monochrome reference to assigne them somewhat logically
ref_color = np.array( self.palette['m5'].getRgb()[:3] )
brightness = [
brightness = [ # estimate brightness of wanted colors "projected" into monochrome color space
(np.sum( ref_color * needed[key] ), key)
for key in needed
]
brightness = sorted(brightness, key=lambda brightness: brightness[0])
# print( brightness )
avail = ('m3','m4','m5','m6','m7','m8')
colors = {}
for idx, (value, key) in enumerate(brightness):
colors[key] = avail[idx]
choice = ('m3','m4','m5','m6','m7','m8') # 6 candidates for 6 needed colors
colors = {
key: choice[idx] # assign in order of dark to bright
for idx, (_, key) in enumerate(brightness)
}
self.add( colors )
def apply(self):

View File

@ -120,7 +120,7 @@ class GraphicsView(QtGui.QGraphicsView):
self.clickAccepted = False
# connect to style update signals from ColorRegistry:
fn.COLOR_REGISTRY.paletteHasChangedSignal.connect(self.styleHasChanged)
fn.COLOR_REGISTRY.graphStyleChanged.connect(self.updateGraphStyle)
def setAntialiasing(self, aa):
@ -400,8 +400,8 @@ class GraphicsView(QtGui.QGraphicsView):
def dragEnterEvent(self, ev):
ev.ignore() ## not sure why, but for some reason this class likes to consume drag events
@QtCore.Slot() # qt.py equates this to pyqtSlot for PyQt
def styleHasChanged(self):
""" called to trigger redraw after all named colors have been updated """
# Slot for graphStyleChanged signal emitted by ColorRegistry, omitted decorator: @QtCore.Slot()
def updateGraphStyle(self):
""" called to trigger redraw after all registered colors have been updated """
self.setBackgroundBrush( self._bgBrush )
# self.update()