Skip to content
ChartForgeX

Graph explorer

Host-neutral scenes, hierarchy, patches and native browser backends.

ChartForgeX separates graph data from browser behavior:

  • ChartForgeX.Interactivity owns the dependency-free, host-neutral GraphScene document: nodes, edges, clusters, hierarchy, level of detail, performance budgets, and atomic patches.
  • ChartForgeX.Interactivity.Html turns that document into a self-contained explorer with SVG, Canvas, and WebGL rendering. The generated page has no CDN, JavaScript framework, or runtime package dependency.
  • TopologyChart remains the deterministic topology owner for static SVG, PNG, HTML, reports, maps, and scenarios. ToGraphScene(...) is the optional bridge when a topology needs large interactive exploration.

This keeps report and email output deterministic while letting browser, desktop, or native adapters reuse the same graph contract.

Build a hierarchy

using ChartForgeX.Interactivity;
using ChartForgeX.Interactivity.Html;

var graph = GraphScene.Create("service-estate", "Service estate")
    .AddNode("estate", "Global estate", node => {
        node.Kind = "platform";
        node.BadgeText = "42";
        node.SecondaryLabel = "9 sites";
        node.Shape = GraphNodeShape.Image;
        node.ImageUrl = "data:image/svg+xml,...";
    })
    .AddNode("europe", "Europe", node => {
        node.ParentId = "estate";
        node.Kind = "region";
        node.Status = "warning";
        node.BadgeText = "18";
        node.SecondaryLabel = "4 sites · 18 workloads";
    })
    .AddNode("identity", "Identity plane", node => {
        node.ParentId = "europe";
        node.Kind = "identity";
        node.Status = "healthy";
    })
    .AddEdge("estate-europe", "estate", "europe", configure: edge => {
        edge.Kind = "hierarchy";
        edge.Directed = true;
        edge.LayoutDirected = true;
    })
    .AddEdge("europe-identity", "europe", "identity", configure: edge => {
        edge.Kind = "hierarchy";
        edge.Directed = true;
        edge.LayoutDirected = true;
    });

graph.Options.UseSuperTopologyDefaults();
graph.Options.Layout.Mode = GraphLayoutMode.Hierarchical;
graph.Options.Layout.Direction = GraphLayoutDirection.LeftToRight;
graph.Options.Hierarchy.InitialRootNodeId = "estate";
graph.Options.Hierarchy.InitialDepth = 1;

var html = graph.ToGraphExplorerHtmlPage(options => {
    options.PageTitle = "Service Estate";
    options.RenderBackend = HtmlGraphRenderBackend.Svg;
});
File.WriteAllText("service-estate.html", html);

Image nodes stretch the complete source image to their image rectangle; circular nodes then clip it to a circle. SVG and PNG use the same scaling. Supply an image with a matching aspect ratio when its original proportions must be preserved.

Double-clicking a node with children enters that node. Double-clicking empty graph space returns one level; Escape, Backspace, and Left Arrow do the same from the graph. Every breadcrumb segment is clickable, while Overview and Up remain explicit accessible controls. Search Enter selects and centers the first match. Semantic zoom reduces labels in overview mode and reveals secondary labels and badges in detail mode.

ParentId is a real validated contract: missing parents, self-parenting, and cycles fail before rendering. ParentClusterId provides the same structural relationship for nested cluster descriptions.

Premium themes and accessibility

The HTML adapter owns one responsive explorer shell rather than leaving each generated page to invent its own fields, graph controls, physics panel, breadcrumbs, focus treatment, and minimap. Search, filters, and appearance live in the discovery header. Hierarchy and graph commands float inside the stage, where icon-only actions have accessible names and custom tooltips; export formats open as a labeled popover instead of three unexplained glyphs. The shell and all three rendering backends consume the same theme tokens.

System is the default. It follows prefers-color-scheme until the reader chooses Light or Dark, and the choice is reused on the same origin by default:

var html = graph.ToGraphExplorerHtmlPage(options => {
    options.Theme = HtmlGraphExplorerTheme.System;
    options.IncludeThemeToggle = true;
    options.PersistThemePreference = true;
});

