Source code for sas.qtgui.Plotting.ColorMap
"""
Allows users to change the range of the current graph
"""
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
import sas.qtgui.path_prepare
import matplotlib as mpl
import numpy
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from sas.qtgui.Plotting.PlotterData import Data2D
from sas.qtgui.Utilities.GuiUtils import formatNumber, DoubleValidator
from .rangeSlider import RangeSlider
DEFAULT_MAP = 'jet'
# Local UI
from sas.qtgui.UI import main_resources_rc
from sas.qtgui.Plotting.UI.ColorMapUI import Ui_ColorMapUI
[docs]class ColorMap(QtWidgets.QDialog, Ui_ColorMapUI):
apply_signal = QtCore.pyqtSignal(tuple, str)
[docs] def __init__(self, parent=None, cmap=None, vmin=0.0, vmax=100.0, data=None):
super(ColorMap, self).__init__()
self.setupUi(self)
assert(isinstance(data, Data2D))
# disable the context help icon
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
self.data = data
self._cmap_orig = self._cmap = cmap if cmap is not None else DEFAULT_MAP
self.all_maps = [m for m in mpl.cm.datad]
self.maps = sorted(m for m in self.all_maps if not m.endswith("_r"))
self.rmaps = sorted(set(self.all_maps) - set(self.maps))
self.vmin = self.vmin_orig = int(vmin)
self.vmax = self.vmax_orig = int(vmax)
# Initialize detector labels
self.initDetectorData()
# Initialize the combo box
self.initMapCombobox()
self.initRangeSlider()
# Add the color map component
self.initColorMap()
# Initialize validators on amplitude textboxes
validator_min = DoubleValidator(self.txtMinAmplitude)
validator_min.setNotation(0)
self.txtMinAmplitude.setValidator(validator_min)
validator_max = DoubleValidator(self.txtMaxAmplitude)
validator_max.setNotation(0)
self.txtMaxAmplitude.setValidator(validator_max)
# Set the initial amplitudes
self.txtMinAmplitude.setText(formatNumber(self.vmin))
self.txtMaxAmplitude.setText(formatNumber(self.vmax))
# Enforce constant size on the widget
self.setFixedSize(self.minimumSizeHint())
# Handle combobox changes
self.cbColorMap.currentIndexChanged.connect(self.onMapIndexChange)
# Handle checkbox changes
self.chkReverse.stateChanged.connect(self.onColorMapReversed)
# Handle the Reset button click
self.buttonBox.button(QtWidgets.QDialogButtonBox.Reset).clicked.connect(self.onReset)
# Handle the Apply button click
self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.onApply)
# Handle the amplitude setup
self.txtMinAmplitude.editingFinished.connect(self.onAmplitudeChange)
self.txtMaxAmplitude.editingFinished.connect(self.onAmplitudeChange)
[docs] def cmap(self):
"""
Getter for the color map
"""
return self._cmap
[docs] def norm(self):
"""
Getter for the color map norm
"""
return (self._norm.vmin, self._norm.vmax)
[docs] def onReset(self):
"""
Respond to the Reset button click
"""
# Go back to original settings
self._cmap = self._cmap_orig
self.vmin = self.vmin_orig
self.vmax = self.vmax_orig
self._norm = mpl.colors.Normalize(vmin=self.vmin, vmax=self.vmax)
self.txtMinAmplitude.setText(formatNumber(self.vmin))
self.txtMaxAmplitude.setText(formatNumber(self.vmax))
self.initMapCombobox()
self.slider.setMinimum(self.vmin)
self.slider.setMaximum(self.vmax)
self.slider.setLowValue(self.vmin)
self.slider.setHighValue(self.vmax)
# Redraw the widget
self.redrawColorBar()
self.canvas.draw()
[docs] def onApply(self):
"""
Respond to the Apply button click.
Send a signal to the plotter with vmin/vmax/cmap for chart update
"""
self.apply_signal.emit(self.norm(), self.cmap())
[docs] def initDetectorData(self):
"""
Fill out the Detector labels
"""
xnpts = len(self.data.x_bins)
ynpts = len(self.data.y_bins)
self.lblWidth.setText(formatNumber(xnpts))
self.lblHeight.setText(formatNumber(ynpts))
xmax = max(self.data.xmin, self.data.xmax)
ymax = max(self.data.ymin, self.data.ymax)
qmax = numpy.sqrt(numpy.power(xmax, 2) + numpy.power(ymax, 2))
self.lblQmax.setText(formatNumber(qmax))
self.lblStopRadius.setText(formatNumber(self.data.xmin))
[docs] def initMapCombobox(self):
"""
Fill out the combo box with all available color maps
"""
if self._cmap in self.rmaps:
maps = self.rmaps
# Assure correct state of the checkbox
self.chkReverse.setChecked(True)
else:
maps = self.maps
# Assure correct state of the checkbox
self.chkReverse.setChecked(False)
self.cbColorMap.addItems(maps)
# Set the default/passed map
self.cbColorMap.setCurrentIndex(self.cbColorMap.findText(self._cmap))
[docs] def initRangeSlider(self):
"""
Create and display the double slider for data range mapping.
"""
self.slider = RangeSlider()
self.slider.setMinimum(self.vmin)
self.slider.setMaximum(self.vmax)
self.slider.setLowValue(self.vmin)
self.slider.setHighValue(self.vmax)
self.slider.setOrientation(QtCore.Qt.Horizontal)
self.slider_label = QtWidgets.QLabel()
self.slider_label.setText("Drag the sliders to adjust color range.")
def set_vmin(value):
self.vmin = int(value)
self.txtMinAmplitude.setText(str(value))
self.updateMap()
def set_vmax(value):
self.vmax = int(value)
self.txtMaxAmplitude.setText(str(value))
self.updateMap()
self.slider.lowValueChanged.connect(set_vmin)
self.slider.highValueChanged.connect(set_vmax)
[docs] def updateMap(self):
self._norm = mpl.colors.Normalize(vmin=self.vmin, vmax=self.vmax)
self.redrawColorBar()
self.canvas.draw()
[docs] def initColorMap(self):
"""
Prepare and display the color map
"""
self.fig = mpl.figure.Figure(figsize=(4, 1))
self.ax1 = self.fig.add_axes([0.05, 0.65, 0.9, 0.15])
self._norm = mpl.colors.Normalize(vmin=self.vmin, vmax=self.vmax)
self.redrawColorBar()
self.canvas = FigureCanvas(self.fig)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.slider_label)
layout.addWidget(self.slider)
layout.addWidget(self.canvas)
self.widget.setLayout(layout)
[docs] def onMapIndexChange(self, index):
"""
Respond to the color map change event
"""
new_map = str(self.cbColorMap.itemText(index))
self._cmap = new_map
self.redrawColorBar()
self.canvas.draw()
[docs] def redrawColorBar(self):
"""
Call ColorbarBase with current values, effectively redrawing the widget
"""
self.cb = mpl.colorbar.ColorbarBase(self.ax1, cmap=mpl.pyplot.get_cmap(self._cmap),
norm=self._norm,
orientation='horizontal')
self.cb.set_label('Color map range')
[docs] def onColorMapReversed(self, isChecked):
"""
Respond to ticking/unticking the color map reverse checkbox
"""
current_map = str(self.cbColorMap.currentText())
if isChecked:
# Add "_r" to map name for the reversed version
new_map = current_map + "_r"
maps = self.rmaps
# Assure the reversed map exists.
if new_map not in maps:
new_map = maps[0]
else:
new_map = current_map[:-2] # "_r" = last two chars
maps = self.maps
# Base map for the reversed map should ALWAYS exist,
# but let's be paranoid here
if new_map not in maps:
new_map = maps[0]
self._cmap = new_map
# Clear the content of the combobox.
# Needs signal blocking, or else onMapIndexChange() spoils it all
self.cbColorMap.blockSignals(True)
self.cbColorMap.clear()
# Add the new set of maps
self.cbColorMap.addItems(maps)
# Remove the signal block before the proper index set
self.cbColorMap.blockSignals(False)
self.cbColorMap.setCurrentIndex(self.cbColorMap.findText(new_map))
[docs] def onAmplitudeChange(self):
"""
Respond to editing the amplitude fields
"""
min_amp = self.vmin
max_amp = self.vmax
try:
min_amp = float(self.txtMinAmplitude.text())
except ValueError:
pass
try:
max_amp = float(self.txtMaxAmplitude.text())
except ValueError:
pass
self._norm = mpl.colors.Normalize(vmin=min_amp, vmax=max_amp)
self.redrawColorBar()
self.canvas.draw()