{
  "id": "dashboards",
  "title": "Dashboards and charts",
  "summary": "Chart geometry as data in any language (scales, ticks, axes, lines, bars, pies, sparklines) plus the statistics and number formatting a dashboard shows.",
  "synonyms": [
    "dashboard",
    "charts",
    "graphs",
    "plots",
    "data visualisation",
    "data visualization",
    "kpi",
    "reporting",
    "analytics",
    "metrics display",
    "sparklines"
  ],
  "apps": [
    "dashboard",
    "analytics dashboard",
    "admin dashboard",
    "reporting page",
    "kpi dashboard",
    "charting library",
    "metrics display"
  ],
  "language": "typescript",
  "includes": [
    "dates-basics"
  ],
  "includedBy": [
    "monitoring"
  ],
  "caveats": [],
  "sections": [
    {
      "key": "complete-charts",
      "title": "Whole charts",
      "purpose": "Get complete chart geometry in one call, for the common charts.",
      "from": "dashboards",
      "members": [
        {
          "id": "charts.line-chart",
          "tier": "core",
          "why": "Plot area, number or date axes, gridlines, series paths, colours and legend for a line chart.",
          "section": "complete-charts",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Complete line chart geometry as data: plot area, number or date axes, gridlines, series paths, colours, legend.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "lineChart"
          ],
          "signatures": [
            "export function lineChart(spec: LineChartSpec): LineChart"
          ],
          "reference": "import { lineChart } from \"#fune/charts.line-chart@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.line-chart"
        },
        {
          "id": "charts.bar-chart",
          "tier": "core",
          "why": "Grouped or stacked bar chart geometry: axes, gridlines, bars, colours and legend.",
          "section": "complete-charts",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Complete bar chart geometry as data, grouped or stacked: plot area, axes, gridlines, bar rectangles, colours, legend.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "barChart"
          ],
          "signatures": [
            "export function barChart(spec: BarChartSpec): BarChart"
          ],
          "reference": "import { barChart } from \"#fune/charts.bar-chart@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.bar-chart"
        },
        {
          "id": "charts.sparkline",
          "tier": "optional",
          "why": "A sparkline path and end marker for a series in a small box.",
          "section": "complete-charts",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "A sparkline's SVG path and end marker from a series, fitted into a box, with gaps left as breaks.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "sparkline"
          ],
          "signatures": [
            "export function sparkline(values: readonly (number | null)[], width: number, height: number, padding: number): Sparkline"
          ],
          "reference": "import { sparkline } from \"#fune/charts.sparkline@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.sparkline"
        }
      ],
      "gaps": []
    },
    {
      "key": "building-blocks",
      "title": "Chart building blocks",
      "purpose": "Compose charts of your own from the same parts.",
      "from": "dashboards",
      "members": [
        {
          "id": "charts.scale",
          "tier": "core",
          "why": "Maps values to positions and back: linear, log, time, band and point scales, nice domains.",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.1.0",
          "summary": "Map data values onto screen positions and back: linear, log, time, band and point scales, and nice domains.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "linearScale",
            "invertLinear",
            "niceDomain",
            "logScale",
            "timeScale",
            "bandScale",
            "pointScale"
          ],
          "signatures": [
            "export function linearScale(domain: readonly number[], range: readonly number[], value: number, clamp: boolean): number",
            "export function invertLinear(domain: readonly number[], range: readonly number[], value: number): number",
            "export function niceDomain(domain: readonly number[], count: number): NiceDomain",
            "export function logScale(domain: readonly number[], range: readonly number[], value: number, clamp: boolean): number",
            "export function timeScale(domain: readonly string[], range: readonly number[], value: string, clamp: boolean): number",
            "export function bandScale(domain: readonly string[], range: readonly number[], value: string, paddingInner: number, paddingOuter: number, align: number): Band",
            "export function pointScale(domain: readonly string[], range: readonly number[], value: string, padding: number, align: number): number"
          ],
          "reference": "import { linearScale, invertLinear, niceDomain, logScale, timeScale, bandScale, pointScale } from \"#fune/charts.scale@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.scale"
        },
        {
          "id": "charts.ticks",
          "tier": "core",
          "why": "Round tick steps and calendar ticks from days to years.",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Axis ticks: a round step (1, 2 or 5 x 10^k), the ticks on it, and calendar ticks from days to years.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "tickStep",
            "niceTicks",
            "timeTickInterval",
            "timeTicks"
          ],
          "signatures": [
            "export function tickStep(start: number, stop: number, count: number): number",
            "export function niceTicks(start: number, stop: number, count: number): readonly number[]",
            "export function timeTickInterval(start: string, stop: string, count: number): TimeTickInterval",
            "export function timeTicks(start: string, stop: string, interval: TimeInterval, step: number): readonly string[]"
          ],
          "reference": "import { tickStep, niceTicks, timeTickInterval, timeTicks } from \"#fune/charts.ticks@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.ticks"
        },
        {
          "id": "charts.axis",
          "tier": "core",
          "why": "Tick positions, labels and gridlines for number, time and category axes.",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.0.1",
          "summary": "Axis geometry as data: tick positions, tick marks, label anchors and gridlines for number, time and category axes.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "axisFromTicks",
            "numberAxis",
            "timeAxis",
            "bandAxis",
            "gridLines"
          ],
          "signatures": [
            "export function axisFromTicks(positions: readonly number[], labels: readonly string[], range: readonly number[], orient: AxisOrient, position: number, tickSize: number): Axis",
            "export function numberAxis(domain: readonly number[], range: readonly number[], count: number, orient: AxisOrient, position: number, tickSize: number): Axis",
            "export function timeAxis(domain: readonly string[], range: readonly number[], count: number, orient: AxisOrient, position: number, tickSize: number): Axis",
            "export function bandAxis(domain: readonly string[], range: readonly number[], paddingInner: number, paddingOuter: number, orient: AxisOrient, position: number, tickSize: number): Axis",
            "export function gridLines(axis: Axis, length: number): readonly Line[]"
          ],
          "reference": "import { axisFromTicks, numberAxis, timeAxis, bandAxis, gridLines } from \"#fune/charts.axis@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.axis"
        },
        {
          "id": "charts.shape",
          "tier": "optional",
          "why": "SVG paths for lines, areas, steps, curves, bars, arcs and pie slices.",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.0.1",
          "summary": "SVG path strings for lines, areas, steps, monotone curves, bars, arcs and pie slices, identical in every language.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "linePath",
            "areaPath",
            "stepPath",
            "monotoneCurve",
            "barRects",
            "arcPath",
            "pieAngles"
          ],
          "signatures": [
            "export function linePath(points: readonly Point[]): string",
            "export function areaPath(points: readonly AreaPoint[]): string",
            "export function stepPath(points: readonly Point[], position: StepPosition): string",
            "export function monotoneCurve(points: readonly Point[]): string",
            "export function barRects(bars: readonly BarSpec[], orientation: Orientation): readonly Rect[]",
            "export function arcPath(arc: Arc): string",
            "export function pieAngles(values: readonly number[], startAngle: number, endAngle: number, padAngle: number): readonly PieSlice[]"
          ],
          "reference": "import { linePath, areaPath, stepPath, monotoneCurve, barRects, arcPath, pieAngles } from \"#fune/charts.shape@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.shape"
        },
        {
          "id": "charts.stack",
          "tier": "optional",
          "why": "Stacks series on zero, normalised to 100% or diverging.",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Stack several series for stacked bars and areas: on zero, normalised to 100%, or diverging around zero.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "stack"
          ],
          "signatures": [
            "export function stack(series: readonly (readonly number[])[], offset: StackOffset): readonly (readonly StackedValue[])[]"
          ],
          "reference": "import { stack } from \"#fune/charts.stack@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.stack"
        },
        {
          "id": "charts.layout",
          "tier": "optional",
          "why": "Plot area inside margins, legend rows, label collision avoidance.",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.0.1",
          "summary": "Chart layout: the plot area inside margins, legend items wrapped into rows, and greedy label collision avoidance.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "plotArea",
            "legendRows",
            "avoidCollisions"
          ],
          "signatures": [
            "export function plotArea(width: number, height: number, margins: Margins): PlotArea",
            "export function legendRows(labels: readonly string[], maxWidth: number, charWidth: number, swatchSize: number, gap: number, rowHeight: number): readonly LegendItem[]",
            "export function avoidCollisions(boxes: readonly LabelBox[], padding: number): readonly boolean[]"
          ],
          "reference": "import { plotArea, legendRows, avoidCollisions } from \"#fune/charts.layout@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.layout"
        },
        {
          "id": "charts.extent",
          "tier": "optional",
          "why": "Min and max of a series, skipping gaps.",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.0.1",
          "summary": "The minimum and maximum of a series as [min, max], skipping gaps (null), or null when there is no value.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "extent"
          ],
          "signatures": [
            "export function extent(values: readonly (number | null)[]): readonly number[] | null"
          ],
          "reference": "import { extent } from \"#fune/charts.extent@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.extent"
        },
        {
          "id": "charts.histogram-bins",
          "tier": "optional",
          "why": "Bins values for a histogram (Sturges, Freedman-Diaconis or fixed width).",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Bin values into histogram bins on round edges, by Sturges' rule, Freedman-Diaconis, or a fixed width.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "histogramBins"
          ],
          "signatures": [
            "export function histogramBins(values: readonly number[], method: BinMethod, binWidth: number | null): readonly Bin[]"
          ],
          "reference": "import { histogramBins } from \"#fune/charts.histogram-bins@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.histogram-bins"
        },
        {
          "id": "charts.downsample",
          "tier": "optional",
          "why": "Thins a long series by LTTB so it draws fast but keeps its shape.",
          "section": "building-blocks",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Reduce a long series to a few points that keep its visual shape, by Largest-Triangle-Three-Buckets (LTTB).",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "downsample"
          ],
          "signatures": [
            "export function downsample(points: readonly Sample[], threshold: number): readonly Sample[]"
          ],
          "reference": "import { downsample } from \"#fune/charts.downsample@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.downsample"
        }
      ],
      "gaps": []
    },
    {
      "key": "colour",
      "title": "Colour",
      "purpose": "Readable, colour-blind-safe colours.",
      "from": "dashboards",
      "members": [
        {
          "id": "charts.palette",
          "tier": "core",
          "why": "n colour-blind-safe series colours (Okabe-Ito or Tol bright).",
          "section": "colour",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "A categorical palette of n colour-blind-safe colours for chart series: Okabe-Ito or Paul Tol's bright scheme.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "palette"
          ],
          "signatures": [
            "export function palette(scheme: string, count: number, cycle: boolean): readonly string[]"
          ],
          "reference": "import { palette } from \"#fune/charts.palette@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.palette"
        },
        {
          "id": "charts.interpolate-color",
          "tier": "optional",
          "why": "Colour along a sequential or diverging ramp, for heatmaps.",
          "section": "colour",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "The colour at a point along a sequential or diverging ramp, mixed in OKLab so steps look even; hex in, hex out.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "interpolateColor"
          ],
          "signatures": [
            "export function interpolateColor(ramp: readonly string[], t: number): string"
          ],
          "reference": "import { interpolateColor } from \"#fune/charts.interpolate-color@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.interpolate-color"
        },
        {
          "id": "charts.contrast",
          "tier": "optional",
          "why": "WCAG contrast ratio and the readable text colour on a background.",
          "section": "colour",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "WCAG 2.x contrast ratio between two colours, and which text colour reads best on a background.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "contrastRatio",
            "readableTextColor"
          ],
          "signatures": [
            "export function contrastRatio(foreground: string, background: string): number",
            "export function readableTextColor(background: string, candidates: readonly string[]): string"
          ],
          "reference": "import { contrastRatio, readableTextColor } from \"#fune/charts.contrast@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.contrast"
        },
        {
          "id": "charts.color",
          "tier": "optional",
          "why": "Hex to RGB and back, and sRGB to linear light.",
          "section": "colour",
          "from": "dashboards",
          "version": "1.0.1",
          "summary": "Hex colours to RGB and back, and sRGB channels to linear light and back, identically in every language.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "parseHex",
            "toHex",
            "srgbToLinear",
            "linearToSrgb"
          ],
          "signatures": [
            "export function parseHex(hex: string): Rgb",
            "export function toHex(rgb: Rgb): string",
            "export function srgbToLinear(channel: number): number",
            "export function linearToSrgb(value: number): number"
          ],
          "reference": "import { parseHex, toHex, srgbToLinear, linearToSrgb } from \"#fune/charts.color@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.color"
        }
      ],
      "gaps": []
    },
    {
      "key": "figures",
      "title": "Figures and statistics",
      "purpose": "The numbers in the tiles and tooltips.",
      "from": "dashboards",
      "members": [
        {
          "id": "charts.format",
          "tier": "core",
          "why": "Tick and label text: SI prefixes (1.2k), percentages, dates and money.",
          "section": "figures",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Axis and label text for charts: tick numbers, SI prefixes (1.2k, 3.4M), percentages, dates and money.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "formatTick",
            "formatSi",
            "formatPercent",
            "formatDate",
            "formatMoneyTick"
          ],
          "signatures": [
            "export function formatTick(value: number, step: number): string",
            "export function formatSi(value: number, significant: number): string",
            "export function formatPercent(value: number, decimals: number): string",
            "export function formatDate(iso: string, label: DateLabel): string",
            "export function formatMoneyTick(minor: number, currency: string): string"
          ],
          "reference": "import { formatTick, formatSi, formatPercent, formatDate, formatMoneyTick } from \"#fune/charts.format@^1\";",
          "url": "http://www.functionalweave.com/functions/charts.format"
        },
        {
          "id": "stats.mean-median",
          "tier": "optional",
          "why": "Mean, median and mode, exactly.",
          "section": "figures",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Mean, median and mode of a list of integers, with the mean and median as exact fractions.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "meanMedianMode"
          ],
          "signatures": [
            "export function meanMedianMode(values: readonly number[]): CentralTendency"
          ],
          "reference": "import { meanMedianMode } from \"#fune/stats.mean-median@^1\";",
          "url": "http://www.functionalweave.com/functions/stats.mean-median"
        },
        {
          "id": "stats.percentile",
          "tier": "optional",
          "why": "Percentiles (nearest-rank or linear), for p95 tiles.",
          "section": "figures",
          "from": "dashboards",
          "version": "2.0.0",
          "summary": "Percentile of a list of numbers by a named method: nearest-rank, or linear interpolation (R-7, Excel PERCENTILE.INC).",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "percentile"
          ],
          "signatures": [
            "export function percentile(values: readonly number[], p: number, method: PercentileMethod, decimals: number): number"
          ],
          "reference": "import { percentile } from \"#fune/stats.percentile@^2\";",
          "url": "http://www.functionalweave.com/functions/stats.percentile"
        },
        {
          "id": "stats.moving-average",
          "tier": "optional",
          "why": "Simple and exponential moving averages for trend lines.",
          "section": "figures",
          "from": "dashboards",
          "version": "2.0.0",
          "summary": "Simple or exponential moving average over a series, one value per full window, rounded to stated decimals.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "movingAverage"
          ],
          "signatures": [
            "export function movingAverage(values: readonly number[], window: number, kind: MovingAverageKind, decimals: number): readonly number[]"
          ],
          "reference": "import { movingAverage } from \"#fune/stats.moving-average@^2\";",
          "url": "http://www.functionalweave.com/functions/stats.moving-average"
        },
        {
          "id": "stats.standard-deviation",
          "tier": "optional",
          "why": "Standard deviation, for bands and variability.",
          "section": "figures",
          "from": "dashboards",
          "version": "2.0.0",
          "summary": "Population or sample standard deviation of a list of numbers, two-pass, rounded to stated decimals.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "standardDeviation"
          ],
          "signatures": [
            "export function standardDeviation(values: readonly number[], kind: DeviationKind, decimals: number): number"
          ],
          "reference": "import { standardDeviation } from \"#fune/stats.standard-deviation@^2\";",
          "url": "http://www.functionalweave.com/functions/stats.standard-deviation"
        },
        {
          "id": "stats.weighted-average",
          "tier": "optional",
          "why": "Weighted averages with explicit rounding.",
          "section": "figures",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Weighted average of integers, computed exactly and rounded once to stated decimals with an explicit rounding mode.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "weightedAverage"
          ],
          "signatures": [
            "export function weightedAverage(values: readonly number[], weights: readonly number[], decimals: number, mode: RoundingMode): number"
          ],
          "reference": "import { weightedAverage } from \"#fune/stats.weighted-average@^1\";",
          "url": "http://www.functionalweave.com/functions/stats.weighted-average"
        },
        {
          "id": "math.percent-change",
          "tier": "optional",
          "why": "Change between two values in basis points, for \"up 4.2%\".",
          "section": "figures",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Percentage change from one value to another, in basis points, with an explicit rounding mode.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "percentChange"
          ],
          "signatures": [
            "export function percentChange(fromValue: number, toValue: number, mode: RoundingMode): number | null"
          ],
          "reference": "import { percentChange } from \"#fune/math.percent-change@^1\";",
          "url": "http://www.functionalweave.com/functions/math.percent-change"
        },
        {
          "id": "text.format-decimal",
          "tier": "optional",
          "why": "Plain decimal text with fixed places and grouping.",
          "section": "figures",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Format a float as plain decimal text with fixed or trimmed places and optional grouping, identically in every language.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "formatDecimal"
          ],
          "signatures": [
            "export function formatDecimal(value: number, decimals: number, trimZeros: boolean, groupSeparator: string): string"
          ],
          "reference": "import { formatDecimal } from \"#fune/text.format-decimal@^1\";",
          "url": "http://www.functionalweave.com/functions/text.format-decimal"
        },
        {
          "id": "money.format",
          "tier": "optional",
          "why": "Money values in tiles with the currency's real decimals.",
          "section": "figures",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Render a monetary amount as text, using the currency's real minor-unit precision.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "formatMoney"
          ],
          "signatures": [
            "export function formatMoney(amount: Money): string"
          ],
          "reference": "import { formatMoney } from \"#fune/money.format@^1\";",
          "url": "http://www.functionalweave.com/functions/money.format"
        },
        {
          "id": "collections.group-by-key",
          "tier": "optional",
          "why": "Groups records by a key before charting them.",
          "section": "figures",
          "from": "dashboards",
          "version": "1.0.0",
          "summary": "Group records by the value at one key, preserving input order inside each group.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "groupByKey"
          ],
          "signatures": [
            "export function groupByKey(records: readonly Readonly<Record<string, unknown>>[], key: string): Readonly<Record<string, readonly Readonly<Record<string, unknown>>[]>>"
          ],
          "reference": "import { groupByKey } from \"#fune/collections.group-by-key@^1\";",
          "url": "http://www.functionalweave.com/functions/collections.group-by-key"
        }
      ],
      "gaps": []
    },
    {
      "key": "date-arithmetic",
      "title": "Calendar arithmetic",
      "purpose": "Move and measure ISO dates exactly, with no date library and no clock reads.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.add-days",
          "tier": "core",
          "why": "Shifts a date by whole days, for due dates and windows.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Shift an ISO date by a whole number of days, forwards or backwards, with exact calendar arithmetic.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addDays"
          ],
          "signatures": [
            "export function addDays(iso: string, days: number): string"
          ],
          "reference": "import { addDays } from \"#fune/dates.add-days@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.add-days"
        },
        {
          "id": "dates.add-months",
          "tier": "core",
          "why": "Adds months clamping to month end, so 31 January plus a month is the end of February.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Add calendar months to an ISO date, clamping to the month end: 31 Jan + 1 month is 28 or 29 Feb.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addMonths"
          ],
          "signatures": [
            "export function addMonths(iso: string, months: number): string"
          ],
          "reference": "import { addMonths } from \"#fune/dates.add-months@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.add-months"
        },
        {
          "id": "dates.days-between",
          "tier": "core",
          "why": "Signed calendar days between two dates, for ages of invoices and stays.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Calendar days from one ISO date to another, signed: negative when the second date is earlier.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "daysBetween"
          ],
          "signatures": [
            "export function daysBetween(startIso: string, endIso: string): number"
          ],
          "reference": "import { daysBetween } from \"#fune/dates.days-between@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.days-between"
        },
        {
          "id": "dates.month-boundaries",
          "tier": "core",
          "why": "First and last day of a date's month, for monthly periods and statements.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The first day, last day and length of the calendar month containing a date.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "monthBoundaries"
          ],
          "signatures": [
            "export function monthBoundaries(iso: string): MonthBoundaries"
          ],
          "reference": "import { monthBoundaries } from \"#fune/dates.month-boundaries@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.month-boundaries"
        },
        {
          "id": "dates.day-of-week",
          "tier": "core",
          "why": "ISO weekday of a date, for weekend rules and opening hours.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The ISO 8601 day of the week for a date: 1 is Monday through 7 is Sunday.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dayOfWeek"
          ],
          "signatures": [
            "export function dayOfWeek(iso: string): number"
          ],
          "reference": "import { dayOfWeek } from \"#fune/dates.day-of-week@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.day-of-week"
        },
        {
          "id": "dates.is-leap-year",
          "tier": "optional",
          "why": "Whether a year is a leap year, for 365/366-day calculations.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Whether a year is a Gregorian leap year: every 4th, except centuries, except every 400th.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isLeapYear"
          ],
          "signatures": [
            "export function isLeapYear(year: number): boolean"
          ],
          "reference": "import { isLeapYear } from \"#fune/dates.is-leap-year@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.is-leap-year"
        },
        {
          "id": "dates.overlap",
          "tier": "optional",
          "why": "Days two date ranges share, for prorating and clash checks.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Days two date ranges have in common, with the end of each range stated as inclusive or exclusive.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "overlapDays"
          ],
          "signatures": [
            "export function overlapDays(aStart: string, aEnd: string, bStart: string, bEnd: string, endInclusive: boolean): number"
          ],
          "reference": "import { overlapDays } from \"#fune/dates.overlap@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.overlap"
        },
        {
          "id": "dates.age",
          "tier": "optional",
          "why": "Age in whole years on a date, handling 29 February birthdays.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Age in whole years on a given date, with 29 February birthdays ageing on 1 March in other years.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "age"
          ],
          "signatures": [
            "export function age(birthDate: string, onDate: string): number"
          ],
          "reference": "import { age } from \"#fune/dates.age@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.age"
        }
      ],
      "gaps": []
    },
    {
      "key": "reporting-periods",
      "title": "Periods and reporting dates",
      "purpose": "Put dates into the periods reports are grouped by.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.quarter",
          "tier": "core",
          "why": "Calendar and fiscal quarter of a date for any year-start month.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Calendar quarter and fiscal quarter of a date, with the fiscal year and quarter date ranges, for any start month.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "quarter"
          ],
          "signatures": [
            "export function quarter(iso: string, fiscalYearStartMonth: number): Quarter"
          ],
          "reference": "import { quarter } from \"#fune/dates.quarter@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.quarter"
        },
        {
          "id": "dates.week-number",
          "tier": "optional",
          "why": "ISO week number and week-year, for weekly reports.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "ISO 8601 week number and week-numbering year of a date: weeks start Monday, week 1 holds the first Thursday.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "weekNumber"
          ],
          "signatures": [
            "export function weekNumber(iso: string): IsoWeek"
          ],
          "reference": "import { weekNumber } from \"#fune/dates.week-number@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.week-number"
        },
        {
          "id": "dates.day-count-fraction",
          "tier": "optional",
          "why": "Year fraction by ACT/365F, ACT/360, 30/360 or ACT/ACT, for interest periods.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Year fraction between two dates by ACT/365F, ACT/360, 30/360, 30E/360 or ACT/ACT ISDA, as an exact fraction.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dayCountFraction"
          ],
          "signatures": [
            "export function dayCountFraction(startIso: string, endIso: string, convention: DayCountConvention): YearFraction"
          ],
          "reference": "import { dayCountFraction } from \"#fune/dates.day-count-fraction@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.day-count-fraction"
        }
      ],
      "gaps": []
    },
    {
      "key": "working-days",
      "title": "Working days and holidays",
      "purpose": "Skip weekends and public holidays when counting or adding days.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.bank-holidays",
          "tier": "core",
          "why": "UK bank holidays for a region and year, as gov.uk publishes them.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.1",
          "summary": "UK bank and public holidays for a region and year, as published by gov.uk, including one-off holidays.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "bankHolidays"
          ],
          "signatures": [
            "export function bankHolidays(region: UkRegion, year: number): readonly BankHoliday[]"
          ],
          "reference": "import { bankHolidays } from \"#fune/dates.bank-holidays@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.bank-holidays"
        },
        {
          "id": "dates.add-business-days",
          "tier": "core",
          "why": "Moves a date by working days, skipping weekends and a holiday list.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Move a date forward or back by working days, skipping weekends and a caller-supplied holiday list.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addBusinessDays"
          ],
          "signatures": [
            "export function addBusinessDays(iso: string, days: number, holidays: readonly string[]): string"
          ],
          "reference": "import { addBusinessDays } from \"#fune/dates.add-business-days@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.add-business-days"
        },
        {
          "id": "dates.business-days-between",
          "tier": "core",
          "why": "Working days between two dates, for SLAs and notice periods.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Working days between two dates, excluding weekends and a caller-supplied holiday list.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "businessDaysBetween"
          ],
          "signatures": [
            "export function businessDaysBetween(startIso: string, endIso: string, holidays: readonly string[]): number"
          ],
          "reference": "import { businessDaysBetween } from \"#fune/dates.business-days-between@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.business-days-between"
        }
      ],
      "gaps": []
    },
    {
      "key": "repeating-dates",
      "title": "Repeating schedules",
      "purpose": "Generate the dates of something that repeats.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.recurrence",
          "tier": "optional",
          "why": "The next n dates of a rule: monthly on the 31st, last Friday, every two weeks.",
          "section": "repeating-dates",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The next n dates of a repeating rule: monthly on a day, last weekday of the month, every k days or weeks, yearly.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "recurrence"
          ],
          "signatures": [
            "export function recurrence(rule: RecurrenceRule, startIso: string, count: number): readonly string[]"
          ],
          "reference": "import { recurrence } from \"#fune/dates.recurrence@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.recurrence"
        }
      ],
      "gaps": []
    },
    {
      "key": "times",
      "title": "Times and durations",
      "purpose": "Work with times of day, durations and timestamps.",
      "from": "dates-basics",
      "members": [
        {
          "id": "time.duration",
          "tier": "optional",
          "why": "Parses and adds durations like 1h30m in whole minutes, and formats the total.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Parse durations written as 1h30m, 90m or 01:30, add them up in whole minutes, and format the total.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "duration"
          ],
          "signatures": [
            "export function duration(parts: readonly string[]): Duration"
          ],
          "reference": "import { duration } from \"#fune/time.duration@^1\";",
          "url": "http://www.functionalweave.com/functions/time.duration"
        },
        {
          "id": "time.minutes-between",
          "tier": "optional",
          "why": "Minutes between two local date-times, across midnight.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Minutes from one local date and time to another, correct across midnight; wall-clock time, no time zones.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "minutesBetween"
          ],
          "signatures": [
            "export function minutesBetween(startDate: string, startTime: string, endDate: string, endTime: string): number"
          ],
          "reference": "import { minutesBetween } from \"#fune/time.minutes-between@^1\";",
          "url": "http://www.functionalweave.com/functions/time.minutes-between"
        },
        {
          "id": "time.round-to-increment",
          "tier": "optional",
          "why": "Rounds minutes to a billing increment such as 6 or 15.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Round a duration in minutes to a billing increment such as 6 or 15 minutes, up, down or to nearest.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "roundToIncrement"
          ],
          "signatures": [
            "export function roundToIncrement(minutes: number, increment: number, mode: RoundingMode): number"
          ],
          "reference": "import { roundToIncrement } from \"#fune/time.round-to-increment@^1\";",
          "url": "http://www.functionalweave.com/functions/time.round-to-increment"
        },
        {
          "id": "time.iso-to-unix",
          "tier": "optional",
          "why": "RFC 3339 timestamps to Unix seconds.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "An RFC 3339 timestamp with a Z or offset, such as 2026-09-26T13:00:00+01:00, to Unix time in whole seconds.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isoToUnix"
          ],
          "signatures": [
            "export function isoToUnix(timestamp: string): number"
          ],
          "reference": "import { isoToUnix } from \"#fune/time.iso-to-unix@^1\";",
          "url": "http://www.functionalweave.com/functions/time.iso-to-unix"
        },
        {
          "id": "time.unix-to-iso",
          "tier": "optional",
          "why": "Unix seconds to an ISO 8601 UTC timestamp.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Unix time in seconds to an ISO 8601 UTC timestamp such as 2026-09-26T12:00:00Z, for years 0001 to 9999.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "unixToIso"
          ],
          "signatures": [
            "export function unixToIso(seconds: number): string"
          ],
          "reference": "import { unixToIso } from \"#fune/time.unix-to-iso@^1\";",
          "url": "http://www.functionalweave.com/functions/time.unix-to-iso"
        },
        {
          "id": "time.countdown",
          "tier": "optional",
          "why": "Seconds left until a moment and a 4:05 style timer text.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Seconds left until a Unix time, whether it has passed, and a timer text like 4:05, for tokens or retry waits.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "countdown"
          ],
          "signatures": [
            "export function countdown(until: number, now: number): Countdown"
          ],
          "reference": "import { countdown } from \"#fune/time.countdown@^1\";",
          "url": "http://www.functionalweave.com/functions/time.countdown"
        }
      ],
      "gaps": []
    }
  ],
  "gaps": [
    {
      "key": "rendering",
      "text": "Drawing: caps returns numbers and SVG path strings; putting them in SVG, canvas or a chart component is yours.",
      "section": null,
      "from": "dashboards"
    },
    {
      "key": "interactivity",
      "text": "Tooltips, zoom, pan, brushing and animation.",
      "section": null,
      "from": "dashboards"
    },
    {
      "key": "data-queries",
      "text": "Querying and aggregating the data at source (SQL, a metrics store); monitor.rollup covers time buckets in memory.",
      "section": null,
      "from": "dashboards"
    },
    {
      "key": "pie-donut-chart",
      "text": "A complete pie or donut chart: charts.shape gives pieAngles and arcPath, assembling legend and labels is yours.",
      "section": null,
      "from": "dashboards"
    },
    {
      "key": "time-zones",
      "text": "Time axes in local time zones: charts work in UTC dates and times.",
      "section": null,
      "from": "dashboards"
    },
    {
      "key": "locale-formatting",
      "text": "Showing dates in a user's language and format (\"3 octobre 2026\"): caps returns ISO dates, not localised text.",
      "section": null,
      "from": "dates-basics"
    },
    {
      "key": "non-uk-holidays",
      "text": "Public holidays outside the UK: dates.bank-holidays is UK only; pass another country's list to the working-day functions yourself.",
      "section": null,
      "from": "dates-basics"
    }
  ],
  "counts": {
    "members": 47,
    "core": 16,
    "optional": 31,
    "unreviewed": 0,
    "deprecated": [],
    "unavailable": [],
    "missing": [],
    "gaps": 7
  },
  "warnings": [],
  "url": "http://www.functionalweave.com/suites/dashboards",
  "owner": null
}