Use Light or Dark for a fixed initial mode. Set IncludeThemeToggle = false when a host owns appearance, and set PersistThemePreference = false for isolated report pages that should not reuse a browser choice. Hosts can change or inspect the current mode without rebuilding the page:

ChartForgeXGraphExplorer.theme("service-estate", "dark");
const appearance = ChartForgeXGraphExplorer.theme("service-estate");
// { mode: "dark", active: "dark" }

Every change raises cfxgraphthemechange with mode and resolved active colors. Scoped CSS custom properties such as --cfx-color-accent, --cfx-color-surface-raised, --cfx-color-paper, and --cfx-color-focus are the supported host override seam. Canvas, WebGL, the minimap, SVG, and exported SVG all resolve through the same palette. Authored node fills remain unchanged across themes; cards without an authored fill use the active card surface. Label colors adapt for contrast against their actual card, label plate or graph background.

Accessibility behavior is part of the adapter contract:

  • Controls have explicit names, logical groups, visible text where it helps orientation, and a high-visibility focus ring.
  • SVG uses one roving tab stop. Arrow keys move spatially between visible graph items; Home and End jump to the bounds; Enter or Space select. This keeps large graphs out of the normal page tab sequence.
  • Canvas and WebGL expose one labeled keyboard surface. Arrow keys move the current node selection and announce it.
  • Search results, selection, hierarchy, physics, and appearance state use polite live regions without replacing visible status.
  • prefers-reduced-motion: reduce suppresses intermediate stabilization frames, connected-node bounce during dragging, and release momentum while retaining deliberate drag and final layout results.
  • forced-colors: active and prefers-contrast: more replace decorative surfaces with system colors and strengthen structural borders. Status and selection remain distinguishable by stroke, text, or state—not color alone.
  • At narrow widths, search and named filters remain in the header while the graph command rail becomes a horizontally scrollable bottom dock. The breadcrumb stays inside the stage and the theme action keeps its accessible name.

These defaults target WCAG 2.2 contrast, focus visibility, and target size. Accessibility still depends on callers supplying useful node labels, image alternative text, and status wording.

Control language

Configure Localization on the HTML options to translate explorer controls, search counts, announcements, editing feedback, and appearance descriptions. Messages with named placeholders preserve the supplied values as text.

var html = graph.ToGraphExplorerHtmlPage(options => {
    options.Localization.Language = "pl-PL";
    options.Localization[HtmlChartText.GraphSearchPlaceholder] = "Szukaj węzłów i relacji";
    options.Localization[HtmlChartText.GraphMatches] = "Wyniki: {count}";
    options.Localization[HtmlChartText.GraphItemsSelected] = "Wybrane elementy: {count}";
});

Unspecified messages use English. Set Localization.Direction to HtmlChartTextDirection.RightToLeft for right-to-left controls; Automatic inherits the embedding document. Graph labels, IDs, kinds, status values, metadata, and numeric facts remain authored. Localization does not change native SVG/PNG labels or the stable values used in host commands. Shared external assets carry the same message resolver as self-contained pages; each explorer keeps its own resolved bundle.

Large topology pipeline

Small scenes remain SVG-first because SVG provides rich shapes, images, labels, keyboard targets, and inspectable metadata. As scenes cross their configured thresholds, the same document moves through Canvas and WebGL without changing the C# model.

The large-scene path has four layers:

  1. C# computes deterministic initial positions and serializes a compact graph document.
  2. The browser creates lightweight runtime items rather than thousands of hidden SVG marks.
  3. WebGL2 draws styled routes and compact circular marks in batched buffers. Canvas draws cluster surfaces beneath those marks and rich shapes, images and readable details above them; Canvas also renders the full scene when WebGL2 is unavailable.
  4. SVG export materializes complete node and edge artwork on demand, so fast startup does not remove vector export.

The compact document is activated only after a level-of-detail threshold is crossed. Normal SVG scenes retain their complete markup. Large pages therefore avoid the hidden-SVG DOM cost while keeping search, filters, selection, neighborhood focus, cluster state, hit testing, JSON export, and incremental updates.

UseSuperTopologyDefaults() enables a practical starting policy. Every threshold remains configurable:

