Date axis
Time-series data uses a category axis with type: 'date'. Combined with scale: 'linear', each point is positioned by its actual date. Note the uneven horizontal spacing below matching the gaps in the data.
// A linear date category axis positions each point by its actual date, so uneven
// sampling shows as uneven spacing. Dates arrive as ISO strings.
import type { MochartInputConfig } from '@mochart/core';
export const config: MochartInputConfig = {
version: '1.0.0',
title: { text: 'Active Users' },
categoryAxis: {
property: 'date',
type: 'date',
scale: 'linear',
tickLabel: { format: '%b %d' }
},
seriesDefaults: { renderer: 'area' },
series: [{ property: 'users', title: 'Active users' }]
};
export const data = [
{ date: '2026-06-01T00:00:00Z', users: 120 },
{ date: '2026-06-02T00:00:00Z', users: 132 },
{ date: '2026-06-03T00:00:00Z', users: 101 },
{ date: '2026-06-05T00:00:00Z', users: 154 },
{ date: '2026-06-08T00:00:00Z', users: 148 },
{ date: '2026-06-09T00:00:00Z', users: 170 },
{ date: '2026-06-12T00:00:00Z', users: 190 }
];How it works
- Date values in the data can be ISO strings (as in the example above),
Dateobjects, or millisecond timestamps.dateUTCdecides whether ticks are placed and labels formatted in UTC or local time. It defaults totrue, so set it tofalsefor data whose day boundaries are local ones. tickLabel.formattakes a d3 time-format string for date axes ('%b %d'→ "Jun 01"), as does the category axisvalueFormatshown in the tooltip.- With
scale: 'ordinal'instead, dates are spaced evenly in data order, which is useful when the gaps are noise (e.g. trading days). min/max(and the soft bounds) window a linear date axis; they take an ISO date string or a timestamp (see axis bounds).- A date that is present but has no value is a gap in the shape;
missingValueModechooses whether the shape breaks there (default), connects across it, or drops to the base. - The
arearenderer fills to the value axisbasewhen one is set; with no base set (the default for an axis without stacks, as here), it fills to the minimum end of the axis. Swap inlineorbarper series viarenderer. - A
barseries is onecategoryValueIntervalwide. The default'auto'is the smallest gap between neighbouring dates, a day in the data above, so the bars are a day wide and the missing days show as empty slots. Set'day','hour'or another period, or a millisecond count, when the data spacing is not the bar width you want.
Labeling chosen dates
A daily ordinal axis has more categories than fit as labels, and the generated ticks are thinned by skipping every Nth category, which lands on arbitrary days. tickStep chooses the ticks by rule instead. With a period as the interval on a date axis the first category of each period gets the tick, here each week's first trading day, and that holds through holidays and as the data window slides:
// tickStep on a daily ordinal date axis: an interval of week gives the first trading
// day of each week the tick, through holidays and as the window slides.
import type { MochartInputConfig } from '@mochart/core';
export const config: MochartInputConfig = {
version: '1.0.0',
title: { text: 'Daily Close (fictional, $)' },
categoryAxis: {
property: 'date',
type: 'date',
scale: 'ordinal',
gridLine: { visible: true },
tickLabel: { format: '%b %d' },
valueFormat: '%a %b %d',
tickStep: { interval: 'week' }
},
valueAxes: [{ tickLabel: { format: '$.0f' } }],
series: [{ property: 'close', renderer: 'line', title: 'Close' }]
};
// Wednesday June 3 start, and Monday June 15 is a holiday
export const data = [
{ date: '2026-06-03', close: 97.6 }, { date: '2026-06-04', close: 98.9 }, { date: '2026-06-05', close: 100.2 },
{ date: '2026-06-08', close: 101.0 }, { date: '2026-06-09', close: 99.7 }, { date: '2026-06-10', close: 100.8 },
{ date: '2026-06-11', close: 102.3 }, { date: '2026-06-12', close: 101.5 }, { date: '2026-06-16', close: 102.2 },
{ date: '2026-06-17', close: 104.0 }, { date: '2026-06-18', close: 103.4 }, { date: '2026-06-19', close: 105.2 },
{ date: '2026-06-22', close: 104.6 }, { date: '2026-06-23', close: 106.1 }, { date: '2026-06-24', close: 105.3 },
{ date: '2026-06-25', close: 107.0 }, { date: '2026-06-26', close: 106.4 }, { date: '2026-06-29', close: 107.8 }
];- On a date axis
intervaltakes a period:second,minute,hour,day,week,monthoryear; weeks start on Monday and the boundaries followdateUTC. A partial first week is a period of its own, so its first day gets a tick;offset: 1skips it. everyandoffsetstep through the candidates:interval: 'week'withevery: 2labels every second week, and on a string axisevery: 5, offset: 3shows the fourth category and every fifth after it. On an ordinal axis,includeFirstalways keeps the first category.- When more ticks survive than fit, every k-th survivor is kept from the first, so a thinned weekly rule still lands on Mondays.
- The categories between the rule's ticks are minor ticks.
minorTickLabel.formatlabels them in a d3 format of their own,%afor the weekday.minorTickLabel,minorTickMarkandminorGridLinehold a minor version of everytickLabel,tickMarkandgridLinesetting under the same name (minorTickLabel.font,minorTickMark.visibleand so on), most defaulting to"major", which uses the value of the matching non-minor setting. The minor labels show only while every one fits beside its neighbours; when one does not they all hide together, and the rule's ticks are unchanged either way. Their tick marks, grid lines and labels carrymochart-axis-minor-tick-mark,mochart-axis-minor-grid-lineandmochart-axis-minor-tick-labelclasses for styling. - On a linear date axis the ticks sit on the period boundaries themselves rather than on categories,
everyandoffsetchoose which boundaries are kept, andminorIntervaltakes a shorter period to place minor ticks between them: a day inside each week, or a week inside each month. - A linear date axis also takes a number of milliseconds as the
interval, for a step no period gives:interval: 12 * 60 * 60 * 1000puts a tick at every midnight and noon. The ticks sit at the multiples of the interval counted from the epoch, or from local midnight of the epoch day whendateUTCisfalse, so they stay put as the data window slides.minorIntervalthen takes a smaller number of milliseconds, and mixing a number and a period is a validation error. The multiples keep a fixed length, so in local time a daylight saving change moves them an hour against the clock, which a period does not. - On a linear number axis
intervaltakes a number in axis values instead, withminorStepssplitting each interval into minor ticks, and value axes take the sametickStep.
To name the dates outright instead, ticks replaces the generated ticks with a list of category values, so only those categories get a tick, grid line and label:
// Explicit category axis ticks: only the Mondays of a daily ordinal date axis
// get a tick and label, where generated ticks would land on arbitrary days.
import type { MochartInputConfig } from '@mochart/core';
export const config: MochartInputConfig = {
version: '1.0.0',
title: { text: 'Daily Close (fictional, $)' },
categoryAxis: {
property: 'date',
type: 'date',
scale: 'ordinal',
gridLine: { visible: true },
tickLabel: { format: '%b %d' },
valueFormat: '%a %b %d',
ticks: [
{ value: '2026-06-01' },
{ value: '2026-06-08' },
{ value: '2026-06-15' },
{ value: '2026-06-22' }
]
},
valueAxes: [{ tickLabel: { format: '$.0f' } }],
series: [{ property: 'close', renderer: 'line', title: 'Close' }]
};
export const data = [
{ date: '2026-06-01', close: 98.4 }, { date: '2026-06-02', close: 99.1 }, { date: '2026-06-03', close: 97.6 },
{ date: '2026-06-04', close: 98.9 }, { date: '2026-06-05', close: 100.2 }, { date: '2026-06-08', close: 101.0 },
{ date: '2026-06-09', close: 99.7 }, { date: '2026-06-10', close: 100.8 }, { date: '2026-06-11', close: 102.3 },
{ date: '2026-06-12', close: 101.5 }, { date: '2026-06-15', close: 103.1 }, { date: '2026-06-16', close: 102.2 },
{ date: '2026-06-17', close: 104.0 }, { date: '2026-06-18', close: 103.4 }, { date: '2026-06-19', close: 105.2 },
{ date: '2026-06-22', close: 104.6 }, { date: '2026-06-23', close: 106.1 }, { date: '2026-06-24', close: 105.3 },
{ date: '2026-06-25', close: 107.0 }, { date: '2026-06-26', close: 106.4 }
];- Each entry names a category by value: an ISO date string or timestamp on a date axis (matched by instant, so either form finds the category), a number on a number axis, or the category string on a string axis. A
labelreplaces the formatted value; without one the tick shows the value intickLabel.format. - A tick naming no category is hidden, so a list of Mondays stays valid as the data window slides. On a linear axis a tick is placed by its value instead, and one outside the domain is hidden.
- The list replaces tick generation entirely: tick counts, intervals and the skipping that keeps labels apart no longer apply, so ticks placed too close together overlap.