Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fix issue where user-specified `color_continuous_scale` was ignored when template had `autocolorscale=True` [[#5439](https://github.com/plotly/plotly.py/pull/5439)], with thanks to @antonymilne for the contribution!
- Update tests to be compatible with numpy 2.4 [[#5522](https://github.com/plotly/plotly.py/pull/5522)], with thanks to @thunze for the contribution!

### Performance
- Optimize `to_dict()` serialization path: eliminate redundant array copies and narwhals overhead in base64 conversion, ~40% faster for data-heavy figures [[#5577](https://github.com/plotly/plotly.py/pull/5577)]

### Updated
- The `__eq__` method for `graph_objects` classes now returns `NotImplemented` to give the other operand an opportunity to handle the comparison [[#5547](https://github.com/plotly/plotly.py/pull/5547)], with thanks to @RazerM for the contribution!

Expand Down
42 changes: 29 additions & 13 deletions _plotly_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,18 @@ def to_typed_array_spec(v):
Convert numpy array to plotly.js typed array spec
If not possible return the original value
"""
v = copy_to_readonly_numpy_array(v)

# Skip b64 encoding if numpy is not installed,
# or if v is not a numpy array, or if v is empty
np = get_module("numpy", should_load=False)
if not np or not isinstance(v, np.ndarray) or v.size == 0:
if not np:
return v

# Convert non-numpy homogeneous types to numpy if needed
if not isinstance(v, np.ndarray):
try:
v = np.asarray(v)
except (ValueError, TypeError):
return v

if v.size == 0:
return v

dtype = str(v.dtype)
Expand Down Expand Up @@ -92,26 +98,36 @@ def to_typed_array_spec(v):
return v


_skipped_keys = frozenset({"geojson", "layer", "layers", "range"})


def is_skipped_key(key):
"""
Return whether the key is skipped for conversion to the typed array spec
"""
skipped_keys = ["geojson", "layer", "layers", "range"]
return any(skipped_key == key for skipped_key in skipped_keys)
return key in _skipped_keys


def convert_to_base64(obj):
np = get_module("numpy", should_load=False)
_convert_to_base64(obj, np)


def _convert_to_base64(obj, np):
if isinstance(obj, dict):
for key, value in obj.items():
if is_skipped_key(key):
if key in _skipped_keys:
continue
elif is_homogeneous_array(value):
elif np is not None and isinstance(value, np.ndarray):
obj[key] = to_typed_array_spec(value)
else:
convert_to_base64(value)
elif isinstance(obj, list) or isinstance(obj, tuple):
elif isinstance(value, dict):
_convert_to_base64(value, np)
elif isinstance(value, (list, tuple)):
_convert_to_base64(value, np)
elif isinstance(obj, (list, tuple)):
for value in obj:
convert_to_base64(value)
if isinstance(value, (dict, list, tuple)):
_convert_to_base64(value, np)


def cumsum(x):
Expand Down