@elitechart/drawings
v0.2.0
Published
Drawing-tool library for ChartForge — trend lines, Fibonacci retracement, rectangles, and more.
Maintainers
Readme
@elitechart/drawings
46 drawing tools for ChartForge — trend lines, Andrews pitchforks, Fibonacci channels, anchored VWAP, fixed-range volume profile, long/short positions, and more. Every tool is pure state plus a render function: safe to serialize, hit-test in screen space, and move via handle or body drag.
Install
pnpm add @elitechart/drawings @elitechart/core@elitechart/core is required — drawings consume the engine's DrawingTool<State> contract.
Quick example
import { createChart } from '@elitechart/core';
import { trendLine, fibRetracement, longPosition, makeLongPosition } from '@elitechart/drawings';
const chart = createChart(container, {
/* …options */
});
// Click-to-place: enter tool mode, the next N clicks place anchors.
chart.setToolMode(trendLine);
// Or add programmatically with a typed state factory.
chart.addDrawing(
fibRetracement,
fibRetracement.fromAnchors([
{ time: lowTime, price: lowPrice },
{ time: highTime, price: highPrice },
]),
);
// Each tool also exports a make* helper that produces the same state typed.
chart.addDrawing(
longPosition,
makeLongPosition({
entryTime,
entryPrice,
stopPrice,
targetPrice,
}),
);Subpath imports
Same tree-shaking story as @elitechart/indicators — import only what you use:
import { trendLine } from '@elitechart/drawings/trend-line';
import { fibRetracement } from '@elitechart/drawings/fib-retracement';Catalog
| Name | Subpath | Anchors |
| ----------------- | ------------------- | ------- |
| trendLine | /trend-line | 2 |
| ray | /ray | 2 |
| extendedLine | /extended-line | 2 |
| horizontalLine | /horizontal-line | 1 |
| horizontalRay | /horizontal-ray | 1 |
| verticalLine | /vertical-line | 1 |
| parallelChannel | /parallel-channel | 3 |
| arrow | /arrow | 2 |
| Name | Subpath | Anchors |
| ------------------ | ------------ | ------- |
| rectangle | /rectangle | 2 |
| rotatedRectangle | – | 3 |
| ellipse | /ellipse | 2 |
| circle | – | 2 |
| arc | – | 3 |
| triangle | – | 3 |
| path | – | N |
| polyline | – | N |
text (/text), callout, note, priceLabel, flagMark, signpost, arrowMarker, arrowMarkUp / Down / Left / Right — single-anchor markers and labels. Plus freehand: brush, highlighter (variable-width strokes).
| Name | Subpath | Anchors |
| ------------------- | ------------------ | ------- |
| fibRetracement | /fib-retracement | 2 |
| fibExtension | /fib-extension | 3 |
| fibChannel | – | 3 |
| fibTimeZone | – | 2 |
| trendBasedFibTime | – | 3 |
andrewsPitchfork, schiffPitchfork, modifiedSchiffPitchfork, insidePitchfork — 3-anchor variants.
anchoredVwap (1 anchor), fixedRangeVolumeProfile (2 anchors).
| Name | Subpath | Anchors |
| ------------------ | ------------------- | ------- |
| longPosition | – | 3 |
| shortPosition | – | 3 |
| positionForecast | – | 2 |
| priceRange | /price-range | 2 |
| dateRange | /date-range | 2 |
| datePriceRange | /date-price-range | 2 |
Anatomy of a tool
Every tool implements DrawingTool<State> from @elitechart/core:
interface DrawingTool<State> {
readonly id: string;
readonly name: string;
readonly anchorCount: number;
fromAnchors(anchors: ReadonlyArray<DataPoint>): State;
anchors(state: State): ReadonlyArray<DataPoint>;
setAnchor(state: State, i: number, p: DataPoint): State;
translate(state: State, dt: number, dp: number): State;
hitTest(state: State, screen: ScreenPoint, viewport: Viewport): HitResult;
render(paint: Paint, vp: Viewport, state: State, style: DrawingStyle, bars: ReadonlyArray<Bar>): void;
}The chart dispatches user input (drag-to-create, handle drag, body drag, style edits) into these primitives — so a custom tool just needs to implement them to plug in alongside the built-ins.
Custom tool
import type { DataPoint, DrawingTool } from '@elitechart/core';
interface MyCircleState {
readonly center: DataPoint;
readonly radiusPrice: number;
}
const myCircle: DrawingTool<MyCircleState> = {
id: 'my-circle',
name: 'My circle',
anchorCount: 2,
fromAnchors: ([center, edge]) => ({
center,
radiusPrice: Math.abs(edge.price - center.price),
}),
anchors: (s) => [s.center, { time: s.center.time, price: s.center.price + s.radiusPrice }],
setAnchor: (s, i, p) => (i === 0 ? { ...s, center: p } : { ...s, radiusPrice: Math.abs(p.price - s.center.price) }),
translate: (s, dt, dp) => ({ ...s, center: { time: s.center.time + dt, price: s.center.price + dp } }),
hitTest: (state, screen, vp) => {
// Convert state.center to screen space, compare distance vs radius — return { hit: boolean }.
return { hit: false };
},
render: (paint, vp, state, style) => {
// Convert center + radius to screen space, call paint.circle / paint.stroke.
},
};
chart.setToolMode(myCircle);Lifecycle: serialize / deserialize
Drawings round-trip through serializeLayout / loadLayout on the chart handle. Provide a tool registry so the engine can re-hydrate states by toolId:
import { trendLine, rectangle, fibRetracement } from '@elitechart/drawings';
const layout = chart.serializeLayout({ savedAt: Date.now() });
localStorage.setItem('my-chart', JSON.stringify(layout));
const registry = new Map([
[trendLine.id, trendLine],
[rectangle.id, rectangle],
[fibRetracement.id, fibRetracement],
]);
const saved = JSON.parse(localStorage.getItem('my-chart')!);
chart.loadLayout(saved, registry);chart.serializeDrawings() returns just { id, toolId, state }[] without the rest of the layout — useful for syncing drawings to a backend.
Compatibility
- Node 20.11+ for the build / SSR-safe imports of factory helpers.
- Browsers Chrome / Safari / Firefox / Edge (evergreen), iOS Safari 16+, Android Chrome.
- SSR safe —
make*factories and tool definitions have no DOM dependency. Render is invoked only when the chart paints in the browser. - Tree-shakeable ESM + CJS dual emit, named exports only,
sideEffects: false.
Links
License
MIT — see LICENSE.
