Skip to content

Commit bf6583c

Browse files
committed
Add "Invert colormap" feature to Colormap Manager with real-time updates
Closes #53
1 parent 63ffa03 commit bf6583c

File tree

3 files changed

+65
-6
lines changed

3 files changed

+65
-6
lines changed

doc/release_notes/release_2.09.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@
1414
* New `quiver()` function in the interactive plotting interface (`plotpy.pyplot`)
1515
* Integrated with plot autoscale (initial zoom and middle-click reset)
1616
* Item icon displayed in the item list widget
17+
* Added "Invert colormap" checkbox directly in the Colormap Manager dialog. This
18+
closes [Issue #53](https://github.com/PlotPyStack/PlotPy/issues/53):
19+
* Reflects the current inversion state when the dialog is opened
20+
* Allows toggling inversion from the colormap selection widget
21+
* Updates the colormap preview in real-time when toggled
22+
* Inversion is treated as a display parameter, independent of the colormap
23+
definition (toggling does not mark the colormap as having unsaved changes)

plotpy/tools/image.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -605,10 +605,13 @@ def activate_command(self, plot: BasePlot, checked: bool) -> None:
605605
):
606606
return
607607
manager = ColorMapManager(
608-
plot.parentWidget(), active_colormap=self._active_colormap.name
608+
plot.parentWidget(),
609+
active_colormap=self._active_colormap.name,
610+
invert=self._active_colormap.invert,
609611
)
610612
manager.SIG_APPLY_COLORMAP.connect(self.update_plot)
611613
if exec_dialog(manager) and (cmap := manager.get_colormap()) is not None:
614+
cmap.invert = manager.get_invert()
612615
self.activate_cmap(cmap)
613616

614617
def activate_cmap(self, cmap: str | EditableColormap) -> None:
@@ -627,17 +630,20 @@ def activate_cmap(self, cmap: str | EditableColormap) -> None:
627630
self.update_plot(self._active_colormap.name)
628631
self.update_status(plot)
629632

630-
def update_plot(self, cmap_name: str) -> None:
633+
def update_plot(self, cmap_name: str, invert: bool | None = None) -> None:
631634
"""Update the plot with the given colormap.
632635
633636
Args:
634637
cmap_name: Colormap name
638+
invert: Invert colormap state. If None, uses the active colormap's
639+
invert state. Defaults to None.
635640
"""
641+
if invert is not None:
642+
self._active_colormap.invert = invert
636643
plot: BasePlot = self.get_active_plot()
637644
items = get_selected_images(plot, IColormapImageItemType)
638645
for item in items:
639-
cmap = item.get_color_map()
640-
item.set_color_map(cmap_name, cmap.invert)
646+
item.set_color_map(cmap_name, self._active_colormap.invert)
641647
plot.SIG_ITEM_PARAMETERS_CHANGED.emit(item)
642648
plot.invalidate()
643649

@@ -659,6 +665,7 @@ def update_status(self, plot: BasePlot) -> None:
659665
if cmap is not None:
660666
icon = build_icon_from_cmap_name(cmap.name)
661667
self._active_colormap = get_cmap(cmap.name)
668+
self._active_colormap.invert = cmap.invert
662669
cmap_name = cmap.name
663670
else:
664671
self.action.setEnabled(False)

