Multi-Point Labels Deep Dive
Trading annotations are multi-point annotations whose shape is defined by an ordered pointsš array rather than only x1š, y1š, x2š and y2š. This page focuses on the shared label system: point labels, segment labels and axis labels.
The shared base class is MultiPointAnnotationBaseš from scichart-financial-tools. It is used by PolyLineAnnotationš, FreehandDrawingAnnotationš and the trading annotations from the same package. It owns the common editing model: point storage, vertex grips, whole-shape dragging, optional snapping and point / segment / axis labels.
MultiPointAnnotationBaseš is not the same thing as CompositeAnnotation. A composite annotation is only a container that keeps other annotations positioned together. It does not provide points, label anchors, snapping or multi-point drag grips.
- TS
const {
AnnotationHoverModifier,
ECursorStyle,
EVerticalTextPosition,
NumberRange,
NumericAxis,
SciChartSurface,
toEngineering
} = SciChart; // or import from "scichart"
const {
EAnnotationVisibilityMode,
EAxisLabelDrawMode,
EMultiPointLabelAnchorMode,
ESegmentLabelRotationMode,
PolyLineAnnotation,
SciTraderLightTheme
} = SciChartFinancialTools; // if using npm, import from "scichart-financial-tools";
const { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId, {
theme: new SciTraderLightTheme()
});
sciChartSurface.xAxes.add(new NumericAxis(wasmContext, { visibleRange: new NumberRange(0, 40) }));
sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { visibleRange: new NumberRange(95, 125) }));
sciChartSurface.annotations.add(
new PolyLineAnnotation({
points: [
{ x: 5, y: 103 },
{ x: 18, y: 116 },
{ x: 27, y: 105 },
{ x: 34, y: 112 }
],
stroke: "#3388FF",
strokeThickness: 3,
selectionBoxStroke: "#3388FF55",
selectionBoxThickness: 10,
isEditable: true,
isSelected: true,
axisLabelStroke: "#FFF",
axisSpanFillOpacity: 0.2,
// axisLabelFill defaults to the annotation stroke color when not set.
labels: [
// 1. Point labels attach to a single point index.
{
text: "P1",
pointIndex: 0,
anchorMode: EMultiPointLabelAnchorMode.Point,
verticalTextPosition: EVerticalTextPosition.Below
},
{
text: "P2",
pointIndex: 1,
anchorMode: EMultiPointLabelAnchorMode.Point,
verticalTextPosition: EVerticalTextPosition.Above
},
{
text: "P4",
pointIndex: 3,
anchorMode: EMultiPointLabelAnchorMode.Point,
verticalTextPosition: EVerticalTextPosition.Above
},
// 2. Segment labels interpolate between two point indexes.
{
text: "Segment P1-P2",
segmentStartIndex: 0,
segmentEndIndex: 1,
anchorMode: EMultiPointLabelAnchorMode.Segment,
segmentRatio: 0.5,
segmentLabelRotationMode: ESegmentLabelRotationMode.Parallel,
verticalTextPosition: EVerticalTextPosition.Above
},
{
text: "Segment P3-P4",
segmentStartIndex: 2,
segmentEndIndex: 3,
anchorMode: EMultiPointLabelAnchorMode.Segment,
segmentRatio: 0.5,
verticalTextPosition: EVerticalTextPosition.Below
},
// 3. Axis labels can draw on the X axis, Y axis or both.
{
anchorMode: EMultiPointLabelAnchorMode.Axis,
pointIndex: 0,
axisLabelDrawMode: EAxisLabelDrawMode.X
},
{
anchorMode: EMultiPointLabelAnchorMode.Axis,
pointIndex: 2,
axisLabelDrawMode: EAxisLabelDrawMode.Both
},
{
anchorMode: EMultiPointLabelAnchorMode.Axis,
pointIndex: 3,
axisLabelDrawMode: EAxisLabelDrawMode.Y
}
],
pointLabelVisibility: EAnnotationVisibilityMode.Always,
segmentLabelVisibility: EAnnotationVisibilityMode.Always,
axisLabelVisibility: EAnnotationVisibilityMode.Always,
// Style callbacks can tune each resolved label before it is drawn.
formatLabelStyle: (params) => ({
fontSize: params.label.anchorMode === EMultiPointLabelAnchorMode.Segment ? 12 : 16,
color: params.label.anchorMode === EMultiPointLabelAnchorMode.Segment ? "gray" : "black"
}),
// Text callbacks can combine the label definition with live anchor values.
formatLabel: (params) => {
if (params.anchorMode === EMultiPointLabelAnchorMode.Point) {
return `${params.label.text} - ${toEngineering(params.anchorValuePoint.y)}`;
}
return params.label.text;
},
gripVisibility: EAnnotationVisibilityMode.Always,
adornerVisibility: EAnnotationVisibilityMode.OnInteraction
})
);
sciChartSurface.chartModifiers.add(
new AnnotationHoverModifier({
enableHover: true,
enableCursor: true,
idleCursor: ECursorStyle.Crosshair
})
);
Label Typesā
Multi-point labels are defined in the labelsš array. Each label is anchored to either a point, a segment or an axis marker position.
Point and segment labels are drawn inside the series view. Axis labels are drawn on the chart axes and can be configured to draw on the X axis, Y axis or both.
Formatting Labelsā
formatLabelš controls label text. formatLabelStyleš can override the resolved style per label. Both callbacks receive the annotation, the original label definition, anchor values and pixel positions.
import { EMultiPointLabelAnchorMode, PolyLineAnnotation } from "scichart-financial-tools";
const annotation = new PolyLineAnnotation({
points: [
{ x: 10, y: 105 },
{ x: 20, y: 112 }
],
labels: [
{ anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 0 },
{ anchorMode: EMultiPointLabelAnchorMode.Axis, pointIndex: 1 }
],
formatLabel: ({ anchorValuePoint, anchorMode }) =>
anchorMode === EMultiPointLabelAnchorMode.Axis
? anchorValuePoint.y.toFixed(2)
: `(${anchorValuePoint.x}, ${anchorValuePoint.y})`,
formatLabelStyle: ({ labelIndex, defaultStyle }) => ({
...defaultStyle,
color: labelIndex === 0 ? "#50C7E0" : "#FFFFFF",
fontSize: 12
})
});
For Builder API JSON configuration, register formatter functions by name and pass the registered name in formatLabelš or formatLabelStyleš.
Advanced Label Formatting Examplesā
Formatter Functions by Anchor Typeā
This example keeps the formatter small by branching on anchorMode: point labels combine the default text with the live Y value, segment labels show percent change between their two points, and axis labels use a compact numeric value.
- TS
const {
EVerticalTextPosition,
NumberRange,
NumericAxis,
SciChartSurface,
toEngineering
} = SciChart; // or import from "scichart"
const {
EAnnotationVisibilityMode,
EAxisLabelDrawMode,
EMultiPointLabelAnchorMode,
ESegmentLabelRotationMode,
PolyLineAnnotation,
SciTraderLightTheme
} = SciChartFinancialTools; // if using npm, import from "scichart-financial-tools";
const { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId, {
theme: new SciTraderLightTheme()
});
sciChartSurface.xAxes.add(new NumericAxis(wasmContext, { visibleRange: new NumberRange(0, 40) }));
sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { visibleRange: new NumberRange(95, 125) }));
sciChartSurface.annotations.add(
new PolyLineAnnotation({
points: [
{ x: 5, y: 101 },
{ x: 14, y: 117 },
{ x: 26, y: 108 },
{ x: 35, y: 121 }
],
stroke: "#3388FF",
strokeThickness: 3,
isEditable: true,
isSelected: true,
labels: [
{ id: "entry", anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 0, text: "Entry" },
{ id: "breakout", anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 1, text: "Breakout" },
{
id: "leg-1",
anchorMode: EMultiPointLabelAnchorMode.Segment,
segmentStartIndex: 0,
segmentEndIndex: 1,
segmentRatio: 0.5,
segmentLabelRotationMode: ESegmentLabelRotationMode.Parallel,
verticalTextPosition: EVerticalTextPosition.Above
},
{
id: "leg-2",
anchorMode: EMultiPointLabelAnchorMode.Segment,
segmentStartIndex: 1,
segmentEndIndex: 2,
segmentRatio: 0.5,
verticalTextPosition: EVerticalTextPosition.Below
},
{
id: "last-price",
anchorMode: EMultiPointLabelAnchorMode.Axis,
pointIndex: 3,
axisLabelDrawMode: EAxisLabelDrawMode.Y
}
],
pointLabelVisibility: EAnnotationVisibilityMode.Always,
segmentLabelVisibility: EAnnotationVisibilityMode.Always,
axisLabelVisibility: EAnnotationVisibilityMode.Always,
formatLabel: ({ label, anchorMode, anchorValuePoint, valuePoints, defaultText }) => {
if (anchorMode === EMultiPointLabelAnchorMode.Point) {
const value = toEngineering(anchorValuePoint.y);
return defaultText?.trim() ? `${defaultText}\n${value}` : value;
}
if (anchorMode === EMultiPointLabelAnchorMode.Segment) {
// @ts-ignore
const start = valuePoints[label.segmentStartIndex];
// @ts-ignore
const end = valuePoints[label.segmentEndIndex];
const delta = end.y - start.y;
const percent = (delta / start.y) * 100;
return `${label.id}: ${delta >= 0 ? "+" : ""}${percent.toFixed(1)}%`;
}
return toEngineering(anchorValuePoint.y);
},
formatLabelStyle: ({ label, defaultStyle }) => {
if (label.anchorMode === EMultiPointLabelAnchorMode.Segment) {
return { ...defaultStyle, color: "#475569", fontSize: 12 };
}
if (label.anchorMode === EMultiPointLabelAnchorMode.Axis) {
return { ...defaultStyle, color: "#FFFFFF", fontSize: 13 };
}
return { ...defaultStyle, color: "#111827", fontSize: label.id === "breakout" ? 16 : 13 };
},
gripVisibility: EAnnotationVisibilityMode.Always
})
);
Geometry-Aware Label Stylesā
This example reuses one formatLabelStyle callback across several annotation types. It colors stop-loss / take-profit segment labels from the active trade direction, changes trend labels when the line slopes down, and hides the channel label that is on the inactive side of the channel.
- TS
const {
NumberRange,
NumericAxis,
SciChartSurface,
toEngineering
} = SciChart; // or import from "scichart"
const {
ChannelAnnotation,
EAnnotationVisibilityMode,
EMultiPointLabelAnchorMode,
ExtendedLineAnnotation,
SciTraderLightTheme,
StopLossTakeProfitAnnotation
} = SciChartFinancialTools; // if using npm, import from "scichart-financial-tools";
const { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId, {
theme: new SciTraderLightTheme()
});
sciChartSurface.xAxes.add(new NumericAxis(wasmContext, { visibleRange: new NumberRange(0, 50) }));
sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { visibleRange: new NumberRange(20, 90) }));
const trendLabelStyle = ({ annotation, label, defaultStyle }) => {
const [p1, p2, p3] = annotation.points;
const isRising = p2.y >= p1.y;
if (annotation instanceof StopLossTakeProfitAnnotation && label.anchorMode === EMultiPointLabelAnchorMode.Segment) {
return { ...defaultStyle, color: isRising ? annotation.takeProfitColor : annotation.stopLossColor, fontSize: 18 };
}
if (annotation instanceof ExtendedLineAnnotation && label.anchorMode === EMultiPointLabelAnchorMode.Point) {
return { ...defaultStyle, color: isRising ? "#111827" : "#DC2626", fontSize: 14 };
}
if (annotation instanceof ChannelAnnotation && label.anchorMode === EMultiPointLabelAnchorMode.Point) {
const offsetAbove = p3.y >= p1.y;
if (label.id === "channel-upper") return { ...defaultStyle, fontSize: offsetAbove ? 13 : 0 };
if (label.id === "channel-lower") return { ...defaultStyle, fontSize: offsetAbove ? 0 : 13 };
}
return defaultStyle;
};
const priceLabel = ({ label, anchorValuePoint, defaultText }) => {
const value = toEngineering(anchorValuePoint.y);
return defaultText?.trim() ? `${defaultText}: ${value}` : `${label.id} ${value}`;
};
sciChartSurface.annotations.add(
new StopLossTakeProfitAnnotation({
points: [{ x: 5, y: 42 }, { x: 21, y: 58 }],
takeProfitColor: "#16A34A",
stopLossColor: "#DC2626",
fillOpacity: 0.18,
strokeThickness: 2,
isEditable: true,
labels: [
{ id: "entry", anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 0, text: "Entry" },
{ id: "target", anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 1, text: "Target" },
{
id: "trade-zone",
anchorMode: EMultiPointLabelAnchorMode.Segment,
segmentStartIndex: 0,
segmentEndIndex: 1,
segmentRatio: 0.5,
text: "Risk / reward"
}
],
pointLabelVisibility: EAnnotationVisibilityMode.Always,
segmentLabelVisibility: EAnnotationVisibilityMode.Always,
formatLabel: priceLabel,
formatLabelStyle: trendLabelStyle
}),
new ExtendedLineAnnotation({
points: [{ x: 26, y: 36 }, { x: 43, y: 52 }],
extendEnd: true,
stroke: "#F59E0B",
isEditable: true,
labels: [
{ id: "trend-start", anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 0, text: "Start" },
{ id: "trend-end", anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 1, text: "Break" }
],
pointLabelVisibility: EAnnotationVisibilityMode.Always,
formatLabel: priceLabel,
formatLabelStyle: trendLabelStyle
}),
new ChannelAnnotation({
points: [{ x: 8, y: 70 }, { x: 38, y: 78 }, { x: 38, y: 61 }],
stroke: "#3388FF",
fill: "#3388FF22",
isEditable: true,
labels: [
{ id: "channel-upper", anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 1, text: "Upper" },
{ id: "channel-lower", anchorMode: EMultiPointLabelAnchorMode.Point, pointIndex: 2, text: "Lower" }
],
pointLabelVisibility: EAnnotationVisibilityMode.Always,
formatLabel: priceLabel,
formatLabelStyle: trendLabelStyle
})
);
Snappingā
Multi-point annotations can snap placement and edits to a series.
import { ESnapMode, PolyLineAnnotation } from "scichart-financial-tools";
const snapped = new PolyLineAnnotation({
points: [
{ x: 10, y: 0 },
{ x: 20, y: 0 }
],
snapMode: ESnapMode.DataPoint,
snapToDataPointRadius: 12,
snapToSeriesId: "priceSeries",
snapToDataPointOnInit: true,
isEditable: true
});
Use ESnapMode.DataPointš when both X and Y should snap to the nearest point. Use ESnapMode.XSliceš when the X value should snap to the nearest data point while preserving the annotation's Y value.
Label Notesā
- OnInteractionš labels appear while the annotation is hovered, selected or dragged.
- Axis labels can be anchored to a point or to a segment interpolation.
- Segment labels use segmentRatioš from
0to1to choose their anchor along the segment. - A formatLabelStyleš callback can suppress a label by returning a style with
fontSize: 0. - Some trading annotations add their own specialized labels in addition to inherited multi-point labels. Fibonacci level labels and Measure labels are separate systems.