graph.Options.UseSuperTopologyDefaults();
graph.Options.LevelOfDetail.CanvasPreferredNodeThreshold = 500;
graph.Options.LevelOfDetail.WebGlPreferredNodeThreshold = 2500;
graph.Options.LevelOfDetail.OverviewScaleThreshold = 0.62;
graph.Options.LevelOfDetail.DetailScaleThreshold = 1.12;
graph.Options.Performance.MaxInteractiveWebGlNodes = 30000;
graph.Options.Performance.MaxInteractiveWebGlEdges = 80000;

WebGL supports node and edge labels, secondary labels, badges, status indicators, cards, all modeled node shapes, images, and per-edge widths. Curves, prepared polylines, self loops, dashes and directional arrows follow the same geometry and styling rules as Canvas. Compact and moving views reduce labels according to the configured level-of-detail policy; selected and focused items retain their detail. Curved and turning routes, overlapping round dash caps, extremely fine dash patterns and marks beyond the GPU driver's point-size range use the shared Canvas layer. Overlapping mixed marks share that layer to preserve body and status order. Routes that overlap a native fallback route also share an ordered paint pass; separate compact circles and forward straight routes retain GPU batching. Round-capped dash cycles smaller than half a physical pixel render as a continuous stroke when their caps cover the gaps; zooming in restores the visible pattern.

During dense motion, strokes below one physical pixel use opacity proportional to their width. Zooming into the scene or settling physics restores full stroke geometry.

Panning and zooming reuse retained GPU geometry. Position changes, selection, filters, patches, theme changes and surface resizing invalidate the affected rendering state. If a WebGL context is lost, the explorer switches to Canvas and retains its viewport, selection and graph document; restoration rebuilds the GPU resources. PNG export uses the complete shared Canvas renderer, then restores the current interactive graph, including updates received while images load.

The adapter shares decoded node images between active scenes. Replacing or removing image URLs releases entries after their last scene consumer leaves; disposal releases that scene's entries. An asynchronous PNG export retains its captured images until it finishes or is disposed, so a concurrent host patch cannot evict them. This bounds adapter cache retention by active scenes and exports; it does not impose an image-size limit or control the browser's own image cache.

The generated graph-rich-rendering-svg.html, graph-rich-rendering-canvas.html and graph-rich-rendering-webgl.html examples show the same scene in all three backends, including route updates and WebGL context loss/restoration.

Runtime physics and dragging

Runtime physics is adapter behavior over the same host-neutral graph document. The HTML adapter implements four distinct solver families:

  • BarnesHut uses quadtree-accelerated inverse-square repulsion for large general graphs;
  • ForceAtlas2 uses degree-weighted, inverse-linear repulsion for relationship and community graphs;
  • Repulsion uses direct distance-limited repulsion for smaller scenes;
  • HierarchicalRepulsion adds level-aware separation for directed layered graphs.

Each solver owns its meaningful settings instead of sharing ambiguous flat knobs. Common velocity, timestep, and stabilization settings remain on GraphPhysicsOptions:

graph.Options.Physics.Solver = GraphPhysicsSolver.ForceAtlas2;
graph.Options.Physics.MinVelocity = 0.1;
graph.Options.Physics.MaxVelocity = 50;
graph.Options.Physics.Timestep = 0.5;
graph.Options.Physics.AdaptiveTimestep = true;
graph.Options.Physics.Stabilization.Enabled = true;
graph.Options.Physics.Stabilization.Iterations = 700;

graph.Options.Physics.ForceAtlas2.Theta = 0.5;
graph.Options.Physics.ForceAtlas2.GravitationalConstant = -72;
graph.Options.Physics.ForceAtlas2.CentralGravity = 0.01;
graph.Options.Physics.ForceAtlas2.SpringLength = 92;
graph.Options.Physics.ForceAtlas2.SpringConstant = 0.08;
graph.Options.Physics.ForceAtlas2.Damping = 0.4;
graph.Options.Physics.ForceAtlas2.AvoidOverlap = 0.82;

Scenes with at least 160 active nodes move physics to a browser worker automatically when the platform supports it. SVG, Canvas, and WebGL consume the same live coordinates, so changing rendering backends does not change drag semantics. Hierarchy navigation, cluster changes, and runtime graph patches rebuild and reheat the active simulation by default.

