|
| 1 | +__author__ = "Jeroen Van Der Donckt, Jonas Van Der Donckt, Emiel Deprost" |
| 2 | + |
| 3 | +from plotly_resampler import FigureResampler, FigureWidgetResampler |
| 4 | +from plotly_resampler.figure_resampler.figure_resampler_interface import ( |
| 5 | + AbstractFigureAggregator, |
| 6 | +) |
| 7 | +from functools import wraps |
| 8 | + |
| 9 | +import plotly |
| 10 | + |
| 11 | +WRAPPED_PREFIX = "[Plotly-Resampler]__" |
| 12 | +PLOTLY_MODULES = [ |
| 13 | + plotly.graph_objs, |
| 14 | + plotly.graph_objects, |
| 15 | +] # wait for this PR https://github.com/plotly/plotly.py/pull/3779 |
| 16 | +PLOTLY_CONSTRUCTOR_WRAPPER = { |
| 17 | + "Figure": FigureResampler, |
| 18 | + "FigureWidget": FigureWidgetResampler, |
| 19 | +} |
| 20 | + |
| 21 | + |
| 22 | +def _already_wrapped(constr): |
| 23 | + return constr.__name__.startswith(WRAPPED_PREFIX) |
| 24 | + |
| 25 | + |
| 26 | +def get_plotly_constr(constr): |
| 27 | + if _already_wrapped(constr): |
| 28 | + return constr.__wrapped__ # get the original constructor |
| 29 | + return constr |
| 30 | + |
| 31 | + |
| 32 | +### Registering the wrappers |
| 33 | + |
| 34 | + |
| 35 | +def _is_ipython_env(): |
| 36 | + """Check if we are in an IPython environment (with a kernel).""" |
| 37 | + try: |
| 38 | + from IPython import get_ipython |
| 39 | + |
| 40 | + return "IPKernelApp" in get_ipython().config |
| 41 | + except Exception: |
| 42 | + return False |
| 43 | + |
| 44 | + |
| 45 | +def _register_wrapper( |
| 46 | + module: type, |
| 47 | + constr_name: str, |
| 48 | + pr_class: AbstractFigureAggregator, |
| 49 | + **aggregator_kwargs, |
| 50 | +): |
| 51 | + constr = getattr(module, constr_name) |
| 52 | + constr = get_plotly_constr(constr) # get the original plotly constructor |
| 53 | + |
| 54 | + # print(f"Wrapping {constr_name} with {pr_class}") |
| 55 | + |
| 56 | + @wraps(constr) |
| 57 | + def wrapped_constr(*args, **kwargs): |
| 58 | + # print(f"Executing constructor wrapper for {constr_name}", constr) |
| 59 | + return pr_class(constr(*args, **kwargs), **aggregator_kwargs) |
| 60 | + |
| 61 | + wrapped_constr.__name__ = WRAPPED_PREFIX + constr_name |
| 62 | + setattr(module, constr_name, wrapped_constr) |
| 63 | + |
| 64 | + |
| 65 | +def register_plotly_resampler(mode="auto", **aggregator_kwargs): # TODO: show kwargs (e.g., port)? |
| 66 | + """Register plotly-resampler to plotly.graph_objects. |
| 67 | +
|
| 68 | + This function results in the use of plotly-resampler under the hood. |
| 69 | +
|
| 70 | + .. Note:: |
| 71 | + We advise to |
| 72 | +
|
| 73 | + Parameters |
| 74 | + ---------- |
| 75 | + mode : str, optional |
| 76 | + The mode of the plotly-resampler. |
| 77 | + Possible values are: 'auto', 'figure', 'widget', None. |
| 78 | + If 'auto' is used, the mode is determined based on the environment; if it is in |
| 79 | + an ipython environment, the mode is 'widget', otherwise it is 'figure'. |
| 80 | + If 'figure' is used, all plotly figures are wrapped as FigureResampler objects. |
| 81 | + If 'widget' is used, all plotly figure widgets are wrapped as |
| 82 | + FigureWidgetResampler objects (we advise to use this mode in ipython environment |
| 83 | + with a kernel). |
| 84 | + If None is used, wrapping is done as expected (go.Figure -> FigureResampler, |
| 85 | + go.FigureWidget -> FigureWidgetResampler). |
| 86 | + aggregator_kwargs : dict, optional |
| 87 | + The keyword arguments to pass to the plotly-resampler decorator its constructor. |
| 88 | + See more details in :class:`FigureResampler <FigureResampler>` and |
| 89 | + :class:`FigureWidgetResampler <FigureWidgetResampler>`. |
| 90 | +
|
| 91 | + """ |
| 92 | + for constr_name, pr_class in PLOTLY_CONSTRUCTOR_WRAPPER.items(): |
| 93 | + if (mode == "auto" and _is_ipython_env()) or mode == "widget": |
| 94 | + pr_class = FigureWidgetResampler |
| 95 | + elif mode == "figure": |
| 96 | + pr_class = FigureResampler |
| 97 | + # else: default mode -> wrap according to PLOTLY_CONSTRUCTOR_WRAPPER |
| 98 | + |
| 99 | + for module in PLOTLY_MODULES: |
| 100 | + _register_wrapper(module, constr_name, pr_class, **aggregator_kwargs) |
| 101 | + |
| 102 | + |
| 103 | +### Unregistering the wrappers |
| 104 | + |
| 105 | + |
| 106 | +def _unregister_wrapper(module: type, constr_name: str): |
| 107 | + constr = getattr(module, constr_name) |
| 108 | + if _already_wrapped(constr): |
| 109 | + constr = constr.__wrapped__ |
| 110 | + setattr(module, constr_name, constr) |
| 111 | + |
| 112 | + |
| 113 | +def unregister_plotly_resampler(): |
| 114 | + """Unregister plotly-resampler from plotly.graph_objects.""" |
| 115 | + for constr in PLOTLY_CONSTRUCTOR_WRAPPER.keys(): |
| 116 | + for module in PLOTLY_MODULES: |
| 117 | + _unregister_wrapper(module, constr) |
0 commit comments