Skip to content

StockSharp/Diagram

Repository files navigation

StockSharp JS Strategy Diagram

Build and test npm version License

StockSharp JS Strategy Diagram is the complete browser strategy-diagram component: a typed StockSharpDiagram API, catalog and palette, canvas renderer, read-only web embed, and versioned diagram document model.

StockSharp JS Strategy Diagram — visual strategy editor with typed connections and element palette

Live demo · StockSharp website · GitHub repository · Issue tracker

The demo uses the same full stack exported to applications. It is not a separate mock renderer.

Quick start

npm install @stocksharp/diagram
import {
  DiagramNode,
  Link,
  Node,
  StockSharpCatalog,
  StockSharpDiagram,
} from '@stocksharp/diagram';

const catalog = new StockSharpCatalog();
catalog.addNodeType(new Node({
  id: 'source',
  name: 'Market Data',
  outPorts: [{ id: 'candles', name: 'Candles', type: 'Candle' }],
}));

const host = document.querySelector<HTMLElement>('#diagram')!;
const diagram = new StockSharpDiagram({ div: host, catalog });

diagram.load([
  new DiagramNode({
    id: 'market',
    typeId: 'source',
    name: 'BTC/USDT',
    outPorts: [{ id: 'candles', name: 'Candles', type: 'Candle' }],
    x: 20,
    y: 80,
  }),
], []);

diagram.setTheme({
  diagramBackground: '#131820',
  gridColor: '#1e2633',
});

The package also ships a ready-to-use browser bundle exposed as window.SSDiagram:

<script src="https://cdn.jsdelivr.net/npm/@stocksharp/diagram@0.1.0/dist/ssdiagram.js"></script>
<script>
  const { StockSharpCatalog, StockSharpDiagram } = window.SSDiagram;
</script>

See the complete example for catalog construction, the draggable palette, typed links, history, read-only mode, resize handling and theme switching.

The palette mirrors the Designer toolbox contract without owning host behaviour. Subscribe to nodeActivated to insert/open an element and to contextMenuRequested to show host-specific help. setExcludedTypeIds() and setNodeTypeExcluded() hide elements dynamically; selection, filtering, category expansion and catalog refreshes remain stable. Call destroy() when the palette host is disposed so its catalog subscription is released.

Read-only mode remains inspectable: nodes, links and ports can still be selected, copied and opened through host actions, while move/link/delete, paste and history commands are disabled. Applications that need a different policy can use setInteractionPermissions().

copySelectionToClipboard() and pasteSelectionFromClipboard() use the browser text clipboard when available and fall back to the last in-memory copy. The versioned payload preserves node, port and link metadata and pastes the complete selection as one undo transaction.

Node dragging snaps to the visible grid by default in StockSharpDiagram. Configure it at construction time with gridSnap / gridSize, or at runtime with setGridSnap(enabled, size). Arrow keys move the current node selection by one grid cell (Shift moves five); the whole gesture is one undo step.

Variadic input sockets use isDynamic: true with dynamicMode: 'onConnect'. Connecting to that anchor creates a single-link sibling typed from the source; disconnecting, relinking, or deleting the source prunes an orphan sibling. The port and wire lifecycle is one undoable transaction and round-trips unchanged.

Port cardinality follows the Designer LinkableMaximum contract on both ends: maxLinks: 0 is unlimited, while a positive value limits that input or output. An unlimited input accepts multiple sources and an unlimited output can fan out to multiple targets; the same output/input pair is still never duplicated. Use updatePort(nodeId, direction, portId, patch) to change maxLinks, type or accepted types at runtime. Lowering a limit keeps existing wires and only rejects new ones. Any, Object, System.Object and * are wildcard socket types and therefore connect to every concrete type.

Drag a connected input socket with one wire to retarget that wire directly. To move an output end, or choose one wire on a socket with several connections, select the wire and drag its diamond endpoint handle. Dragging an output socket itself always means "create another wire", so fan-out behaviour stays unambiguous. Every successful relink is one undoable edit.

