Don't leave empty dict containers behind when nested properties are set to None - #5637
Don't leave empty dict containers behind when nested properties are set to None#5637ahmojo wants to merge 2 commits into
Conversation
…et to None
Setting a nested property such as marker.colorbar.thicknessmode to None
left a residual empty {} container in the figure's props, which was then
emitted in the figure JSON. For splom traces this residual
marker.colorbar {} made a later Plotly.restyle of colorbar attributes
misconvert the colorbar thickness and collapse the scatter-matrix layout.
_set_in now treats removal of a non-existent path as a no-op and prunes
emptied dict parents, and property assignment to None prunes emptied
compound-child dicts (lists are preserved as positional placeholders).
Fixes plotly#5615
|
Thanks for the PR! We'll take a look and follow up. We're a bit backed up at the moment, so it might take a while to get through it. |
|
Hi @ahmojo, thanks for the contribution! This is an interesting change. Pruning empty dicts left behind after a value is removed by setting it to The reproduction example in #5637 never actually calls |
Thanks for pointing this out @emilykl. I tested this with a live FigureWidget using only Python. A direct restyle on a pristine widget works normally, but the same set-and-clear history from the issue reproduces the collapse: Then, after it renders: The clear leaves colorbar: {} in the browser-side trace. The final restyle changes the thickness to 16,290px and shrinks the plot width from 543px to 64px. |
Link to issue
Related to #5615
Description of change
Setting a nested property to
Noneafter it had been set could leave an empty compound container in the emitted figure JSON. For example, clearingmarker.colorbar.thicknessmodeleftmarker: {"colorbar": {}}. The same residue could be created whenplotly_restyle(..., None)targeted a never-set nested path, even though no restyle message was sent.This is inconsistent with construction, where empty compound containers are already pruned. This PR makes removal of a missing path a no-op and prunes emptied scalar compound containers while preserving list positions.
In the static-HTML reproduction from #5615, removing that residue prevents it from being included in the initial payload and avoids the subsequent JavaScript restyle collapse. This does not change plotly.js itself: a live
FigureWidgetwhose browser-side state already containscolorbar: {}can still collapse after a later restyle.Demo
I isolated the Python serialization trigger using the issue's Playwright reproduction (1200x800 viewport, the same bundled plotly.js for every variant):
colorbarkeymain{"colorbar": {}}colorbarkeyWith this change, the static-HTML output matches the pristine control. The separate live-
FigureWidgetpath remains a plotly.js/frontend-state issue and is outside the scope of this cleanup.Testing strategy
Added two regression tests in
tests/test_core/test_figure_messages/test_plotly_restyle.py:Targeted checks:
uv run --with-editable . --with pytest python -m pytest tests/test_core/test_figure_messages/test_plotly_restyle.py- 10 passeduv run --with-editable . --with pytest python -m pytest tests/test_core/test_graph_objs/test_property_assignment.py- 28 passeduv run --with ruff ruff check plotly/basedatatypes.py tests/test_core/test_figure_messages/test_plotly_restyle.py- passedAdditional information (optional)
This changes serialized output for set-then-unset scalar compound properties across the library, bringing assignment and restyle cleanup in line with construction-time behavior. Compound-array/list entries remain in place as positional placeholders, and batch updates are pruned when the batch flushes.
Guidelines