From ffb925817e497fcad9a2956e6069de289bc10ce5 Mon Sep 17 00:00:00 2001 From: Markus Reinert Date: Mon, 16 Aug 2021 15:41:16 +0200 Subject: [PATCH 1/2] Add two buttons for easier colorbar handling This commit adds two buttons to the slicing tab if the current figure has a colorbar. A click on the first button makes the colorbar symmetric about zero, the second button allows to quickly switch between recently used colormaps. It is often useful to show positive and negative values in a colorplot with two different colors, for example when we look at velocity. To do this, it has so far been necessary to click "Properties", set the minimum and maximum value of the colorbar to be symmetric about zero and to choose a diverging colormap. This commit simplifies this workflow by putting all these actions in a single button. Since this new button changes the colormap, we might want to switch back to the previously used colormap when we look at a different quantity that is not symmetric about zero. For this purpose, a menu button is added as well, which allows to switch from the current colormap back to a recently used colormap. This menu also suggests some commonly used colormaps, and is a lot faster to use than the complete colormap-menu in the "Properties" dialog. --- pyncview/pyncview.py | 77 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/pyncview/pyncview.py b/pyncview/pyncview.py index 576c91c..ae91bdb 100755 --- a/pyncview/pyncview.py +++ b/pyncview/pyncview.py @@ -443,10 +443,14 @@ class SliceWidget(QtWidgets.QWidget): stopAnimation = QtCore.Signal() onRecord = QtCore.Signal(object) sliceChanged = QtCore.Signal(bool) + makeSymmetric = QtCore.Signal() + changeColormap = QtCore.Signal(str) def __init__(self,parent=None,variable=None,figure=None,defaultslices=None,dimnames=None,animatecallback=None): super(SliceWidget,self).__init__(parent) + self.figure = figure + assert variable is not None,'Variable must be specified.' if defaultslices is None: defaultslices = {} @@ -511,7 +515,20 @@ def __init__(self,parent=None,variable=None,figure=None,defaultslices=None,dimna layout.addWidget(self.bnChangeAxes,2+len(dims),0,1,2) #layout.addWidget(self.bnAnimate, 3+len(dims),0,1,2) - layout.setRowStretch(4+len(dims),1) + # Add button for making the colorbar symmetric + self.bnSymmetric = QtWidgets.QPushButton('Make colorbar symmetric about 0',self) + self.bnSymmetric.clicked.connect(self.onColorbarSymmetric) + layout.addWidget(self.bnSymmetric,3+len(dims),0,1,2) + + # Add button with menu for choosing a colormap + self.bnColormap = QtWidgets.QPushButton('Choose colormap ...',self) + self.menuColormap = QtWidgets.QMenu(self) + self.bnColormap.setMenu(self.menuColormap) + layout.addWidget(self.bnColormap,4+len(dims),0,1,2) + self.colormapActions = {} + self.updateColormapMenu() + + layout.setRowStretch(6+len(dims),1) self.setLayout(layout) @@ -523,8 +540,6 @@ def __init__(self,parent=None,variable=None,figure=None,defaultslices=None,dimna self.setWindowTitle('Specify slice') - self.figure = figure - def closeEvent(self,event): # Make sure that the slice widget behaves as if no slices are specified, while # the widget is closing and after it is closed. @@ -539,6 +554,13 @@ def __init__(self,slicewidget,dim): def event(self): self.slicewidget.onAxesBounds(self.dim) + class ChangeColormapAction: + def __init__(self,slicewidget,colormap): + self.slicewidget = slicewidget + self.colormap = colormap + def event(self): + self.slicewidget.chooseColormap(self.colormap) + def getRange(self,dim): for c in self.dimcontrols: if c[0]==dim: @@ -575,6 +597,15 @@ def onAnimate(self): def onAxesBounds(self,dim=None): self.setAxesBounds.emit(dim) + def onColorbarSymmetric(self): + """Make the colorbar symmetric and choose a diverging colormap.""" + self.makeSymmetric.emit() + self.chooseColormap('RdBu_r') + + def chooseColormap(self, colormap): + self.changeColormap.emit(colormap) + self.updateColormapMenu() + def onCheckChanged(self,state=None): sliceddims = [] for (dim,checkbox,spin,bnanimate) in self.dimcontrols: @@ -614,6 +645,16 @@ def getSlices(self): slics[dim] = int(spin.value()) return slics + def updateColormapMenu(self): + """Add new entries (actions) to the colormap menu.""" + for colormap, description in self.figure.colormapQuickList.items(): + if colormap not in self.colormapActions: + action = SliceWidget.ChangeColormapAction(self, colormap) + self.menuColormap.addAction(colormap + ": " + description, action.event) + # Save action-object for colormap-event, otherwise + # the object is lost and the event cannot be triggered + self.colormapActions[colormap] = action + class NcPropertiesDialog(QtWidgets.QDialog): def __init__(self,item,parent,flags=QtCore.Qt.Dialog): QtWidgets.QDialog.__init__(self,parent,flags) @@ -860,6 +901,14 @@ def __init__(self,parent=None): self.figurepanel.figure.autosqueeze = False self.store = self.figurepanel.figure.source + # Create a collection of often used colormaps (will be extended as the user chooses other colormaps) + self.figurepanel.figure.colormapQuickList = { + 'jet': 'rainbow colormap (PyNcView default)', + 'viridis': 'sequential colormap (matplotlib default)', + 'RdBu_r': 'diverging colormap', + 'binary': 'grayscale colormap', + } + self.labelMissing = QtWidgets.QLabel('',central) self.labelMissing.setWordWrap(True) self.labelMissing.setVisible(False) @@ -1259,6 +1308,9 @@ def redraw(self,preserveproperties=True,preserveaxesbounds=True): ndim = len(varshape) - nsliced else: ndim = 1 + # Make buttons regarding colorbar visible if and only if there is a colorbar + self.slicetab.bnSymmetric.setVisible(ndim == 2) + self.slicetab.bnColormap.setVisible(ndim == 2) if ndim not in (1,2): if ndim>2: # More than 2 dimensions @@ -1471,6 +1523,23 @@ def iterdim(curslices,todoslices,progweight=1.,cumprog=0.): # Restore original cursor QtWidgets.QApplication.restoreOverrideCursor() + def makeColorbarSymmetric(self): + for axisnode in self.figurepanel.figure['Axes'].children: + if axisnode.getSecondaryId() == 'colorbar': + vamin = axisnode['Minimum'].getValue(usedefault=True) + vamax = axisnode['Maximum'].getValue(usedefault=True) + absmax = max(abs(vamax), abs(vamin)) + axisnode['Minimum'].setValue(-absmax) + axisnode['Maximum'].setValue(absmax) + + def changeColormap(self, colormap): + """Set colormap of figure to the given name and save previously used colormap.""" + figure = self.figurepanel.figure + old_colormap = figure['ColorMap'].getValue(usedefault=True) + figure['ColorMap'].setValue(colormap) + if old_colormap not in figure.colormapQuickList: + figure.colormapQuickList[old_colormap] = 'recently used' + def onRecordAnimation(self,dim): # Get the string specifying the currently selected variable (without slices applied!) varname = self.getSelectedVariable() @@ -1576,6 +1645,8 @@ def onSelectionChanged(self): if self.slicetab is not None: self.slicetab.close() self.slicetab = SliceWidget(None,var,figure=self.figurepanel.figure,defaultslices=self.defaultslices,dimnames=self.store.getVariableLongNames(),animatecallback=self.onAnimation) self.slicetab.sliceChanged.connect(self.onSliceChanged) + self.slicetab.makeSymmetric.connect(self.makeColorbarSymmetric) + self.slicetab.changeColormap.connect(self.changeColormap) self.slicetab.setAxesBounds.connect(self.setAxesBounds) self.slicetab.onRecord.connect(self.onRecordAnimation) self.slicetab.startAnimation.connect(self.figurepanel.startAnimation) From 0cc21f1ea8db292a24d7797e8c560d704eba2ec5 Mon Sep 17 00:00:00 2001 From: Markus Reinert Date: Fri, 4 Feb 2022 14:25:54 +0100 Subject: [PATCH 2/2] Update pyncview version number When the two buttons for colorbar handling are accepted, this will be a new version of pyncview, so I increased the version number by one. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3e3f629..1e68c47 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,7 @@ def addtreewithwildcard(sourceroot,path,localtarget): from setuptools import setup setup(name='pyncview', - version='0.99.29', + version='0.99.30', description='NetCDF viewer written in Python', url='http://github.com/BoldingBruggeman/pyncview', author='Jorn Bruggeman', @@ -111,4 +111,4 @@ def addtreewithwildcard(sourceroot,path,localtarget): ] }, packages=['pyncview'], - package_data={'pyncview': ['pyncview.png']}) \ No newline at end of file + package_data={'pyncview': ['pyncview.png']})