Skip to main content

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.

note

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.

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.

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.

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 0 to 1 to 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.

See Also​