SciChart.js JavaScript 2D Charts API > Annotations API > Editable Annotations
Editable Annotations

The annotations API allows you to mark any annotation as editable by setting isEditable true.  Editable annotations can be selected and dragged, and some can be resized.  This page describes how you can respond to a user's interaction with an annotation, and how to customise the style of the selected view of the annotation.

Annotation Interactions

All annotations have the following properties and events which can be used to run code on user interaction:

AnnotationBase Property Description
isSelected

Set true when an editable annotation is clicked.  This causes the selection box and the drag points to be shown.  These are known as the adorners.  Setting this programatically is not advised         

selectedChanged

An event that is fired when isSelected changes.

dragStartedonDragStarted

dragStarted is an event which fires on mouseDown of an editable annotation.  This is fired by the call to onDragStarted which is overridden in various annotations to determine which dragging point is being used, setting the adornerDraggingPoint property.  If this is not set, dragging will not be performed.  You can pass a callback for the event via the onDragStarted property of the IAnnotationsBase options object when constructing. 

dragDeltaonDragAdorner

 

dragDelta is the event which fires during dragging.  This is fired by the call to onDragAdorner which translates the mouse point to xy coordinates and calls calcDragDistance, which is where the coordinates of the annotation are updated.  You can pass a callback for the event via the onDrag property of the IAnnotationsBase options object when constructing. 

dragEndedonDragEnded

dragDelta is an event which is fires on mouseUp when dragging has finished.  This is fired by the call to onDragEnded.  You can pass a callback for the event via the onDragEnded property of the IAnnotationsBase options object when constructing. 

You usually want to either get or set some properties of the annotation being dragged in the callback.  It is possible to do this even when passing the callback as a constructor option, thanks to the way arrow functions capture their context.  Don't use "this"!

Get Annotation values while dragging
Copy Code
const textAnnotationDrag = new TextAnnotation({
        x1: 1,
        y1: 3,
        fontSize: 24,
        fontFamily: "Arial",
        text: "Moveable TextAnnotation",
        isEditable: true,
        onDrag: (args) => {
            textAnnotationDrag.text = `I was dragged to ${textAnnotationDrag.x1.toFixed(2)}, ${textAnnotationDrag.y1.toFixed(2)}`
        },
    });

 

Dragging to discrete values

Sometimes you want an annotation to snap to particular values as you drag.  The way to do this is to override onDragAdorner and convert to discete points there, then pass these to calcDragDistance. Here is an example of an axis marker that can only take discrete values, from our Rich Interactions Demo.

Discrete dragging
Copy Code
export class DiscreteAxisMarker extends AxisMarkerAnnotation {   
    public stepSize = 500;
    public minValue = 0;
    public maxValue = 30000;
    public onDragAdorner(args: ModifierMouseArgs): void {
        const xyValues = this.getValuesFromCoordinates(args.mousePoint, true);
        if (xyValues) {
            let { x, y } = xyValues;
            if (this.x1 !== undefined) {
                x = Math.floor(x / this.stepSize) * this.stepSize;
            } else if (this.y1 !== undefined) {
                y = Math.floor(y / this.stepSize) * this.stepSize;
            }
            this.calcDragDistance(new Point(x, y));
            if (this.x1 !== undefined) {
                this.x1 = Math.min(Math.max(this.x1, this.minValue), this.maxValue);
            } else if (this.y1 !== undefined) {
                this.y1 = Math.min(Math.max(this.y1, this.minValue), this.maxValue);
            }   
        }
        this.dragDelta.raiseEvent(new AnnotationDragDeltaEventArgs());
    }
}

 

Styling Selected Annotations

The Annotations API allows to customize the interaction adorners style of an editable annotation. This includes:

  1. specifying the grip points that could be used for interaction with the annotation
  2. specifying the radius of the grip points
  3. setting a custom svg template for the grips

There is a number of common properties which could be used to customize the look and behavior of interactive annotations. They could be passed as constructor options, which are described by the IAnnotationBaseOptions. And the relevant properties are defined as follows:

Discrete dragging
Copy Code
/** The direction in which the annotation is allowed to be resized or dragged */
resizeDirections?: EXyDirection;
/** The stroke color for the adorner drag handle */
annotationsGripsStroke?: string;
/** The fill color for the adorner drag handle */
annotationsGripsFill?: string;
/** The radius of the adorner drag handle */
annotationsGripsRadius?: number;
/** The stroke color for the adorner selection box */
selectionBoxStroke?: string;
/** How much bigger the selection box is than the bounding box of the annotation, in pixels */
selectionBoxDelta?: number;
/** The thickness of the selection box line */
selectionBoxThickness?: number;
/** The dragPoints that should be enabled for this annotation */
dragPoints?: readonly EDraggingGripPoint[];