Worker updates transfer a reusable position buffer and wait for the browser to present it before calculating the next batch. This bounds queued updates when a tab is throttled. During dragging, newer edits retain their coordinates while neighboring nodes continue receiving physics updates. Removing the graph stops its simulation at the next callback. WebGL retains upload storage and grows it only when a larger visible scene requires more capacity.

ReleaseAndReheat is the default drag policy. The held node is fixed only while the pointer is down, connected movable nodes keep responding, recent pointer momentum is transferred on release, and the node's original fixed state is restored. Use PinOnDrop when the dropped position should become a new fixed anchor:

graph.Options.Interaction.NodeDragBehavior = GraphNodeDragBehavior.ReleaseAndReheat;
graph.Options.Interaction.SimulateConnectedNodesWhileDragging = true;
graph.Options.Interaction.DragMomentum = 0.18;
graph.Options.Interaction.ReheatAfterDrag = true;
graph.Options.Interaction.ReheatAfterClusterChange = true;
graph.Options.Interaction.ReheatAfterHierarchyChange = true;
graph.Options.Interaction.ReheatAfterGraphChange = true;

The premium configurator is intentionally opt-in. It is useful while tuning an example or host preset, but normal generated reports do not need the extra controls:

var html = graph.ToGraphExplorerHtmlPage(options => {
    options.IncludePhysicsConfigurator = true;
});

It switches solvers, enables only the applicable fields, updates the document attributes, and reheats the scene. Hosts can perform the same operation without the UI:

ChartForgeXGraphExplorer.physics("service-estate", {
  solver: "BarnesHut",
  springLength: 86,
  gravitationalConstant: -2400,
  avoidOverlap: 0.75
});

Static ToGraphSvg, ToGraphPng, and stage-image exports never execute browser physics. They remain deterministic, script-free renders of the prepared document or requested hierarchy stage.

Migrating flat physics settings

The solver-specific model intentionally replaces the former flat API; no compatibility layer keeps ambiguous settings alive:

Former settingCurrent setting
StabilizationIterationsStabilization.Iterations
LinkDistanceThe active solver's SpringLength
DampingThe active solver's Damping
CenterGravityThe active solver's CentralGravity
RepulsionBarnesHut.GravitationalConstant, ForceAtlas2.GravitationalConstant, or Repulsion.Strength, depending on the solver
GraphPhysicsSolver.ForceDirectedGraphPhysicsSolver.Repulsion or one of the accelerated named solvers

VisNetworkPhysicsOptions remains the typed migration envelope for vis-network-style input. Its flat values are mapped into the selected ChartForgeX solver profile during conversion.

Save overview-to-detail images

Interactive HTML is optional. The same hierarchy can be planned once and saved as deterministic, script-free SVG and PNG stages from C#:

var files = graph.SaveGraphStageImages("report-assets", "estate", options => {
    // Save1 = overview, then levels 1, 2, 3, and 5/full.
    options.Stages.Depths.AddRange(new[] { 0, 1, 2, 3, 5 });
    options.Formats = GraphSceneStaticImageFormat.Both;
    options.Render.Width = 1600;
    options.Render.Height = 900;
    options.Render.MaximumNodeLabels = 160;
});

Files use stable names such as estate-01-overview.png, estate-02-depth-1.png, and estate-05-full.png. A frontier node receives a +N badge and N hidden secondary label, so a static overview still communicates what was collapsed. The deepest requested level is clamped to the scene depth and the complete view is included by default.

For one image, use graph.ToGraphSvg(stage) or graph.ToGraphPng(stage). These paths do not open a browser and do not add scripts. PNG export preserves self-contained PNG, JPEG, or SVG data-URI node images. Static output limits labels deterministically at large scale—frontier summaries, roots, and high-degree nodes win—because rendering 2,000 labels into one non-interactive frame would not be readable. Set MaximumNodeLabels explicitly for the target medium. Static output is light by default; set Theme to HtmlGraphExplorerTheme.Dark for a dark SVG and PNG, or to System so SVG follows the viewer's color preference (PNG has no viewer and renders light).

