Make plotting with gradients more fun (#1742)
* added convenience functions to better handle plotting with gradients * docstring correction, example name correction * retrying to get documentation format right * another attempt at cleaning up docs * Don't hardcode timer type (and docs fixing attempt) * more docstring re-formatting * linebreaks in docstrings * more documentation adjustments * documentation pass * some corrections to documentation * more adjustments to documentation * again? * removed some whitespace and redundant blank lines, changed some checks '== None' to 'is None' * fixed mis-spelling QColor as Qcolor
This commit is contained in:
parent
2fb7cdafbd
commit
bfc63bb730
@ -1,8 +1,81 @@
|
||||
ColorMap
|
||||
========
|
||||
Color Maps
|
||||
==========
|
||||
|
||||
A color map defines a relationship between scalar data values and a range of colors. Color maps are
|
||||
commonly used to generate false color images, color scatter-plot points, and illustrate the height
|
||||
of surface plots.
|
||||
|
||||
PyQtGraph's :class:`~pyqtgraph.ColorMap` can conveniently be applied to images and interactively
|
||||
adjusted by using :class:`~pyqtgraph.ColorBarItem`.
|
||||
To provide interactively user-defined color mappings, see
|
||||
:class:`~pyqtgraph.GradientEditorItem` and :class:`~pyqtgraph.GradientWidget`, which wraps it.
|
||||
:class:`~pyqtgraph.GradientEditorItem` combines the editing with a histogram and controls for
|
||||
interactively adjusting image levels.
|
||||
|
||||
ColorMap can also be used a convenient source of colors from a consistent palette or to generate
|
||||
QPen and QBrush objects used to draw lines and fills that are colored according to their values
|
||||
along the horizontal or vertical axis.
|
||||
|
||||
|
||||
Sources for color maps
|
||||
----------------------
|
||||
|
||||
Color maps can be user defined by assigning a number of *stops* over the range of 0 to 1. A color
|
||||
is given for each stop, and the in-between values are generated by interpolation.
|
||||
|
||||
When map colors directly represent values, an improperly designed map can obscure detail over
|
||||
certain ranges of values, while creating false detail in others. PyQtGraph includes the
|
||||
perceptually uniform color maps provided by the
|
||||
`Colorcet project <https://colorcet.holoviz.org/>`_. Color maps can also be imported from the
|
||||
``colorcet`` library or from ``matplotlib``, if either of these is installed.
|
||||
|
||||
To see all available color maps, please run the `ColorMap` demonstration available in the suite of
|
||||
:ref:`examples`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
False color display of a 2D data set. Display levels are controlled by
|
||||
a :class:`ColorBarItem <pyqtgraph.ColorBarItem>`:
|
||||
|
||||
.. literalinclude:: images/gen_example_false_color_image.py
|
||||
:lines: 18-28
|
||||
:dedent: 8
|
||||
|
||||
Using QtGui.QPen and QtGui.QBrush to color plots according to the plotted value:
|
||||
|
||||
.. literalinclude:: images/gen_example_gradient_plot.py
|
||||
:lines: 16-33
|
||||
:dedent: 8
|
||||
|
||||
.. image::
|
||||
images/example_false_color_image.png
|
||||
:width: 49%
|
||||
:alt: Example of a false color image
|
||||
|
||||
.. image::
|
||||
images/example_gradient_plot.png
|
||||
:width: 49%
|
||||
:alt: Example of drawing and filling plots with gradients
|
||||
|
||||
The use of color maps is also demonstrated in the `ImageView`, `Color Gradient Plots` and `ColorBarItem`
|
||||
:ref:`examples`.
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. autofunction:: pyqtgraph.colormap.listMaps
|
||||
|
||||
.. autofunction:: pyqtgraph.colormap.get
|
||||
|
||||
.. autofunction:: pyqtgraph.colormap.getFromMatplotlib
|
||||
|
||||
.. autofunction:: pyqtgraph.colormap.getFromColorcet
|
||||
|
||||
|
||||
|
||||
.. autoclass:: pyqtgraph.ColorMap
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.ColorMap.__init__
|
||||
|
||||
|
@ -41,23 +41,33 @@ Export Formats
|
||||
Exporting from the API
|
||||
----------------------
|
||||
|
||||
To export a file programatically, follow this example::
|
||||
To export a file programatically, follow this example:
|
||||
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.exporters
|
||||
.. code-block:: python
|
||||
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.exporters
|
||||
|
||||
# generate something to export
|
||||
plt = pg.plot([1,5,2,4,3])
|
||||
# generate something to export
|
||||
plt = pg.plot([1,5,2,4,3])
|
||||
|
||||
# create an exporter instance, as an argument give it
|
||||
# the item you wish to export
|
||||
exporter = pg.exporters.ImageExporter(plt.plotItem)
|
||||
# create an exporter instance, as an argument give it
|
||||
# the item you wish to export
|
||||
exporter = pg.exporters.ImageExporter(plt.plotItem)
|
||||
|
||||
# set export parameters if needed
|
||||
exporter.parameters()['width'] = 100 # (note this also affects height parameter)
|
||||
# set export parameters if needed
|
||||
exporter.parameters()['width'] = 100 # (note this also affects height parameter)
|
||||
|
||||
# save to file
|
||||
exporter.export('fileName.png')
|
||||
# save to file
|
||||
exporter.export('fileName.png')
|
||||
|
||||
To export the overall layout of a GraphicsLayoutWidget `grl`, the exporter initialization is
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
exporter = pg.exporters.ImageExporter( grl.scene() )
|
||||
|
||||
instead.
|
||||
|
||||
|
||||
Exporting 3D Graphics
|
||||
@ -69,5 +79,3 @@ generate an image from a GLViewWidget by using QGLWidget.grabFrameBuffer or QGLW
|
||||
glview.grabFrameBuffer().save('fileName.png')
|
||||
|
||||
See the Qt documentation for more information.
|
||||
|
||||
|
||||
|
7
doc/source/graphicsItems/colorbaritem.rst
Normal file
7
doc/source/graphicsItems/colorbaritem.rst
Normal file
@ -0,0 +1,7 @@
|
||||
ColorBarItem
|
||||
============
|
||||
|
||||
.. autoclass:: pyqtgraph.ColorBarItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.ColorBarItem.__init__
|
@ -12,6 +12,7 @@ Contents:
|
||||
plotdataitem
|
||||
plotitem
|
||||
imageitem
|
||||
colorbaritem
|
||||
pcolormeshitem
|
||||
graphitem
|
||||
viewbox
|
||||
|
BIN
doc/source/images/example_false_color_image.png
Normal file
BIN
doc/source/images/example_false_color_image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
doc/source/images/example_gradient_plot.png
Normal file
BIN
doc/source/images/example_gradient_plot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
45
doc/source/images/gen_example_false_color_image.py
Normal file
45
doc/source/images/gen_example_false_color_image.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""
|
||||
generates 'example_false_color_image.png'
|
||||
"""
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.exporters as exp
|
||||
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, mkQApp
|
||||
|
||||
class MainWindow(pg.GraphicsLayoutWidget):
|
||||
""" example application main window """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.resize(420,400)
|
||||
self.show()
|
||||
|
||||
plot = self.addPlot() # title="non-interactive")
|
||||
|
||||
# prepare demonstration data:
|
||||
data = np.fromfunction(lambda i, j: (1+0.3*np.sin(i)) * (i)**2 + (j)**2, (100, 100))
|
||||
noisy_data = data * (1 + 0.2 * np.random.random(data.shape) )
|
||||
|
||||
# Example: False color image with interactive level adjustment
|
||||
img = pg.ImageItem(image=noisy_data) # create monochrome image from demonstration data
|
||||
plot.addItem( img ) # add to PlotItem 'plot'
|
||||
cm = pg.colormap.get('CET-L9') # prepare a linear color map
|
||||
bar = pg.ColorBarItem( values= (0, 20_000), cmap=cm ) # prepare interactive color bar
|
||||
# Have ColorBarItem control colors of img and appear in 'plot':
|
||||
bar.setImageItem( img, insert_in=plot )
|
||||
|
||||
self.timer = pg.QtCore.QTimer( singleShot=True )
|
||||
self.timer.timeout.connect(self.export)
|
||||
self.timer.start(100)
|
||||
|
||||
def export(self):
|
||||
print('exporting')
|
||||
exporter = exp.ImageExporter(self.scene())
|
||||
exporter.parameters()['width'] = 420
|
||||
exporter.export('example_false_color_image.png')
|
||||
|
||||
mkQApp("False color image example")
|
||||
main_window = MainWindow()
|
||||
|
||||
## Start Qt event loop
|
||||
if __name__ == '__main__':
|
||||
mkQApp().exec_()
|
55
doc/source/images/gen_example_gradient_plot.py
Normal file
55
doc/source/images/gen_example_gradient_plot.py
Normal file
@ -0,0 +1,55 @@
|
||||
"""
|
||||
generates 'example_gradient_plot.png'
|
||||
"""
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.exporters as exp
|
||||
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, mkQApp
|
||||
|
||||
class MainWindow(pg.GraphicsLayoutWidget):
|
||||
""" example application main window """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.resize(420,400)
|
||||
self.show()
|
||||
|
||||
# Prepare demonstration data
|
||||
raw = np.linspace(0.0, 2.0, 400)
|
||||
y_data1 = ( (raw+0.1)%1 ) ** 4
|
||||
y_data2 = ( (raw+0.1)%1 ) ** 4 - ( (raw+0.6)%1 ) ** 4
|
||||
|
||||
# Example 1: Gradient pen
|
||||
cm = pg.colormap.get('CET-L17') # prepare a linear color map
|
||||
cm.reverse() # reverse it to put light colors at the top
|
||||
pen = cm.getPen( span=(0.0,1.0), width=5 ) # gradient from blue (y=0) to white (y=1)
|
||||
# plot a curve drawn with a pen colored according to y value:
|
||||
curve1 = pg.PlotDataItem( y=y_data1, pen=pen )
|
||||
|
||||
# Example 2: Gradient brush
|
||||
cm = pg.colormap.get('CET-D1') # prepare a diverging color map
|
||||
cm.setMappingMode('diverging') # set mapping mode
|
||||
brush = cm.getBrush( span=(-1., 1.) ) # gradient from blue at -1 to red at +1
|
||||
# plot a curve that is filled to zero with the gradient brush:
|
||||
curve2 = pg.PlotDataItem( y=y_data2, pen='w', brush=brush, fillLevel=0.0 )
|
||||
|
||||
for idx, curve in enumerate( (curve1, curve2) ):
|
||||
plot = self.addPlot(row=idx, col=0)
|
||||
plot.getAxis('left').setWidth(25)
|
||||
plot.addItem( curve )
|
||||
|
||||
self.timer = pg.QtCore.QTimer( singleShot=True )
|
||||
self.timer.timeout.connect(self.export)
|
||||
self.timer.start(100)
|
||||
|
||||
def export(self):
|
||||
print('exporting')
|
||||
exporter = exp.ImageExporter(self.scene())
|
||||
exporter.parameters()['width'] = 420
|
||||
exporter.export('example_gradient_plot.png')
|
||||
|
||||
mkQApp("Gradient plotting example")
|
||||
main_window = MainWindow()
|
||||
|
||||
## Start Qt event loop
|
||||
if __name__ == '__main__':
|
||||
mkQApp().exec_()
|
147
examples/ColorGradientPlots.py
Normal file
147
examples/ColorGradientPlots.py
Normal file
@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This example demonstrates plotting with color gradients.
|
||||
It also shows multiple plots with timed rolling updates
|
||||
"""
|
||||
# Add path to library (just for examples; you do not need this)
|
||||
import initExample
|
||||
|
||||
import numpy as np
|
||||
import time
|
||||
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, mkQApp
|
||||
import pyqtgraph as pg
|
||||
|
||||
class DataSource(object):
|
||||
""" source of buffered demonstration data """
|
||||
def __init__(self, sample_rate=200., signal_period=0.55, negative_period=None, max_length=300):
|
||||
""" prepare, but don't start yet """
|
||||
self.rate = sample_rate
|
||||
self.period = signal_period
|
||||
self.neg_period = negative_period
|
||||
self.start_time = 0.
|
||||
self.sample_idx = 0 # number of next sample to be taken
|
||||
|
||||
def start(self, timestamp):
|
||||
""" start acquiring simulated data """
|
||||
self.start_time = timestamp
|
||||
self.sample_idx = 0
|
||||
|
||||
def get_data(self, timestamp, max_length=6000):
|
||||
""" return all data acquired since last get_data call """
|
||||
next_idx = int( (timestamp - self.start_time) * self.rate )
|
||||
if next_idx - self.sample_idx > max_length:
|
||||
self.sample_idx = next_idx - max_length # catch up if needed
|
||||
# create some mildly intersting data:
|
||||
sample_phases = np.arange( self.sample_idx, next_idx, dtype=np.float64 )
|
||||
self.sample_idx = next_idx
|
||||
|
||||
sample_phase_pos = sample_phases / (self.period*self.rate)
|
||||
sample_phase_pos %= 1.0
|
||||
if self.neg_period is None:
|
||||
return sample_phase_pos**4
|
||||
sample_phase_neg = sample_phases / (self.neg_period*self.rate)
|
||||
sample_phase_neg %= 1.0
|
||||
return sample_phase_pos**4 - sample_phase_neg**4
|
||||
|
||||
class MainWindow(pg.GraphicsLayoutWidget):
|
||||
""" example application main window """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle('pyqtgraph example: gradient plots')
|
||||
self.resize(800,800)
|
||||
self.show()
|
||||
|
||||
layout = self # we are using a GraphicsLayoutWidget as main window for convenience
|
||||
cm = pg.colormap.get('CET-L17')
|
||||
cm.reverse()
|
||||
pen0 = cm.getPen( span=(0.0,1.0), width=5 )
|
||||
curve0 = pg.PlotDataItem(pen=pen0 )
|
||||
comment0 = 'Clipped color map applied to vertical axis'
|
||||
|
||||
cm = pg.colormap.get('CET-D1')
|
||||
cm.setMappingMode('diverging')
|
||||
brush = cm.getBrush( span=(-1., 1.), orientation='vertical' )
|
||||
curve1 = pg.PlotDataItem(pen='w', brush=brush, fillLevel=0.0 )
|
||||
comment1 = 'Diverging vertical color map used as brush'
|
||||
|
||||
cm = pg.colormap.get('CET-L17')
|
||||
cm.setMappingMode('mirror')
|
||||
pen2 = cm.getPen( span=(400.0,600.0), width=5, orientation='horizontal' )
|
||||
curve2 = pg.PlotDataItem(pen=pen2 )
|
||||
comment2 = 'Mirrored color map applied to horizontal axis'
|
||||
|
||||
cm = pg.colormap.get('CET-C2')
|
||||
cm.setMappingMode('repeat')
|
||||
pen3 = cm.getPen( span=(100, 200), width=5, orientation='horizontal' )
|
||||
curve3 = pg.PlotDataItem(pen=pen3 ) # vertical diverging fill
|
||||
comment3 = 'Repeated color map applied to horizontal axis'
|
||||
|
||||
curves = (curve0, curve1, curve2, curve3)
|
||||
comments = (comment0, comment1, comment2, comment3)
|
||||
|
||||
length = int( 3.0 * 200. ) # length of display in samples
|
||||
self.top_plot = None
|
||||
for idx, (curve, comment) in enumerate( zip(curves,comments) ):
|
||||
plot = layout.addPlot(row=idx+1, col=0)
|
||||
text = pg.TextItem( comment, anchor=(0,1) )
|
||||
text.setPos(0.,1.)
|
||||
if self.top_plot is None:
|
||||
self.top_plot = plot
|
||||
else:
|
||||
plot.setXLink( self.top_plot )
|
||||
plot.addItem( curve )
|
||||
plot.addItem( text )
|
||||
plot.setXRange( 0, length )
|
||||
if idx != 1: plot.setYRange( 0. , 1.1 )
|
||||
else : plot.setYRange( -1. , 1.2 ) # last plot include positive/negative data
|
||||
|
||||
self.traces = (
|
||||
{'crv': curve0, 'buf': np.zeros( length ), 'ptr':0, 'ds': DataSource( signal_period=0.55 ) },
|
||||
{'crv': curve1, 'buf': np.zeros( length ), 'ptr':0, 'ds': DataSource( signal_period=0.61, negative_period=0.55 ) },
|
||||
{'crv': curve2, 'buf': np.zeros( length ), 'ptr':0, 'ds': DataSource( signal_period=0.65 ) },
|
||||
{'crv': curve3, 'buf': np.zeros( length ), 'ptr':0, 'ds': DataSource( signal_period=0.52 ) },
|
||||
)
|
||||
self.timer = QtCore.QTimer(timerType=QtCore.Qt.PreciseTimer)
|
||||
self.timer.timeout.connect(self.update)
|
||||
timestamp = time.perf_counter()
|
||||
for dic in self.traces:
|
||||
dic['ds'].start( timestamp )
|
||||
self.last_update = time.perf_counter()
|
||||
self.mean_dt = None
|
||||
self.timer.start(33)
|
||||
|
||||
def update(self):
|
||||
""" called by timer at 30 Hz """
|
||||
timestamp = time.perf_counter()
|
||||
# measure actual update rate:
|
||||
dt = timestamp - self.last_update
|
||||
if self.mean_dt is None:
|
||||
self.mean_dt = dt
|
||||
else:
|
||||
self.mean_dt = 0.95 * self.mean_dt + 0.05 * dt # average over fluctuating measurements
|
||||
self.top_plot.setTitle(
|
||||
'refresh: {:0.1f}ms -> {:0.1f} fps'.format( 1000*self.mean_dt, 1/self.mean_dt )
|
||||
)
|
||||
# handle rolling buffer:
|
||||
self.last_update = timestamp
|
||||
for dic in self.traces:
|
||||
new_data = dic['ds'].get_data( timestamp )
|
||||
idx_a = dic['ptr']
|
||||
idx_b = idx_a + len( new_data )
|
||||
len_buffer = dic['buf'].shape[0]
|
||||
if idx_b < len_buffer: # data does not cross buffer boundary
|
||||
dic['buf'][idx_a:idx_b] = new_data
|
||||
else: # part of the new data needs to roll over to beginning of buffer
|
||||
len_1 = len_buffer - idx_a # this many elements still fit
|
||||
dic['buf'][idx_a:idx_a+len_1] = new_data[:len_1] # first part of data at end
|
||||
idx_b = len(new_data) - len_1
|
||||
dic['buf'][0:idx_b] = new_data[len_1:] # second part of data at re-start
|
||||
dic['ptr'] = idx_b
|
||||
dic['crv'].setData( dic['buf'] )
|
||||
|
||||
mkQApp("Gradient plotting example")
|
||||
main_window = MainWindow()
|
||||
|
||||
## Start Qt event loop
|
||||
if __name__ == '__main__':
|
||||
mkQApp().exec_()
|
@ -13,6 +13,7 @@ examples = OrderedDict([
|
||||
('Timestamps on x axis', 'DateAxisItem.py'),
|
||||
('Image Analysis', 'imageAnalysis.py'),
|
||||
('Color Maps', 'colorMaps.py'),
|
||||
('Color Gradient Plots', 'ColorGradientPlots.py'),
|
||||
('ViewBox Features', Namespace(filename='ViewBoxFeatures.py', recommended=True)),
|
||||
('Dock widgets', 'dockarea.py'),
|
||||
('Console', 'ConsoleWidget.py'),
|
||||
|
@ -9,15 +9,22 @@ _mapCache = {}
|
||||
|
||||
def listMaps(source=None):
|
||||
"""
|
||||
Warning, highly experimental, subject to change.
|
||||
.. warning:: Experimental, subject to change.
|
||||
|
||||
List available color maps
|
||||
=============== =================================================================
|
||||
**Arguments:**
|
||||
source 'matplotlib' lists maps that can be imported from MatPlotLib
|
||||
'colorcet' lists maps that can be imported from ColorCET
|
||||
otherwise local maps are listed
|
||||
=============== =================================================================
|
||||
List available color maps.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
source: str, optional
|
||||
Color map source. If omitted, locally stored maps are listed. Otherwise
|
||||
|
||||
- 'matplotlib' lists maps that can be imported from Matplotlib
|
||||
- 'colorcet' lists maps that can be imported from ColorCET
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of str
|
||||
Known color map names.
|
||||
"""
|
||||
if source is None:
|
||||
pathname = path.join(path.dirname(__file__), 'colors','maps')
|
||||
@ -32,7 +39,7 @@ def listMaps(source=None):
|
||||
import matplotlib.pyplot as mpl_plt
|
||||
list_of_maps = mpl_plt.colormaps()
|
||||
return list_of_maps
|
||||
except ModuleNotFoundError:
|
||||
except ModuleNotFoundError:
|
||||
return []
|
||||
elif source.lower() == 'colorcet':
|
||||
try:
|
||||
@ -40,23 +47,34 @@ def listMaps(source=None):
|
||||
list_of_maps = list( colorcet.palette.keys() )
|
||||
list_of_maps.sort()
|
||||
return list_of_maps
|
||||
except ModuleNotFoundError:
|
||||
except ModuleNotFoundError:
|
||||
return []
|
||||
return []
|
||||
return []
|
||||
|
||||
|
||||
def get(name, source=None, skipCache=False):
|
||||
"""
|
||||
Warning, highly experimental, subject to change.
|
||||
.. warning:: Experimental, subject to change.
|
||||
|
||||
Returns a ColorMap object from a local definition or imported from another library
|
||||
=============== =================================================================
|
||||
**Arguments:**
|
||||
name Name of color map. Can be a path to a defining file.
|
||||
source 'matplotlib' imports a map defined by Matplotlib
|
||||
'colorcet' imports a maps defined by ColorCET
|
||||
otherwise local data is used
|
||||
=============== =================================================================
|
||||
Returns a ColorMap object from a local definition or imported from another library.
|
||||
The generated ColorMap objects are cached for fast repeated access.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
Name of color map. In addition to the included maps, this can also
|
||||
be a path to a file in the local folder. See the files in the
|
||||
``pyqtgraph/colors/maps/`` folder for examples of the format.
|
||||
source: str, optional
|
||||
If omitted, a locally stored map is returned. Otherwise
|
||||
|
||||
- 'matplotlib' imports a map defined by Matplotlib.
|
||||
- 'colorcet' imports a map defined by ColorCET.
|
||||
|
||||
skipCache: bool, optional
|
||||
If `skipCache=True`, the internal cache is skipped and a new
|
||||
ColorMap object is generated. This can load an unaltered copy
|
||||
when the previous ColorMap object has been modified.
|
||||
"""
|
||||
if not skipCache and name in _mapCache:
|
||||
return _mapCache[name]
|
||||
@ -95,7 +113,7 @@ def _getFromFile(name):
|
||||
color_tuple = tuple( [ int(255*float(c)+0.5) for c in comp ] )
|
||||
else:
|
||||
hex_str = parts[0]
|
||||
if hex_str[0] == '#':
|
||||
if hex_str[0] == '#':
|
||||
hex_str = hex_str[1:] # strip leading #
|
||||
if len(hex_str) < 3: continue # not enough information
|
||||
if len(hex_str) == 3: # parse as abbreviated RGB
|
||||
@ -109,13 +127,16 @@ def _getFromFile(name):
|
||||
# end of line reading loop
|
||||
# end of open
|
||||
cm = ColorMap(
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
color=color_list) #, names=color_names)
|
||||
_mapCache[name] = cm
|
||||
return cm
|
||||
|
||||
def getFromMatplotlib(name):
|
||||
""" import colormap from matplotlib definition """
|
||||
"""
|
||||
Generates a ColorMap object from a Matplotlib definition.
|
||||
Same as ``colormap.get(name, source='matplotlib')``.
|
||||
"""
|
||||
# inspired and informed by "mpl_cmaps_in_ImageItem.py", published by Sebastian Hoefer at
|
||||
# https://github.com/honkomonk/pyqtgraph_sandbox/blob/master/mpl_cmaps_in_ImageItem.py
|
||||
try:
|
||||
@ -147,7 +168,7 @@ def getFromMatplotlib(name):
|
||||
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)
|
||||
cm = 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 )
|
||||
@ -156,7 +177,7 @@ def getFromMatplotlib(name):
|
||||
return cm
|
||||
|
||||
def getFromColorcet(name):
|
||||
""" import colormap from colorcet definition """
|
||||
""" Generates a ColorMap object from a colorcet definition. Same as ``colormap.get(name, source='colorcet')``. """
|
||||
try:
|
||||
import colorcet
|
||||
except ModuleNotFoundError:
|
||||
@ -165,14 +186,14 @@ def getFromColorcet(name):
|
||||
color_list = []
|
||||
for hex_str in color_strings:
|
||||
if hex_str[0] != '#': continue
|
||||
if len(hex_str) != 7:
|
||||
if len(hex_str) != 7:
|
||||
raise ValueError('Invalid color string '+str(hex_str)+' in colorcet import.')
|
||||
color_tuple = tuple( bytes.fromhex( hex_str[1:] ) )
|
||||
color_list.append( color_tuple )
|
||||
if len(color_list) == 0:
|
||||
if len(color_list) == 0:
|
||||
return None
|
||||
cm = ColorMap(
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
color=color_list) #, names=color_names)
|
||||
_mapCache[name] = cm
|
||||
return cm
|
||||
@ -180,22 +201,20 @@ def getFromColorcet(name):
|
||||
|
||||
class ColorMap(object):
|
||||
"""
|
||||
A ColorMap defines a relationship between a scalar value and a range of colors.
|
||||
ColorMaps are commonly used for false-coloring monochromatic images, coloring
|
||||
scatter-plot points, and coloring surface plots by height.
|
||||
ColorMap(pos, color, mapping=ColorMap.CLIP)
|
||||
|
||||
Each color map is defined by a set of colors, each corresponding to a
|
||||
particular scalar value. For example:
|
||||
ColorMap stores a mapping of specific data values to colors, for example:
|
||||
|
||||
| 0.0 -> black
|
||||
| 0.2 -> red
|
||||
| 0.6 -> yellow
|
||||
| 1.0 -> white
|
||||
| 0.0 → black
|
||||
| 0.2 → red
|
||||
| 0.6 → yellow
|
||||
| 1.0 → white
|
||||
|
||||
The colors for intermediate values are determined by interpolating between
|
||||
The colors for intermediate values are determined by interpolating between
|
||||
the two nearest colors in RGB color space.
|
||||
|
||||
To provide user-defined color mappings, see :class:`GradientWidget <pyqtgraph.GradientWidget>`.
|
||||
A ColorMap object provides access to the interpolated colors by indexing with a float value:
|
||||
``cm[0.5]`` returns a QColor corresponding to the center of ColorMap `cm`.
|
||||
"""
|
||||
|
||||
## mapping modes
|
||||
@ -221,29 +240,26 @@ class ColorMap(object):
|
||||
|
||||
def __init__(self, pos, color, mapping=CLIP, mode=None): #, names=None):
|
||||
"""
|
||||
=============== =======================================================================
|
||||
**Arguments:**
|
||||
pos Array of positions where each color is defined
|
||||
color Array of colors.
|
||||
Values are interpreted via
|
||||
:func:`mkColor() <pyqtgraph.mkColor>`.
|
||||
mapping Mapping mode (ColorMap.CLIP, REPEAT, MIRROR or DIVERGING)
|
||||
controlling mapping of relative index to color. String representations
|
||||
'clip', 'repeat', 'mirror' or 'diverging' are also accepted.
|
||||
CLIP maps colors to [0.0;1.0] and is the default.
|
||||
REPEAT maps colors to repeating intervals [0.0;1.0];[1.0-2.0],...
|
||||
MIRROR maps colors to [0.0;-1.0] and [0.0;+1.0] identically
|
||||
DIVERGING maps colors to [-1.0;+1.0]
|
||||
=============== =======================================================================
|
||||
__init__(pos, color, mapping=ColorMap.CLIP)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pos: array_like of float in range 0 to 1
|
||||
Assigned positions of specified colors
|
||||
color: array_like of colors
|
||||
List of colors, interpreted via :func:`mkColor() <pyqtgraph.mkColor>`.
|
||||
mapping: str or int, optional
|
||||
Controls how values outside the 0 to 1 range are mapped to colors.
|
||||
See :func:`setMappingMode() <ColorMap.setMappingMode>` for details.
|
||||
|
||||
The default of `ColorMap.CLIP` continues to show
|
||||
the colors assigned to 0 and 1 for all values below or above this range, respectively.
|
||||
"""
|
||||
if mode is not None:
|
||||
warnings.warn(
|
||||
"'mode' argument is deprecated and does nothing.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
if isinstance(mapping, str):
|
||||
mapping = self.enumMap[mapping.lower()]
|
||||
|
||||
self.pos = np.array(pos)
|
||||
order = np.argsort(self.pos)
|
||||
self.pos = self.pos[order]
|
||||
@ -252,39 +268,86 @@ class ColorMap(object):
|
||||
axis = -1,
|
||||
arr = color,
|
||||
)[order]
|
||||
|
||||
self.mapping_mode = self.CLIP # default to CLIP mode
|
||||
if mapping is not None:
|
||||
self.setMappingMode( mapping )
|
||||
self.stopsCache = {}
|
||||
|
||||
self.mapping_mode = self.CLIP # default to CLIP mode
|
||||
def setMappingMode(self, mapping):
|
||||
"""
|
||||
Sets the way that values outside of the range 0 to 1 are mapped to colors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mapping: int or str
|
||||
Sets mapping mode to
|
||||
|
||||
- `ColorMap.CLIP` or 'clip': Values are clipped to the range 0 to 1. ColorMap defaults to this.
|
||||
- `ColorMap.REPEAT` or 'repeat': Colors repeat cyclically, i.e. range 1 to 2 repeats the colors for 0 to 1.
|
||||
- `ColorMap.MIRROR` or 'mirror': The range 0 to -1 uses same colors (in reverse order) as 0 to 1.
|
||||
- `ColorMap.DIVERGING` or 'diverging': Colors are mapped to -1 to 1 such that the central value appears at 0.
|
||||
"""
|
||||
if isinstance(mapping, str):
|
||||
mapping = self.enumMap[mapping.lower()]
|
||||
if mapping in [self.CLIP, self.REPEAT, self.DIVERGING, self.MIRROR]:
|
||||
self.mapping_mode = mapping # only allow defined values
|
||||
self.stopsCache = {}
|
||||
else:
|
||||
raise ValueError("Undefined mapping type '{:s}'".format(str(mapping)) )
|
||||
|
||||
def __getitem__(self, key):
|
||||
""" Convenient shorthand access to palette colors """
|
||||
if isinstance(key, int): # access by color index
|
||||
if isinstance(key, int): # access by color index
|
||||
return self.getByIndex(key)
|
||||
# otherwise access by map
|
||||
try: # accept any numerical format that converts to float
|
||||
float_idx = float(key)
|
||||
float_idx = float(key)
|
||||
return self.mapToQColor(float_idx)
|
||||
except ValueError: pass
|
||||
return None
|
||||
|
||||
def reverse(self):
|
||||
"""
|
||||
Reverses the color map, so that the color assigned to a value of 1 now appears at 0 and vice versa.
|
||||
This is convenient to adjust imported color maps.
|
||||
"""
|
||||
self.pos = 1.0 - np.flip( self.pos )
|
||||
self.color = np.flip( self.color, axis=0 )
|
||||
self.stopsCache = {}
|
||||
|
||||
def map(self, data, mode=BYTE):
|
||||
"""
|
||||
Return an array of colors corresponding to the values in *data*.
|
||||
Data must be either a scalar position or an array (any shape) of positions.
|
||||
|
||||
The *mode* argument determines the type of data returned:
|
||||
map(data, mode=ColorMap.BYTE)
|
||||
|
||||
=========== ===============================================================
|
||||
byte (default) Values are returned as 0-255 unsigned bytes.
|
||||
float Values are returned as 0.0-1.0 floats.
|
||||
qcolor Values are returned as an array of QColor objects.
|
||||
=========== ===============================================================
|
||||
Returns an array of colors corresponding to a single value or an array of values.
|
||||
Data must be either a scalar position or an array (any shape) of positions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data: float or array_like of float
|
||||
Scalar value(s) to be mapped to colors
|
||||
|
||||
mode: str or int, optional
|
||||
Determines return format:
|
||||
|
||||
- `ColorMap.BYTE` or 'byte': Colors are returned as 0-255 unsigned bytes. (default)
|
||||
- `ColorMap.FLOAT` or 'float': Colors are returned as 0.0-1.0 floats.
|
||||
- `ColorMap.QCOLOR` or 'qcolor': Colors are returned as QColor objects.
|
||||
|
||||
Returns
|
||||
-------
|
||||
array of color.dtype
|
||||
for `ColorMap.BYTE` or `ColorMap.FLOAT`:
|
||||
|
||||
RGB values for each `data` value, arranged in the same shape as `data`.
|
||||
list of QColor objects
|
||||
for `ColorMap.QCOLOR`:
|
||||
|
||||
Colors for each `data` value as Qcolor objects.
|
||||
"""
|
||||
if isinstance(mode, str):
|
||||
mode = self.enumMap[mode.lower()]
|
||||
|
||||
|
||||
if mode == self.QCOLOR:
|
||||
pos, color = self.getStops(self.BYTE)
|
||||
else:
|
||||
@ -316,7 +379,7 @@ class ColorMap(object):
|
||||
return [QtGui.QColor(*x) for x in interp]
|
||||
else:
|
||||
return interp
|
||||
|
||||
|
||||
def mapToQColor(self, data):
|
||||
"""Convenience function; see :func:`map() <pyqtgraph.ColorMap.map>`."""
|
||||
return self.map(data, mode=self.QCOLOR)
|
||||
@ -330,37 +393,115 @@ class ColorMap(object):
|
||||
return self.map(data, mode=self.FLOAT)
|
||||
|
||||
def getByIndex(self, idx):
|
||||
"""Retrieve palette QColor by index"""
|
||||
"""Retrieve a QColor by the index of the stop it is assigned to."""
|
||||
return QtGui.QColor( *self.color[idx] )
|
||||
|
||||
def getGradient(self, p1=None, p2=None):
|
||||
"""Return a QLinearGradient object spanning from QPoints p1 to p2."""
|
||||
if p1 == None:
|
||||
"""
|
||||
Returns a QtGui.QLinearGradient corresponding to this ColorMap.
|
||||
The span and orientiation is given by two points in plot coordinates.
|
||||
|
||||
When no parameters are given for `p1` and `p2`, the gradient is mapped to the
|
||||
`y` coordinates 0 to 1, unless the color map is defined for a more limited range.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
p1: QtCore.QPointF, default (0,0)
|
||||
Starting point (value 0) of the gradient.
|
||||
p2: QtCore.QPointF, default (dy,0)
|
||||
End point (value 1) of the gradient. Default parameter `dy` is the span of ``max(pos) - min(pos)``
|
||||
over which the color map is defined, typically `dy=1`.
|
||||
"""
|
||||
if p1 is None:
|
||||
p1 = QtCore.QPointF(0,0)
|
||||
if p2 == None:
|
||||
if p2 is None:
|
||||
p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0)
|
||||
g = QtGui.QLinearGradient(p1, p2)
|
||||
|
||||
grad = QtGui.QLinearGradient(p1, p2)
|
||||
|
||||
pos, color = self.getStops(mode=self.BYTE)
|
||||
color = [QtGui.QColor(*x) for x in color]
|
||||
g.setStops(list(zip(pos, color)))
|
||||
return g
|
||||
if self.mapping_mode == self.MIRROR:
|
||||
pos_n = (1. - np.flip(pos)) / 2
|
||||
col_n = np.flip( color, axis=0 )
|
||||
pos_p = (1. + pos) / 2
|
||||
col_p = color
|
||||
pos = np.concatenate( (pos_n, pos_p) )
|
||||
color = np.concatenate( (col_n, col_p) )
|
||||
grad.setStops(list(zip(pos, color)))
|
||||
if self.mapping_mode == self.REPEAT:
|
||||
grad.setSpread( QtGui.QGradient.RepeatSpread )
|
||||
return grad
|
||||
|
||||
def getBrush(self, span=(0.,1.), orientation='vertical'):
|
||||
"""
|
||||
Returns a QBrush painting with the color map applied over the selected span of plot values.
|
||||
When the mapping mode is set to `ColorMap.MIRROR`, the selected span includes the color map twice,
|
||||
first in reversed order and then normal.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
span : tuple (min, max), default (0.0, 1.0)
|
||||
Span of data values covered by the gradient:
|
||||
|
||||
- Color map value 0.0 will appear at `min`,
|
||||
- Color map value 1.0 will appear at `max`.
|
||||
|
||||
orientation : str, default 'vertical'
|
||||
Orientiation of the gradient:
|
||||
|
||||
- 'vertical': `span` corresponds to the `y` coordinate.
|
||||
- 'horizontal': `span` corresponds to the `x` coordinate.
|
||||
"""
|
||||
if orientation == 'vertical':
|
||||
grad = self.getGradient( p1=QtCore.QPointF(0.,span[0]), p2=QtCore.QPointF(0.,span[1]) )
|
||||
elif orientation == 'horizontal':
|
||||
grad = self.getGradient( p1=QtCore.QPointF(span[0],0.), p2=QtCore.QPointF(span[1],0.) )
|
||||
else:
|
||||
raise ValueError("Orientation must be 'vertical' or 'horizontal'")
|
||||
return QtGui.QBrush(grad)
|
||||
|
||||
def getPen(self, span=(0.,1.), orientation='vertical', width=1.0):
|
||||
"""
|
||||
Returns a QPen that draws according to the color map based on vertical or horizontal position.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
span : tuple (min, max), default (0.0, 1.0)
|
||||
Span of the data values covered by the gradient:
|
||||
|
||||
- Color map value 0.0 will appear at `min`.
|
||||
- Color map value 1.0 will appear at `max`.
|
||||
|
||||
orientation : str, default 'vertical'
|
||||
Orientiation of the gradient:
|
||||
|
||||
- 'vertical' creates a vertical gradient, where `span` corresponds to the `y` coordinate.
|
||||
- 'horizontal' creates a horizontal gradient, where `span` correspnds to the `x` coordinate.
|
||||
|
||||
width : int or float
|
||||
Width of the pen in pixels on screen.
|
||||
"""
|
||||
brush = self.getBrush( span=span, orientation=orientation )
|
||||
pen = QtGui.QPen(brush, width)
|
||||
pen.setCosmetic(True)
|
||||
return pen
|
||||
|
||||
def getColors(self, mode=None):
|
||||
"""Return list of all color stops converted to the specified mode.
|
||||
If mode is None, then no conversion is done."""
|
||||
"""Returns a list of all color stops, converted to the specified mode.
|
||||
If `mode` is None, no conversion is performed.
|
||||
"""
|
||||
if isinstance(mode, str):
|
||||
mode = self.enumMap[mode.lower()]
|
||||
|
||||
|
||||
color = self.color
|
||||
if mode in [self.BYTE, self.QCOLOR] and color.dtype.kind == 'f':
|
||||
color = (color * 255).astype(np.ubyte)
|
||||
elif mode == self.FLOAT and color.dtype.kind != 'f':
|
||||
color = color.astype(float) / 255.
|
||||
|
||||
|
||||
if mode == self.QCOLOR:
|
||||
color = [QtGui.QColor(*x) for x in color]
|
||||
|
||||
|
||||
return color
|
||||
|
||||
def getStops(self, mode):
|
||||
@ -374,20 +515,39 @@ class ColorMap(object):
|
||||
self.stopsCache[mode] = (self.pos, color)
|
||||
return self.stopsCache[mode]
|
||||
|
||||
def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte'):
|
||||
def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode=BYTE):
|
||||
"""
|
||||
Return an RGB(A) lookup table (ndarray).
|
||||
|
||||
=============== =============================================================================
|
||||
**Arguments:**
|
||||
start The starting value in the lookup table (default=0.0)
|
||||
stop The final value in the lookup table (default=1.0)
|
||||
nPts The number of points in the returned lookup table.
|
||||
alpha True, False, or None - Specifies whether or not alpha values are included
|
||||
in the table. If alpha is None, it will be automatically determined.
|
||||
mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'.
|
||||
See :func:`map() <pyqtgraph.ColorMap.map>`.
|
||||
=============== =============================================================================
|
||||
getLookupTable(start=0.0, stop=1.0, nPts=512, alpha=None, mode=ColorMap.BYTE)
|
||||
|
||||
Returns an equally-spaced lookup table of RGB(A) values created
|
||||
by interpolating the specified color stops.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start: float, default=0.0
|
||||
The starting value in the lookup table
|
||||
stop: float, default=1.0
|
||||
The final value in the lookup table
|
||||
nPts: int, default is 512
|
||||
The number of points in the returned lookup table.
|
||||
alpha: True, False, or None
|
||||
Specifies whether or not alpha values are included in the table.
|
||||
If alpha is None, it will be automatically determined.
|
||||
mode: int or str, default is `ColorMap.BYTE`
|
||||
Determines return type as described in :func:`map() <pyqtgraph.ColorMap.map>`, can be
|
||||
either `ColorMap.BYTE` (0 to 255), `ColorMap.FLOAT` (0.0 to 1.0) or `ColorMap.QColor`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
array of color.dtype
|
||||
for `ColorMap.BYTE` or `ColorMap.FLOAT`:
|
||||
|
||||
RGB values for each `data` value, arranged in the same shape as `data`.
|
||||
If alpha values are included the array has shape (`nPts`, 4), otherwise (`nPts`, 3).
|
||||
list of QColor objects
|
||||
for `ColorMap.QCOLOR`:
|
||||
|
||||
Colors for each `data` value as QColor objects.
|
||||
"""
|
||||
if isinstance(mode, str):
|
||||
mode = self.enumMap[mode.lower()]
|
||||
@ -404,13 +564,13 @@ class ColorMap(object):
|
||||
return table
|
||||
|
||||
def usesAlpha(self):
|
||||
"""Return True if any stops have an alpha < 255"""
|
||||
"""Returns `True` if any stops have assigned colors with alpha < 255."""
|
||||
max = 1.0 if self.color.dtype.kind == 'f' else 255
|
||||
return np.any(self.color[:,3] != max)
|
||||
|
||||
def isMapTrivial(self):
|
||||
"""
|
||||
Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0.
|
||||
Returns `True` if the gradient has exactly two stops in it: Black at 0.0 and white at 1.0.
|
||||
"""
|
||||
if len(self.pos) != 2:
|
||||
return False
|
||||
|
Loading…
x
Reference in New Issue
Block a user