Also, the same properties could be modified using the properties on an annotation instance:

  • AnnotationBase.resizeDirections
  • AnnotationBase.annotationsGripsStroke
  • AnnotationBase.annotationsGripsFill
  • AnnotationBase.annotationsGripsRadius
  • AnnotationBase.selectionBoxStroke
  • AnnotationBase.selectionBoxDelta
  • AnnotationBase.selectionBoxThickness
  • AnnotationBase.dragPoints

Default Adorners Style

We will start by creating a BoxAnnotation on a surface and will use it as a boilerplate for further examples (other types of annotations could be modified similarly). In order to make the annotation interactable, we will set IAnnotationBaseOptions.isEditable flag. Also we will set IAnnotationBaseOptions.isSelected to display adorners of the annotation.

import { SciChartSurface } from 'scichart';
import { BoxAnnotation } from 'scichart/Charting/Visuals/Annotations/BoxAnnotation';
import { NumericAxis } from 'scichart/Charting/Visuals/Axis/NumericAxis';
// ...
const { sciChartSurface, wasmContext } = await SciChartSurface.create(divElementId);
sciChartSurface.xAxes.add(new NumericAxis(wasmContext));
sciChartSurface.yAxes.add(new NumericAxis(wasmContext));
const boxAnnotation = new BoxAnnotation({
    x1: 3,
    x2: 7,
    y1: 3,
    y2: 7,
    isEditable: true,
    isSelected: true,
});
sciChartSurface.annotations.add(boxAnnotation);
import { chartBuilder } from 'scichart/Builder/chartBuilder';
import { EAnnotationType } from 'scichart/Charting/Visuals/Annotations/IAnnotation';
// ...
const { sciChartSurface, wasmContext } = await chartBuilder.build2DChart(divElementId, {
    annotations: {
        type: EAnnotationType.RenderContextBoxAnnotation,
        options: {
            x1: 3,
            x2: 7,
            y1: 3,
            y2: 7,
            isEditable: true,
            isSelected: true,
        },
    },
});

 As you can see the adorners consist of the outlining selection box used for highlighting a selected annotation, and dragging grip points - used to resize or move an annotation when dragged by a cursor. 

 

Custom Adorners Style

Here we will demonstrate how to apply custom styles for the adorners. So in this example we changed the colors and sizes of the selection box and grip points.

const boxAnnotation = new BoxAnnotation({
    x1: 3,
    x2: 7,
    y1: 3,
    y2: 7,
    isEditable: true,
    isSelected: true,
    // add custom styling
    annotationsGripsStroke: 'Blue',
    annotationsGripsFill: 'Black',
    selectionBoxStroke: 'Green',
    annotationsGripsRadius: 10,
    selectionBoxDelta: 30,
    selectionBoxThickness: 9,
});
const { sciChartSurface, wasmContext } = await chartBuilder.build2DChart(divElementId, {
    annotations: {
        type: EAnnotationType.RenderContextBoxAnnotation,
        options: {
            x1: 3,
            x2: 7,
            y1: 3,
            y2: 7,
            isEditable: true,
            isSelected: true,
            // add custom styling
            annotationsGripsStroke: 'Blue',
            annotationsGripsFill: 'Black',
            selectionBoxStroke: 'Green',
            annotationsGripsRadius: 10,
            selectionBoxDelta: 30,
            selectionBoxThickness: 9,
        },
    },
});

 

 Modifying Dragging Grip Points

 By default, an annotation uses all of the predefined grip points for interactions (corners and body), but this can be changed to allow dragging and resizing only using specific ones. For this we will use the IAnnotationBaseOptions.dragPoints (or AnnotationBase.dragPoints) property.

import { EDraggingGripPoint } from 'scichart/Charting/Visuals/Annotations/AnnotationBase';
// ...
const boxAnnotation = new BoxAnnotation({
    x1: 3,
    x2: 7,
    y1: 3,
    y2: 7,
    isEditable: true,
    isSelected: true,
    // custom drag points
    dragPoints: [EDraggingGripPoint.Body, EDraggingGripPoint.x2y1],
});
import { EDraggingGripPoint } from 'scichart/Charting/Visuals/Annotations/AnnotationBase';
// ...
const { sciChartSurface, wasmContext } = await chartBuilder.build2DChart(divElementId, {
    annotations: {
        type: EAnnotationType.RenderContextBoxAnnotation,
        options: {
            x1: 3,
            x2: 7,
            y1: 3,
            y2: 7,
            isEditable: true,
            isSelected: true,
            // custom drag points
            dragPoints: [EDraggingGripPoint.Body, EDraggingGripPoint.x2y1],
        },
    },
});

Resize Direction

Another property of interactable annotation is the dimension where it can be moved or resized. By default it is possible to move a BoxAnnotation towards each side of the chart. In the next example we will demonstrate a usage of the IAnnotationBaseOptions.resizeDirections (or AnnotationBase.resizeDirections) property. We will limit the annotation to resize and move only along the X Axis.

