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
| Plugin | Clicks | Description |
|---|---|---|
MeasureTool | 2 | Measure price difference, percentage, and bar count between two points |
LineTool | 2 | Draw trend lines with draggable endpoints |
Fibonacci Tools
| Plugin | Clicks | Description |
|---|---|---|
FibonacciTool | 2 | Fibonacci retracement levels (0, 0.236, 0.382, 0.5, 0.618, 0.786, 1) |
FibonacciChannelTool | 3 | Parallel channel lines at Fibonacci ratios. Click 1-2 set the baseline, click 3 sets channel width |
FibSpeedResistanceFanTool | 2 | Diagonal fan rays at Fibonacci ratios of the price/time rectangle |
FibTrendExtensionTool | 3 | Projects Fibonacci extension levels (up to 2.618) from a trend + retracement |
Chart Pattern Tools
| Plugin | Clicks | Description |
|---|---|---|
XABCDPatternTool | 5 | Harmonic XABCD pattern (Gartley, Butterfly, Bat, Crab) with Fib ratios |
ABCDPatternTool | 4 | Simple ABCD harmonic pattern with BC/AB and CD/BC ratios |
CypherPatternTool | 5 | Cypher harmonic pattern with XC/XA ratio |
HeadAndShouldersTool | 7 | Head & Shoulders with neckline, shoulder/head fills, and LS/H/RS labels |
TrianglePatternTool | 5 | Triangle with upper/lower trendlines (extended) and zigzag fill |
ThreeDrivesPatternTool | 7 | Three 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 / Property | Description |
|---|---|
this.context | The ChartContext — access to chart, data, events, coordinate conversion |
this.chart | Shortcut to this.context.getChart() (ECharts instance) |
this.marketData | Shortcut 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: truedon’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:clickchart:resize,chart:dataZoom,chart:updatedplugin: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 clickeddrawing:deselected— emitted when clicking background while a drawing was selecteddrawing: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.
}