Skip to content

Architecture ​

This page is for contributors. It explains how the core is organized and the rules that keep it maintainable.

Repository ​

packages/core        @kumochart-sdk/core, the SDK (no runtime dependencies)
packages/vue         @kumochart-sdk/vue, the Vue 3 adapter
packages/react       @kumochart-sdk/react, the React adapter
examples             JavaScript, Vue and React examples, the end-to-end test page and the benchmark
e2e                  Playwright tests (Chromium, Firefox, WebKit)
scripts              architecture and bundle-size checks, release helpers
docs                 documentation

Packages resolve each other's sources through the @kumochart-sdk/source export condition, so the examples and the tests run against src without a build.

Layers ​

Modules of packages/core/src may import modules of their own or a lower layer. npm run check:architecture enforces this and rejects runtime import cycles.

LayerModulesResponsibility
0utilsErrors, validation, merging, caches, registries
1events, data, options, canvasEvent bus, bar storage, options and themes, canvas helpers
2format, scales, layout, feedNumber/date formatting, time and price scales, ticks, pane layout, intervals, trading calendar, aggregation and bar transforms
3panes, series, indicatorsPane store, series types and renderers, indicator engine
4render, interaction, objectsFrame building and painting, input state machine, object coordination
5drawings, tradingDrawing tools and editing, orders and markers
6alertsAlert evaluation and lines
7chartChartCore, the public API facades, state, plugins

Update path ​

Every change follows one path:

  1. A public API method runs inside core.run(), which defers event dispatch.
  2. It mutates a model (bars, panes, indicators, objects…) and calls invalidate() (geometry and all layers) or invalidateLayers(...) (some layers).
  3. The FrameScheduler paints once in the next animation frame. Painting first builds the frame if the geometry is stale: layout, visible bars, fitted price scales, ticks and render-ready point buffers. All three layers and every coordinate query read the same frame.
  4. Events emitted along the way are dispatched after the operation completes.

Queries that need geometry (timeToX, getVisibleRange, …) build the frame synchronously when it is stale, without painting.

Data ​

BarStore keeps bars in typed-array columns. Each mutation increases revision and records what changed; consumers call changesSince(theirRevision) and get null, { kind: 'tail', from } or { kind: 'reset' }. This lets every derived structure (indicator values, time-axis marks) update incrementally without tracking object identity.

The bars the chart displays are derived. A Feed keeps the source points (the bars and trades the application sends, at their source interval), the calendar bucket of every source point, and a replay cutoff. The displayed store holds one point per bucket, aggregated from the source points up to the cutoff. A source update re-aggregates from the bucket of the first changed point; when only the last displayed bar changes or bars are appended, the displayed store records a tail change, so indicators and alerts stay O(1) per tick. Switching the timeframe, changing the calendar, prepending history or moving the replay cursor back rebuilds the displayed store as a reset. Comparison series use the same Feed with value points.

With a bar transform (Heikin-Ashi, Renko, range bars or a custom one), the feed displays into an intermediate store of time bars and a TransformStage builds the chart's bars from it. The stage keeps the transform stream from before the last time bar: a tail change re-runs that bar on a clone and feeds appended bars, so ticks stay incremental; a reset starts a new stream. Outputs with equal times are spaced by a millisecond so the displayed times stay strictly ascending.

MarketData (in chart) owns the source interval, the timeframe, the calendar and the transform, and applies every write to the main bars and all series together, after validating all of them. Replay moves the cutoff and drives playback with the window's timers. Code outside the chart (plugins, custom drawings, chart.data.bars) reads the bars through read-only BarsViews; the source view stops at the replay cutoff.

Indicators ​

An indicator is a stream: next(bar) returns its outputs for one bar. IndicatorSeries stores the outputs in columns and keeps a clone of the stream from before the latest bar. A tick that changes the latest bar restores the clone and replays one bar, so every indicator updates in O(1).

Price scales and coordinates ​

Every pane owns a set of price scales: right, left and overlays, each fitted to the series and indicators placed on it. Internally, x 0 is the left edge of the plots, so a left axis lies at negative x and nothing that paints or hit-tests inside the plots needs to know about it. The canvases are translated by the left axis width when a frame is painted, pointer input is shifted at the input controller, and the public API (layout(), timeScale coordinates, pointer events, the crosshair, drawings.bounds()) reports x from the chart's left edge.

Rendering ​

Three canvases are stacked: scene (grid, series, indicators, axes), objects (drawings, orders, alerts, markers) and cursor (crosshair, legends, latest-price label). Hovering repaints only the cursor layer; dragging an object repaints the objects layer.

Series renderers receive a PointBuffer already converted to pixels. Buffers are owned by a BufferPool and reused between frames.

Interaction ​

InputController turns DOM input into intents on an InteractionHost. Gestures form a small state machine (pressed, pan, drag, pinch, axis drags, separator, touch crosshair, tool). Every gesture can be cancelled by pointercancel, lostpointercapture, window blur or Escape; cancelling an object drag restores the object. Hover hit tests and wheel zoom are coalesced to one per frame.

Objects ​

Drawings, orders, alerts and markers implement ObjectDomain: hit testing, painting and drag sessions. ObjectsController picks the best hit across domains (buttons over handles over labels over lines over bodies, then the selected object, then the closest), and keeps the single selection and hover state.

Drawings ​

A drawing type is a DrawingTypeDefinition: it paints and hit-tests its anchors, and may declare handles, moveHandle, axisMarks, bounds and editableText. Built-in types (drawings/built-in) use the same contract as custom ones. DrawingsDomain is generic: it runs the tool session (tool.ts), resolves anchors with the magnet and the Shift angle constraint (anchoring.ts), paints and hits handles (handles.ts) and axis marks (axis-marks.ts), and opens the in-place text editor (text-editor.ts). Anchors are stored as time and price, so drawings survive timeframe changes and replay.

Plugins ​

PluginSet (in chart) holds the plugins of a chart. The scene painter calls it for every pane, below and above the series, with the context clipped and translated to the plot; plugins see only public types and the read-only data views. Optional features that most users do not need belong in packages of their own on top of this interface. A feature belongs in the core when nearly every user needs it or when it needs the internals, such as bar transforms, which change the data path.

Principles ​

  • API calls never emit events. Only user actions and alerts do. Applications can mirror chart state and feed it back without loops.
  • Validate, then mutate. Invalid input throws a ChartError that names the value before anything changes.
  • No silent fallbacks. Values that cannot be computed stay empty; impossible states throw.
  • No compatibility code. There is one way to do each thing; the API changes in the open.
  • Caches are owned and bounded. Every cache belongs to a chart instance, is keyed by revisions rather than object identity, and has a size limit.

Quality gates ​

npm run verify runs the architecture check, ESLint (strictTypeChecked), Prettier, TypeScript (strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes), unit tests, the build and the bundle-size budget. CI also runs the end-to-end tests in three browsers.

Released under the MIT license.