Wrap a host properties form in transaction(label, action) when it changes several node or port fields. The edits are committed as one undo/redo operation; if the action throws, every edit made inside it is rolled back.

The component shows a fullscreen request button in the diagram's top-right corner. Clicking it only emits fullscreenRequested; it never changes page layout or calls the browser Fullscreen API. The host decides whether to use a CSS overlay, a modal, or requestFullscreen(), then acknowledges the applied state with setFullscreenState(value). That updates the icon and emits fullscreenChanged. Set showFullscreenButton: false at construction time, or call setFullscreenButtonVisible(false), to hide the control. The button accepts --ssdiagram-control-background, --ssdiagram-control-border and --ssdiagram-control-color CSS overrides.

Viewport preferences are deliberately separate from the strategy document. Persist diagram.saveViewState() in host settings and restore it with diagram.loadViewState(value). The versioned snapshot contains zoom, pan and overview visibility; viewChanged fires for programmatic and interactive viewport changes. A damaged settings value throws DiagramViewStateError without modifying the current viewport.

takeScreenshot() returns a detached canvas exactly like the Charts API and the WPF SaveToImage flow. With no options it copies the current viewport; takeScreenshot({ scope: 'content', pixelRatio: 2 }) renders the complete scheme without moving or resizing the visible editor. Export options control padding, background, grid, overview, selection and transient runtime state; encode the returned canvas with toBlob() or toDataURL().

The Docs/Portal helpers return a DiagramEmbedHandle. Re-rendering the same host disposes its previous canvas, observers and timers; removing the host from the DOM also disposes it automatically. Custom integrations can call handle.destroy() or destroyRenderedDiagram(host) explicitly. If a saved scheme references an element absent from the current palette, its node is rendered as a transient red placeholder whose hover tooltip names the missing type. Sites can localize that message through data-diagram-missing-element="Missing: {typeId}" on the host.

Node actions and errors

Double-click handling is opt-in. Give only the node types controlled by the host a non-empty openAction, then dispatch that value from nodeOpen:

catalog.addNodeType(new Node({
  id: 'indicator',
  name: 'Indicator',
  openAction: 'indicatorSettings',
}));

diagram.on('nodeOpen', ({ nodes }) => {
  const node = nodes[0];
  if (node?.openAction === 'indicatorSettings') openIndicatorDialog(node);
});

Socket input is reported separately through portClicked, with leftClick/rightClick, direction, and keyboard modifiers. A right-click never starts a wire. contextMenuRequested also includes the exact port when the menu was opened over a socket.

Runtime failures flash the node border before leaving it red. Errors found while loading a scheme use a red background. Hovering either state shows the full error text in a tooltip:

diagram.setNodeError('orders', 'Order volume is not configured.');

diagram.load(nodes, links, {
  nodeErrors: {
    indicator_2: 'The saved Period value is invalid.',
  },
});

diagram.clearNodeError('orders');

Use { kind: 'load' } with setNodeError to add a load-style error after the initial load. Errors applied through this API are transient and are not written by save().

Debugger state

Execution state is deliberately separate from the saved scheme and undo history. The host can mark the active element, publish socket values and breakpoints, or cover an unusable scheme with a global status:

diagram.setActiveNode('indicator_2');
diagram.setPortRuntimeState('indicator_2', 'out', 'value', {
  breakpoint: true,
  breakpointActive: true,
  value: '102.45',
});
diagram.setGlobalError('This strategy is encrypted.', 'encrypted');

Use setRuntimeState() for an atomic debugger snapshot and clearRuntimeState() when execution stops. runtimeStateChanged always returns a detached snapshot safe for host-side state stores.

If loadDocument() receives malformed JSON or an unsupported document, the currently displayed scheme is left intact, a global load overlay shows the failure, and documentLoadFailed is emitted. The original exception is still thrown so callers can log or report it; loading a valid document clears the overlay.

Architecture

The component has one document model and a separate rendering layer:

