Markers and labels
Markers draw a shape at each value of a series; labels render a data value next to each shape. Both are per-series config.
ts
// Markers draw a shape at each value of a line series; labels render the
// value of labelProperty next to each shape — point it at the series'
// own property to show value labels.
import type { MochartInputConfig } from '@mochart/core';
export const config: MochartInputConfig = {
version: '1.0.0',
title: { text: 'Release Velocity' },
categoryAxis: { property: 'sprint', type: 'string', scale: 'ordinal' },
series: [
{
property: 'planned',
title: 'Planned',
renderer: 'bar',
labelProperty: 'planned',
label: {
format: ',.0f',
position: 'inside',
// Only the colors of the normal state are overridden — the opacities,
// the stroke width, and the focused/defocused states keep their defaults.
textStyle: { normal: { strokeColor: '#ffffff', fillColor: '#ffffff' } },
minRangeFraction: 0.05
}
},
{
property: 'shipped',
title: 'Shipped',
renderer: 'line',
marker: {
shape: 'circle',
size: 5
}
}
]
};
export const data = [
{ sprint: 'S1', planned: 12, shipped: 9 },
{ sprint: 'S2', planned: 14, shipped: 13 },
{ sprint: 'S3', planned: 11, shipped: 12 },
{ sprint: 'S4', planned: 15, shipped: 14 },
{ sprint: 'S5', planned: 13, shipped: 11 }
];How it works
marker.shapepicks fromcircle,cross,diamond,square,star,triangleandwye;line,areaandnoneseries default tocircle, bars tonull(no marker).marker.sizesets the size (default 6px) andmarker.stylestyles it — stroke and fill colors, opacities and widths pernormal/focused/defocusedstate. PointmarkerPropertyat a data property to scale marker size per value — see bubbles below.- Labels come from
labelProperty— point it at the series' ownproperty(as above) for value labels, or at any other data property.label.formatformats the value ("auto"derives a format from the data), andlabel.prefix/label.suffixwrap it with text a d3 format can't express, such as a unit. They are separate from the tooltip'svaluePrefix/valueSuffixbecause a label may show a different property than the series value. label.positionplaces labelsinside,center(the default) oroutsidethe shape, andlabel.offsetnudges every label by a fixed pixel amount along the value axis.- Three fraction guards hide labels that wouldn't fit:
label.minRangeFraction(used above — it hides labels on bars shorter than 5% of the axis extent), andlabel.minPositionFraction/label.maxPositionFraction, which hide labels whose values sit too close to the value axisbaseor too close to the domain end they run toward, each by a fraction of the domain extent. Where the axis has nobase, the guards use the domain minimum as the base.basedefaults to0on any axis with stacks. label.position,label.offsetand the two position-fraction guards each have a variant underlabel.aboveBase/label.belowBase(label.aboveBase.position,label.belowBase.offset, …) that apply only to values above or below the value axisbase— handy for labeling positive and negative bars differently. Their default'auto'inherits the plain setting, except the below-base offset, which inherits the negatedlabel.offsetso both sides shift the same distance in opposite directions.label.textStylestyles the label text, again per focus state. Its colors accept the palette modes (series,seriesIndex,categoryIndex) as well as literal colors — seecolorPalette. The example above sets onlylabel.textStyle.normal.strokeColorand.fillColor; every other member, including both other states, keeps its default.
Scatter and bubble charts
Markers on their own make a scatter chart: set renderer to none so a series draws no shape, and only its markers remain.
ts
// A scatter chart is marker-only series (renderer 'none') on a linear category
// axis, so points sit at their measured x values. Point markerProperty at a
// data property to scale marker size per point — a bubble chart.
import type { MochartInputConfig } from '@mochart/core';
export const config: MochartInputConfig = {
version: '1.0.0',
title: { text: 'Latency under Load' },
categoryAxis: {
title: { text: 'Requests per second' },
property: 'load',
type: 'number',
scale: 'linear'
},
chart: {
margin: { right: 5 }
},
valueAxes: [{ id: 'VA0', title: { text: 'Latency (ms)' } }],
seriesDefaults: { renderer: 'none' },
series: [
{
property: 'v1',
title: 'v1',
marker: {
shape: 'circle',
size: 6
}
},
{
property: 'v2',
title: 'v2',
marker: {
shape: 'diamond',
minSize: 4,
size: 16
},
markerProperty: 'v2Errors'
}
]
};
export const data = [
{ load: 12, v1: 38, v2: 31, v2Errors: 0 },
{ load: 45, v1: 42, v2: 33, v2Errors: 1 },
{ load: 70, v1: 55, v2: 41, v2Errors: 2 },
{ load: 160, v1: 74, v2: 52, v2Errors: 3 },
{ load: 240, v1: 92, v2: 60, v2Errors: 8 },
{ load: 310, v1: 121, v2: 71, v2Errors: 14 },
{ load: 470, v1: 168, v2: 95, v2Errors: 25 }
];- Use a
linearcategory axisscale(withnumberordatetype) so points are positioned by their measured x values rather than evenly spaced category slots. - For bubbles, point
markerPropertyat a data property; marker sizes scale betweenmarker.minSize(default 1px) andmarker.sizewith the property's value. marker.sizeScalepicks how they scale: the defaultsqrtscales each marker's area with its value — the way readers judge bubble magnitude — whilelinearscales its diameter, which visually exaggerates differences. Themarker.minSizefloor keeps the smallest bubble visible (and hoverable); for exactly value-proportional areas, set it to0on data whose minimum is0.- Every series reads its x from the row's category value, so series share x positions. For series with points at different x values, give each x its own row and leave the other series' properties out — a row draws a marker only for the series that have a value there.