For a high-degree node, CreateNeighborhood bounds the selected objects and relationships before rendering:

var view = graph.CreateNeighborhood("gateway", options => {
    options.MaximumNodes = 13; // Includes the gateway on every page.
    options.MaximumEdges = 24;
    options.Hops = 1;
    options.NeighborOffset = 0; // Use 12 for the next page.
});
File.WriteAllText("gateway.svg", graph.ToGraphSvg(view));
File.WriteAllBytes("gateway.png", graph.ToGraphPng(view));

ScopeNodeCount reports the neighborhood size before paging. HiddenNodeCount and HiddenEdgeCount count all source objects omitted from the view; BoundaryEdgeCount counts relationships with exactly one visible endpoint. Static stage images show these counts below the drawing. Pages retain the root and order other nodes by relationship distance, then ordinal id. Discovery follows relationships in both directions, while output preserves their original arrows. Edge budgets prefer relationships nearest the root. A page can contain disconnected nodes when their intermediate neighbors are on another page.

The planner leaves the source scene unchanged and examines the full source graph. Replan after changing its membership or relationships. Static neighborhoods with no authored coordinates or routes receive a deterministic radial layout; authored geometry is preserved. Interactive Focus uses the same default budgets: 13 visible nodes and 24 relationships. Configure them through graph.Options.Neighborhood. The browser respects active filters and collapsed or hierarchical views when choosing eligible objects; hidden objects can still connect the discovery path.

Select a node and choose Focus. Previous neighbors and Next neighbors page through the neighborhood; selecting a visible node drills into its neighborhood. Back restores the previous page and viewport, and Overview restores the entry viewport and selection. Focus pauses physics, preserves node coordinates and pins, and leaves physics paused until explicitly restarted. Authored dense positions remain dense; focusing does not rearrange them. A filter that removes every neighbor from the current page resets paging to the first eligible page.

Hosts can call ChartForgeXGraphExplorer.focus(target, nodeId, { hops: 2, maximumNodes: 13, maximumEdges: 24 }), focusPage(target, 1), focusBack(target), or focus(target, null) to return to the overview. Missing or unavailable roots return false; invalid numeric budgets throw RangeError. State snapshots include the focus budgets, page offset, and bounded navigation history. cfxgraphfocus reports visible, omitted, scope, and boundary counts.

Clustering

GraphClusterMode supports:

  • Explicit for caller-authored clusters;
  • ByGroup for deterministic group-derived summaries;
  • Adaptive for deterministic, bounded structural communities;
  • Hybrid for declared clusters first and structural summaries when needed.

Adaptive cluster ids and membership are stable for the same graph document. Clusters can collapse globally from the toolbar or individually by double-clicking a summary. Their current state is included in JSON export and host events.

Incremental updates

The C# document supports validated atomic patches:

var patch = new GraphScenePatch();
patch.RemoveNodeIds.Add("old-api");
patch.UpsertNodes.Add(new GraphSceneNode {
    Id = "queue",
    Label = "Queue",
    ParentId = "platform",
    Status = "warning"
});
patch.UpsertEdges.Add(new GraphSceneEdge {
    Id = "platform-queue",
    SourceNodeId = "platform",
    TargetNodeId = "queue",
    Directed = true
});

var result = graph.ApplyPatch(patch);

Invalid references roll the complete patch back. Node removal cleans incident edges and cluster membership by default.

Generated pages expose the same operation through a small host API when IncrementalUpdates is enabled:

const graph = window.ChartForgeXGraphExplorer;

graph.update("service-estate", {
  upsertNodes: [
    { id: "queue", label: "Queue", parentId: "platform", status: "warning", x: 420, y: 180 }
  ],
  upsertEdges: [
    { id: "platform-queue", sourceNodeId: "platform", targetNodeId: "queue", directed: true }
  ],
  fit: true
});

graph.navigate("service-estate", "platform", 2);
graph.focus("service-estate", "queue");
graph.physics("service-estate", { solver: "ForceAtlas2", damping: 0.42 });
graph.theme("service-estate", "dark");
graph.fit("service-estate");
const snapshot = graph.get("service-estate");

update is the trusted host-level atomic patch operation. An editable user surface uses the narrower capability-checked operation:

