1818from collections .abc import MutableMapping
1919from numbers import Real
2020
21+
2122import cycler
2223import matplotlib as mpl
2324import matplotlib .colors as mcolors
2627import matplotlib .style .core as mstyle
2728import numpy as np
2829from matplotlib import RcParams
30+ from typing import Callable , Any , Dict
2931
3032from .internals import ic # noqa: F401
3133from .internals import (
@@ -158,6 +160,34 @@ def get_ipython():
158160docstring ._snippet_manager ["rc.cmap_exts" ] = _cmap_exts_docstring
159161docstring ._snippet_manager ["rc.cycle_exts" ] = _cycle_exts_docstring
160162
163+ _rc_register_handler_docstring = """
164+ Register a callback function to be executed when a setting is modified.
165+
166+ This is an extension point for "special" settings that require complex
167+ logic or have side-effects, such as updating other matplotlib settings.
168+ It is used internally to decouple the configuration system from other
169+ subsystems and avoid circular imports.
170+
171+ Parameters
172+ ----------
173+ name : str
174+ The name of the setting (e.g., ``'cycle'``).
175+ func : callable
176+ The handler function to be executed. The function must accept a
177+ single positional argument, which is the new `value` of the
178+ setting, and must return a dictionary. The keys of the dictionary
179+ should be valid ``matplotlib`` rc setting names, and the values
180+ will be applied to the ``rc_matplotlib`` object.
181+
182+ Example
183+ -------
184+ >>> def _cycle_handler(value):
185+ ... # ... logic to create a cycler object from the value ...
186+ ... return {'axes.prop_cycle': new_cycler}
187+ >>> rc.register_handler('cycle', _cycle_handler)
188+ """
189+ docstring ._snippet_manager ["rc.register_handler" ] = _rc_register_handler_docstring
190+
161191
162192def _init_user_file ():
163193 """
@@ -764,8 +794,17 @@ def __init__(self, local=True, user=True, default=True, **kwargs):
764794 %(rc.params)s
765795 """
766796 self ._context = []
797+ self ._setting_handlers = {}
767798 self ._init (local = local , user = user , default = default , ** kwargs )
768799
800+ def register_handler (
801+ self , name : str , func : Callable [[Any ], Dict [str , Any ]]
802+ ) -> None :
803+ """
804+ %(rc.register_handler)s
805+ """
806+ self ._setting_handlers [name ] = func
807+
769808 def __getitem__ (self , key ):
770809 """
771810 Return an `rc_matplotlib` or `rc_ultraplot` setting using dictionary notation
@@ -849,7 +888,7 @@ def __exit__(self, *args): # noqa: U100
849888 rc_matplotlib .update (kw_matplotlib )
850889 del self ._context [- 1 ]
851890
852- def _init (self , * , local , user , default , skip_cycle = False ):
891+ def _init (self , * , local , user , default ):
853892 """
854893 Initialize the configurator.
855894 """
@@ -863,9 +902,7 @@ def _init(self, *, local, user, default, skip_cycle=False):
863902 rc_matplotlib .update (rcsetup ._rc_matplotlib_default )
864903 rc_ultraplot .update (rcsetup ._rc_ultraplot_default )
865904 for key , value in rc_ultraplot .items ():
866- kw_ultraplot , kw_matplotlib = self ._get_item_dicts (
867- key , value , skip_cycle = skip_cycle
868- )
905+ kw_ultraplot , kw_matplotlib = self ._get_item_dicts (key , value )
869906 rc_matplotlib .update (kw_matplotlib )
870907 rc_ultraplot .update (kw_ultraplot )
871908
@@ -950,7 +987,7 @@ def _get_item_context(self, key, mode=None):
950987 if mode == 0 : # otherwise return None
951988 raise KeyError (f"Invalid rc setting { key !r} ." )
952989
953- def _get_item_dicts (self , key , value , skip_cycle = False ):
990+ def _get_item_dicts (self , key , value ):
954991 """
955992 Return dictionaries for updating the `rc_ultraplot` and `rc_matplotlib`
956993 properties associated with this key. Used when setting items, entering
@@ -970,13 +1007,13 @@ def _get_item_dicts(self, key, value, skip_cycle=False):
9701007 with warnings .catch_warnings ():
9711008 warnings .simplefilter ("ignore" , mpl .MatplotlibDeprecationWarning )
9721009 warnings .simplefilter ("ignore" , warnings .UltraPlotWarning )
973- for key in keys :
974- if key in rc_matplotlib :
975- kw_matplotlib [key ] = value
976- elif key in rc_ultraplot :
977- kw_ultraplot [key ] = value
1010+ for key_i in keys :
1011+ if key_i in rc_matplotlib :
1012+ kw_matplotlib [key_i ] = value
1013+ elif key_i in rc_ultraplot :
1014+ kw_ultraplot [key_i ] = value
9781015 else :
979- raise KeyError (f"Invalid rc setting { key !r} ." )
1016+ raise KeyError (f"Invalid rc setting { key_i !r} ." )
9801017
9811018 # Special key: configure inline backend
9821019 if contains ("inlineformat" ):
@@ -989,14 +1026,9 @@ def _get_item_dicts(self, key, value, skip_cycle=False):
9891026 kw_matplotlib .update (ikw_matplotlib )
9901027 kw_ultraplot .update (_infer_ultraplot_dict (ikw_matplotlib ))
9911028
992- # Cycler
993- # NOTE: Have to skip this step during initial ultraplot import
994- elif contains ("cycle" ) and not skip_cycle :
995- from .colors import _get_cmap_subtype
996-
997- cmap = _get_cmap_subtype (value , "discrete" )
998- kw_matplotlib ["axes.prop_cycle" ] = cycler .cycler ("color" , cmap .colors )
999- kw_matplotlib ["patch.facecolor" ] = "C0"
1029+ # Generic handler for special properties
1030+ if key in self ._setting_handlers :
1031+ kw_matplotlib .update (self ._setting_handlers [key ](value ))
10001032
10011033 # Turning bounding box on should turn border off and vice versa
10021034 elif contains ("abc.bbox" , "title.bbox" , "abc.border" , "title.border" ):
@@ -1820,7 +1852,7 @@ def changed(self):
18201852
18211853#: Instance of `Configurator`. This controls both `rc_matplotlib` and `rc_ultraplot`
18221854#: settings. See the :ref:`configuration guide <ug_config>` for details.
1823- rc = Configurator (skip_cycle = True )
1855+ rc = Configurator ()
18241856
18251857# Deprecated
18261858RcConfigurator = warnings ._rename_objs (
0 commit comments