1818* :py:func:`.get_cmap`: get a colormap from its name
1919* :py:func:`.cmap_exists`: check if a colormap exists
2020* :py:func:`.add_cmap`: add a colormap to the list of available colormaps
21+ * :py:func:`.get_custom_colormaps_path`: get the path of the custom colormaps file
22+ * :py:func:`.reload_custom_colormaps`: reload custom colormaps from this file
2123* :py:func:`.build_icon_from_cmap`: build an icon representing the colormap
2224* :py:func:`.build_icon_from_cmap_name`: build an icon representing the colormap
2325 from its name
2628logarithmic scale (e.g. for the color bar of images shown with a logarithmic
2729Z-axis).
2830
31+ Custom colormaps (e.g. created with the colormap manager) are saved in the
32+ configuration directory of PlotPy: ``.plotpy`` by default, in the user's configuration
33+ directory (``~/.config`` on Linux, the user's home directory otherwise). Applications
34+ may save them in their own configuration directory by setting the application name of
35+ PlotPy's configuration, e.g. ``plotpy.config.CONF.set_application("MyApp/plotpy",
36+ "1.0.0")``: custom colormaps are then reloaded from this directory. The first time,
37+ custom colormaps saved by previous versions of PlotPy (in the ``.none`` or ``.plotpy``
38+ directory) are copied there.
39+
2940Reference
3041^^^^^^^^^
3142
3243.. autofunction:: get_cmap
3344.. autofunction:: cmap_exists
3445.. autofunction:: add_cmap
46+ .. autofunction:: get_custom_colormaps_path
47+ .. autofunction:: reload_custom_colormaps
3548.. autofunction:: build_icon_from_cmap
3649.. autofunction:: build_icon_from_cmap_name
3750.. autoclass:: LogScaleColorMap
4356import json
4457import math
4558import os
46- from typing import Dict , Literal , Sequence
59+ import os .path as osp
60+ import warnings
61+ from typing import Any , Dict , Literal , Sequence
4762
4863import numpy as np
4964import qtpy .QtCore as QC
5065import qtpy .QtGui as QG
5166from guidata .configtools import get_module_data_path
67+ from guidata .userconfig import get_config_basedir
5268from qwt import QwtInterval , toQImage
5369from qwt .color_map import QwtColorMap
5470
@@ -133,6 +149,85 @@ def get_cmap_path(config_path: str):
133149 return user_config_path
134150
135151
152+ def _save_raw_colormaps (json_path : str , raw_colormaps : dict [str , Any ]) -> None :
153+ """Saves raw colormaps into the given json file, creating its directory if needed.
154+
155+ Args:
156+ json_path: absolute path of the json file in which to save the colormaps
157+ raw_colormaps: Dictionnary of colormaps names -> raw colormap sequences
158+ """
159+ os .makedirs (osp .dirname (json_path ), exist_ok = True )
160+ with open (json_path , "w" , encoding = "utf-8" ) as f :
161+ json .dump (raw_colormaps , f , indent = 4 )
162+
163+
164+ def save_colormaps (json_filename : str , colormaps : CmapDictType ):
165+ """Saves colormaps into the given json file. Refer ton function get_cmap_path to
166+ know what json_filename can be used.
167+
168+ Args:
169+ json_filename: json file name/path in which to save the colormaps
170+ colormaps: Dictionnary of colormpas names -> CustomQwtLinearColormap
171+ """
172+ raw_colormaps = {cmap .name : cmap .to_tuples () for cmap in colormaps .values ()}
173+ _save_raw_colormaps (CONF .get_path (json_filename ), raw_colormaps )
174+
175+
176+ #: Configuration directories in which previous versions of PlotPy saved the custom
177+ #: colormaps of all applications (``.none`` when PlotPy's configuration had no
178+ #: application name, ``.plotpy`` when the host application named it "plotpy")
179+ LEGACY_CUSTOM_COLORMAPS_DIRS = (".none" , ".plotpy" )
180+
181+
182+ def _get_custom_colormaps_filename () -> str :
183+ """Returns the "colormaps/custom" option of PlotPy's configuration"""
184+ return CONF .get (
185+ "colormaps" ,
186+ "colormaps/custom" ,
187+ default = "colormaps_custom.json" , # type: ignore
188+ )
189+
190+
191+ def get_custom_colormaps_path () -> str :
192+ """Returns the path of the json file in which custom colormaps are saved.
193+
194+ This file is located in the configuration directory of the application set with
195+ ``plotpy.config.CONF.set_application`` (``.plotpy`` by default, in the user's
196+ configuration directory), unless the "colormaps/custom" option of the "colormaps"
197+ configuration section is an absolute path.
198+
199+ Returns:
200+ Absolute path of the custom colormaps json file (which may not exist yet)
201+ """
202+ return CONF .get_path (_get_custom_colormaps_filename ())
203+
204+
205+ def load_custom_colormaps () -> CmapDictType :
206+ """Loads the custom colormaps of the current application (see function
207+ get_custom_colormaps_path).
208+
209+ If the custom colormaps file of the application does not exist yet, the custom
210+ colormaps saved by previous versions of PlotPy (see LEGACY_CUSTOM_COLORMAPS_DIRS)
211+ are first copied into it. The legacy files are left untouched.
212+
213+ Returns:
214+ Dictionnary of colormaps names -> CustomQwtLinearColormap
215+ """
216+ filename = _get_custom_colormaps_filename ()
217+ json_path = CONF .get_path (filename )
218+ if not osp .isfile (json_path ) and not osp .isabs (filename ):
219+ raw_colormaps : dict [str , Any ] = {}
220+ for dirname in LEGACY_CUSTOM_COLORMAPS_DIRS :
221+ legacy_path = osp .join (get_config_basedir (), dirname , filename )
222+ raw_colormaps .update (load_raw_colormaps_from_json (legacy_path ))
223+ if raw_colormaps :
224+ try :
225+ _save_raw_colormaps (json_path , raw_colormaps )
226+ except OSError as exc :
227+ warnings .warn (f"Unable to copy custom colormaps to { json_path } : { exc } " )
228+ return load_qwt_colormaps_from_json (json_path )
229+
230+
136231# Load default colormaps path from the config file
137232DEFAULT_COLORMAPS_PATH = get_cmap_path (
138233 CONF .get (
@@ -141,18 +236,10 @@ def get_cmap_path(config_path: str):
141236 default = "colormaps_default.json" , # type: ignore
142237 )
143238)
144- # Load custom colormaps path from the config file
145- CUSTOM_COLORMAPS_PATH = get_cmap_path (
146- CONF .get (
147- "colormaps" ,
148- "colormaps/custom" ,
149- default = "colormaps_custom.json" , # type: ignore
150- )
151- )
152239
153240# Load default and custom colormaps from json files
154241DEFAULT_COLORMAPS : CmapDictType = load_qwt_colormaps_from_json (DEFAULT_COLORMAPS_PATH )
155- CUSTOM_COLORMAPS : CmapDictType = load_qwt_colormaps_from_json ( CUSTOM_COLORMAPS_PATH )
242+ CUSTOM_COLORMAPS : CmapDictType = load_custom_colormaps ( )
156243
157244# Merge default and custom colormaps into a single dictionnary to simplify access
158245ALL_COLORMAPS : CmapDictType = {** DEFAULT_COLORMAPS , ** CUSTOM_COLORMAPS }
@@ -161,18 +248,31 @@ def get_cmap_path(config_path: str):
161248DEFAULT = ALL_COLORMAPS ["jet" ]
162249
163250
164- def save_colormaps ( json_filename : str , colormaps : CmapDictType ) :
165- """Saves colormaps into the given json file. Refer ton function get_cmap_path to
166- know what json_filename can be used .
251+ def reload_custom_colormaps () -> None :
252+ """Reloads the custom colormaps of the current application (see function
253+ load_custom_colormaps) .
167254
168- Args:
169- json_filename: json file name/path in which to save the colormaps
170- colormaps: Dictionnary of colormpas names -> CustomQwtLinearColormap
255+ This function is called each time the application name of PlotPy's configuration
256+ is set with ``plotpy.config.CONF.set_application``. The CUSTOM_COLORMAPS and
257+ ALL_COLORMAPS global variables are updated in place.
171258 """
172- raw_colormaps = {cmap .name : cmap .to_tuples () for cmap in colormaps .values ()}
173- json_abs_path = CONF .get_path (json_filename )
174- with open (json_abs_path , "w" , encoding = "utf-8" ) as f :
175- json .dump (raw_colormaps , f , indent = 4 )
259+ custom_colormaps = load_custom_colormaps ()
260+ CUSTOM_COLORMAPS .clear ()
261+ CUSTOM_COLORMAPS .update (custom_colormaps )
262+ ALL_COLORMAPS .clear ()
263+ ALL_COLORMAPS .update (DEFAULT_COLORMAPS )
264+ ALL_COLORMAPS .update (custom_colormaps )
265+
266+
267+ CONF .register_application_callback (reload_custom_colormaps )
268+
269+
270+ def __getattr__ (name : str ) -> Any :
271+ """Returns module attributes computed on demand"""
272+ if name == "CUSTOM_COLORMAPS_PATH" :
273+ # Kept for backward compatibility: this path depends on the application name
274+ return get_custom_colormaps_path ()
275+ raise AttributeError (f"module { __name__ !r} has no attribute { name !r} " )
176276
177277
178278def build_icon_from_cmap (
@@ -300,15 +400,15 @@ def add_cmap(cmap: EditableColormap) -> None:
300400 global ALL_COLORMAPS , CUSTOM_COLORMAPS
301401 ALL_COLORMAPS [cmap .name .lower ()] = cmap
302402 CUSTOM_COLORMAPS [cmap .name .lower ()] = cmap
303- save_colormaps (CUSTOM_COLORMAPS_PATH , CUSTOM_COLORMAPS )
403+ save_colormaps (get_custom_colormaps_path () , CUSTOM_COLORMAPS )
304404
305405
306406def delete_cmap (cmap : EditableColormap ) -> None :
307407 """Deletes the given colormap from both ALL_COLORMAPS and CUSTOM_COLORMAPS global"""
308408 global ALL_COLORMAPS , CUSTOM_COLORMAPS
309409 if CUSTOM_COLORMAPS .pop (cmap .name .lower (), None ) is not None :
310410 del ALL_COLORMAPS [cmap .name .lower ()]
311- save_colormaps (CUSTOM_COLORMAPS_PATH , CUSTOM_COLORMAPS )
411+ save_colormaps (get_custom_colormaps_path () , CUSTOM_COLORMAPS )
312412
313413
314414class LogScaleColorMap (QwtColorMap ):
0 commit comments