You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Validate an architecture that resolves per-type vertex/edge styling neither through per-type Cytoscape selectors nor through per-element data() — so the canvas can be O(1) in style-context count (the #2104 win) and free of per-element display state (the #1887 reconciler's requirement), while making restyles explicit and cheap. This must be settled before the reconciler or further style work commits to an implementation, because #2104 (landed) and #1887 (planned) currently hold contradictory models of what an element carries.
Background / why now
Schema view lockup from per-type Cytoscape style selectors in useGraphStyles #2104 fixed the schema-view lockup by replacing ~20k per-type selectors (node[type="X"]) with a single node/edge rule reading precomputed ge_* values off ele.data(). Style-context count O(elements × selectors) → O(1); Cytoscape STYLE self-time ~88% → ~0% at 10k types. It is a deliberate stepping stone, not the durable end state.
It introduced two downsides:
Data duplication — every element carries ~13 ge_* fields whose values are per-type yet copied per-element; the current cy.json(cloneDeep(...)) full-replace copies them all on every change.
Implicit reactivity — a style edit reaches the canvas only because the element-build hooks re-read style atoms and rebuild the element array; there is no explicit style→canvas channel.
Cytoscape natively offers only per-type selectors (fails #1) or per-element data (fails #2). The spike finds and validates the escape.
Hypothesis to validate (the "unifying bet")
Combine three moves so styling stops being smuggled through the element pipeline:
Class-based dedup — each element carries one stable style-class keyed on its distinct resolved style (not its type). Stylesheet has one rule per distinct style (usually ≪ types; a uniform schema collapses to one rule). Element data shrinks to ~1 class ref. Degrades to O(types) only in the pathological "every type uniquely styled" case — the same worst case as selectors, so no regression.
Style-registry bridge — an external store (mirroring the existing iconRegistry + useSyncExternalStore pattern) owns type→style; a thin bridge patches the stylesheet / ele.data() imperatively on change, outside React render, decoupled from element identity.
Result: element data ≈ 1 class ref; style values live once per distinct style; restyle = patch one rule / the registry, no element rebuild. Both downsides dissolve.
Class-membership churn: when a type's style changes bucket, element class assignments must update — measure that this stays cheap (or that the registry / data-patch path handles it).
A recommendation (function-mapper vs class-based; whether a registry bridge is warranted) backed by a throwaway prototype measuring style-context count, restyle latency, element-data memory, and reference stability under a Vertex-Type color edit at 10k+.
Goal
Validate an architecture that resolves per-type vertex/edge styling neither through per-type Cytoscape selectors nor through per-element
data()— so the canvas can be O(1) in style-context count (the #2104 win) and free of per-element display state (the #1887 reconciler's requirement), while making restyles explicit and cheap. This must be settled before the reconciler or further style work commits to an implementation, because #2104 (landed) and #1887 (planned) currently hold contradictory models of what an element carries.Background / why now
node[type="X"]) with a singlenode/edgerule reading precomputedge_*values offele.data(). Style-context count O(elements × selectors) → O(1); Cytoscape STYLE self-time ~88% → ~0% at 10k types. It is a deliberate stepping stone, not the durable end state.ge_*fields whose values are per-type yet copied per-element; the currentcy.json(cloneDeep(...))full-replace copies them all on every change.ge_*-on-elements reintroduces exactly the baked-in display state that defeats reference diffing — a color edit changes every element'sge_*→ new references → the reconciler sees "everything changed." Reconcile the graph canvas — stop rebuilding on every change #1887's body also still assumesnode[type="X"]selector-driven styles (pre-Schema view lockup from per-type Cytoscape style selectors in useGraphStyles #2104).The three-way constraint
A durable answer must satisfy all three at once:
Cytoscape natively offers only per-type selectors (fails #1) or per-element data (fails #2). The spike finds and validates the escape.
Hypothesis to validate (the "unifying bet")
Combine three moves so styling stops being smuggled through the element pipeline:
iconRegistry+useSyncExternalStorepattern) owns type→style; a thin bridge patches the stylesheet /ele.data()imperatively on change, outside React render, decoupled from element identity.cloneDeepfull-replace) so element data is diffed/patched, not rebuilt + cloned.Result: element data ≈ 1 class ref; style values live once per distinct style; restyle = patch one rule / the registry, no element rebuild. Both downsides dissolve.
Open questions the spike must answer
data(): a Cytoscape style function mapper (ele => typeStyleMap.get(ele.data('type'))) gives O(1) selectors + a shared type→style map + no per-element style data — and Reconcile the graph canvas — stop rebuilding on every change #1887 already proposes exactly this for edge labels. Why did Push node background-color to Cytoscape ele.data() with styled type atoms #1725 deliberately choosedata()string mappers over function mappers? Resolve this (function-mapper caching / perf cost?) — it decides between the function-mapper and class approaches.Expected Outcome
Out of Scope
Related Issues
data()conversion)Important
Internal only — this issue is maintained by the core team and is not accepting external contributions.