Skip to content

Allow dynamic changes to st.pydeck_chart parameters when key is provided #13703

Merged
lukasmasuch merged 10 commits intodevelopfrom
lukasmasuch/pydeck-key-selection
Jan 29, 2026
Merged

Allow dynamic changes to st.pydeck_chart parameters when key is provided #13703
lukasmasuch merged 10 commits intodevelopfrom
lukasmasuch/pydeck-key-selection

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

Describe your changes

This PR enables st.pydeck_chart to preserve selection state when map data or parameters change, when a user provides a key parameter. Selection state now persists across data updates, similar to st.vega_lite_chart.

Backend Change: Set key_as_main_identity={"selection_mode"} so the element ID depends only on the selection mode, not the spec.

Frontend Changes:

  • Add selection sanitization to remove orphaned indices when data shrinks or layers are removed
  • Improve the anyLayersHaveSelection check to validate indices before applying dimming
  • Add proper React key in ElementNodeRenderer matching ArrowVegaLiteChart pattern

Testing Plan

  • Tested locally that TypeScript compiles without errors
  • Manual testing recommended: verify selection persists when data updates with key="my_map"
  • Verify backward compatibility: selection resets without a key or when selection_mode changes
  • Edge case: verify no visual glitches when data shrinks (orphaned indices are sanitized)

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

…meter

When a user provides a `key` parameter to st.pydeck_chart with selections
enabled, the element ID now remains stable across data/spec changes, allowing
selection state to persist similar to st.vega_lite_chart.

Changes:
- Backend: Set key_as_main_identity={"selection_mode"} to use only the
  selection mode for ID computation when key is provided
- Frontend: Add selection sanitization useEffect to remove orphaned indices
  when data shrinks or layers are removed
- Frontend: Improve anyLayersHaveSelection check to validate indices before
  applying dimming, preventing visual glitches
- Frontend: Add proper React key in ElementNodeRenderer matching
  ArrowVegaLiteChart pattern

Co-Authored-By: Claude <[email protected]>
Copilot AI review requested due to automatic review settings January 27, 2026 02:20
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Jan 27, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 27, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13703/streamlit-1.53.1-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13703.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements selection state preservation for st.pydeck_chart when users provide a key parameter, matching the established pattern used by st.vega_lite_chart and st.dataframe. The implementation allows map selections to persist across data updates without forcing a full reset.

Changes:

  • Backend: Modified element ID computation to use key_as_main_identity={"selection_mode"}, making element identity stable when a key is provided
  • Frontend: Added selection sanitization logic to remove orphaned indices when data shrinks or layers are removed
  • Frontend: Enhanced the anyLayersHaveSelection validation to prevent visual glitches with stale selections
  • Frontend: Added React key pattern in ElementNodeRenderer consistent with ArrowVegaLiteChart

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/streamlit/elements/deck_gl_json_chart.py Changed key_as_main_identity from False to {"selection_mode"} to preserve element ID across spec changes when a key is provided
frontend/lib/src/components/elements/DeckGlJsonChart/useDeckGl.tsx Added sanitization useEffect to clean orphaned selections and improved selection validation logic
frontend/lib/src/components/core/Block/ElementNodeRenderer.tsx Added conditional React key based on element ID, matching the ArrowVegaLiteChart pattern
dynamic-pydeck-chart-opus.md Documentation file providing implementation analysis and context (not production code)
dynamic-pydeck-chart-codex.md Documentation file with technical deep-dive (not production code)

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

- Add Python unit tests for element ID stability with key
- Add E2E test for selection preservation when data updates
- Add selection sanitization effect for orphaned indices
- Add smarter dimming check to validate indices before applying
- Remove planning documents

Co-Authored-By: Claude <[email protected]>
@lukasmasuch lukasmasuch added change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users security-assessment-completed labels Jan 27, 2026
- Fix non-array layer data handling: preserve selections for layers with
  URL strings, GeoJSON objects, or undefined data instead of clearing them
- Extract duplicated layer data lengths logic into getLayerDataLengths helper
- Remove data.selection from useEffect dependency array to avoid unnecessary
  re-executions (only sanitize when spec changes)
- Fix unit tests to properly verify element ID stability with same key
  using mock to bypass duplicate key restriction

Co-Authored-By: Claude <[email protected]>
@lukasmasuch lukasmasuch changed the title Support selection state preservation in st.pydeck_chart with key Allow dynamic changes to st.pydeck_chart parameters when key is provided Jan 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 27, 2026

📈 Frontend coverage change detected

The frontend unit test (vitest) coverage has increased by 0.1300%

  • Current PR: 86.4000% (13626 lines, 1853 missed)
  • Latest develop: 86.2700% (13562 lines, 1862 missed)

🎉 Great job on improving test coverage!

📊 View detailed coverage comparison

@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR makes st.pydeck_chart selection state stable across spec changes when a key is provided, adds frontend sanitization to drop orphaned selection indices, and aligns DeckGL rendering with widget-style React keys. It also adds a dynamic data e2e scenario and Python unit tests for element ID stability.

Code Quality