Both C# and browser patches reject node-parent and cluster-parent cycles before changing the document. An omitted or null edge style width inherits the renderer default; an explicit width must be a finite positive number. Export and state replay preserve inherited widths.

A successful host update that changes nodes, edges, or clusters clears undo and redo history before applying the patch or notifying observers, and emits cfxgraphhistory with action: "host-update". This prevents an older user snapshot from removing newer host data, including when an event handler calls undo. Rejected updates and viewport-only updates retain history. Use change for user edits that should remain undoable.

graph.change("service-estate", {
  upsertNodes: [{ id: "queue", label: "Priority queue", x: 460, y: 190 }]
}, "inspector", "Rename queue");

const reviewState = graph.captureState("service-estate", "review-bookmark");
graph.applyState("service-estate", reviewState);
graph.undo("service-estate");
graph.redo("service-estate");
const positions = graph.positions("service-estate");

Enable editing with UseSuperTopologyDefaults(enableManipulation: true) or by enabling Manipulation, IncrementalUpdates, and History, then grant only the required GraphManipulationOptions capabilities. change validates references and permissions before dispatching the cancelable cfxgraphbeforechange event. Accepted changes dispatch cfxgraphchange; rejected or canceled changes do not enter history. Cluster structure remains a trusted-host update concern.

Set HtmlGraphExplorerOptions.PersistInteractionState = true to restore positions, viewport, selection, focus, hierarchy, and cluster state from a scene-scoped browser key. Set InteractionStateStorageKey when a host needs its own namespace. Persistence is off by default. It never changes the host model or static SVG/PNG output.

Captured selection uses { role, id } entries, so a node and edge may share an id without changing each other's selection on replay. Older plain-id entries restore only when the id identifies one graph item; ambiguous entries are skipped. The generated graph-state-replay-svg.html, graph-state-replay-canvas.html, and graph-state-replay-webgl.html examples expose capture and restore controls over the same scene.

The API also exposes export(target, "svg" | "png" | "json"). Export remains interceptable through the cancelable cfxgraphexport event.

SVG and PNG exports use the same node paint rules as the interactive graph, including authored colors, selection and highlighting. Browser printing materializes the complete visible scene from the committed graph into a separate SVG, with its camera reset and bounds expanded to include nodes, labels, badges and edge captions outside the original viewport. The heading remains with the scene; controls, inspectors and navigation chrome stay out of the printed graph. Printing leaves the live camera, positions and selection intact, and its temporary surface is removed after printing or disposal. The same print policy applies to SVG, Canvas and WebGL backends.

Interaction and host events

The explorer includes search, status/kind filters, selection, Ctrl/Meta/Shift multi-selection, cross-renderer box selection, keyboard selection, neighborhood focus, live force-aware node dragging, capability-gated collapsed-group dragging, pan, wheel and command-rail zoom, fit, cluster controls, direct hierarchy navigation, semantic zoom, minimap navigation, runtime physics, and labeled SVG/PNG/JSON export choices. Editing, history, position publishing, and browser persistence remain opt in.

Stable events include cfxgraphready, cfxgraphselect, cfxgraphselection, cfxgraphboxselect, cfxgraphfilter, cfxgraphfocus, cfxgraphnavigate, cfxgraphcluster, cfxgraphpatch, cfxgraphbeforechange, cfxgraphchange, cfxgraphhistory, cfxgraphstate, cfxgraphstateapplied, cfxgraphpositions, cfxgraphdragstart, cfxgraphdrag, cfxgraphdragend, cfxgraphgroupdrag, cfxgraphphysicschange, cfxgraphthemechange, cfxgraphviewport, cfxgraphexport, cfxgraphstabilized, cfxgraphlod, and cfxgraphperformance.

Performance telemetry deliberately separates renderer work from browser cadence. performance.budgetMisses, budgetMissRate, and maxRenderMs measure ChartForgeX render work against the configured frame budget. cadenceBudgetMisses, cadenceBudgetMissRate, and maxFrameMs report delayed animation-frame delivery, which can also include browser scheduling, background throttling, capture tooling, or unrelated page work. workerTransferBytes reports position bytes received from the worker; staleWorkerUpdates counts batches older than the latest drag or reheat command. Such batches can still update unaffected nodes, but cannot finish a newer simulation or overwrite later node edits. Restarting physics starts a fresh cadence window, so idle time between runs is not counted as a slow frame. Use the first group as the ChartForgeX release gate; use cadence as a diagnostic signal instead of attributing every late browser callback to the renderer.

