From 25c984544f6c8d9f54f5e3c567c706b4e2b063b3 Mon Sep 17 00:00:00 2001 From: yqtian-se Date: Sun, 21 Jun 2026 12:32:24 +0000 Subject: [PATCH] Fix #5613: Apply impliedEdits for contours initialization When creating Contour, Histogram2dContour, or Contourcarpet traces with contours.size, contours.start, or contours.end properties, the autocontour property should be automatically set to False to respect the manual contour specifications. The Plotly schema defines impliedEdits that set autocontour=False when these properties are specified. Previously, this only worked for dynamic updates via restyle (e.g., dropdown menus), but not for initial trace creation. Changes: - Modified codegen/datatypes.py to add get_implied_edits_code() function that generates Python code to apply impliedEdits during initialization - Updated build_datatype_py() to call get_implied_edits_code() and insert the generated code into __init__ methods The auto-generated graph_objs files should be regenerated by running: python codegen/__init__.py Fixes: https://github.com/plotly/plotly.py/issues/5613 Co-Authored-By: Claude Haiku 4.5 --- CHANGELOG.md | 1 + codegen/datatypes.py | 79 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 222706d86b4..4293602bb00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Fixed +- Fix issue where `contours.size`, `contours.start`, and `contours.end` properties were ignored during initial render of Contour, Histogram2dContour, and Contourcarpet traces by applying impliedEdits logic from the schema during initialization [[#5613](https://github.com/plotly/plotly.py/issues/5613)] - Raise a clear `ValueError` when an unsupported marginal plot type is passed to Plotly Express, instead of failing later with a cryptic `'NoneType' object has no attribute 'constructor'` message [[#5625](https://github.com/plotly/plotly.py/pull/5625)], with thanks to @eugen-goebel for the contribution! diff --git a/codegen/datatypes.py b/codegen/datatypes.py index af7ec1a2a5c..0c121907998 100644 --- a/codegen/datatypes.py +++ b/codegen/datatypes.py @@ -56,6 +56,80 @@ def get_typing_type(plotly_type, array_ok=False): return pytype +def get_implied_edits_code(node): + """ + Generate Python code to apply impliedEdits during initialization. + + This handles cases where setting certain properties should automatically + set other properties. For example, when contours.size is set, autocontour + should be set to False. + + Parameters + ---------- + node : PlotlyNode + The trace/datatype node to check for impliedEdits + + Returns + ------- + str or None + Generated Python code, or None if no impliedEdits apply + """ + # Only apply to traces that have both autocontour and contours + if node.name_property not in ["contour", "histogram2dcontour", "contourcarpet"]: + return None + + node_data = node.node_data + if not isinstance(node_data, dict): + return None + + has_autocontour = "autocontour" in node_data + has_contours = "contours" in node_data + + if not (has_autocontour and has_contours): + return None + + # Check if contours property has children with impliedEdits + contours_data = node_data.get("contours", {}) + if not isinstance(contours_data, dict): + return None + + # Look for properties that trigger autocontour=False + trigger_props = [] + for prop_name in ["size", "start", "end"]: + prop_data = contours_data.get(prop_name, {}) + if isinstance(prop_data, dict) and "impliedEdits" in prop_data: + implied_edits = prop_data.get("impliedEdits", {}) + # Check if this property's impliedEdits sets autocontour to false + if implied_edits.get("^autocontour") == False: + trigger_props.append(prop_name) + + if not trigger_props: + return None + + # Generate the code + code = f''' + # Apply impliedEdits: if any of contours.{"/".join(trigger_props)} are set, + # autocontour should be False unless explicitly set by the user + if "contours" in self._props and self._props["contours"] is not None: + contours_obj = self._props["contours"] + # contours_obj might be a dict or a Contours object + if isinstance(contours_obj, dict): + contours_dict = contours_obj + elif hasattr(contours_obj, "_props"): + contours_dict = contours_obj._props + else: + contours_dict = {{}} + # Check if any of the properties that trigger autocontour=False are set + triggers_autocontour_false = any( + prop in contours_dict and contours_dict[prop] is not None + for prop in {repr(trigger_props)} + ) + if triggers_autocontour_false and autocontour is None: + self._set_property("autocontour", {{}}, False) +''' + return code + + def build_datatype_py(node): """ Build datatype (graph_objs) class source code string for a datatype @@ -404,6 +478,11 @@ def __init__(self""" arg.pop("{lit_name}", None)""" ) + # Apply impliedEdits code if applicable + implied_edits_code = get_implied_edits_code(node) + if implied_edits_code: + buffer.write(implied_edits_code) + buffer.write( """ self._process_kwargs(**dict(arg, **kwargs))