plotpy/widgets/colormap/manager.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ class ColorMapManager(QW.QDialog):
122122
Args:
123123
parent: parent QWidget. Defaults to None.
124124
active_colormap: name of the active colormap.
125+
invert: initial state of the 'Invert colormap' checkbox.
126+
Defaults to False.
125127
126128
.. note::
127129
@@ -134,12 +136,13 @@ class ColorMapManager(QW.QDialog):
134136
that the colormap cannot be removed.
135137
"""
136138

137-
SIG_APPLY_COLORMAP = QC.Signal(str)
139+
SIG_APPLY_COLORMAP = QC.Signal(str, bool)
138140

139141
def __init__(
140142
self,
141143
parent: QW.QWidget | None = None,
142144
active_colormap: str | None = None,
145+
invert: bool = False,
143146
) -> None:
144147
super().__init__(parent)
145148
self.setWindowIcon(get_icon("cmap_edit.png"))
@@ -148,6 +151,7 @@ def __init__(
148151
self.active_cmap_name = default_colormap = active_colormap
149152

150153
self.__returned_colormap: EditableColormap | None = None
154+
self.__returned_invert: bool = invert
151155

152156
if default_colormap is None or not cmap_exists(default_colormap, ALL_COLORMAPS):
153157
default_colormap = next(iter(ALL_COLORMAPS))
@@ -171,6 +175,10 @@ def __init__(
171175
self._remove_btn.setEnabled(is_custom_cmap)
172176
self._remove_btn.clicked.connect(self.remove_colormap)
173177

178+
self._invert_checkbox = QW.QCheckBox(_("Invert colormap"))
179+
self._invert_checkbox.setChecked(invert)
180+
self._invert_checkbox.toggled.connect(self._on_invert_toggled)
181+
174182
select_gbox = QW.QGroupBox(_("Select or create a colormap"))
175183
select_label = QW.QLabel(_("Colormap presets:"))
176184
select_gbox_layout = QW.QHBoxLayout()
@@ -179,6 +187,8 @@ def __init__(
179187
select_gbox_layout.addSpacing(10)
180188
select_gbox_layout.addWidget(add_btn)
181189
select_gbox_layout.addWidget(self._remove_btn)
190+
select_gbox_layout.addSpacing(10)
191+
select_gbox_layout.addWidget(self._invert_checkbox)
182192
select_gbox.setLayout(select_gbox_layout)
183193

184194
# Edit the selected colormap
@@ -192,6 +202,13 @@ def __init__(
192202
current_cmap = None
193203
self.colormap_editor = ColorMapEditor(self, colormap=current_cmap)
194204

205+
# Apply initial invert state to the colormap preview
206+
if invert:
207+
cmap = self.colormap_editor.get_colormap()
208+
if cmap is not None:
209+
cmap.invert = True
210+
self.colormap_editor.colormap_widget.COLORMAP_CHANGED.emit()
211+
195212
self._edit_gbox = QW.QGroupBox(_("Edit the selected colormap"))
196213
edit_gbox_layout = QW.QVBoxLayout()
197214
edit_gbox_layout.setContentsMargins(0, 0, 0, 0)
@@ -237,14 +254,33 @@ def button_clicked(self, button: QW.QAbstractButton) -> None:
237254
if not self.current_changes_saved and not self.save_colormap():
238255
return
239256
self._apply_btn.setEnabled(False)
240-
self.SIG_APPLY_COLORMAP.emit(self.colormap_editor.get_colormap().name)
257+
self.SIG_APPLY_COLORMAP.emit(
258+
self.colormap_editor.get_colormap().name,
259+
self._invert_checkbox.isChecked(),
260+
)
241261
elif button is self._save_btn:
242262
self.save_colormap()
243263
elif self.bbox.buttonRole(button) == QW.QDialogButtonBox.AcceptRole:
244264
self.accept()
245265
else:
246266
self.reject()
247267

268+
def _on_invert_toggled(self, checked: bool) -> None:
269+
"""Callback for the 'Invert colormap' checkbox toggle.
270+
271+
Inversion is a display parameter independent of the colormap definition,
272+
so toggling it only updates the preview without marking the colormap as
273+
having unsaved changes.
274+
275+
Args:
276+
checked: True if the checkbox is checked, False otherwise.
277+
"""
278+
self.__returned_invert = checked
279+
cmap = self.colormap_editor.get_colormap()
280+
if cmap is not None:
281+
cmap.invert = checked
282+
self.colormap_editor.colormap_widget.draw_colormap_image()
283+
248284
def _changes_not_saved(self) -> None:
249285
"""Callback function to be called when the colormap is modified. Enables the
250286
save button and sets the current_changes_saved attribute to False."""
@@ -270,6 +306,7 @@ def set_colormap(self, index: int) -> None:
270306
index: index of the colormap in the QComboBox.
271307
"""
272308
cmap_copy: EditableColormap = deepcopy(self._cmap_choice.itemData(index))
309+
cmap_copy.invert = self._invert_checkbox.isChecked()
273310
self.colormap_editor.set_colormap(cmap_copy)
274311

275312
is_custom_cmap = cmap_exists(cmap_copy.name, CUSTOM_COLORMAPS)
@@ -287,6 +324,14 @@ def get_colormap(self) -> EditableColormap | None:
287324
"""
288325
return self.__returned_colormap
289326

327+
def get_invert(self) -> bool:
328+
"""Return the current state of the 'Invert colormap' checkbox.
329+
330+
Returns:
331+
True if the colormap should be inverted, False otherwise.
332+
"""
333+
return self.__returned_invert
334+
290335
def __get_new_colormap_name(self, title: str, name: str) -> str | None:
291336
"""Return a new colormap name that does not already exists in the list of
292337
colormaps.

0 commit comments

Comments
 (0)