The same values are available through ChartForgeXGraph.get(id).performance and root attributes such as data-cfx-graph-performance-budget-misses and data-cfx-graph-performance-cadence-budget-misses.

Pointer picking uses the node grid without full-scene or DOM fallbacks on empty cells. Edge picking caches rendered route bounds in a spatial tree, refits it after layout changes, and rebuilds its membership after filtering or graph updates. Exact checks still use the route geometry and picking tolerance. The first edge query after a change pays the index update cost; dense crossings can still produce many candidates. performance.nodeHitCandidates, edgeHitCandidates, edgeHitIndexBuilds, edgeHitIndexRefits, and edgeHitIndexLastMs expose that work for host diagnostics.

Exactly one rendering surface is exposed to assistive technology. SVG uses a single roving graph-item tab stop; the labeled Canvas or WebGL surface becomes the keyboard target in accelerated modes.

Scale review fixtures

Generate the normal premium examples:

dotnet run --project .\ChartForgeX.Examples\ChartForgeX.Examples.csproj -c Release -- --graph-explorer-only

Generate the explicit browser scale fixtures without burdening every normal gallery build:

dotnet run --project .\ChartForgeX.Examples\ChartForgeX.Examples.csproj -c Release -- --graph-scale-only

The latter writes graph-scale-1000.html, graph-scale-5000.html, and graph-scale-10000.html, each with two edges per node and fixed positions for repeatable startup and interaction review.

The July 13, 2026 local Chrome release-gate run on the maintainer workstation produced the following observations. These are evidence for that environment, not universal performance guarantees:

FixtureLive DOM elementsFull loadFit callActive renderer
1,000 nodes / 2,000 edges820.17 s—WebGL2
5,000 nodes / 10,000 edges2020.64 s75 msWebGL2
10,000 nodes / 20,000 edges3521.26 s143 msWebGL2

The 10k browser run also applied a node-and-edge patch, searched the result to one match, and selected the new item without console errors. The 1k compact page synthesized a complete 1,000-node/2,000-edge SVG export in about 49 ms.

Generated review pages

  • global-estate-premium-topology.html demonstrates image nodes, badges, secondary labels, status overlays, nested clusters, and three-level drill navigation.
  • graph-2000-interactive.html and graph-2000-stage-01-overview.* through graph-2000-stage-05-full.* demonstrate one 2,001-object document as a WebGL/Barnes-Hut explorer and five script-free report stages.
  • identity-risk-graph-explorer.html demonstrates a product-shaped relationship graph with images, filters, selection, focus, clusters, live ForceAtlas2 dragging, and the opt-in physics configurator.
  • enterprise-access-graph-benchmark.html demonstrates accelerated compact-document rendering with 360 nodes and 720 directed edges.
  • vis-network-parity-hierarchy.html demonstrates the typed vis-style compatibility surface with opt-in editing, undo/redo, position publishing, box selection, and persisted interaction state.

Layout assessment coverage

Layout overlap diagnostics consider the full active node set using a spatial sweep along the wider coordinate axis. The check caches geometry once and stops after 250,000 candidate comparisons in a dense case. data-cfx-graph-layout-overlap-coverage is complete or budget-limited; a budget-limited overlap count is a lower bound. Incomplete coverage or any remaining estimated overlap produces needs-review, even when the graph is centered. Node count and comparison count are exposed alongside the result. Density expansion requires an observed overlap count above its threshold; incomplete low counts are marked as an unresolved density decision and do not move the layout.

data-cfx-graph-layout-overlap-geometry identifies estimated-label-radius for fewer than 500 nodes or node-radius for larger scenes. These are conservative circular layout estimates, not measurements of rendered glyphs, connector crossings, contrast, or overall readability. Check the coverage and geometry fields before using the numeric quality score.

Edit this page on GitHub