pyqtgraph/examples/PaletteTestAndEdit.py
2021-05-09 22:36:02 +09:00

434 lines
17 KiB
Python

#!/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
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', []),
('relaxed (light)', 'relaxed_light', []),
('pastels (light)', 'pastels', []),
('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',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_acc', hoverBrush='gr_reg')
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',2),('p1',2),('p2',2),('p3',2),('p4',2),('p5',2),('p6',2),('p7',2)] # 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)
col_list = [
('k','black'),('b','blue'),('c','cyan'),('g','green'),('y','yellow'),
('r','red'),('m','magenta'),('w','white')]
for idx in range(10):
name = 'm{:d}'.format(idx)
col_list.append( (name, '## '+name+' ##') )
for idx, (color,text) in enumerate( col_list ):
text_item = pg.TextItem(text, color=color, anchor=(1,0))
text_item.setPos( self.num_points, 7.7 - idx/3 )
plt.addItem(text_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()
key = self.ui['color_key_from_widget'][source]
requested = source.text()
if len(requested) < 1:
value = 0x808080
else:
if requested[0] == '#':
requested = requested[1:]
try:
value = int(requested,16)
except ValueError:
value = 0x808080
color = '#{:06x}'.format(value)
print('color value is',color)
# source.setText(color)
self.open_palette[key] = color
self.update_color_fields(self.open_palette)
self.open_palette.apply()
print('color update requested for',key)
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')
BG_LIGHT = QtGui.QColor('#505354') # #32414B
BG_NORMAL = QtGui.QColor('#2e3132') # #212a31
BG_DARK = QtGui.QColor('#0e1112') # #10151
FG_LIGHT = QtGui.QColor('#f0f4f5') # #F0F0F0
FG_NORMAL = QtGui.QColor('#d4d8d9') # #AAAAAA
FG_DARK = QtGui.QColor('#b8bcbd') # #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_NORMAL )
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('System style:')
l_layout.addWidget( label, row_idx,0, 1,2 )
label = QtWidgets.QLabel('Select a palette:')
l_layout.addWidget( label, row_idx,2, 1,2 )
row_idx += 1
btn = QtWidgets.QPushButton('dark GUI')
btn.setCheckable(True)
btn.setChecked(False)
btn.clicked.connect(self.handle_dark_button)
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)
l_layout.addWidget( box, row_idx,2, 1,2 )
l_layout.addWidget( btn, row_idx,0, 1,2 )
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()
edt.editingFinished.connect(self.handle_color_update)
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_()