Skip to content

Commit ea5aa2d

Browse files
authored
Merge pull request #1 from Khanz9664/pr2-axis-types
Pr2 axis types
2 parents 39d50e4 + 0d839f0 commit ea5aa2d

File tree

3 files changed

+69
-18
lines changed

3 files changed

+69
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
55
## Unreleased
66

77
### Added
8-
- Add `get_computed_values()` method to `BaseFigure` for programmatically accessing values calculated by Plotly.js (starting with computed axis ranges) [[#5552](https://github.com/plotly/plotly.py/issues/5552)]
9-
### Fixed
10-
- 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!
8+
- Add `get_computed_values()` method to `BaseFigure` for programmatically accessing values calculated by Plotly.js, supporting `axis_ranges` and `axis_types` retrieval via a single-pass layout traversal [[#5552](https://github.com/plotly/plotly.py/issues/5552)]
119

1210

1311
## [6.7.0] - 2026-04-09

plotly/basedatatypes.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3489,7 +3489,7 @@ def get_computed_values(self, include=None):
34893489
34903490
This method provides a lightweight interface to access information that is
34913491
not explicitly defined in the source figure but is computed by the
3492-
rendering engine (e.g., autoranged axis limits).
3492+
rendering engine (e.g., autoranged axis limits, detected axis types).
34933493
34943494
Note: This initial implementation relies on full_figure_for_development()
34953495
(via Kaleido) to extract computed values. While the returned object is
@@ -3501,6 +3501,8 @@ def get_computed_values(self, include=None):
35013501
include: list or tuple of str
35023502
The calculated values to retrieve. Supported keys include:
35033503
- 'axis_ranges': The final [min, max] range for each axis.
3504+
- 'axis_types': The detected type for each axis (e.g. 'linear',
3505+
'log', 'date', 'category').
35043506
If None, defaults to ['axis_ranges'].
35053507
35063508
Returns
@@ -3514,6 +3516,10 @@ def get_computed_values(self, include=None):
35143516
>>> fig = go.Figure(go.Scatter(x=[1, 2, 3], y=[10, 20, 30]))
35153517
>>> fig.get_computed_values(include=['axis_ranges'])
35163518
{'axis_ranges': {'xaxis': [0.8, 3.2], 'yaxis': [8.0, 32.0]}}
3519+
3520+
>>> fig.get_computed_values(include=['axis_ranges', 'axis_types'])
3521+
{'axis_ranges': {'xaxis': [0.8, 3.2], 'yaxis': [8.0, 32.0]},
3522+
'axis_types': {'xaxis': 'linear', 'yaxis': 'linear'}}
35173523
"""
35183524
# Validate input
35193525
# --------------
@@ -3529,7 +3535,7 @@ def get_computed_values(self, include=None):
35293535
if not include:
35303536
return {}
35313537

3532-
supported_keys = ["axis_ranges"]
3538+
supported_keys = ["axis_ranges", "axis_types"]
35333539
for key in include:
35343540
if key not in supported_keys:
35353541
raise ValueError(
@@ -3543,20 +3549,22 @@ def get_computed_values(self, include=None):
35433549
full_fig_dict = self.full_figure_for_development(warn=False, as_dict=True)
35443550
full_layout = full_fig_dict.get("layout", {})
35453551

3552+
# Initialize result buckets for each requested key
35463553
result = {}
3547-
3548-
# Extract axis ranges
3549-
# -------------------
35503554
if "axis_ranges" in include:
3551-
axis_ranges = {}
3552-
for key, val in full_layout.items():
3553-
if key.startswith(("xaxis", "yaxis")):
3554-
# Safety checks for axis object and range property
3555-
if isinstance(val, dict) and "range" in val:
3556-
# Explicit conversion to list for JSON serialization consistency
3557-
axis_ranges[key] = list(val["range"])
3558-
3559-
result["axis_ranges"] = axis_ranges
3555+
result["axis_ranges"] = {}
3556+
if "axis_types" in include:
3557+
result["axis_types"] = {}
3558+
3559+
# Single-pass traversal: extract all requested properties in one iteration
3560+
# -------------------------------------------------------------------------
3561+
for key, val in full_layout.items():
3562+
if key.startswith(("xaxis", "yaxis")) and isinstance(val, dict):
3563+
if "axis_ranges" in include and "range" in val:
3564+
# Explicit conversion to list for JSON serialization consistency
3565+
result["axis_ranges"][key] = list(val["range"])
3566+
if "axis_types" in include and "type" in val:
3567+
result["axis_types"][key] = val["type"]
35603568

35613569
return result
35623570

tests/test_core/test_update_objects/test_get_computed_values.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def test_invalid_include_parameter(self):
8585
# Test unsupported key and deterministic error message
8686
with self.assertRaisesRegex(
8787
ValueError,
88-
r"Unsupported key 'invalid'.*Supported keys are: \['axis_ranges'\]",
88+
r"Unsupported key 'invalid'.*Supported keys are: \['axis_ranges', 'axis_types'\]",
8989
):
9090
fig.get_computed_values(include=["invalid"])
9191

@@ -105,3 +105,48 @@ def test_safe_extraction_handling(self):
105105

106106
expected = {"axis_ranges": {"xaxis2": [1, 2]}}
107107
self.assertEqual(computed, expected)
108+
109+
def test_get_computed_axis_types(self):
110+
# Verify standalone extraction of axis types
111+
fig = go.Figure()
112+
mock_full_fig = {
113+
"layout": {
114+
"xaxis": {"range": [0, 10], "type": "linear"},
115+
"yaxis": {"range": [0, 100], "type": "log"},
116+
"xaxis2": {"type": "date"},
117+
}
118+
}
119+
fig.full_figure_for_development = MagicMock(return_value=mock_full_fig)
120+
121+
computed = fig.get_computed_values(include=["axis_types"])
122+
123+
expected = {"axis_types": {"xaxis": "linear", "yaxis": "log", "xaxis2": "date"}}
124+
self.assertEqual(computed, expected)
125+
# axis_ranges should not be present when not requested
126+
self.assertNotIn("axis_ranges", computed)
127+
128+
def test_get_computed_values_combined(self):
129+
# Verify that requesting multiple keys returns all of them correctly
130+
fig = go.Figure()
131+
mock_full_fig = {
132+
"layout": {
133+
"xaxis": {"range": [0, 1], "type": "linear"},
134+
"yaxis": {"range": [0, 10], "type": "log"},
135+
# axis with no range — should appear in axis_types but not axis_ranges
136+
"xaxis2": {"type": "date"},
137+
}
138+
}
139+
fig.full_figure_for_development = MagicMock(return_value=mock_full_fig)
140+
141+
computed = fig.get_computed_values(include=["axis_ranges", "axis_types"])
142+
143+
self.assertEqual(
144+
computed["axis_ranges"],
145+
{"xaxis": [0, 1], "yaxis": [0, 10]},
146+
)
147+
self.assertEqual(
148+
computed["axis_types"],
149+
{"xaxis": "linear", "yaxis": "log", "xaxis2": "date"},
150+
)
151+
# Verify single call to the rendering engine despite two keys
152+
fig.full_figure_for_development.assert_called_once()

0 commit comments

Comments
 (0)