The new selection sanitization is cleanly encapsulated, but it only updates client-side state. Because updates are marked fromUi: false, the backend selection can remain stale on the rerun that introduces a data shrink or layer removal, which can diverge from what the UI displays and what app code sees.

  useEffect(() => {
    if (!isSelectionModeActivated || !parsedPydeckJson.layers) {
      return
    }
    // ...
    if (changed) {
      setSelection({
        fromUi: false,
        value: { selection: { indices: newIndices, objects: newObjects } },
      })
    }
    // ...
  }, [parsedPydeckJson, isSelectionModeActivated, setSelection])
            pydeck_proto.id = compute_and_register_element_id(
                "deck_gl_json_chart",
                user_key=key,
                # When a key is provided, only selection_mode affects the element ID.
                # This allows selection state to persist across data/spec changes.
                # Note: This can lead to orphaned selections if data length shrinks,
                # but the frontend handles this by sanitizing invalid indices.
                key_as_main_identity={"selection_mode"},

Test Coverage

The new e2e test verifies selection persistence on data updates, but it does not cover the orphaned-index cases the sanitization logic targets (data shrink or layer removal), and it lacks a “must NOT happen” assertion per the e2e guidelines.

@pytest.mark.only_browser("chromium")
@pytest.mark.flaky(reruns=4)
def test_pydeck_chart_selection_preserved_when_data_updates(app: Page):
    """
    Test that selection state is preserved when data updates and a key is provided.
    This tests the key_as_main_identity={"selection_mode"} behavior.
    """
    _select_chart_type(app, "dynamic")
    wait_for_chart(app)
    # ...
    # Selection should still be preserved because key is provided
    expect_prefixed_markdown(app, markdown_prefix, FIRST_POINT_SELECTION)
    expect_prefixed_markdown(app, markdown_prefix_session_state, FIRST_POINT_SELECTION)

Backwards Compatibility

Behavior changes are gated behind providing key and enabling selections; the non-keyed path continues to reset selection on spec changes. No protobuf changes are introduced.

Security & Risk

No security issues identified. The main risk is stale selection indices when data shrinks, which can lead to UI/backend divergence or out-of-range indexing in app code on the rerun that introduces the data change.

Recommendations

  1. Add a test case that shrinks data or removes a layer to exercise the sanitization logic and include a “must NOT happen” assertion (e.g., no dimming or no stale selection after update).
  2. Consider syncing the sanitized selection back to the backend or documenting that selection objects may be stale after data updates, especially when data shrinks.
  3. Align the new Python tests with the unit test guide by adding type annotations and avoiding direct MagicMock/patch imports if feasible.

Verdict

APPROVED: Changes are solid and behavior is well-motivated; only follow-up coverage/consistency improvements are recommended.


This is an automated AI review using gpt-5.2-codex-high. Please verify the feedback and use your judgment.

lukasmasuch and others added 4 commits January 27, 2026 14:30
Add comprehensive tests for:
- Clear selection button rendering in DeckGlJsonChart
- Selection sanitization in useDeckGl hook (layer removal, data shrinking, URL data preservation)
- PydeckSelectionSerde serialization/deserialization
- parse_selection_mode function validation
- Callback handling with on_select parameter

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude (claude-opus-4-5) <[email protected]>
The hasUnknownLayerId flag was incorrectly preserving orphaned
selections when any layer lacked an ID. Now only preserve when
ALL layers lack IDs (layerData is empty), allowing proper cleanup
of non-existent layer selections in mixed scenarios.

Co-Authored-By: Claude (claude-opus-4-5) <[email protected]>
}
)

it("should render clear selection button when there is an active selection and not disabled", async () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this test related, or is it just opportunistically adding more test coverage?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's partly related to the selection state itself, which we are touching in this PR, but I also requested Claude to add some more tests around the selection logic (which was previously a but undercovered)

parsedPydeckJson.layers
)

// Filter out orphaned selections
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make this into a separate function for readability/testing?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, extracted it!

continue
}

const objects = data.selection.objects[layerId] || []
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this block could be a small function.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated as well 👍

const { layerData } = getLayerDataInfo(jsonCopy.layers)

// Only consider a selection valid if the layer exists and has at least one
// index within the current data length. This prevents dimming when selection
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very familiar with this element, is dimming referring to changing the node's appearance to unselected?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, when a selection is active, unselected points are dimmed by reducing their opacity to 40%, making selected points stand out. I've updated the comment to clarify this behavior.



class PydeckSelectionSerdeTest(DeltaGeneratorTestCase):
"""Test PydeckSelectionSerde serialization and deserialization."""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these opportunistic test coverage? Or related to the changes?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, see the other comment

assert "Selection mode must be a single value" in str(e.value)


class PydeckCallbackTest(DeltaGeneratorTestCase):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this one also, is opportunistic added coverage for callbacks and not directly related?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indirectly related since it covers the selection logic itself. So a bit more opportunistic to fill in missing coverage for selection logic on pydeck_chart.

lukasmasuch and others added 2 commits January 29, 2026 21:45
Extract selection sanitization logic into reusable helper functions
for better readability and testability:
- sanitizeSelection(): main orchestration function
- filterValidIndicesForLayer(): index validation for array data

Also clarify "dimming" comment to explain it reduces opacity to 40%.

Co-Authored-By: Claude (claude-opus-4-5) <[email protected]>
@lukasmasuch lukasmasuch merged commit 6f66a1c into develop Jan 29, 2026
42 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/pydeck-key-selection branch January 29, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants