@@ -478,6 +478,34 @@ class is a subclass of both BaseFigure and widgets.DOMWidget.
478478
479479 # Initialize validation
480480 self ._validate = kwargs .pop ("_validate" , True )
481+ self ._as_dict_mode = kwargs .pop ("_as_dict" , False )
482+
483+ if self ._as_dict_mode :
484+ # Fast path: minimal init for to_dict()/show()/to_json() to work.
485+ self ._grid_str = None
486+ self ._grid_ref = None
487+
488+ # Handle Figure-like dict input
489+ if isinstance (data , dict ) and (
490+ "data" in data or "layout" in data or "frames" in data
491+ ):
492+ layout_plotly = data .get ("layout" , layout_plotly )
493+ frames = data .get ("frames" , frames )
494+ data = data .get ("data" , None )
495+
496+ # Store data directly - no validate_coerce, no deepcopy
497+ self ._data = list (data ) if data else []
498+ self ._data_objs = ()
499+ self ._data_defaults = [{} for _ in self ._data ]
500+
501+ # Store layout directly
502+ self ._layout = layout_plotly if isinstance (layout_plotly , dict ) else {}
503+ self ._layout_defaults = {}
504+
505+ # Frames
506+ self ._frame_objs = ()
507+
508+ return # Skip everything else
481509
482510 # Assign layout_plotly to layout
483511 # ------------------------------
@@ -974,6 +1002,8 @@ def data(self):
9741002 -------
9751003 tuple[BaseTraceType]
9761004 """
1005+ if getattr (self , "_as_dict_mode" , False ):
1006+ return tuple (self ._data )
9771007 return self ["data" ]
9781008
9791009 @data .setter
@@ -1412,6 +1442,27 @@ def update_layout(self, dict1=None, overwrite=False, **kwargs):
14121442 BaseFigure
14131443 The Figure object that the update_layout method was called on
14141444 """
1445+ if getattr (self , "_as_dict_mode" , False ):
1446+
1447+ def _recursive_update (d , u ):
1448+ for k , v in u .items ():
1449+ if isinstance (v , dict ) and k in d and isinstance (d [k ], dict ):
1450+ _recursive_update (d [k ], v )
1451+ else :
1452+ d [k ] = v
1453+
1454+ if overwrite :
1455+ if dict1 :
1456+ self ._layout .update (dict1 )
1457+ if kwargs :
1458+ self ._layout .update (kwargs )
1459+ else :
1460+ if dict1 :
1461+ _recursive_update (self ._layout , dict1 )
1462+ if kwargs :
1463+ _recursive_update (self ._layout , kwargs )
1464+ return self
1465+
14151466 self .layout .update (dict1 , overwrite = overwrite , ** kwargs )
14161467 return self
14171468
@@ -1522,6 +1573,18 @@ def _add_annotation_like(
15221573 secondary_y = None ,
15231574 exclude_empty_subplots = False ,
15241575 ):
1576+ if getattr (self , "_as_dict_mode" , False ):
1577+ if hasattr (new_obj , "to_plotly_json" ):
1578+ obj_dict = new_obj .to_plotly_json ()
1579+ elif isinstance (new_obj , dict ):
1580+ obj_dict = new_obj
1581+ else :
1582+ obj_dict = {}
1583+ if prop_plural not in self ._layout :
1584+ self ._layout [prop_plural ] = []
1585+ self ._layout [prop_plural ].append (obj_dict )
1586+ return self
1587+
15251588 # Make sure we have both row and col or neither
15261589 if row is not None and col is None :
15271590 raise ValueError (
@@ -2203,6 +2266,13 @@ def add_traces(
22032266 Figure(...)
22042267 """
22052268
2269+ if getattr (self , "_as_dict_mode" , False ):
2270+ if not isinstance (data , (list , tuple )):
2271+ data = [data ]
2272+ self ._data .extend (data )
2273+ self ._data_defaults .extend ([{} for _ in data ])
2274+ return self
2275+
22062276 # Validate traces
22072277 data = self ._data_validator .validate_coerce (data )
22082278
@@ -2556,6 +2626,8 @@ def layout(self):
25562626 -------
25572627 plotly.graph_objs.Layout
25582628 """
2629+ if getattr (self , "_as_dict_mode" , False ):
2630+ return self ._layout
25592631 return self ["layout" ]
25602632
25612633 @layout .setter
@@ -4066,6 +4138,36 @@ def _process_multiple_axis_spanning_shapes(
40664138 Add a shape or multiple shapes and call _make_axis_spanning_layout_object on
40674139 all the new shapes.
40684140 """
4141+ if getattr (self , "_as_dict_mode" , False ):
4142+ shape_kwargs , annotation_kwargs = shapeannotation .split_dict_by_key_prefix (
4143+ kwargs , "annotation_"
4144+ )
4145+ shape_dict = {** shape_args , ** shape_kwargs }
4146+ if "xref" not in shape_dict :
4147+ shape_dict ["xref" ] = "x"
4148+ if "yref" not in shape_dict :
4149+ shape_dict ["yref" ] = "y"
4150+ if shape_type in ["vline" , "vrect" ]:
4151+ shape_dict ["yref" ] = shape_dict ["yref" ] + " domain"
4152+ elif shape_type in ["hline" , "hrect" ]:
4153+ shape_dict ["xref" ] = shape_dict ["xref" ] + " domain"
4154+ if "shapes" not in self ._layout :
4155+ self ._layout ["shapes" ] = []
4156+ self ._layout ["shapes" ].append (shape_dict )
4157+ augmented_annotation = shapeannotation .axis_spanning_shape_annotation (
4158+ annotation , shape_type , shape_args , annotation_kwargs
4159+ )
4160+ if augmented_annotation is not None :
4161+ annotation_dict = (
4162+ augmented_annotation
4163+ if isinstance (augmented_annotation , dict )
4164+ else {}
4165+ )
4166+ if "annotations" not in self ._layout :
4167+ self ._layout ["annotations" ] = []
4168+ self ._layout ["annotations" ].append (annotation_dict )
4169+ return
4170+
40694171 if shape_type in ["vline" , "vrect" ]:
40704172 direction = "vertical"
40714173 elif shape_type in ["hline" , "hrect" ]:
@@ -4324,6 +4426,13 @@ class BasePlotlyType(object):
43244426 _path_str = ""
43254427 _valid_props = set ()
43264428
4429+ def __new__ (cls , * args , ** kwargs ):
4430+ if kwargs .pop ("_as_dict" , False ):
4431+ kwargs .pop ("skip_invalid" , None )
4432+ kwargs .pop ("_validate" , None )
4433+ return kwargs
4434+ return super (BasePlotlyType , cls ).__new__ (cls )
4435+
43274436 def __init__ (self , plotly_name , ** kwargs ):
43284437 """
43294438 Construct a new BasePlotlyType
@@ -4335,6 +4444,9 @@ def __init__(self, plotly_name, **kwargs):
43354444 kwargs : dict
43364445 Invalid props/values to raise on
43374446 """
4447+ # Remove _as_dict if it was passed (handled by __new__)
4448+ kwargs .pop ("_as_dict" , None )
4449+
43384450 # ### _skip_invalid ##
43394451 # If True, then invalid properties should be skipped, if False then
43404452 # invalid properties will result in an exception
@@ -4439,6 +4551,7 @@ def _process_kwargs(self, **kwargs):
44394551 """
44404552 Process any extra kwargs that are not predefined as constructor params
44414553 """
4554+ kwargs .pop ("_as_dict" , None )
44424555 for k , v in kwargs .items ():
44434556 err = _check_path_in_prop_tree (self , k , error_cast = ValueError )
44444557 if err is None :
@@ -6015,6 +6128,14 @@ class BaseTraceType(BaseTraceHierarchyType):
60156128 subclasses of this class.
60166129 """
60176130
6131+ def __new__ (cls , * args , ** kwargs ):
6132+ if kwargs .pop ("_as_dict" , False ):
6133+ kwargs .pop ("skip_invalid" , None )
6134+ kwargs .pop ("_validate" , None )
6135+ kwargs ["type" ] = cls ._path_str
6136+ return kwargs
6137+ return super (BaseTraceType , cls ).__new__ (cls )
6138+
60186139 def __init__ (self , plotly_name , ** kwargs ):
60196140 super (BaseTraceHierarchyType , self ).__init__ (plotly_name , ** kwargs )
60206141
0 commit comments