added colormap sampling functions
This commit is contained in:
parent
172cef1628
commit
244182d2bf
@ -24,6 +24,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.setWindowTitle('pyqtgraph example: Palette application test')
|
||||
self.resize(600,600)
|
||||
|
||||
test_palette = pg.palette.get('system')
|
||||
|
||||
pg.palette.get('relaxed-dark').apply()
|
||||
|
||||
main_layout = QtWidgets.QGridLayout( main_wid )
|
||||
@ -63,10 +65,17 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
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.curve1 = pg.PlotDataItem(
|
||||
# pen='r',
|
||||
# symbol='o', symbolSize=10, symbolPen='gr_fg', symbolBrush=('y',127),
|
||||
# hoverable=True, hoverPen='w', hoverBrush='w')
|
||||
self.curve1 = pg.ScatterPlotItem(
|
||||
symbol='o', symbolSize=12, symbolPen='gr_fg', symbolBrush=('y',127),
|
||||
hoverable=True, hoverPen='gr_acc', hoverBrush='gr_reg')
|
||||
# self.curve1.setHoverable(True)
|
||||
self.plt.addItem(self.curve1)
|
||||
|
||||
self.curve2 = pg.PlotCurveItem(pen='w', brush='d')
|
||||
self.curve2 = pg.PlotCurveItem(pen='l', brush='d')
|
||||
self.curve2.setFillLevel(0)
|
||||
self.plt.addItem(self.curve2)
|
||||
self.show()
|
||||
|
393
examples/PaletteTestAndEdit.py
Normal file
393
examples/PaletteTestAndEdit.py
Normal file
@ -0,0 +1,393 @@
|
||||
#!/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 qdarkstyle
|
||||
import numpy as np
|
||||
|
||||
from pyqtgraph.Qt import mkQApp, QtCore, QtGui, QtWidgets
|
||||
from pyqtgraph.ptime import time
|
||||
import pyqtgraph as pg
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
""" example application main window """
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainWindow, self).__init__(*args, **kwargs)
|
||||
|
||||
force_dark = True # start in dark mode on Windows
|
||||
|
||||
self.setWindowTitle('pyqtgraph example: Palette application test')
|
||||
self.resize(600,600)
|
||||
|
||||
self.palette_options = (
|
||||
('system', 'system', []),
|
||||
('legacy', 'legacy', []),
|
||||
('relaxed (dark)', 'relaxed_dark', []),
|
||||
('mono green', 'monochrome', ['green']),
|
||||
('mono amber', 'monochrome', ['amber']),
|
||||
('mono blue' , 'monochrome', ['blue' ]),
|
||||
('synthwave' , 'synthwave', []),
|
||||
)
|
||||
|
||||
self.colormap_options = (
|
||||
'CET-C1', 'CET-C2','CET-C6','CET-C7', 'CET-R2', 'CET-R4',
|
||||
'CET-L8', 'CET-L16', 'none'
|
||||
# , 'none', 'CET-C1', 'CET-C2', 'CET-C3', 'CET-C4', 'CET-C5', 'CET-C6', 'CET-C7', 'CET-CBC1', 'CET-CBC2'
|
||||
)
|
||||
|
||||
app = QtWidgets.QApplication.instance()
|
||||
self.q_palette = {
|
||||
'system' : app.palette(),
|
||||
'dark' : self.make_dark_QPalette()
|
||||
}
|
||||
app.setStyle("Fusion")
|
||||
|
||||
self.ui = self.prepare_ui() # relocate long-winded window layout
|
||||
# dictionary self.ui contains references to:
|
||||
# 'sample_start' QLineEdit for start of colormap sampling
|
||||
# 'sample_step' QLineEdit for step of colormap sampling
|
||||
# 'dark' QPushButton for toggling dark / standard GUI
|
||||
|
||||
if force_dark:
|
||||
self.ui['dark'].setChecked(True)
|
||||
self.handle_dark_button(True)
|
||||
|
||||
self.open_palette = pg.palette.get('system')
|
||||
self.open_palette.apply()
|
||||
self.update_color_fields( self.open_palette )
|
||||
|
||||
self.num_points = 30
|
||||
|
||||
# configure overview plot with four colors:
|
||||
plt = self.ui['plot1']
|
||||
plt.enableAutoRange(False)
|
||||
plt.setYRange( 0, 4, padding=0 )
|
||||
plt.setXRange( 0, self.num_points, padding=0 )
|
||||
for key in ('left','right','top','bottom'):
|
||||
ax = plt.getAxis(key)
|
||||
ax.show()
|
||||
ax.setZValue(0.1)
|
||||
|
||||
self.curves = []
|
||||
curve = pg.PlotCurveItem(pen='p0', brush='p0') # ('p0',127))
|
||||
curve.setFillLevel(0)
|
||||
self.curves.append( (1, 1, curve) ) # dataset 1, vertical offset 3
|
||||
plt.addItem(curve)
|
||||
curve = pg.ScatterPlotItem(
|
||||
symbol='o', size=5, pen='p0', brush='p0', # ('p0',127),
|
||||
hoverable=True, hoverPen='gr_hlt', hoverBrush='gr_fg')
|
||||
self.curves.append( (1, 1, curve) ) # dataset 1, vertical offset 2
|
||||
plt.addItem(curve)
|
||||
|
||||
pen_list = ['p2', 'p4', 'p6'] # add three more plots
|
||||
for idx, pen in enumerate( pen_list ):
|
||||
curve = pg.PlotCurveItem()
|
||||
curve.setPen(pen, width=5)
|
||||
self.curves.append( (3+2*idx, 1.5+0.8*idx, curve) ) # datasets 2+, vertical offset 3+
|
||||
plt.addItem(curve)
|
||||
|
||||
# configure tall plot with eight colors and region overlay:
|
||||
plt = self.ui['plot2']
|
||||
plt.enableAutoRange(False)
|
||||
plt.setYRange( -0.6, 7.6, padding=0 )
|
||||
plt.setXRange( 0, self.num_points, padding=0 )
|
||||
plt.getAxis('bottom').hide()
|
||||
plt.getAxis('left').setLabel('plot color')
|
||||
plt.getAxis('left').setGrid(0.5) # 63)
|
||||
|
||||
pen_list = [('p0',255),'p1','p2','p3','p4','p5','p6','p7'] # add right-side plots for each main color
|
||||
for idx, pen in enumerate( pen_list ):
|
||||
curve = pg.PlotCurveItem(pen=pen)
|
||||
self.curves.append( (1+idx, idx, curve) ) # datasets 2+, vertical offset by index
|
||||
plt.addItem(curve)
|
||||
item = pg.LinearRegionItem( values=(4, 8), orientation='vertical' )
|
||||
plt.addItem(item)
|
||||
|
||||
self.show()
|
||||
|
||||
# prepare for continuous updates and frame rate measurement
|
||||
self.last_time = time()
|
||||
self.fps = None
|
||||
self.timer = QtCore.QTimer(singleShot=False)
|
||||
self.timer.timeout.connect( self.timed_update )
|
||||
|
||||
# prepare initial data and display in plots
|
||||
self.data = np.zeros((9, self.num_points ))
|
||||
self.data[0,:] = np.arange( self.data.shape[1] ) # used as x data
|
||||
self.phases = np.zeros(9)
|
||||
self.timed_update()
|
||||
|
||||
|
||||
### handle GUI interaction ###############################################
|
||||
def update_color_fields(self, pal):
|
||||
""" update line edit fields for selected palette """
|
||||
if pal is None:
|
||||
print('palette is None!')
|
||||
return
|
||||
for key in self.ui['widget_from_color_key']:
|
||||
wid = self.ui['widget_from_color_key'][key]
|
||||
qcol = pal[key]
|
||||
if wid is not None:
|
||||
wid.setText( qcol.name() )
|
||||
|
||||
def handle_palette_select(self, idx):
|
||||
""" user selected a palette in dropdown menu """
|
||||
text, identifier, args = self.palette_options[idx]
|
||||
del text # not needed here
|
||||
self.open_palette = pg.palette.get(identifier, *args)
|
||||
print('loaded palette:', identifier, args)
|
||||
|
||||
if identifier in pg.palette.PALETTE_DEFINITIONS:
|
||||
info = pg.palette.PALETTE_DEFINITIONS[identifier]
|
||||
colormap_sampling = info['colormap_sampling']
|
||||
if colormap_sampling is None:
|
||||
identifier, start, step = 'none', 0.000, 0.125
|
||||
else:
|
||||
identifier, start, step = colormap_sampling
|
||||
self.ui['sample_start'].setText('{:+.3f}'.format(start))
|
||||
self.ui['sample_step' ].setText('{:+.3f}'.format(step) )
|
||||
for idx, map_id in enumerate( self.colormap_options ):
|
||||
if map_id == identifier:
|
||||
# print('found colormap at idx',idx)
|
||||
self.ui['colormaps'].setCurrentIndex(idx)
|
||||
self.update_color_fields(self.open_palette)
|
||||
self.open_palette.apply()
|
||||
|
||||
def handle_colormap_select(self, param=None):
|
||||
""" user selected a colormap in dropdown menu or changed start / step vales """
|
||||
del param # drop index sent by QComboBox
|
||||
identifier = self.ui['colormaps'].currentText()
|
||||
if identifier == 'none':
|
||||
return
|
||||
start = self.ui['sample_start'].text()
|
||||
step = self.ui['sample_step' ].text()
|
||||
try:
|
||||
start = float(start)
|
||||
except ValueError:
|
||||
start = 0.0
|
||||
self.ui['sample_start'].setText('{:+.3f}'.format(start) )
|
||||
try:
|
||||
step = float(step)
|
||||
except ValueError:
|
||||
step = 0.125
|
||||
self.ui['sample_step'].setText('{:+.3f}'.format(step) )
|
||||
self.open_palette.sampleColorMap( cmap=identifier, start=start, step=step )
|
||||
# print('applied color map {:s} starting at {:.3f} with step {:3f}'.format(identifier, start, step) )
|
||||
|
||||
self.update_color_fields(self.open_palette)
|
||||
self.open_palette.apply()
|
||||
|
||||
def handle_color_update(self):
|
||||
""" figure out what color field was updated """
|
||||
source = self.sender()
|
||||
print('color update requested by field',source)
|
||||
|
||||
def handle_update_button(self, active):
|
||||
""" start/stop timer """
|
||||
if active:
|
||||
self.timer.start(1)
|
||||
else:
|
||||
self.timer.stop()
|
||||
|
||||
def handle_dark_button(self, active):
|
||||
""" manually switch to dark palette to test on windows """
|
||||
app = QtWidgets.QApplication.instance()
|
||||
if active:
|
||||
app.setPalette( self.q_palette['dark'] ) # apply dark QPalette
|
||||
else:
|
||||
app.setPalette( self.q_palette['system'] ) # reapply QPalette stored at start-up
|
||||
|
||||
def timed_update(self):
|
||||
""" update loop, called by timer """
|
||||
self.speed = np.linspace(0.01, 0.06, 9)
|
||||
self.phases += self.speed * np.random.normal(1, 1, size=9)
|
||||
for idx in range(1, self.data.shape[0]):
|
||||
self.data[idx, :-1] = self.data[idx, 1:] # roll
|
||||
# self.data[idx, -1] = np.random.normal()
|
||||
self.data[1:, -1] = 0.5 * np.sin( self.phases[1:] )
|
||||
xdata = self.data[0,:]
|
||||
for idx, offset, curve in self.curves:
|
||||
curve.setData( x=xdata, y=( offset + self.data[idx,:] ) )
|
||||
|
||||
now = time()
|
||||
dt = now - self.last_time
|
||||
self.last_time = 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.ui['plot2'].setTitle('%0.2f fps' % self.fps)
|
||||
QtWidgets.QApplication.processEvents() ## force complete redraw for every plot
|
||||
|
||||
|
||||
### Qt color definitions for dark palette on Windows #####################
|
||||
def make_dark_QPalette(self):
|
||||
# color definitions match QDarkstyle
|
||||
BLACK = QtGui.QColor('#000000')
|
||||
BG_LIGHT = QtGui.QColor('#505F69')
|
||||
BG_NORMAL = QtGui.QColor('#32414B')
|
||||
BG_DARK = QtGui.QColor('#19232D')
|
||||
FG_LIGHT = QtGui.QColor('#F0F0F0')
|
||||
FG_NORMAL = QtGui.QColor('#AAAAAA')
|
||||
FG_DARK = QtGui.QColor('#787878')
|
||||
SEL_LIGHT = QtGui.QColor('#148CD2')
|
||||
SEL_NORMAL = QtGui.QColor('#1464A0')
|
||||
SEL_DARK = QtGui.QColor('#14506E')
|
||||
qpal = QtGui.QPalette( QtGui.QColor(BG_DARK) )
|
||||
for ptype in ( QtGui.QPalette.Active, QtGui.QPalette.Inactive ):
|
||||
qpal.setColor( ptype, QtGui.QPalette.Window, BG_NORMAL )
|
||||
qpal.setColor( ptype, QtGui.QPalette.WindowText, FG_LIGHT ) # or white?
|
||||
qpal.setColor( ptype, QtGui.QPalette.Base, BG_DARK )
|
||||
qpal.setColor( ptype, QtGui.QPalette.Text, FG_LIGHT )
|
||||
qpal.setColor( ptype, QtGui.QPalette.AlternateBase, BG_DARK )
|
||||
qpal.setColor( ptype, QtGui.QPalette.ToolTipBase, BG_LIGHT )
|
||||
qpal.setColor( ptype, QtGui.QPalette.ToolTipText, FG_LIGHT )
|
||||
qpal.setColor( ptype, QtGui.QPalette.Button, BG_DARK )
|
||||
qpal.setColor( ptype, QtGui.QPalette.ButtonText, FG_LIGHT )
|
||||
qpal.setColor( ptype, QtGui.QPalette.Link, SEL_NORMAL )
|
||||
qpal.setColor( ptype, QtGui.QPalette.LinkVisited, FG_NORMAL )
|
||||
qpal.setColor( ptype, QtGui.QPalette.Highlight, SEL_LIGHT )
|
||||
qpal.setColor( ptype, QtGui.QPalette.HighlightedText, BLACK )
|
||||
qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.Button, BG_NORMAL )
|
||||
qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, FG_DARK )
|
||||
qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, FG_DARK )
|
||||
return qpal
|
||||
|
||||
##########################################################################
|
||||
def prepare_ui(self):
|
||||
""" Boring Qt window layout code is implemented here """
|
||||
ui = {}
|
||||
main_wid = QtWidgets.QWidget()
|
||||
self.setCentralWidget(main_wid)
|
||||
|
||||
color_fields = (
|
||||
# key, description, reference to line edit field)
|
||||
['gr_bg' , (0,0), 'graph background' ],
|
||||
['gr_fg' , (1,0), 'graph foreground' ],
|
||||
['gr_txt', (2,0), 'graph text' ],
|
||||
['gr_reg', (3,0), 'graph region' ],
|
||||
['gr_acc', (4,0), 'graphical accent' ],
|
||||
['gr_hlt', (5,0), 'graphical highlight'],
|
||||
['p0', (0,1), ' plot 0'], ['p1', (1,1), ' plot 1'],
|
||||
['p2', (2,1), ' plot 2'], ['p3', (3,1), ' plot 3'],
|
||||
['p4', (4,1), ' plot 4'], ['p5', (5,1), ' plot 5'],
|
||||
['p6', (6,1), ' plot 6'], ['p7', (7,1), ' plot 7']
|
||||
)
|
||||
|
||||
gr_wid1 = pg.GraphicsLayoutWidget(show=True)
|
||||
ui['plot1'] = gr_wid1.addPlot()
|
||||
|
||||
gr_wid2 = pg.GraphicsLayoutWidget(show=True)
|
||||
ui['plot2'] = gr_wid2.addPlot()
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout( main_wid )
|
||||
l_wid = QtWidgets.QWidget()
|
||||
main_layout.addWidget(l_wid)
|
||||
main_layout.addWidget(gr_wid2)
|
||||
|
||||
l_layout = QtWidgets.QGridLayout( l_wid )
|
||||
l_layout.setContentsMargins(0,0,0,0)
|
||||
l_layout.setSpacing(1)
|
||||
row_idx = 0
|
||||
|
||||
label = QtWidgets.QLabel('Select a palette:')
|
||||
l_layout.addWidget( label, row_idx,0, 1,2 )
|
||||
row_idx += 1
|
||||
|
||||
box = QtWidgets.QComboBox()
|
||||
for text, identifier, args in self.palette_options:
|
||||
del identifier, args # not needed here
|
||||
box.addItem(text)
|
||||
box.activated.connect(self.handle_palette_select)
|
||||
btn = QtWidgets.QPushButton('dark GUI')
|
||||
btn.setCheckable(True)
|
||||
btn.setChecked(False)
|
||||
btn.clicked.connect(self.handle_dark_button)
|
||||
l_layout.addWidget( box, row_idx,0, 1,2 )
|
||||
l_layout.addWidget( btn, row_idx,3, 1,1 )
|
||||
ui['dark'] = btn
|
||||
row_idx += 1
|
||||
|
||||
label = QtWidgets.QLabel('Sampled color map:')
|
||||
l_layout.addWidget( label, row_idx,0, 1,2 )
|
||||
label = QtWidgets.QLabel('start')
|
||||
l_layout.addWidget( label, row_idx,2, 1,1 )
|
||||
label = QtWidgets.QLabel('step')
|
||||
l_layout.addWidget( label, row_idx,3, 1,1 )
|
||||
row_idx += 1
|
||||
|
||||
box = QtWidgets.QComboBox()
|
||||
for identifier in self.colormap_options:
|
||||
box.addItem(identifier)
|
||||
ui['colormaps'] = box
|
||||
ui['colormaps'].activated.connect(self.handle_colormap_select)
|
||||
ui['sample_start'] = QtWidgets.QLineEdit(' 0.000')
|
||||
ui['sample_start'].editingFinished.connect(self.handle_colormap_select)
|
||||
ui['sample_step' ] = QtWidgets.QLineEdit('+0.125')
|
||||
ui['sample_step' ].editingFinished.connect(self.handle_colormap_select)
|
||||
l_layout.addWidget( box, row_idx,0, 1,2 )
|
||||
l_layout.addWidget( ui['sample_start'], row_idx,2, 1,1 )
|
||||
l_layout.addWidget( ui['sample_step' ], row_idx,3, 1,1 )
|
||||
row_idx += 1
|
||||
|
||||
spacer = QtWidgets.QWidget()
|
||||
spacer.setFixedHeight(10)
|
||||
l_layout.addWidget( spacer, row_idx,0, 1,2 )
|
||||
row_idx += 1
|
||||
|
||||
label = QtWidgets.QLabel('Functional colors:')
|
||||
l_layout.addWidget( label, row_idx,0, 1,2 )
|
||||
row_idx += 1
|
||||
|
||||
row = 0
|
||||
ui['widget_from_color_key'] = {} # look-up for color editing fields
|
||||
ui['color_key_from_widget'] = {} # reverse look-up for color editing fields
|
||||
for field_list in color_fields:
|
||||
key, pos, text = field_list
|
||||
lab = QtWidgets.QLabel(text)
|
||||
lab.setAlignment(QtCore.Qt.AlignCenter)
|
||||
edt = QtWidgets.QLineEdit()
|
||||
row = row_idx + pos[0]
|
||||
col = 2 * pos[1] # 0 or 2
|
||||
l_layout.addWidget( lab, row,col+0, 1,1 )
|
||||
l_layout.addWidget( edt, row,col+1, 1,1 )
|
||||
ui['color_key_from_widget'][edt] = key
|
||||
ui['widget_from_color_key'][key] = edt
|
||||
row_idx = row
|
||||
|
||||
btn = QtWidgets.QPushButton('generate continuous data')
|
||||
btn.setCheckable(True)
|
||||
btn.setChecked(False)
|
||||
btn.clicked.connect(self.handle_update_button)
|
||||
l_layout.addWidget( btn, row_idx,0, 1,2 )
|
||||
row_idx += 1
|
||||
|
||||
spacer = QtWidgets.QWidget()
|
||||
spacer.setFixedHeight(10)
|
||||
l_layout.addWidget( spacer, row_idx,0, 1,2 )
|
||||
row_idx += 1
|
||||
|
||||
label = QtWidgets.QLabel('Overview:')
|
||||
l_layout.addWidget( label, row_idx,0, 1,4 )
|
||||
row_idx += 1
|
||||
|
||||
l_layout.addWidget( gr_wid1, row_idx,0, 1,4 )
|
||||
row_idx += 1
|
||||
|
||||
return ui
|
||||
|
||||
|
||||
|
||||
mkQApp("Palette test application")
|
||||
main_window = MainWindow()
|
||||
|
||||
## Start Qt event loop
|
||||
if __name__ == '__main__':
|
||||
QtWidgets.QApplication.instance().exec_()
|
@ -54,7 +54,7 @@ monochrome_colors = ('blue', 'green', 'amber', 'red', 'pink', 'lavender', (0.5,
|
||||
for mono_val in monochrome_colors:
|
||||
num_bars += 1
|
||||
lw.addLabel(str(mono_val))
|
||||
cmap = pg.colormap.make_monochrome(mono_val)
|
||||
cmap = pg.colormap.makeMonochrome(mono_val)
|
||||
imi = pg.ImageItem()
|
||||
imi.setImage(img)
|
||||
imi.setLookupTable( cmap.getLookupTable(alpha=True) )
|
||||
|
@ -21,6 +21,7 @@ examples = OrderedDict([
|
||||
('Remote Plotting', 'RemoteSpeedTest.py'),
|
||||
('Scrolling plots', 'scrollingPlots.py'),
|
||||
('Color Maps', 'colorMaps.py'),
|
||||
('Palette tester','PaletteTestAndEdit.py'),
|
||||
('Palette adjustment','PaletteApplicationExample.py'),
|
||||
('HDF5 big data', 'hdf5.py'),
|
||||
('Demos', OrderedDict([
|
||||
|
@ -86,7 +86,6 @@ def _get_from_file(name):
|
||||
else:
|
||||
csv_mode = False
|
||||
for line in fh:
|
||||
name = None
|
||||
line = line.strip()
|
||||
if len(line) == 0: continue # empty line
|
||||
if line[0] == ';': continue # comment
|
||||
@ -110,11 +109,11 @@ def _get_from_file(name):
|
||||
idx += 1
|
||||
# end of line reading loop
|
||||
# end of open
|
||||
cm = ColorMap(
|
||||
cmap = ColorMap( name=name,
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
color=color_list) #, names=color_names)
|
||||
_mapCache[name] = cm
|
||||
return cm
|
||||
_mapCache[name] = cmap
|
||||
return cmap
|
||||
|
||||
def _get_from_matplotlib(name):
|
||||
""" import colormap from matplotlib definition """
|
||||
@ -124,7 +123,7 @@ def _get_from_matplotlib(name):
|
||||
import matplotlib.pyplot as mpl_plt
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
cm = None
|
||||
cmap = None
|
||||
col_map = mpl_plt.get_cmap(name)
|
||||
if hasattr(col_map, '_segmentdata'): # handle LinearSegmentedColormap
|
||||
data = col_map._segmentdata
|
||||
@ -142,20 +141,21 @@ def _get_from_matplotlib(name):
|
||||
positions[idx2] = tup[0]
|
||||
comp_vals[idx2] = tup[1] # these are sorted in the raw data
|
||||
col_data[:,idx] = np.interp(col_data[:,3], positions, comp_vals)
|
||||
cm = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
|
||||
cmap = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
|
||||
# some color maps (gnuplot in particular) are defined by RGB component functions:
|
||||
elif ('red' in data) and isinstance(data['red'], collections.Callable):
|
||||
col_data = np.zeros((64, 4))
|
||||
col_data[:,-1] = np.linspace(0., 1., 64)
|
||||
for idx, key in enumerate(['red','green','blue']):
|
||||
col_data[:,idx] = np.clip( data[key](col_data[:,-1]), 0, 1)
|
||||
cm = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
|
||||
cmap = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
|
||||
elif hasattr(col_map, 'colors'): # handle ListedColormap
|
||||
col_data = np.array(col_map.colors)
|
||||
cm = ColorMap(pos=np.linspace(0.0, 1.0, col_data.shape[0]), color=255*col_data[:,:3]+0.5 )
|
||||
if cm is not None:
|
||||
_mapCache[name] = cm
|
||||
return cm
|
||||
cmap = ColorMap( name=name,
|
||||
pos = np.linspace(0.0, 1.0, col_data.shape[0]), color=255*col_data[:,:3]+0.5 )
|
||||
if cmap is not None:
|
||||
_mapCache[name] = cmap
|
||||
return cmap
|
||||
|
||||
def _get_from_colorcet(name):
|
||||
""" import colormap from colorcet definition """
|
||||
@ -173,22 +173,23 @@ def _get_from_colorcet(name):
|
||||
color_list.append( color_tuple )
|
||||
if len(color_list) == 0:
|
||||
return None
|
||||
cm = ColorMap(
|
||||
cmap = ColorMap( name=name,
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
color=color_list) #, names=color_names)
|
||||
_mapCache[name] = cm
|
||||
return cm
|
||||
_mapCache[name] = cmap
|
||||
return cmap
|
||||
|
||||
def make_monochrome(color='green'):
|
||||
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' or 'amber'
|
||||
'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)
|
||||
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])
|
||||
@ -210,8 +211,8 @@ def make_monochrome(color='green'):
|
||||
g * delta + leak,
|
||||
b * delta + leak )
|
||||
color_list.append(color_tuple)
|
||||
cm = ColorMap(pos=stops, color=color_list )
|
||||
return cm
|
||||
cmap = ColorMap(name=name, pos=stops, color=color_list )
|
||||
return cmap
|
||||
|
||||
class ColorMap(object):
|
||||
"""
|
||||
@ -260,7 +261,7 @@ class ColorMap(object):
|
||||
'qcolor': QCOLOR,
|
||||
}
|
||||
|
||||
def __init__(self, pos, color, mode=None, mapping=None): #, names=None):
|
||||
def __init__(self, pos, color, name=None, mode=None, mapping=None): #, names=None):
|
||||
"""
|
||||
=============== =================================================================
|
||||
**Arguments:**
|
||||
@ -280,6 +281,7 @@ class ColorMap(object):
|
||||
DIVERGING maps colors to [-1.0;+1.0]
|
||||
=============== =================================================================
|
||||
"""
|
||||
self.name = name # storing a name helps identify ColorMaps sampled by Palette
|
||||
self.pos = np.array(pos)
|
||||
order = np.argsort(self.pos)
|
||||
self.pos = self.pos[order]
|
||||
@ -304,6 +306,12 @@ class ColorMap(object):
|
||||
self.mapping_mode = self.CLIP
|
||||
|
||||
self.stopsCache = {}
|
||||
|
||||
def __str__(self):
|
||||
""" provide human-readable identifier """
|
||||
if self.name is None:
|
||||
return 'unnamed ColorMap({:d})'.format(len(self.pos))
|
||||
return "ColorMap({:d}):'{:s}'".format(len(self.pos),self.name)
|
||||
|
||||
def __getitem__(self, key):
|
||||
""" Convenient shorthand access to palette colors """
|
||||
|
@ -25,7 +25,7 @@ for key, col in [ # add functional colors
|
||||
('gr_fg','d'), # graphical foreground
|
||||
('gr_bg','k'), # graphical background
|
||||
('gr_txt','d'), # graphical text color
|
||||
('gr_hov','r') # graphical hover color
|
||||
('gr_hlt','r') # graphical hover color
|
||||
]:
|
||||
DEFAULT_COLORS[key] = DEFAULT_COLORS[col]
|
||||
|
||||
|
@ -1,254 +1,428 @@
|
||||
from .Qt import QtGui
|
||||
from . import Qt
|
||||
from .Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import functions as fn # namedColorManager
|
||||
from . import colormap
|
||||
|
||||
__all__ = ['Palette']
|
||||
|
||||
LEGACY_RAW = { # legacy raw colors:
|
||||
'b': ( 0, 0,255,255),
|
||||
'g': ( 0,255, 0,255),
|
||||
'r': (255, 0, 0,255),
|
||||
'c': ( 0,255,255,255),
|
||||
'm': (255, 0,255,255),
|
||||
'y': (255,255, 0,255),
|
||||
'k': ( 0, 0, 0,255),
|
||||
'w': (255,255,255,255),
|
||||
'd': (150,150,150,255),
|
||||
'l': (200,200,200,255),
|
||||
's': (100,100,150,255)
|
||||
}
|
||||
LEGACY_FUNC = { # functional colors:
|
||||
'gr_fg' : 'd',
|
||||
'gr_bg' : 'k',
|
||||
'gr_txt' : 'd',
|
||||
'gr_acc' : (200,200,100,255),
|
||||
'gr_hov' : 'r',
|
||||
'gr_reg' : ( 0, 0,255, 50)
|
||||
}
|
||||
LEGACY_PLOT = [ # plot / accent colors:
|
||||
'l','y','r','m','b','c','g','d'
|
||||
]
|
||||
#### todo list ####
|
||||
# define legacy colors for relaxed-dark
|
||||
# find color definitions for relaxed-light
|
||||
# define color names for relaxed palettes
|
||||
# enable color adjustment in PaletteTestandEdit.py!
|
||||
|
||||
RELAXED_RAW = { # "fresh" raw colors:
|
||||
'col_orange':'#A64D21', 'col_l_orange':'#D98A62', 'col_d_orange':'#732E0B',
|
||||
'col_red' :'#B32424', 'col_l_red' :'#E66767', 'col_d_red' :'#800D0D',
|
||||
'col_purple':'#991F66', 'col_l_purple':'#D956A3', 'col_d_purple':'#660A31',
|
||||
'col_violet':'#7922A6', 'col_l_violet':'#BC67E6', 'col_d_violet':'#5A0C80',
|
||||
'col_indigo':'#5F29CC', 'col_l_indigo':'#9673FF', 'col_d_indigo':'#380E8C',
|
||||
'col_blue' :'#2447B3', 'col_l_blue' :'#6787E6', 'col_d_blue' :'#0D2980',
|
||||
'col_sky' :'#216AA6', 'col_l_sky' :'#77ADD9', 'col_d_sky' :'#0B4473',
|
||||
'col_cyan' :'#1C8C8C', 'col_l_cyan' :'#73BFBF', 'col_d_cyan' :'#095959',
|
||||
'col_green' :'#1F9952', 'col_l_green' :'#7ACC9C', 'col_d_green' :'#0A6630',
|
||||
'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_black' :'#000000', 'col_gr1' :'#161619', 'col_gr2' :'#43434D',
|
||||
'col_gr3' :'#70707F', 'col_gr4' :'#9D9DB2', 'col_gr5' :'#C9C9E5',
|
||||
'col_white' :'#FFFFFF'
|
||||
}
|
||||
RELAXED_DARK_FUNC= { # functional colors:
|
||||
'gr_fg' : 'col_gr5',
|
||||
'gr_bg' : 'col_gr1',
|
||||
'gr_txt' : 'col_gr5',
|
||||
'gr_acc' : 'col_cyan',
|
||||
'gr_hov' : 'col_white',
|
||||
'gr_reg' : ('col_cyan', 30),
|
||||
# legacy colors:
|
||||
'b': 'col_l_blue' , 'c': 'col_l_cyan', 'g': 'col_l_green',
|
||||
'y': 'col_l_yellow', 'r': 'col_l_red' , 'm': 'col_l_violet',
|
||||
'k': 'col_black' , 'w': 'col_white',
|
||||
'd': 'col_gr2' , 'l': 'col_gr4' , 's': 'col_l_sky'
|
||||
}
|
||||
RELAXED_DARK_PLOT = [ # plot / accent colors:
|
||||
'col_l_sky' ,
|
||||
'col_l_indigo',
|
||||
'col_l_purple',
|
||||
'col_l_red' ,
|
||||
'col_l_gold' ,
|
||||
'col_l_grass' ,
|
||||
'col_l_cyan' ,
|
||||
'col_l_blue' ,
|
||||
'col_l_violet',
|
||||
'col_l_orange',
|
||||
'col_l_yellow',
|
||||
'col_l_green'
|
||||
]
|
||||
PALETTE_DEFINITIONS = {
|
||||
'legacy': {
|
||||
'colormap_sampling' : None,
|
||||
'b': ( 0, 0,255,255), 'g': ( 0,255, 0,255), 'r': (255, 0, 0,255),
|
||||
'c': ( 0,255,255,255), 'm': (255, 0,255,255), 'y': (255,255, 0,255),
|
||||
'k': ( 0, 0, 0,255), 'w': (255,255,255,255),
|
||||
'd': (150,150,150,255), 'l': (200,200,200,255), 's': (100,100,150,255),
|
||||
# --- functional colors ---
|
||||
'gr_fg' : 'd', 'gr_bg' : 'k',
|
||||
'gr_txt': 'd', 'gr_acc': (200,200,100,255),
|
||||
'gr_hlt': 'r', 'gr_reg': ( 0, 0,255,100),
|
||||
# --- manually assigned plot colors ---
|
||||
'p0':'l', 'p1':'y', 'p2':'r', 'p3':'m',
|
||||
'p4':'b', 'p5':'c', 'p6':'g', 'p7':'d'
|
||||
},
|
||||
'relaxed_dark':{
|
||||
'colormap_sampling': ('CET-C6', 0.430, -0.125),
|
||||
'col_black' :'#000000', 'col_white' :'#FFFFFF',
|
||||
'col_gr1':'#19232D', 'col_gr2':'#32414B', 'col_gr3':'#505F69', # match QDarkStyle background colors
|
||||
'col_gr4':'#787878', 'col_gr5':'#AAAAAA', 'col_gr6':'#F0F0F0', # match QDarkstyle foreground colors
|
||||
# --- functional colors ---
|
||||
'gr_fg' : 'col_gr4', 'gr_bg' : 'col_gr1',
|
||||
'gr_txt': 'col_gr5', 'gr_acc': '#1464A0', #col_cyan',
|
||||
'gr_hlt': 'col_white', 'gr_reg': ('#1464A0',100)
|
||||
# legacy colors:
|
||||
# 'b': 'col_l_blue' , 'c': 'col_l_cyan', 'g': 'col_l_green',
|
||||
# 'y': 'col_l_yellow', 'r': 'col_l_red' , 'm': 'col_l_violet',
|
||||
# 'k': 'col_black' , 'w': 'col_white',
|
||||
# 'd': 'col_gr2' , 'l': 'col_gr4' , 's': 'col_l_sky'
|
||||
},
|
||||
'synthwave':{
|
||||
'colormap_sampling': ('CET-L8', 0.275, 0.100),
|
||||
'col_black' :'#000000', 'col_white' :'#FFFFFF',
|
||||
'col_gr1':'#19232D', 'col_gr2':'#32414B', 'col_gr3':'#505F69', # match QDarkStyle background colors
|
||||
'col_gr4':'#787878', 'col_gr5':'#AAAAAA', 'col_gr6':'#F0F0F0', # match QDarkstyle foreground colors
|
||||
# --- functional colors ---
|
||||
'gr_fg' : 'col_gr4', 'gr_bg' : 'col_gr1',
|
||||
'gr_txt': 'col_gr5', 'gr_acc': '#1464A0', #col_cyan',
|
||||
'gr_hlt': 'col_white', 'gr_reg': ('#1464A0',100)
|
||||
# legacy colors:
|
||||
# 'b': 'col_l_blue' , 'c': 'col_l_cyan', 'g': 'col_l_green',
|
||||
# 'y': 'col_l_yellow', 'r': 'col_l_red' , 'm': 'col_l_violet',
|
||||
# 'k': 'col_black' , 'w': 'col_white',
|
||||
# 'd': 'col_gr2' , 'l': 'col_gr4' , 's': 'col_l_sky'
|
||||
}
|
||||
|
||||
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'
|
||||
]
|
||||
|
||||
# RELAXED_RAW = { # "fresh" raw colors:
|
||||
# 'col_orange':'#A64D21', 'col_l_orange':'#D98A62', 'col_d_orange':'#732E0B',
|
||||
# 'col_red' :'#B32424', 'col_l_red' :'#E66767', 'col_d_red' :'#800D0D',
|
||||
# 'col_purple':'#991F66', 'col_l_purple':'#D956A3', 'col_d_purple':'#660A31',
|
||||
# 'col_violet':'#7922A6', 'col_l_violet':'#BC67E6', 'col_d_violet':'#5A0C80',
|
||||
# 'col_indigo':'#5F29CC', 'col_l_indigo':'#9673FF', 'col_d_indigo':'#380E8C',
|
||||
# 'col_blue' :'#2447B3', 'col_l_blue' :'#6787E6', 'col_d_blue' :'#0D2980',
|
||||
# 'col_sky' :'#216AA6', 'col_l_sky' :'#77ADD9', 'col_d_sky' :'#0B4473',
|
||||
# 'col_cyan' :'#1C8C8C', 'col_l_cyan' :'#73BFBF', 'col_d_cyan' :'#095959',
|
||||
# 'col_green' :'#1F9952', 'col_l_green' :'#7ACC9C', 'col_d_green' :'#0A6630',
|
||||
# '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_black' :'#000000', 'col_gr1' :'#161619', 'col_gr2' :'#43434D',
|
||||
# 'col_gr3' :'#70707F', 'col_gr4' :'#9D9DB2', 'col_gr5' :'#C9C9E5',
|
||||
# 'col_white' :'#FFFFFF'
|
||||
# }
|
||||
# RELAXED_DARK_FUNC= { # functional colors:
|
||||
# 'gr_fg' : 'col_gr5',
|
||||
# 'gr_bg' : 'col_gr1',
|
||||
# 'gr_txt' : 'col_gr5',
|
||||
# 'gr_acc' : 'col_cyan',
|
||||
# 'gr_hlt' : 'col_white',
|
||||
# 'gr_reg' : ('col_cyan',100),
|
||||
# # legacy colors:
|
||||
# 'b': 'col_l_blue' , 'c': 'col_l_cyan', 'g': 'col_l_green',
|
||||
# 'y': 'col_l_yellow', 'r': 'col_l_red' , 'm': 'col_l_violet',
|
||||
# 'k': 'col_black' , 'w': 'col_white',
|
||||
# 'd': 'col_gr2' , 'l': 'col_gr4' , 's': 'col_l_sky'
|
||||
# }
|
||||
# RELAXED_DARK_PLOT = [ # plot / accent colors:
|
||||
# 'col_l_sky' ,
|
||||
# 'col_l_indigo',
|
||||
# 'col_l_purple',
|
||||
# 'col_l_red' ,
|
||||
# 'col_l_gold' ,
|
||||
# 'col_l_grass' ,
|
||||
# 'col_l_cyan' ,
|
||||
# 'col_l_blue' ,
|
||||
# 'col_l_violet',
|
||||
# 'col_l_orange',
|
||||
# 'col_l_yellow',
|
||||
# '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_reg' : ('col_blue',100),
|
||||
# # 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:
|
||||
# 'name'
|
||||
# ('name', alpha)
|
||||
# (R,G,B) / (R,G,B,alpha)
|
||||
if isinstance(block, QtGui.QColor):
|
||||
return block # this is already a QColor
|
||||
def identifier_to_QColor( identifier, color_dict=None ):
|
||||
"""
|
||||
Convert color information to a QColor
|
||||
=================== =============================================================
|
||||
**allowed formats**
|
||||
'name' name must be a hex value or a key in 'color_dict'
|
||||
('name', alpha) new copy will be assigned the specified alpha value
|
||||
QColor will be copied to avoid interaction if changing alpha values
|
||||
Qt.GlobalColor will result in a matching QColor
|
||||
(R,G,B) a new Qcolor will be created
|
||||
(R,G,B, alpha) a new Qcolor with specified opacity will be created
|
||||
=================== =============================================================
|
||||
"""
|
||||
if isinstance(identifier, (QtGui.QColor, QtCore.Qt.GlobalColor)):
|
||||
return QtGui.QColor(identifier)
|
||||
alpha = None
|
||||
if isinstance(block, str): # return known QColor
|
||||
name = block
|
||||
if dic is None or name not in dic:
|
||||
if isinstance(identifier, str): # return known QColor
|
||||
name = identifier
|
||||
if color_dict is None or name not in color_dict:
|
||||
if name[0] != '#':
|
||||
raise ValueError('Undefined color name '+str(block))
|
||||
raise ValueError('Undefined color name '+str(identifier))
|
||||
return QtGui.QColor( name )
|
||||
else:
|
||||
return dic[name]
|
||||
if not hasattr(block, '__len__'):
|
||||
raise ValueError('Invalid color definition '+str(block))
|
||||
return color_dict[name]
|
||||
if not hasattr(identifier, '__len__'):
|
||||
raise ValueError('Invalid color definition '+str(identifier))
|
||||
qcol = None
|
||||
if len(block) == 2:
|
||||
name, alpha = block
|
||||
if dic is None or name not in dic:
|
||||
if len(identifier) == 2:
|
||||
name, alpha = identifier
|
||||
if color_dict is None or name not in color_dict:
|
||||
if name[0] != '#':
|
||||
raise ValueError('Undefined color name '+str(block))
|
||||
raise ValueError('Undefined color identifier '+str(identifier))
|
||||
qcol = QtGui.QColor( name )
|
||||
else:
|
||||
qcol = dic[ name ]
|
||||
elif len(block) in (3,4):
|
||||
qcol = QtGui.QColor( *block )
|
||||
qcol = color_dict[ name ]
|
||||
elif len(identifier) in (3,4):
|
||||
qcol = QtGui.QColor( *identifier )
|
||||
|
||||
if alpha is not None and qcol is not None:
|
||||
qcol = QtGui.QColor(qcol) # make a copy before changing alpha
|
||||
# distinct QColors are now created for each color
|
||||
# qcol = QtGui.QColor(qcol) # make a copy before changing alpha
|
||||
qcol.setAlpha( alpha )
|
||||
return qcol
|
||||
|
||||
|
||||
def assemble_palette( raw_col, func_col, plot_col=None ):
|
||||
def get(identifier, *args):
|
||||
"""
|
||||
assemble palette color dictionary from parts:
|
||||
raw_col should contain color information in (R,G,B,(A)) or hex format
|
||||
func_col typically contains keys of colors defined before
|
||||
plot_col is a list of plotting colors to be included as 'c0' to 'cX' (in hex)
|
||||
"""
|
||||
pal = {}
|
||||
for part in [raw_col, func_col]:
|
||||
for key in part:
|
||||
col = part[key]
|
||||
pal[key] = block_to_QColor( col, pal )
|
||||
if plot_col is not None:
|
||||
for idx, col in enumerate( plot_col ):
|
||||
key = 'p{:X}'.format(idx) # plot color 'pX' does not overlap hexadecimal codes.
|
||||
pal[key] = block_to_QColor( col, pal )
|
||||
return pal
|
||||
|
||||
DEFAULT_PALETTE = assemble_palette( LEGACY_RAW, LEGACY_FUNC, LEGACY_PLOT )
|
||||
Returns a Palette object that can be applied to update the PyQtGraph color scheme
|
||||
=============== ====================================================================
|
||||
**Arguments:**
|
||||
identifier 'system' (default): Colors are based on current Qt QPalette
|
||||
'legacy': The color scheme of previous versions of PyQtGraph
|
||||
'monochrome' ['color identifier']: Creates a palette that imitates
|
||||
a monochrome computer monitor.
|
||||
'color identifier' can be one of
|
||||
'green', 'amber', 'blue', 'red', 'lavender', 'pink'
|
||||
or a tuple of relative R,G,B contributions in range 0.0 to 1.0
|
||||
|
||||
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 )
|
||||
else:
|
||||
pal = DEFAULT_PALETTE
|
||||
return Palette( colors=pal )
|
||||
|
||||
|
||||
def make_monochrome(color='green', n_colors=8):
|
||||
"""
|
||||
Returns a Palette object imitating a monochrome computer screen
|
||||
=============== =================================================================
|
||||
**Arguments:**
|
||||
color Primary color description. Can be one of predefined identifiers
|
||||
'green' or 'amber'
|
||||
or a tuple of relative R,G,B contributions in range 0.0 to 1.0
|
||||
=============== =================================================================
|
||||
{dictionary}: full palette specification, see palette.py for details
|
||||
=============== ====================================================================
|
||||
"""
|
||||
cm = colormap.make_monochrome(color)
|
||||
if cm is None: return None
|
||||
raw = {}
|
||||
for idx in range(8):
|
||||
key = 'col_m{:d}'.format(idx)
|
||||
raw[key] = cm[ idx/9 ]
|
||||
# print('added color',key,'as',raw[key].name(),raw[key].getRgb())
|
||||
func = {
|
||||
'gr_bg' : 'col_m0',
|
||||
'gr_fg' : 'col_m4',
|
||||
'gr_txt': 'col_m6',
|
||||
'gr_acc': 'col_m5',
|
||||
'gr_hov': 'col_m7',
|
||||
'gr_reg': ('col_m1', 30),
|
||||
'k': 'col_m0', 'd': 'col_m1', 's': 'col_m3', 'l': 'col_m6', 'w': 'col_m7'
|
||||
}
|
||||
avail = raw.copy() # generate a disctionary of available colors
|
||||
del avail['col_m0'] # already taken by black
|
||||
del avail['col_m7'] # already taken by white
|
||||
needed = {
|
||||
'b': ( 0, 0,255), 'c': ( 0,255,255), 'g': ( 0,255, 0),
|
||||
'y': (255,255, 0), 'r': (255, 0, 0), 'm': (255, 0,255)
|
||||
}
|
||||
for nd_key in needed:
|
||||
nd_tup = needed[nd_key] # this is the int RGB tuple we are looking to represent
|
||||
best_dist = 1e10
|
||||
best_key = None
|
||||
for av_key in avail:
|
||||
av_tup = avail[av_key].getRgb() # returns (R,G,B,A) tuple
|
||||
dist = (nd_tup[0]-av_tup[0])**2 + (nd_tup[1]-av_tup[1])**2 + (nd_tup[2]-av_tup[2])**2
|
||||
if dist < best_dist:
|
||||
best_dist = dist
|
||||
best_key = av_key
|
||||
# print('assigning',nd_key,'as',best_key,':',avail[best_key].getRgb() )
|
||||
func[nd_key] = avail[best_key]
|
||||
del avail[best_key] # remove from available list
|
||||
pal = assemble_palette( raw, func )
|
||||
return Palette( colors=pal, cmap=cm, n_colors=8 )
|
||||
if identifier == 'system':
|
||||
pal = Palette()
|
||||
return pal # default QPalette based settings
|
||||
if identifier == 'monochrome':
|
||||
pal = Palette()
|
||||
pal.setMonochrome( *args )
|
||||
return pal
|
||||
if identifier in PALETTE_DEFINITIONS:
|
||||
info = PALETTE_DEFINITIONS[identifier].copy()
|
||||
sampling_info = info.pop('colormap_sampling', None)
|
||||
pal = Palette( cmap=sampling_info, colors=info )
|
||||
return pal
|
||||
raise KeyError("Unknown palette identifier '"+str(identifier)+"'")
|
||||
|
||||
class Palette(object):
|
||||
# minimum colors to be defined:
|
||||
def __init__(self, colors=None, cmap=None, n_colors=8):
|
||||
"""
|
||||
A Palette object provides a set of colors that can conveniently applied
|
||||
to the PyQtGraph color scheme.
|
||||
It specifies at least the following colors, but additional one can be added:
|
||||
Primary colors:
|
||||
'b', 'c', 'g', 'y', 'r', 'm'
|
||||
Gray scale:
|
||||
'k', 'd', 'l', 'w' ranging from black to white
|
||||
's' slate gray
|
||||
System colors:
|
||||
'gr_bg', 'gr_fg', 'gr_txt' graph background, foreground and text colors
|
||||
'gr_wdw' window background color
|
||||
'gr_reg' partially transparent region shading color
|
||||
'gr_acc' accent for UI elements
|
||||
'gr_hlt' highlight for selected elements
|
||||
Plot colors:
|
||||
'p0' to 'p7' typically sampled from a ColorMap
|
||||
"""
|
||||
def __init__(self, cmap=None, colors=None ):
|
||||
super().__init__()
|
||||
self.palette = colors
|
||||
self.cmap = cmap
|
||||
self.n_colors = int(n_colors)
|
||||
if self.n_colors < 8: self.n_colors = 8 # enforce minimum number of plot colors
|
||||
if self.cmap is not None:
|
||||
sep = 1/n_colors
|
||||
for idx in range(self.n_colors):
|
||||
key = 'p{:x}'.format( idx )
|
||||
if key in self.palette:
|
||||
continue # do not overwrite user-provided plot colors
|
||||
val = sep * (0.5 + idx)
|
||||
col = self.cmap[val]
|
||||
self.palette[key] = col
|
||||
# print('assigning',key,'as',col,':',col.name())
|
||||
self.palette = { # populate dictionary of QColors with legacy defaults
|
||||
'b': QtGui.QColor( 0, 0,255,255), 'g': QtGui.QColor( 0,255, 0,255),
|
||||
'r': QtGui.QColor(255, 0, 0,255), 'c': QtGui.QColor( 0,255,255,255),
|
||||
'm': QtGui.QColor(255, 0,255,255), 'y': QtGui.QColor(255,255, 0,255),
|
||||
'k': QtGui.QColor( 0, 0, 0,255), 'w': QtGui.QColor(255,255,255,255),
|
||||
'd': QtGui.QColor(150,150,150,255), 'l': QtGui.QColor(200,200,200,255),
|
||||
's': QtGui.QColor(100,100,150,255)
|
||||
}
|
||||
self.dark = None # is initially set when assigning system palette
|
||||
self.emulateSystem()
|
||||
self.cmap = None
|
||||
if cmap is not None: # prepare plot colors from provided colormap
|
||||
if isinstance(cmap, (str, colormap.ColorMap) ): # sampleColorMap will convert if needed
|
||||
self.sampleColorMap( cmap=cmap)
|
||||
if isinstance(cmap, (tuple,list)): # ('identifier', start, step)
|
||||
cmap, start, step = cmap
|
||||
self.sampleColorMap(cmap=cmap, start=start, step=step)
|
||||
if colors is not None:
|
||||
# print('color dictionary:', colors)
|
||||
self.add(colors) # override specified colors
|
||||
|
||||
def __getitem__(self, key):
|
||||
""" Convenient shorthand access to palette colors """
|
||||
if isinstance(key, str): # access by color name
|
||||
return self.palette.get(key,None)
|
||||
if isinstance(key, int): # access by plot color index
|
||||
idx = key % 8 # map to 0 to 8
|
||||
key = 'p'+str(idx)
|
||||
return self.palette.get(key,None)
|
||||
return None
|
||||
|
||||
def colorMap(self):
|
||||
"""
|
||||
Return the ColorMap object used to create plot colors or 'None' if not assigned.
|
||||
"""
|
||||
return self.cmap
|
||||
|
||||
# needs: addColors
|
||||
# needs to be aware of number of plot colors
|
||||
# needs to be indexable by key and numerical plot color
|
||||
# indexed plot colors need to wrap around to work for any index.
|
||||
# needs: clearColors
|
||||
def sampleColorMap(self, cmap=None, n_colors=8, prefix='p', start=0., step=None ):
|
||||
"""
|
||||
Sample a ColorMap to update defined plot colors
|
||||
============= ===============================================================================
|
||||
**Arguments**
|
||||
cmap a ColorMap object to be sampled. If not given, a default color map is used
|
||||
n_colors default '8': Number of assigned colors.
|
||||
The default set needs to include 'p0' to 'p7'
|
||||
prefix default 'p' assigns colors as 'p0' to 'pXX', at least 'p7' is required.
|
||||
Additional sets can be defined with a different prefix, e.g. 'col_0' to 'col_7'
|
||||
All prefixes need to start with 'p' or 'col' to avoid namespace overlap with
|
||||
functional colors and hexadecimal numbers
|
||||
start first sampled value (default is 0.000)
|
||||
step step between samples. Default 'None' equally samples n colors from a
|
||||
linear colormap, including values 0.0 and 1.0.
|
||||
Color values > 1. and < 0. wrap around!
|
||||
============= ===============================================================================
|
||||
"""
|
||||
valid = prefix[0]=='p' or ( len(prefix)>=3 and prefix[:3]=='col')
|
||||
if not valid:
|
||||
raise ValueError("'prefix' of plot color needs to start with 'p'.")
|
||||
if cmap is None:
|
||||
cmap = self.cmap
|
||||
if isinstance(cmap, str):
|
||||
cmap = colormap.get(cmap) # obtain ColorMap if identifier is given
|
||||
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).")
|
||||
|
||||
if prefix == 'p':
|
||||
self.cmap = cmap # replace default color map
|
||||
n_colors = 8 # always define 8 primary plot colors
|
||||
if step is None:
|
||||
step = 1 / (n_colors - 1) # sample 0. to 1. (inclusive) by default
|
||||
for cnt in range(n_colors):
|
||||
val = start + cnt * step
|
||||
# print( val )
|
||||
if val > 1.0 or val < 0.0: # don't touch 1.0 value
|
||||
val = val % 1. # but otherwise map to 0 to 1 range
|
||||
qcol = cmap[val]
|
||||
key = prefix + str(cnt)
|
||||
self.palette[key] = qcol
|
||||
|
||||
def add(self, colors):
|
||||
"""
|
||||
Add colors given in dictionary 'colors' to the palette
|
||||
All colors will be converted to QColor.
|
||||
Setting 'gr_bg' with a mean color value < 127 will set the palette's 'dark' property
|
||||
"""
|
||||
for key in colors:
|
||||
col = identifier_to_QColor( colors[key], color_dict=self.palette )
|
||||
if key == 'gr_bg':
|
||||
bg_tuple = col.getRgb()
|
||||
self.dark = bool( sum( bg_tuple[:3] ) < 3 * 127 ) # dark mode?
|
||||
self.palette[key] = col
|
||||
|
||||
def emulateSystem(self):
|
||||
"""
|
||||
Retrieves the current Qt 'active' palette and extracts the following colors:
|
||||
===================================== ============================================================================
|
||||
'gr_fg','gr_txt' from QPalette.Text (foreground color used with Base)
|
||||
'gr_bg' from QPalette.Base (background color for e.g. text entry widgets)
|
||||
'gr_wdw' from QPalette.Window (a general background color)
|
||||
'gr_reg' from QPalette.AlternateBase (alternating row background color)
|
||||
'gr_acc' from QPalette.Link (color used for unvisited hyperlinks)
|
||||
'gr_hlt' from QPalette.Highlight (color to indicate a selected item)
|
||||
===================================== ============================================================================
|
||||
"""
|
||||
app = QtWidgets.QApplication.instance()
|
||||
if app is None: return None
|
||||
qPalette = app.palette()
|
||||
col_grp = QtGui.QPalette.Active
|
||||
colors = {}
|
||||
for key, alpha, col_role in (
|
||||
('gr_bg' , None, QtGui.QPalette.Base), # background color for e.g. text entry
|
||||
('gr_fg' , None, QtGui.QPalette.WindowText), # overall foreground text color
|
||||
('gr_txt', None, QtGui.QPalette.Text), # foreground color used with Base
|
||||
('gr_reg', 100, QtGui.QPalette.AlternateBase), # alternating row background color
|
||||
('gr_acc', None, QtGui.QPalette.Link), # color of unvisited hyperlink
|
||||
('gr_hlt', None, QtGui.QPalette.Highlight), # color to indicate a selected item
|
||||
('ui_wind', None, QtGui.QPalette.Window), # a general background color
|
||||
('ui_text', None, QtGui.QPalette.WindowText) # overall foreground text color
|
||||
):
|
||||
qcol = qPalette.color(col_grp, col_role)
|
||||
if alpha is not None: qcol.setAlpha(alpha)
|
||||
colors[key] = qcol
|
||||
self.add(colors)
|
||||
colors = {
|
||||
'b': QtCore.Qt.blue , 'c': QtCore.Qt.cyan, 'g': QtCore.Qt.green ,
|
||||
'y': QtCore.Qt.yellow, 'r': QtCore.Qt.red , 'm': QtCore.Qt.magenta,
|
||||
'w': QtCore.Qt.white , 's': QtCore.Qt.gray, 'k': QtCore.Qt.black ,
|
||||
'l': QtCore.Qt.lightGray, 'd': QtCore.Qt.darkGray
|
||||
}
|
||||
if not self.dark: # darken some colors for light mode
|
||||
colors['c'] = QtCore.Qt.darkCyan
|
||||
colors['g'] = QtCore.Qt.darkGreen
|
||||
colors['y'] = QtCore.Qt.darkYellow
|
||||
colors['m'] = QtCore.Qt.darkMagenta
|
||||
self.add(colors)
|
||||
colors = {
|
||||
'p'+str(idx) : name
|
||||
for idx, name in enumerate( ('gr_fg', 'y','r','m','b','c','g','s') )
|
||||
}
|
||||
self.add(colors)
|
||||
|
||||
def setMonochrome(self, color='green'):
|
||||
"""
|
||||
Updates graph colors with a set based on 'monochrome' color map,
|
||||
imitating a monochrome computer screen
|
||||
============== =================================================================
|
||||
**Arguments:**
|
||||
color Primary color description. Can be one of predefined identifiers
|
||||
'green', 'amber', 'blue'
|
||||
or a tuple of relative R,G,B contributions in range 0.0 to 1.0
|
||||
============== =================================================================
|
||||
"""
|
||||
cmap = colormap.makeMonochrome(color)
|
||||
if cmap is None:
|
||||
raise ValueError("Failed to generate color for '"+str(color)+"'")
|
||||
self.sampleColorMap( cmap=cmap, start=1.0, step=-1/8 ) # assign bright to dark, don't go all the way to background.
|
||||
# define colors 'm0' (near-black) to 'm8' (near-white):
|
||||
self.sampleColorMap( n_colors=9, cmap=cmap, step=1/8, prefix='col_m' )
|
||||
colors = {
|
||||
'gr_bg' : 'col_m0', 'gr_fg' : 'col_m4',
|
||||
'gr_txt': 'col_m5', 'gr_acc': 'col_m6',
|
||||
'gr_hlt': 'col_m7', 'gr_reg': ('col_m1', 30),
|
||||
'k': 'col_m0', 'd': 'col_m1', 's': 'col_m3', 'l': 'col_m6', 'w': 'col_m7'
|
||||
}
|
||||
self.add( colors )
|
||||
# make a dictionary of plot colors (except darkest and lightest) to emulate primary colors:
|
||||
avail = { key: self.palette[key] for key in ('p0','p1','p2','p3','p4','p5','p6') }
|
||||
needed = { # int RGB colors that we are looking to emulate:
|
||||
'b': ( 0, 0,255), 'c': ( 0,255,255), 'g': ( 0,255, 0),
|
||||
'y': (255,255, 0), 'r': (255, 0, 0), 'm': (255, 0,255)
|
||||
}
|
||||
colors = {}
|
||||
for nd_key in needed:
|
||||
nd_tup = needed[nd_key] # int RGB tuple to be represented
|
||||
best_dist = 1e10
|
||||
best_key = None
|
||||
for av_key in avail:
|
||||
av_tup = avail[av_key].getRgb() # returns (R,G,B,A) tuple
|
||||
sq_dist = (nd_tup[0]-av_tup[0])**2 + (nd_tup[1]-av_tup[1])**2 + (nd_tup[2]-av_tup[2])**2
|
||||
if sq_dist < best_dist:
|
||||
best_dist = sq_dist
|
||||
best_key = av_key
|
||||
# print('assigning',nd_key,'as',best_key,':',avail[best_key].getRgb() )
|
||||
colors[nd_key] = avail[best_key]
|
||||
del avail[best_key] # remove from available list
|
||||
self.add( colors )
|
||||
|
||||
def apply(self):
|
||||
"""
|
||||
provides palette to NamedColorManager, which triggers a global refresh of named colors
|
||||
Applies this palette to the overall PyQtGraph color scheme.
|
||||
This provides the palette to NamedColorManager, which triggers a global refresh of named colors
|
||||
"""
|
||||
fn.NAMED_COLOR_MANAGER.redefinePalette( colors=self.palette )
|
||||
|
Loading…
Reference in New Issue
Block a user