Skip to content

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.

ts
// 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), Date objects, or millisecond timestamps. dateUTC decides whether ticks are placed and labels formatted in UTC or local time. It defaults to true, so set it to false for data whose day boundaries are local ones.
  • tickLabel.format takes a d3 time-format string for date axes ('%b %d' → "Jun 01"), as does the category axis valueFormat shown 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; missingValueMode chooses whether the shape breaks there (default), connects across it, or drops to the base.
  • The area renderer fills to the value axis base when 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 in line or bar per series via renderer.
  • A bar series is one categoryValueInterval wide. 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:

ts
// 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 interval takes a period: second, minute, hour, day, week, month or year; weeks start on Monday and the boundaries follow dateUTC. A partial first week is a period of its own, so its first day gets a tick; offset: 1 skips it.
  • every and offset step through the candidates: interval: 'week' with every: 2 labels every second week, and on a string axis every: 5, offset: 3 shows the fourth category and every fifth after it. On an ordinal axis, includeFirst always 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.format labels them in a d3 format of their own, %a for the weekday. minorTickLabel, minorTickMark and minorGridLine hold a minor version of every tickLabel, tickMark and gridLine setting under the same name (minorTickLabel.font, minorTickMark.visible and 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 carry mochart-axis-minor-tick-mark, mochart-axis-minor-grid-line and mochart-axis-minor-tick-label classes for styling.
  • On a linear date axis the ticks sit on the period boundaries themselves rather than on categories, every and offset choose which boundaries are kept, and minorInterval takes 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 * 1000 puts 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 when dateUTC is false, so they stay put as the data window slides. minorInterval then 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 interval takes a number in axis values instead, with minorSteps splitting each interval into minor ticks, and value axes take the same tickStep.

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:

ts
// 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 label replaces the formatted value; without one the tick shows the value in tickLabel.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.

Released under the MIT License.