Annotations
The Annotations component allows you to add informative text to your visualizations. It is compatible with both XY and Single containers.
Basic Configuration
Include the Annotations component in your Single or XY container with an array of items.
A basic example looks like:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisXYContainer yDomain={[0,15]} data={data}>
<VisArea x={x} y={y} curveType="linear"/>
<VisAnnotations items={items}/>
</VisXYContainer>
<vis-xy-container [yDomain]="[0,15]" [data]="data">
<vis-area [x]="x" [y]="y" curveType="linear"></vis-area>
<vis-annotations [items]="items"></vis-annotations>
</vis-xy-container>
<VisXYContainer yDomain={[0,15]} {data}>
<VisArea {x} {y} curveType="linear"/>
<VisAnnotations {items}/>
</VisXYContainer>
<VisXYContainer :yDomain="[0,15]" :data="data">
<VisArea :x="x" :y="y" curveType="linear" />
<VisAnnotations :items="items" />
</VisXYContainer>
<VisXYContainer yDomain={[0,15]} data={data}>
<VisArea x={x} y={y} curveType="linear"/>
<VisAnnotations items={items}/>
</VisXYContainer>
const container = new XYContainer<DataRecord>(node, {
yDomain: [0,15],
components: [new Area({ x, y, curveType: "linear" })],
annotations: new Annotations({ items })
}, data)
where items has the following definition:
let items: AnnotationItem[] = [
{ x: 12, y: 10, content: { text: 'Chart Title', fontSize: 30 }},
{ x: 15, y: 50, width: 200, content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam rutrum dignissim lorem, a mollis lacus lacinia eget' },
{ x: '50%', y: '20%', content: 'Label #1', subject: { x: '42%', y: '38%' }},
{ x: '70%', y: '40%', content: 'Label #2', subject: { x: '86%', y: '50%' }},
]
Items
The items property accepts an array of AnnotationItem objects. For minimum configuration,
include content (the text) and an optional subject (the highlighted area).
AnnotationItem Type
type AnnotationItem = {
// Base properties
content: string | UnovisText | UnovisText[];
subject?: AnnotationSubject;
// Position and size properties
x?: number | `${number}%` | `${number}px`
y?: number | `${number}%` | `${number}px`
width?: number | `${number}%` | `${number}px`
height?: number | `${number}%` | `${number}px`
// Wrapping and alignment properties
separator?: string | string[];
verticalAlign?: VerticalAlign | string;
textAlign?: TextAlign | string;
wordBreak?: boolean;
fastMode?: boolean;
// Miscellaneous
cursor: string;
}
Text Content
The content property accepts a string, a UnovisText object, or an array of UnovisText objects.
As a basic example, you can use a string:
let items: AnnotationItem[] = [
{ content: 'Item 1' }
]
When we place Annotations in an empty chart with the above items array, we get the default text appearance:
Styled Text
To customize font or color of your text, provide content with an object of type UnovisText.
In this case, you put the desired string content to the text property,
and additional properties can be set.
items = [{
content: { text: 'Item 2', color: 'red' }
}]
Multi-Line Text
You can group a single annotation into multiple lines of text by providing an array of UnovisText
items to the content property.
The following is also a valid input for items:
items = [{
content: [
{
text: 'Item 3',
fontSize: 24,
fontWeight: 600
},
{
text: 'contains multiple text blocks lines',
fontFamily: 'cursive',
fontSize: 16
},
{
text: 'with custom styles',
color: 'green',
fontWeight: 'bold'
},
],
}]
UnovisText type
type UnovisText = {
// The text content to be displayed.
text: string;
// The font size of the text in pixels.
fontSize?: number;
// The font family of the text.
fontFamily?: string;
// The font weight of the text.
fontWeight?: number;
// The color of the text.
color?: string;
// The line height scaling factor for the text.
lineHeight?: number;
// The top margin of the text block in pixels
marginTop?: number;
// The bottom margin of the text block in pixels
marginBottom?: number;
// The font width-to-height ratio
fontWidthToHeightRatio?: number;
};
Positioning
The x and y properties define the position of the annotation.
They can be specified in pixels (number or string) or as a percentage of the container size.
You can also add textAlign and verticalAlign properties to adjust the alignment of the text.
In an XY container, these coordinates are relative to the area inside the chart margins.
The origin is at the top-left, and y increases downwards. The same rules apply to subject coordinates.
Numbers represent pixels, not data values. To highlight a data point, use the component's scales as
described in Annotating Data Points.
For example, the following is a valid definition of
const items= [
{
content: 'Item A',
x: 0,
y: 10,
subject: { x: 50, y: 50 }
},
{
content: 'Item B',
x: '150px',
y: '20px',
textAlign: 'right',
subject: { x: '100px', y: '60px' }
},
{
content: 'Item C',
x: '50%',
y: '100%',
textAlign: 'center',
verticalAlign: 'bottom',
subject: { x: '50%', y: '70%' },
},
]
Computed Percentages in TypeScript
Annotation positions and sizes use the LengthUnit type:
type LengthUnit = number | `${number}%` | `${number}px`
When storing a computed percentage in a variable, annotate it with LengthUnit so TypeScript
checks the template string against the accepted format:
import type { AnnotationItem } from '@unovis/ts/components/annotations/types'
import type { LengthUnit } from '@unovis/ts/types/misc'
const x = 0.9
const y = 0.8
const xs: LengthUnit = `${Math.round(100 * x)}%`
const ys: LengthUnit = `${Math.round(100 * y)}%`
const items: AnnotationItem[] = [{
x: '50%',
y: '50%',
content: 'Computed position',
subject: { x: xs, y: ys, radius: 6 },
}]
Without the type annotation, the computed value can be inferred as string, which is broader
than LengthUnit. This pattern provides type checking without an as LengthUnit assertion.
Subjects
Subjects are focal points of the annotation. They can be used to highlight a specific area or point.
The minimum configuration of a subject is an x and y coordinate.
items = [{
content: 'Item with Subject',
subject: { x: '50%', y: 20 }
}]
Subject Points
The point corresponds to the center of the subject area. When a radius is provided, the point becomes visible.
items = [{
content: 'Item with Subject',
subject: { x: '50%', y: 2, radius: 4 }
}]
Annotating Data Points
To align a subject with a scatter point, convert the data coordinates to pixels using the scatter
component's xScale and yScale. Use callbacks so the coordinates are evaluated with the current
scales whenever annotations render, including after a resize.
Converting data values to percentages manually can produce an offset: the chart's scale ranges include space for point sizes ("bleed"), while annotation percentages use the full area inside the margins. Rounding percentages introduces additional positioning error.
For example, using the TypeScript core:
import { Annotations } from '@unovis/ts/components/annotations'
import { Axis } from '@unovis/ts/components/axis'
import { Scatter } from '@unovis/ts/components/scatter'
import { XYContainer } from '@unovis/ts/containers/xy-container'
type Datum = { x: number; y: number }
const data: Datum[] = [{ x: 0, y: 0 }, { x: 22, y: 70 }, { x: 100, y: 100 }]
const point = data[1]
const scatter = new Scatter<Datum>({ x: d => d.x, y: d => d.y, size: 24 })
const annotations = new Annotations({
items: [{
x: '50%',
y: '50%',
content: `Point (${point.x}, ${point.y})`,
subject: {
x: () => scatter.xScale(point.x),
y: () => scatter.yScale(point.y),
radius: 6,
},
}],
})
const container = document.getElementById('vis-container')
if (!container) throw new Error('Chart container not found')
const chart = new XYContainer<Datum>(container, {
height: 400,
components: [scatter],
xAxis: new Axis(),
yAxis: new Axis(),
annotations,
}, data)
Use the scales of the component containing the point you want to annotate. The annotation text can still use pixel or percentage positioning independently of its subject.
Unovis's Scale export provides D3 scale functions.
A standalone scaleLinear([min, max]) uses that single argument as its range, with a default
domain of [0, 1]. Even when both domain and range are supplied, a separate scale must match the
chart's final pixel range to align a subject correctly. Using the component's scales avoids
duplicating those layout calculations.
AnnotationSubject Type
export type AnnotationSubject = {
x: LengthUnit | (() => LengthUnit);
y: LengthUnit | (() => LengthUnit);
/** Subject radius */
radius?: number;
/** Subject fill color */
fillColor?: string;
/** Subject stroke color */
strokeColor?: string;
/** Subject stroke-dasharray configuration */
strokeDasharray?: string;
/** Padding between the subject and the connector line */
padding?: number;
/** Connector line color */
connectorLineColor?: string;
/** Connector line stroke-dasharray configuration */
connectorLineStrokeDasharray?: string;
};
CSS Variables
--vis-annotations-connector-stroke-color: #444;
--vis-annotations-connector-stroke-width: 1px;
--vis-annotations-connector-stroke-dasharray: none;
--vis-annotations-subject-stroke-color: #444;
--vis-annotations-subject-fill-color: none;
--vis-annotations-subject-stroke-dasharray: none;
--vis-dark-annotations-conntector-stroke-color: #fff;
--vis-dark-annotations-subject-stroke-color: #fff;
Component Props
| Name | Type | Description |
|---|---|---|
| * required property |