Version: 1.0.0
Series Primitives
Series Primitives are drawing overlays bound directly to a specific series. Unlike basic indicators, they have deep access to the coordinate system and can render custom graphics on the main pane, the Y-axis price scale, and the X-axis time scale.
Rendering on Axis Scales
Series primitives can extend their canvas painting beyond the clipped main viewport to render text tags and markers on the Y-axis (price scale) and X-axis (time scale) by implementing:
drawPriceScale(ctx, priceScaleW, chartH): Called inside the price scale render cycle. Exposes the axis canvas context, scale width, and pane height.drawTimeScale(ctx, chartW, timeScaleH): Called inside the time scale render cycle. Exposes the axis canvas context, pane width, and scale height.
Example: Custom Price Line Primitive
Below is the full implementation of CustomPriceLinePrimitive, which draws a dashed line at a specific target price and places a price marker on the Y-axis scale:
class CustomPriceLinePrimitive {
constructor(price, color) {
this.price = price;
this.color = color || '#2962ff';
}
attached({ chart }) {
this.chart = chart;
}
// Draw the line on the main pane
draw(ctx, chartW, chartH) {
const minMax = this.chart.getVisibleMinMax();
const y = this.chart.priceToY(this.price, minMax.minPrice, minMax.maxPrice);
ctx.save();
ctx.strokeStyle = this.color;
ctx.lineWidth = 1.5;
ctx.setLineDash([6, 4]);
ctx.beginPath();
ctx.moveTo(0, y); ctx.lineTo(chartW, y);
ctx.stroke();
ctx.restore();
}
// Draw the marker on the Y-axis scale
drawPriceScale(ctx, priceScaleW, chartH) {
const minMax = this.chart.getVisibleMinMax();
const y = this.chart.priceToY(this.price, minMax.minPrice, minMax.maxPrice);
const chartW = this.chart.logicalWidth - this.chart.paddingRight;
ctx.save();
ctx.fillStyle = this.color;
ctx.fillRect(chartW + 2, y - 9, priceScaleW - 10, 18);
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 10px Inter, Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this.price.toFixed(2), chartW + 2 + (priceScaleW - 10)/2, y);
ctx.restore();
}
}
constructor(price, color) {
this.price = price;
this.color = color || '#2962ff';
}
attached({ chart }) {
this.chart = chart;
}
// Draw the line on the main pane
draw(ctx, chartW, chartH) {
const minMax = this.chart.getVisibleMinMax();
const y = this.chart.priceToY(this.price, minMax.minPrice, minMax.maxPrice);
ctx.save();
ctx.strokeStyle = this.color;
ctx.lineWidth = 1.5;
ctx.setLineDash([6, 4]);
ctx.beginPath();
ctx.moveTo(0, y); ctx.lineTo(chartW, y);
ctx.stroke();
ctx.restore();
}
// Draw the marker on the Y-axis scale
drawPriceScale(ctx, priceScaleW, chartH) {
const minMax = this.chart.getVisibleMinMax();
const y = this.chart.priceToY(this.price, minMax.minPrice, minMax.maxPrice);
const chartW = this.chart.logicalWidth - this.chart.paddingRight;
ctx.save();
ctx.fillStyle = this.color;
ctx.fillRect(chartW + 2, y - 9, priceScaleW - 10, 18);
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 10px Inter, Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this.price.toFixed(2), chartW + 2 + (priceScaleW - 10)/2, y);
ctx.restore();
}
}