Layer Source Purpose
Document core src/core/* Versioned document serialization plus independent runtime, view and selection state
Public component API src/diagram/stocksharp-diagram.ts StockSharpDiagram: catalog-aware nodes, ports, validation, events, persistence, history and theming
Models and palette src/diagram/{types,catalog,palette}.ts Public data model and draggable HTML element palette
Web embed src/embed.ts Self-contained read-only rendering for web applications
Canvas renderer src/canvas-renderer.ts Internal drawing, routing, selection, editing, zoom, touch and overview

Applications use StockSharpDiagram or the read-only embed. The renderer is an implementation detail and is bundled into both entry points, so consumers do not need a second runtime or copied source files.

Repository layout

src/
  index.ts                 complete public entry point
  core/
    model.ts               canonical versioned document
    document.ts            validation and serialization
    state.ts               runtime, view and selection state
    history.ts             commands, transactions, undo and redo
    action-registry.ts     executable context and host actions
  diagram/
    stocksharp-diagram.ts  StockSharpDiagram API
    api.ts                 typed public events and options
    types.ts               Node, DiagramNode, Port and Link models
    catalog.ts             node and socket-type catalog
    palette.ts             draggable HTML palette
    event-emitter.ts
  embed.ts                 self-contained read-only web renderer
  canvas-renderer.ts       internal canvas renderer
examples/basic.ts          full-stack demo source
demo/                      Charts-style GitHub Pages shell
tests/                     core, renderer and public-API integration tests

Source-first consumption

Applications can let their own esbuild/Vite build compile the TypeScript published inside the package:

{
  "dependencies": {
    "@stocksharp/diagram": "^0.1.0"
  }
}
import { StockSharpDiagram } from '@stocksharp/diagram/source';
import { renderAll } from '@stocksharp/diagram/source/embed';

For sibling-repository development, replace the version with "file:../../Diagram"; the import paths stay identical.

Dedicated entry points are available for consumers with narrower needs:

  • @stocksharp/diagram/document - versioned document parser and serializer;
  • @stocksharp/diagram/state - runtime/view/selection state helpers;
  • @stocksharp/diagram/history - command and transaction history;
  • @stocksharp/diagram/actions - typed action registry;
  • @stocksharp/diagram/embed — compiled read-only web renderer;
  • @stocksharp/diagram/catalog, @stocksharp/diagram/palette, @stocksharp/diagram/types.

Build output

npm run build produces:

File Purpose
dist/esm/** complete ESM module tree
dist/types/** TypeScript declarations
dist/ssdiagram.js complete browser IIFE exposed as window.SSDiagram
demo/dist/demo.js full-stack interactive example (excluded from the package)

Commands

npm ci
npm test
npm run build
npm run serve
npm run pack:check
npm run release:patch
npm run release:minor
npm run release:major
npm run api:check
npm run api:update  # only after reviewing an intentional public API change
npm run test:browser

The local demo is served at http://localhost:8792/demo/index.html.

The browser suite covers Chromium smoke, interaction and lifecycle scenarios at DPR 1 and 2.

CI verifies type checking, the reviewed declaration snapshot, unit/integration tests, all bundles and tarball contents. The Playwright browser suite runs locally via npm run test:browser (not in CI — the headless unit tests already cover the same behaviour). GitHub Pages publishes the demo from main.

Publishing is driven by the version in package.json. release.yml runs on every push to main and publishes only when that version is not yet on npm, so an ordinary push is a no-op. To cut a release, bump the version and push:

npm run release:patch   # or release:minor / release:major
git push

The new version triggers release.yml, which rebuilds and tests the repository, publishes to npm with provenance, then creates the v<version> tag and GitHub Release. A failed publication can be retried from the workflow's Run workflow action; already-published versions are detected and skipped.

Publishing authenticates through npm trusted publishing (OIDC) — there is no NPM_TOKEN secret. The workflow grants id-token: write, and npm exchanges the GitHub OIDC token for a short-lived publish token. The npm Trusted Publisher is bound to the workflow filename release.yml, so do not rename that workflow without updating the trusted publisher configuration on npmjs first.

License

Copyright © 2010-present StockSharp Platform LLC and/or its affiliates. All rights reserved. Use is governed by the StockSharp EULA and LICENSE.

Releases

Packages

Contributors

Languages