Skip to main content

Plugin System

QFChart supports a flexible plugin system that allows you to add custom interactive tools and extensions to the chart. Plugins can be used to implement drawing tools, chart patterns, or additional UI controls.

Registering a Plugin

To use a plugin, create an instance and register it with the chart:

import { QFChart, MeasureTool, LineTool } from "qfchart";

const chart = new QFChart(container, options);
chart.registerPlugin(new MeasureTool());
chart.registerPlugin(new LineTool());

Once registered, the plugin adds its button/icon to the chart's toolbar automatically.

Tool Groups

Multiple related plugins can be grouped under a single toolbar button using ToolGroup. Clicking the group button opens a dropdown menu to select between the tools.

import { ToolGroup, FibonacciTool, FibonacciChannelTool, FibTrendExtensionTool } from "qfchart";

const fibGroup = new ToolGroup({
name: "Fibonacci Tools",
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 6h16M4 12h16M4 18h16"/>
</svg>`,
});
fibGroup.add(new FibonacciTool());
fibGroup.add(new FibonacciChannelTool());
fibGroup.add(new FibTrendExtensionTool());
chart.registerPlugin(fibGroup);

When a tool from the group is selected, the group's toolbar icon updates to show the active sub-tool.

Built-in Plugins

Drawing Tools

PluginClicksDescription
MeasureTool2Measure price difference, percentage, and bar count between two points
LineTool2Draw trend lines with draggable endpoints

Fibonacci Tools

PluginClicksDescription
FibonacciTool2Fibonacci retracement levels (0, 0.236, 0.382, 0.5, 0.618, 0.786, 1)
FibonacciChannelTool3Parallel channel lines at Fibonacci ratios. Click 1-2 set the baseline, click 3 sets channel width
FibSpeedResistanceFanTool2Diagonal fan rays at Fibonacci ratios of the price/time rectangle
FibTrendExtensionTool3Projects Fibonacci extension levels (up to 2.618) from a trend + retracement

Chart Pattern Tools

PluginClicksDescription
XABCDPatternTool5Harmonic XABCD pattern (Gartley, Butterfly, Bat, Crab) with Fib ratios
ABCDPatternTool4Simple ABCD harmonic pattern with BC/AB and CD/BC ratios
CypherPatternTool5Cypher harmonic pattern with XC/XA ratio
HeadAndShouldersTool7Head & Shoulders with neckline, shoulder/head fills, and LS/H/RS labels
TrianglePatternTool5Triangle with upper/lower trendlines (extended) and zigzag fill
ThreeDrivesPatternTool7Three Drives pattern with drive-to-drive ratio annotations

Registration Example (All Tools)

// Individual tools
chart.registerPlugin(new MeasureTool());
chart.registerPlugin(new LineTool());

// Fibonacci group
const fibGroup = new ToolGroup({ name: "Fibonacci Tools", icon: "..." });
fibGroup.add(new FibonacciTool());
fibGroup.add(new FibonacciChannelTool());
fibGroup.add(new FibSpeedResistanceFanTool());
fibGroup.add(new FibTrendExtensionTool());
chart.registerPlugin(fibGroup);

// Pattern group
const patternGroup = new ToolGroup({ name: "Patterns", icon: "..." });
patternGroup.add(new XABCDPatternTool());
patternGroup.add(new ABCDPatternTool());
patternGroup.add(new CypherPatternTool());
patternGroup.add(new HeadAndShouldersTool());
patternGroup.add(new TrianglePatternTool());
patternGroup.add(new ThreeDrivesPatternTool());
chart.registerPlugin(patternGroup);

Point Snapping (Ctrl / Cmd)

All drawing tools support snap-to-candle. Hold Ctrl (or Cmd on Mac) while clicking or moving the mouse during any drawing tool to snap the cursor to the nearest candle's closest OHLC value (open, high, low, or close).

A visual snap indicator (small blue circle) appears at the snapped position while the modifier key is held, providing visual feedback before you click.

Creating Custom Plugins

You can create custom plugins by extending the AbstractPlugin class. Each plugin lives in its own folder with a tool class, an optional drawing renderer, and a barrel index.ts.

Plugin Folder Structure

plugins/
└── MyCustomTool/
├── MyCustomTool.ts # Interaction logic (clicks, preview)
├── MyCustomDrawingRenderer.ts # Permanent chart rendering
└── index.ts # Barrel export

The AbstractPlugin Base Class

import { AbstractPlugin } from "qfchart";

export class MyCustomTool extends AbstractPlugin {
constructor() {
super({
id: "my-tool",
name: "My Tool",
icon: "<svg>...</svg>",
});
}

protected onInit(): void {
// Register drawing renderer (if this tool creates persistent drawings)
this.context.registerDrawingRenderer(new MyCustomDrawingRenderer());
}

protected onActivate(): void {
// Bind ZRender click/mousemove listeners
const zr = this.context.getChart().getZr();
zr.on("click", this.onClick);
zr.on("mousemove", this.onMouseMove);
}

protected onDeactivate(): void {
// Unbind listeners, clean up preview graphics
const zr = this.context.getChart().getZr();
zr.off("click", this.onClick);
zr.off("mousemove", this.onMouseMove);
}

private onClick = (params: any) => {
// Use this.getPoint(params) instead of [params.offsetX, params.offsetY]
// to get snap-to-candle support automatically
const [x, y] = this.getPoint(params);
// ... handle click
};

private onMouseMove = (params: any) => {
const [x, y] = this.getPoint(params);
// ... update preview graphic
};
}

Key AbstractPlugin Helpers

Method / PropertyDescription
this.contextThe ChartContext — access to chart, data, events, coordinate conversion
this.chartShortcut to this.context.getChart() (ECharts instance)
this.marketDataShortcut to this.context.getMarketData()
this.getPoint(params)Returns [x, y] with automatic snap-to-candle when Ctrl/Cmd is held
this.on(event, handler)Register event listener (auto-cleaned on destroy)
this.off(event, handler)Remove event listener

Drawing Renderer

To create persistent drawings that survive chart re-renders, implement the DrawingRenderer interface:

import { DrawingRenderer, DrawingRenderContext } from "qfchart";

export class MyCustomDrawingRenderer implements DrawingRenderer {
type = "my_custom_type"; // Matches the type in addDrawing()

render(ctx: DrawingRenderContext): any {
const { drawing, pixelPoints, isSelected } = ctx;
// pixelPoints: array of [x, y] pixel coordinates for each point

return {
type: "group",
children: [
// Lines, circles, polygons, text — ECharts graphic elements
{
type: "line",
name: "line", // 'line' name enables drag-to-move
shape: { x1: pixelPoints[0][0], y1: pixelPoints[0][1], x2: pixelPoints[1][0], y2: pixelPoints[1][1] },
style: { stroke: "#3b82f6", lineWidth: 2 },
},
// Control points must use name 'point-N' (point-0, point-1, etc.)
{
type: "circle",
name: "point-0",
shape: { cx: pixelPoints[0][0], cy: pixelPoints[0][1], r: 4 },
style: { fill: "#fff", stroke: "#3b82f6", lineWidth: 1, opacity: isSelected ? 1 : 0 },
},
],
};
}
}

Important naming conventions:

  • Elements named 'line' can be dragged to move the entire drawing
  • Elements named 'point-N' (where N is 0, 1, 2...) are control points that can be individually dragged
  • Elements with silent: true don't respond to mouse events

Saving a Drawing

After the user finishes placing points, convert pixel coordinates to data coordinates and call addDrawing:

private saveDrawing() {
const start = this.context.coordinateConversion.pixelToData({ x: x1, y: y1 });
const end = this.context.coordinateConversion.pixelToData({ x: x2, y: y2 });

if (start && end) {
this.context.addDrawing({
id: `my-drawing-${Date.now()}`,
type: "my_custom_type", // Must match renderer's type
points: [start, end],
paneIndex: start.paneIndex || 0,
style: { color: "#3b82f6", lineWidth: 2 },
});
}
}

The Chart Context

The ChartContext provides access to the chart instance, data, and utilities. It is available in plugins via this.context.

interface ChartContext {
// Core Access
getChart(): echarts.ECharts;
getMarketData(): OHLCV[];
getTimeToIndex(): Map<number, number>;
getOptions(): QFChartOptions;

// Event Bus
events: EventBus;

// Coordinate Conversion
coordinateConversion: {
pixelToData: (point: { x; y }) => { timeIndex; value; paneIndex } | null;
dataToPixel: (point: { timeIndex; value; paneIndex? }) => { x; y } | null;
};

// Interaction Control
disableTools(): void;
lockChart(): void;
unlockChart(): void;
setZoom(start: number, end: number): void;

// Drawing Management
addDrawing(drawing: DrawingElement): void;
removeDrawing(id: string): void;
getDrawing(id: string): DrawingElement | undefined;
updateDrawing(drawing: DrawingElement): void;

// Drawing Renderer Registration
registerDrawingRenderer(renderer: DrawingRenderer): void;

// Snap to nearest candle OHLC
snapToCandle(point: { x; y }): { x; y };
}

Event Bus

The chart exposes an Event Bus via context.events for communication between plugins and the chart core.

Standard Events

  • mouse:down, mouse:move, mouse:up, mouse:click
  • chart:resize, chart:dataZoom, chart:updated
  • plugin:activated, plugin:deactivated

Drawing Events

When using the native drawing system (via addDrawing), the chart emits granular events for interactions:

Shape Events (triggered on elements named 'line'):

  • drawing:hover, drawing:mouseout, drawing:mousedown, drawing:click

Control Point Events (triggered on elements named 'point-N'):

  • drawing:point:hover, drawing:point:mouseout, drawing:point:mousedown, drawing:point:click

Selection Events:

  • drawing:selected — emitted when a drawing is clicked
  • drawing:deselected — emitted when clicking background while a drawing was selected
  • drawing:deleted — emitted when a drawing is removed

All drawing events carry a payload:

{
id: string, // The drawing's ID
type?: string, // Drawing type (e.g., "line", "fibonacci")
pointIndex?: number // For point events: 0, 1, 2, etc.
}