import { EXyDirection } from 'scichart/types/XyDirection';
// ...
const boxAnnotation = new BoxAnnotation({
    x1: 3,
    x2: 7,
    y1: 3,
    y2: 7,
    isEditable: true,
    isSelected: true,
    // custom resize direction
    resizeDirections: EXyDirection.XDirection,
});
import { EXyDirection } from 'scichart/types/XyDirection';
// ...
const { sciChartSurface, wasmContext } = await chartBuilder.build2DChart(divElementId, {
    annotations: {
        type: EAnnotationType.RenderContextBoxAnnotation,
        options: {
            x1: 3,
            x2: 7,
            y1: 3,
            y2: 7,
            isEditable: true,
            isSelected: true,
            // custom resize direction
            resizeDirections: EXyDirection.XDirection,
        },
    },
});

Custom Adorners SVG

 More advanced option to customize the adorners is to override the SVG template for the selection box and grips. To do this we can create a derived annotation class, which in this case extends BoxAnnotation. In the class we are overriding the AnnotationBase.getAnnotationGripSvg and AnnotationBase.svgStringAdornerTemplate methods, which are used to create the adorners.

class CustomBoxAnnotation extends BoxAnnotation {
    getAnnotationGripSvg(x, y) {
        const size = this.annotationsGripsRadius;
        return `<rect x="${x - size / 2}" y="${y - size / 2}" width="${size}" height="${size}" fill="${
            this.annotationsGripsFill
        }" stroke="${this.annotationsGripsStroke}"/>`;
    }
    svgStringAdornerTemplate(x1, y1, x2, y2) {
        const width = x2 - x1;
        const height = y2 - y1;
        let svg = `<svg xmlns="http://www.w3.org/2000/svg">
        <style type="text/css">
            line { stroke: #474747;  }
        </style>
        <defs>
            <pattern id="grid1" patternUnits="userSpaceOnUse" width="10" height="10">
                <line x1="0" y1="0" x2="10" y2="10" />
            </pattern>
        </defs>
        <rect x="${x1}" y="${y1}" width="${width}" height="${height}" fill="url(#grid1)"/>
        `;
        const grips = this.getAdornerAnnotationBorders(false, true);
        if (this.canDragPoint(EDraggingGripPoint.x1y1)) {
            svg += this.getAnnotationGripSvg(grips.x1, grips.y1);
        }
        if (this.canDragPoint(EDraggingGripPoint.x2y2)) {
            svg += this.getAnnotationGripSvg(grips.x2, grips.y2);
        }
        if (this.canDragPoint(EDraggingGripPoint.x2y1)) {
            svg += this.getAnnotationGripSvg(grips.x2, grips.y1);
        }
        if (this.canDragPoint(EDraggingGripPoint.x1y2)) {
            svg += this.getAnnotationGripSvg(grips.x1, grips.y2);
        }
        svg += '</svg>';
        return svg;
    }
}
class CustomBoxAnnotation extends BoxAnnotation {
    public getAnnotationGripSvg(x: number, y: number) {
        const size = this.annotationsGripsRadius;
        return `<rect x="${x - size / 2}" y="${y - size / 2}" width="${size}" height="${size}" fill="${
            this.annotationsGripsFill
        }" stroke="${this.annotationsGripsStroke}"/>`;
    }
    public svgStringAdornerTemplate(x1: number, y1: number, x2: number, y2: number): string {
        const width = x2 - x1;
        const height = y2 - y1;
        let svg = `<svg xmlns="http://www.w3.org/2000/svg">
        <style type="text/css">
            line { stroke: #474747;  }
        </style>
        <defs>
            <pattern id="grid1" patternUnits="userSpaceOnUse" width="10" height="10">
                <line x1="0" y1="0" x2="10" y2="10" />
            </pattern>
        </defs>
        <rect x="${x1}" y="${y1}" width="${width}" height="${height}" fill="url(#grid1)"/>
        `;
        const grips = this.getAdornerAnnotationBorders(false, true);
        if (this.canDragPoint(EDraggingGripPoint.x1y1)) {
            svg += this.getAnnotationGripSvg(grips.x1, grips.y1);
        }
        if (this.canDragPoint(EDraggingGripPoint.x2y2)) {
            svg += this.getAnnotationGripSvg(grips.x2, grips.y2);
        }
        if (this.canDragPoint(EDraggingGripPoint.x2y1)) {
            svg += this.getAnnotationGripSvg(grips.x2, grips.y1);
        }
        if (this.canDragPoint(EDraggingGripPoint.x1y2)) {
            svg += this.getAnnotationGripSvg(grips.x1, grips.y2);
        }
        svg += '</svg>';
        return svg;
    }
}

Then simply create and use an instance of the customized annotation:

Custom Adorners SVG
Copy Code
// use extended class for creating the annotation
const boxAnnotation = new CustomBoxAnnotation({
    x1: 3,
    x2: 7,
    y1: 3,
    y2: 7,
    isEditable: true,
    isSelected: true,
});

See Also