SankeyChart
A flow from one set of nodes to another, a band per row.
Flow between two columns
source, target and value name the three columns a flow needs, so the data is one row per band. The nodes are the de-duplicated union of the source and target columns: a node that a row sends flow to and another row sends flow from sits between the two, which is how a multi-stage flow is drawn. select carries the row behind the band.
Vertical flow
orient="vertical" turns the flow downwards, which suits a tall card or a graph with few stages. format prints every value the chart shows, on the node labels and in the tooltip alike. nodeAlign decides where a node with nothing leaving it sits: justify pushes it to the far end, left and right pin it to the end they name.
Flow runs one way
A sankey lays its nodes out in columns, which only exists if the flow never comes back on itself. A row whose target already flows into its source — a node linked to itself included — has no column to go in, so it is dropped with a warning in development. Aggregate the data into stages before plotting it.
API Reference
Show types
import type { ComputedRef } from 'vue'
import type { ECharts } from 'echarts/core'
import type { TimeGrain } from './format'
export type ChartDir = 'ltr' | 'rtl'
/** What an echarts-backed chart hands back through a template ref. */
export type ChartExposed = {
/** The echarts instance, once the plot has a size to initialise into. */
chart: ComputedRef<ECharts | undefined>
}
/** Deep-merged into the generated echarts option as a last-resort escape hatch. */
export type EchartOptionsOverride = Record<string, any>
export type ChartPaletteName = 'sequential' | 'categorical' | 'diverging'
/**
* Which ramp series colors come from, or an explicit list of colors to cycle.
* The named ramps read `--chart-*` from CSS, so they follow the app's theme.
*/
export type ChartPalette = ChartPaletteName | string[]
export type ChartXAxisConfig = {
key: string
/**
* Inferred: `'time'` when every value in `key` is a `Date` or ISO date
* string, `'category'` otherwise. Set it to override — e.g. `'category'` to
* line dates up as evenly spaced buckets rather than on a real timeline.
*
* `'value'` reads the column as a quantity: a point sits at its own number,
* so a row at 1 and a row at 100 stand a hundred apart rather than in
* neighbouring slots. It is only ever asked for, never inferred — a category
* column often holds numbers, and re-spacing those would redraw a chart
* nobody changed. It is ignored on a horizontal bar chart.
*/
type?: 'category' | 'time' | 'value'
/** Label granularity on a time axis. Inferred from the spacing of the data. */
timeGrain?: TimeGrain
title?: string
echartOptions?: EchartOptionsOverride
}
export type ChartYAxisConfig = {
title?: string
min?: number
max?: number
echartOptions?: EchartOptionsOverride
}
/**
* What a series draws as. Every cartesian chart can hold every mark, so a bar
* chart with one line series and an area chart are the same option with a
* different default.
*/
export type ChartMark = 'bar' | 'line' | 'area'
export type AxisChartSeriesConfig = {
/** Key in each data row that holds this series' value, and its identity. */
name: string
/** Display name. Falls back to the formatted `name`. */
label?: string
color?: string
/** Mark this series draws as. Defaults to the chart's own mark. */
type?: ChartMark
/**
* Which value axis this series is measured against. `'y2'` gives a series in
* a different unit or magnitude its own scale, opposite the primary. Ignored
* on a horizontal bar chart, which has no second value axis.
*/
axis?: 'y' | 'y2'
showDataLabels?: boolean
/**
* Groups series into separate stacks. Only read when `stacked` is on, and
* only by the marks that stack — a line never does.
*/
stackName?: string
/** Dash pattern of the line itself. Defaults to a solid stroke. */
lineType?: 'solid' | 'dashed' | 'dotted'
/** Stroke width in px. Defaults to 2. */
lineWidth?: number
/**
* Marks every datapoint with a dot. Off by default — a clean line reads
* better, and the dot for the hovered point appears anyway.
*/
showDataPoints?: boolean
/** Rounds the corners of the line instead of drawing straight segments. */
smooth?: boolean
/** Overrides the chart-level `fillOpacity`. Read by an area series. */
fillOpacity?: number
echartOptions?: EchartOptionsOverride
}
/**
* A rule drawn across the plot at a fixed position: a target, a threshold, a
* budget, or the date something changed. An annotation rather than a series —
* it has no legend entry, cannot be switched off, and is never in the tooltip.
*/
export type ReferenceLine = {
/**
* Where the line sits: a number on a value axis, or whatever the category
* column holds on the category axis. A value outside the range the plot
* covers is not drawn; the scale follows the data, not the annotation.
*/
value: number | string | Date
/**
* Which axis `value` is read against. `'y'` (the default) and `'y2'` draw a
* rule across the plot at a measured value; `'x'` draws one down it at a
* category. `'y2'` reads against the primary axis on a chart that draws only
* one, exactly as a series does.
*
* On a scatter both axes are value axes, so `'x'` is a number on the
* horizontal scale rather than a category, and `'y2'` names an axis a scatter
* does not have — it reads as `'y'`, with a dev-mode warning. An axis chart
* with `xAxis.type: 'value'` reads `'x'` the same way: a number on the scale.
*/
axis?: 'y' | 'y2' | 'x'
/** Printed at the far end of the line. Left out, the rule carries no text. */
label?: string
/** Defaults to the ink data labels are printed in, so it reads as an annotation. */
color?: string
/** Breaks the rule up, for a line that should not read as a hard boundary. */
dashed?: boolean
}
/** Everything a cartesian chart config carries whatever it draws. */
export type AxisChartBaseConfig = {
data: Record<string, any>[]
xAxis: ChartXAxisConfig
yAxis?: ChartYAxisConfig
/**
* The second value axis, drawn opposite the primary. Only read when a series
* sets `axis: 'y2'`, and never on a horizontal bar chart — two value axes
* along the top and bottom of the plot are unreadable.
*/
y2Axis?: ChartYAxisConfig
series: AxisChartSeriesConfig[]
/** Rules drawn over the plot at fixed positions. Not series: see `ReferenceLine`. */
referenceLines?: ReferenceLine[]
title?: string
subtitle?: string
/**
* Ramp series colors are drawn from. Defaults to `'sequential'`: one series
* gets a single mid-blue, more get evenly spaced stops running dark to light.
*/
palette?: ChartPalette
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
echartOptions?: EchartOptionsOverride
}
/**
* What every cartesian chart hands its option builder. Bar, line and area
* differ in one value — the mark their unmarked series draw as — so a combo
* chart is not a fourth shape, it is this one with a mixed series list.
*/
export type AxisChartConfig = AxisChartBaseConfig & {
/** Mark for series that name none: the chart component the caller picked. */
type: ChartMark
/**
* Series sum on top of each other rather than standing side by side. Read by
* the marks that stack, i.e. bars and areas; a line never stacks.
* `'normalized'` plots each value as its share of its stack — the 100%
* stacked reading — and pins the value axis that carries it to 0-100.
*/
stacked?: boolean | 'normalized'
/** Bars run left-to-right; the category axis moves to Y. Bars only. */
horizontal?: boolean
/**
* Bridges gaps left by null or non-numeric values. Off by default: a break in
* the line is how missing data should read. Line and area series only.
*/
connectNulls?: boolean
/**
* Alpha of the fill under an area series. Defaults to a faint wash that fades
* out towards the axis; areas that stack into a band default to solid.
*/
fillOpacity?: number
}
export type DonutChartConfig = {
data: Record<string, any>[]
/** Row key holding the slice name. */
categoryColumn: string
/** Row key holding the slice size. */
valueColumn: string
title?: string
subtitle?: string
/**
* How many slices the ring holds, "Others" included: past that it keeps the
* largest `maxSlices - 1` and sums the tail into "Others". A ring stops being
* readable long before the palette runs out, so it defaults to 9.
*/
maxSlices?: number
/**
* Ramp slice colors are drawn from. Defaults to `'categorical'`: slices are
* unrelated categories, not steps of one magnitude, so they read as separate
* hues rather than as a ramp.
*/
palette?: ChartPalette
/**
* Prints each slice's name and share next to the ring. Off by default: the
* legend carries the same information without the leader lines.
*/
showInlineLabels?: boolean
/** Caption under the total in the middle. Defaults to the value column name. */
centerLabel?: string
/** `'half'` draws the ring as a semicircle; only the geometry changes. */
variant?: DonutVariant
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
echartOptions?: EchartOptionsOverride
}
export type DonutVariant = 'full' | 'half'
/** One arc of the ring, after sorting, "Others" grouping and color assignment. */
export type DonutSlice = {
/** Identity used by echarts actions and the legend. Unique within the ring. */
name: string
/** The category value as it should read; not unique. */
label: string
value: number
/** Share of the *visible* total, so hiding a slice re-percentages the rest. */
percent: number
color: string
hidden: boolean
/** The row behind this slice, or every grouped row for the "Others" slice. */
rows: Record<string, any>[]
isOthers: boolean
}
export type DonutSliceEvent = {
/** The slice as it reads, i.e. the category value or "Others". */
name: string
value: number
percent: number
/** One row, or every grouped row when the "Others" slice was clicked. */
rows: Record<string, any>[]
}
export type FunnelChartConfig = {
/** One row per stage, in process order. Rows are drawn as they arrive. */
data: Record<string, any>[]
/** Row key holding the stage name. */
categoryColumn: string
/** Row key holding how many reached the stage. */
valueColumn: string
title?: string
subtitle?: string
/**
* Ramp the columns are colored from. Defaults to `'sequential'` reversed —
* palest at the top of the funnel, deepest at the end — so the color darkens
* as the population narrows.
*/
palette?: ChartPalette
/**
* Prints each stage's share of the first stage under its value. On by
* default: the conversion rate is what a funnel is read for.
*/
showPercentages?: boolean
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
}
/** One stage of the funnel, after coercion and percentage arithmetic. */
export type FunnelStage = {
index: number
label: string
value: number
/** Share of the first stage, i.e. the conversion rate to here. 0-100. */
percentOfFirst: number
/** Share of the preceding stage. 0-100; the first stage's is 100. */
percentOfPrevious: number
row: Record<string, any>
}
export type FunnelStageEvent = {
label: string
value: number
index: number
row: Record<string, any>
}
/**
* Which continuous ramp cells are colored from, or an explicit list of stops to
* interpolate between. Only the continuous ramps: a heatmap reads one measure
* across a scale, so the categorical palette has nothing to say here.
*/
export type HeatmapPalette = 'sequential' | 'diverging' | string[]
export type HeatmapChartConfig = {
/** One row per cell. Rows with no numeric value leave their cell undrawn. */
data: Record<string, any>[]
/** Row key holding the column a cell sits in. */
xColumn: string
/** Row key holding the row a cell sits in. */
yColumn: string
/** Row key holding the magnitude the cell is colored by. */
valueColumn: string
title?: string
subtitle?: string
/**
* Ramp cells are colored from. Defaults to `'sequential'`, which is what a
* magnitude reads as; `'diverging'` is for signed data, and centers the scale
* on zero unless `min`/`max` say otherwise.
*/
palette?: HeatmapPalette
/** Bottom of the color scale. Defaults to the smallest value in the data. */
min?: number
/** Top of the color scale. Defaults to the largest value in the data. */
max?: number
/**
* Prints each cell's value inside it. Labels that would collide with a
* neighbour are dropped, so a grid too fine to carry numbers shows none.
*/
showValues?: boolean
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
echartOptions?: EchartOptionsOverride
}
/** One drawn cell of the grid, after category indexing and color assignment. */
export type HeatmapCell = {
/** The x category as it reads. */
x: string
/** The y category as it reads. */
y: string
xIndex: number
yIndex: number
value: number
color: string
row: Record<string, any>
}
/** The grid as the plot, the tooltip and the ramp scale all read it. */
export type HeatmapMatrix = {
/** Columns, in the order the rows first mention them. */
xCategories: string[]
/** Rows, in the order the rows first mention them. Drawn top to bottom. */
yCategories: string[]
cells: HeatmapCell[]
/** Bottom of the color scale, config or data. */
min: number
/** Top of the color scale, config or data. */
max: number
/** The ramp the scale runs along, low end first. */
stops: string[]
}
export type HeatmapCellEvent = {
x: string
y: string
value: number
row: Record<string, any>
}
export type NumberCardConfig = {
title: string
/** Null renders the empty state; a KPI with no number is not a zero. */
value: number | null
prefix?: string
suffix?: string
/** Change against the comparison period. Sign drives the arrow. */
delta?: number | null
/** Unit printed after the delta, e.g. `'%'`. */
deltaSuffix?: string
/** What the delta is measured against, e.g. `'vs last month'`. */
deltaCaption?: string
/** Flips the delta colors, for metrics like churn or cost. */
negativeIsBetter?: boolean
/** Decimal places. Defaults to as many as the value carries, up to 2. */
precision?: number
/** Shortens the value, `12300` -> `12.3K`. */
compact?: boolean
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
/** A trend across the bottom of the card: shape only, no axes to read against. */
sparkline?: NumberCardSparkline
}
export type NumberCardSparkline = {
/** Oldest reading first. Gaps are skipped, not drawn as zero. */
data: (number | null | undefined)[]
/**
* `line` for a continuous reading, `bar` for one the reader counts in
* periods. Defaults to `line`.
*/
type?: NumberCardSparklineType
/** Overrides the sequential-palette blue the sparkline is drawn in. */
color?: string
}
export type NumberCardSparklineType = 'line' | 'bar'
/** Which way the flow runs: columns of nodes left to right, or rows top to bottom. */
export type SankeyOrient = 'horizontal' | 'vertical'
/**
* Where a node sits along the flow. `'justify'` pushes a node with no outgoing
* flow to the far end, `'left'` and `'right'` pin every node to the end it is
* named after.
*/
export type SankeyNodeAlign = 'left' | 'right' | 'justify'
export type SankeyChartConfig = {
/** One row per flow. Rows with no numeric value draw no band. */
data: Record<string, any>[]
/** Row key holding the node a flow leaves. */
sourceColumn: string
/** Row key holding the node a flow arrives at. */
targetColumn: string
/** Row key holding how much flows along the link. */
valueColumn: string
title?: string
subtitle?: string
orient?: SankeyOrient
nodeAlign?: SankeyNodeAlign
/**
* Ramp node colors are drawn from. Defaults to `'categorical'`: the nodes of
* a flow are unrelated categories, not steps of one magnitude.
*/
palette?: ChartPalette
echartOptions?: EchartOptionsOverride
}
/** One node of the flow, after de-duplication and color assignment. */
export type SankeyNode = {
/** The source or target value as it reads. Unique within the graph. */
name: string
/**
* What passes through the node: the larger of what arrives and what leaves,
* which is the side that decides how tall echarts draws it.
*/
value: number
color: string
}
/** One drawn link, i.e. one row of the data. */
export type SankeyLink = {
source: string
target: string
value: number
/** The source node's color, which is what the band is painted in. */
color: string
row: Record<string, any>
}
/** The flow as the plot, the labels and the tooltip all read it. */
export type SankeyGraph = {
/** Nodes in the order the rows first mention them, source before target. */
nodes: SankeyNode[]
links: SankeyLink[]
}
export type SankeyLinkEvent = {
source: string
target: string
value: number
row: Record<string, any>
}
export type ScatterChartConfig = {
/** One row per point. A row missing either coordinate draws nothing. */
data: Record<string, any>[]
/** Row key holding the horizontal measure. */
xColumn: string
/** Row key holding the vertical measure. */
yColumn: string
/** Row key holding the magnitude each point is sized by. */
sizeColumn?: string
/** Grouping column: one series per distinct value. */
seriesColumn?: string
/** Row key holding the point's own name, which heads its tooltip. */
labelColumn?: string
/** Prints the point's own name beside it. Needs `labelColumn` to have one. */
showDataLabels?: boolean
title?: string
subtitle?: string
/** Both axes are value axes: a scatter reads one measure against another. */
xAxis?: ChartYAxisConfig
yAxis?: ChartYAxisConfig
/**
* Rules drawn over the plot at fixed positions, quadrant dividers among them.
* Not series: see `ReferenceLine`.
*/
referenceLines?: ReferenceLine[]
/**
* Ramp series colors are drawn from. Defaults to `'categorical'`: the groups
* of a scatter are unrelated categories, not steps of one magnitude.
*/
palette?: ChartPalette
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
echartOptions?: EchartOptionsOverride
}
/** One drawn point, after coercion and size scaling. */
export type ScatterPoint = {
x: number
y: number
/** The magnitude behind the symbol. Null when the chart has no size column. */
size: number | null
/** Symbol diameter in px: the magnitude mapped into the readable range. */
symbolSize: number
/** The point's own name, when the config names a label column. */
label?: string
row: Record<string, any>
}
/** One group of points, i.e. one value of the grouping column. */
export type ScatterSeries = {
/** The grouping value as it reads, or the y column when nothing groups. Unique. */
name: string
label: string
color: string
points: ScatterPoint[]
}
export type ScatterPointEvent = {
seriesName: string
x: number
y: number
/** Null when the chart has no size column. */
size: number | null
label?: string
row: Record<string, any>
}
/**
* Which edge of the plot the value-axis title heads, i.e. the edge that axis is
* drawn on: the top for a column chart, the bottom for a row chart.
*/
export type PlotLabelPlacement = 'top' | 'bottom'
export type ChartLegendItem = {
/** Series name, i.e. the identity used by echarts actions. */
name: string
label: string
color: string
hidden: boolean
/** Muted note after the label, e.g. a donut slice's share of the total. */
hint?: string
}
export type ChartTooltipItem = {
name: string
label: string
color: string
value: number
formattedValue: string
/** Share of the total, printed after the value. Only part-to-whole charts set it. */
percent?: number
}
export type ChartDatapointEvent = {
seriesName: string
dataIndex: number
value: number
row: Record<string, any>
}
// ---------------------------------------------------------------------------
// Component props: the public surface every chart takes.
//
// These sit in `types.ts` rather than a `props.ts` of their own because
// studio reads `<Component>Props` out of the family folder's `types.ts` to
// build its block schemas, and matches the declaration by name — a re-export
// from another file does not satisfy it.
// ---------------------------------------------------------------------------
/** Formats a measured value wherever it is printed: axis labels, tooltip, readouts. */
export type ChartValueFormatter = (value: number) => string
/** A category axis carries whatever the column holds, so its formatter takes any. */
export type ChartCategoryFormatter = (value: any) => string
export type ChartBaseProps = {
/** Heads the card. Left out, the chart draws no header row at all. */
title?: string
/** A second line under the title, e.g. the period the numbers cover. */
subtitle?: string
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
/** Draws the placeholder in place of the plot, for data still on its way. */
loading?: boolean
/**
* Puts the chart in its error state and prints this message under it. A
* chart that fails to draw sets its own; this is for a failed request.
*/
error?: string | null
}
export type ChartXAxisOptions = {
/** Heads the axis, under its labels. Left out, the axis carries no name. */
title?: string
/**
* Inferred: `'time'` when every value in the `x` column is a `Date` or ISO
* date string, `'category'` otherwise.
*
* `'value'` reads the column as a quantity and places every point by its own
* number, the way a scatter reads its x. Ask for it — it is never inferred,
* because a category column that happens to hold numbers still reads as a
* list of categories. Ignored when `horizontal` is set.
*/
type?: 'category' | 'time' | 'value'
/** Label granularity on a time axis. Inferred from the spacing of the data. */
timeGrain?: TimeGrain
/** Prints each category label. Takes whatever the column holds. */
format?: ChartCategoryFormatter
/** Escape hatch: deep-merged into this axis' echarts option. */
echartOptions?: EchartOptionsOverride
}
export type ChartValueAxisOptions = {
/**
* Names what the axis measures. Drawn above the plot rather than turned
* sideways along it, so it reads with the chart title.
*/
title?: string
/** Bottom of the scale. Defaults to a round number under the data. */
min?: number
/** Top of the scale. Defaults to a round number over the data. */
max?: number
/** Prints each tick label, and every value this axis carries elsewhere. */
format?: ChartValueFormatter
/** Escape hatch: deep-merged into this axis' echarts option. */
echartOptions?: EchartOptionsOverride
}
/**
* Per-series look. Every key is optional: an unstyled series renders with
* defaults. One style covers every mark, so a series keeps its label and color
* when `type` changes, and the keys the mark it draws as does not read are
* ignored rather than dropped.
*/
export type SeriesStyle = {
/** Display name. The `seriesConfig` key stays the identity. */
label?: string
/** Takes this series out of the palette, e.g. to pin one to a brand color. */
color?: string
/**
* Mark this series draws as. Defaults to the mark of the chart component it
* sits in, so `BarChart` with one `'line'` series is a combo chart.
*/
type?: ChartMark
/**
* Which value axis this series is measured against. `'y2'` gives a series in
* another unit or magnitude its own scale, drawn opposite the primary.
* Defaults to `'y'`. Ignored on a horizontal bar chart, which has no second
* value axis, and on a chart where no series asks for `'y2'` the second axis
* is not drawn at all.
*
* Moving a series here never moves it in the chart: the series are drawn in
* `y` order whatever axis each one sits on, so a series keeps its color.
*/
axis?: 'y' | 'y2'
/** Prints this series' value beside each of its marks. */
showDataLabels?: boolean
/**
* Groups series into separate stacks. Only read when `stacked` is on, and
* only by the marks that stack: bars stack with bars, areas with areas.
*/
stackName?: string
/** Line and area series. */
lineType?: 'solid' | 'dashed' | 'dotted'
/** Line and area series. */
lineWidth?: number
/** Line and area series. */
showDataPoints?: boolean
/** Line and area series. */
smooth?: boolean
/** Overrides the chart-level `fillOpacity`. Area series only. */
fillOpacity?: number
/** Escape hatch: deep-merged into this series' echarts option. */
echartOptions?: EchartOptionsOverride
}
export type AxisChartProps = ChartBaseProps & {
/** The rows to plot. One row is one position on the category axis. */
data: Record<string, any>[]
/** Column holding the category or time each point sits at. */
x: string
/**
* Value column(s). A list reads wide data: one series per column, drawn and
* colored in the order given. `seriesConfig[key].axis` moves one of them to
* the second value axis without moving it in the list.
*/
y: string | string[]
/** Grouping column, i.e. long data. Use with a single `y`. */
series?: string
/**
* Caps how many series the `series` column produces. The rest are summed
* into a single "Others" series, keyed `OTHERS_KEY` so `seriesConfig` can
* style it. Uncapped by default, and ignored when `y` names the columns:
* those the caller chose one by one.
*/
maxSeries?: number
/** Keyed by series identity: a `y` column, or a value of the `series` column. */
seriesConfig?: Record<string, SeriesStyle>
/**
* Series the legend has switched off, by name. Bind it with
* `v-model:hiddenSeries` to drive the legend from the app, or to keep what a
* reader hid across a reload. Left unbound, the legend owns it.
*/
hiddenSeries?: string[]
/** The category axis: its title, how the `x` column reads, and label format. */
xAxis?: ChartXAxisOptions
/** The primary value axis: its title, its range, and how a value prints. */
yAxis?: ChartValueAxisOptions
/** The second value axis. Only drawn when a series sits on `axis: 'y2'`. */
y2Axis?: ChartValueAxisOptions
/** Ramp series colors are drawn from. Defaults to `'sequential'`. */
palette?: ChartPalette
/**
* Series sum on top of each other. Bar and area series; a line never stacks.
* `'normalized'` reads each value as its share of the stack it sits in
* instead of its own magnitude, and pins that value axis to 0-100.
*/
stacked?: boolean | 'normalized'
/** Bridges gaps left by nulls. Line and area series. */
connectNulls?: boolean
/** Chart-level fill alpha; `seriesConfig` overrides it per series. Area series. */
fillOpacity?: number
/**
* Targets, thresholds and other fixed marks drawn over the plot. They are
* annotations, not series: no legend entry, and no way to switch one off.
*/
referenceLines?: ReferenceLine[]
/** Escape hatch: deep-merged into the echarts option the props built. */
echartOptions?: EchartOptionsOverride
}
export type BarChartProps = AxisChartProps & {
/** Bars run left-to-right; the category axis moves to Y. Bars only. */
horizontal?: boolean
}
/** A line chart is an axis chart whose unmarked series draw as lines. */
export type LineChartProps = AxisChartProps
/** An area chart is a line chart whose unmarked series carry a fill. */
export type AreaChartProps = AxisChartProps
export type DonutChartProps = ChartBaseProps & {
/** The rows to plot. One row is one slice, before the "Others" grouping. */
data: Record<string, any>[]
/** Row key holding the slice name. */
category: string
/** Row key holding the slice size. */
value: string
/**
* How many slices the ring holds, "Others" included. Past that the ring keeps
* the largest `maxSlices - 1` and sums the tail into a single "Others" slice,
* named `OTHERS_KEY`. Defaults to 9 — a ring stops being readable long before
* the palette runs out.
*/
maxSlices?: number
/**
* Prints each slice's name and share beside the ring, and drops the readout
* in the middle. Off by default: the legend says the same without the
* leader lines.
*/
showInlineLabels?: boolean
/** Caption under the total in the middle. Defaults to the `value` key. */
centerLabel?: string
/** `'half'` draws the ring as a semicircle; only the geometry changes. */
variant?: DonutVariant
/** Prints every number the ring shows: the readout, the tooltip, the labels. */
format?: ChartValueFormatter
/** Defaults to `'categorical'`: slices are unrelated categories, not steps. */
palette?: ChartPalette
/** Escape hatch: deep-merged into the echarts option the props built. */
echartOptions?: EchartOptionsOverride
}
export type FunnelChartProps = ChartBaseProps & {
/** One row per stage, in process order. Rows are drawn as they arrive. */
data: Record<string, any>[]
/** Row key holding the stage name. */
category: string
/** Row key holding how many reached the stage. */
value: string
/** Prints each stage's share of the first stage. On by default. */
showPercentages?: boolean
/** Prints every number the funnel shows: the stage values and the tooltip. */
format?: ChartValueFormatter
/** Defaults to `'sequential'` reversed, so color darkens as the funnel narrows. */
palette?: ChartPalette
}
export type HeatmapChartProps = ChartBaseProps & {
/** One row per cell. */
data: Record<string, any>[]
/** Row key holding the column a cell sits in. */
x: string
/** Row key holding the row a cell sits in. */
y: string
/** Row key holding the magnitude the cell is colored by. */
value: string
/** Bottom of the color scale. Defaults to the smallest value in the data. */
min?: number
/** Top of the color scale. Defaults to the largest value in the data. */
max?: number
/**
* Prints each cell's value inside it. A label that would collide with its
* neighbour is dropped, so a grid too fine to carry numbers shows none.
*/
showValues?: boolean
/** Prints every number the grid shows: the cells, the scale ends, the tooltip. */
format?: ChartValueFormatter
/**
* Ramp cells are colored from. Defaults to `'sequential'`, which is what a
* magnitude reads as; `'diverging'` is for signed data and centers on zero.
*/
palette?: HeatmapPalette
/** Escape hatch: deep-merged into the echarts option the props built. */
echartOptions?: EchartOptionsOverride
}
export type SankeyChartProps = ChartBaseProps & {
/** One row per flow, i.e. one band from a source node to a target node. */
data: Record<string, any>[]
/** Row key holding the node a flow leaves. */
source: string
/** Row key holding the node a flow arrives at. */
target: string
/** Row key holding how much flows along the link. */
value: string
/** Defaults to `'horizontal'`: the flow runs left to right. */
orient?: SankeyOrient
/** Where a node sits along the flow. Defaults to `'justify'`. */
nodeAlign?: SankeyNodeAlign
/** Prints every number the flow shows, i.e. what a band or node carries. */
format?: ChartValueFormatter
/** Defaults to `'categorical'`: nodes are unrelated categories. */
palette?: ChartPalette
/** Escape hatch: deep-merged into the echarts option the props built. */
echartOptions?: EchartOptionsOverride
}
export type ScatterChartProps = ChartBaseProps & {
/** One row per point. */
data: Record<string, any>[]
/** Row key holding the horizontal measure. */
x: string
/** Row key holding the vertical measure. */
y: string
/** Row key holding the magnitude each point is sized by. */
size?: string
/** Grouping column: one series per distinct value. */
series?: string
/**
* Groups the legend has switched off, by name. Bind it with
* `v-model:hiddenSeries` to drive the legend from the app. Left unbound, the
* legend owns it.
*/
hiddenSeries?: string[]
/** Row key holding the point's own name, which heads its tooltip. */
label?: string
/**
* Prints the point's own name beside it, the way an axis series prints its
* value. `label` is what it prints, so a chart that names no label column has
* nothing to show and says so in a dev-mode warning. Names that would collide
* with a neighbour are dropped, so a dense cloud carries few.
*/
showDataLabels?: boolean
/** The horizontal scale. Both axes are value axes: a scatter has no categories. */
xAxis?: ChartValueAxisOptions
/** The vertical scale. */
yAxis?: ChartValueAxisOptions
/** Defaults to `'categorical'`: the groups are unrelated categories. */
palette?: ChartPalette
/**
* Targets, thresholds and quadrant dividers drawn over the plot. They are
* annotations, not series: no legend entry, and no way to switch one off.
* Both axes are measured here, so `axis: 'x'` takes a number too — a pair of
* lines, one per axis, is what divides a scatter into quadrants.
*/
referenceLines?: ReferenceLine[]
/**
* Prints every number the chart shows. `xAxis.format` and `yAxis.format`
* override it for their own axis; the size measure has no axis, so this is
* what prints it.
*/
format?: ChartValueFormatter
/** Escape hatch: deep-merged into the echarts option the props built. */
echartOptions?: EchartOptionsOverride
}
/** No `subtitle`: the card is one reading, and the caption row says what it compares against. */
export type NumberCardProps = Omit<ChartBaseProps, 'subtitle'> &
Pick<ChartCardProps, 'card'> & {
/** What the reading is, printed above the number. */
title: string
/** A string renders as given: the formatting props only apply to a number. */
value: number | string | null
/**
* Ink the reading is printed in, e.g. the color of the series it summarizes
* on a dashboard. One color for one mark, the way `SeriesStyle.color` names
* a series' own — it does not restyle the card, and the delta keeps the
* tone that says which way the number moved.
*/
color?: string
/** Printed before the number, e.g. a currency sign. */
prefix?: string
/** Printed after the number, e.g. a unit. */
suffix?: string
/** Change against the comparison period. Sign drives the arrow. */
delta?: number | null
/** Unit printed after the delta, e.g. `'%'`. */
deltaSuffix?: string
/** What the delta is measured against, e.g. `'vs last month'`. */
deltaCaption?: string
/** Flips the delta colors, for metrics like churn or cost. */
negativeIsBetter?: boolean
/** Decimal places. Defaults to as many as the value carries, up to 2. */
precision?: number
/** Shortens the value, `12300` -> `12.3K`. */
compact?: boolean
/** A trend across the bottom of the card: shape only, no axes to read against. */
sparkline?: NumberCardSparkline
}
// The chrome, i.e. everything around the plot. A chart an app draws itself
// composes these and reads as one of the family.
export type ChartCardProps = {
/**
* Draws the card surface: border, background, corner radius and padding. On
* by default. Set it to `false` for a chart the app has already placed inside
* a card of its own, so a bordered box does not nest in a bordered box.
*/
card?: boolean
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
}
export type ChartContainerProps = {
/** Heads the card. Left out, the container draws no header row at all. */
title?: string
/** A second line under the title, e.g. the period the numbers cover. */
subtitle?: string
/** Value-axis title, drawn above the plot instead of inside it. */
plotLabel?: string
/** Title of the second value axis, drawn over the edge that axis sits on. */
plotLabelSecondary?: string
/** Edge of the plot the value-axis titles head. Defaults to the top. */
plotLabelPlacement?: PlotLabelPlacement
/** Draws the placeholder in place of the plot, for data still on its way. */
loading?: boolean
/** Non-empty switches the container into its error state. */
error?: string | null
/** Draws the empty state: there is data, and it plots to nothing. */
empty?: boolean
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
}
export type ChartLegendProps = {
/** One entry per series, in the order they are drawn. */
items: ChartLegendItem[]
}
export type ChartTooltipProps = {
/**
* Draws the tooltip. It is measured before it is placed, so it flips at the
* viewport edge rather than running off it.
*/
open: boolean
/** Viewport x of the point the tooltip hangs off, i.e. the pointer. */
x: number
/** Viewport y of the point the tooltip hangs off, i.e. the pointer. */
y: number
/** Heads the tooltip, e.g. the category the readings below it belong to. */
label?: string
/** One row per reading, in the order they should be read. */
items: ChartTooltipItem[]
/** Forces layout direction; defaults to document.documentElement.dir */
dir?: ChartDir
}
// ---------------------------------------------------------------------------
// Component emits and slots. Declared here beside the props so a consumer can
// name a handler's payload or a slot's props — `(e: ChartDatapointEvent)` reads
// as the family's own type rather than as an inline literal nobody can import.
// ---------------------------------------------------------------------------
/**
* The three states, forwarded by every chart. A slot replaces the whole state
* rather than a line inside it, so an app reaching one corner of the chrome
* does not have to rebuild the rest of it.
*/
export type ChartStateSlots = {
/** Replaces the whole placeholder, e.g. with a skeleton of the app's own. */
loading?: () => unknown
/** Replaces the message, e.g. to put a retry button beside it. */
error?: (props: { error?: string | null }) => unknown
/** Replaces the "no data" line, e.g. with a hint about the filters. */
empty?: () => unknown
}
/** Controls at the top right of the card, e.g. a period Select or a Dropdown. */
export type ChartActionsSlot = {
actions?: () => unknown
}
export type AxisChartEmits = {
/**
* A mark was selected, by click or by Enter on the keyboard cursor. Carries
* the series it belongs to, its position along the category axis, and the
* row behind it.
*/
select: [event: ChartDatapointEvent]
}
export type AxisChartSlots = ChartActionsSlot &
ChartStateSlots & {
/**
* Replaces the tooltip body. `items` holds one entry per visible series at
* the hovered category, biggest first.
*/
tooltip?: (props: { label?: string; items: ChartTooltipItem[] }) => unknown
}
export type BarChartEmits = AxisChartEmits
export type BarChartSlots = AxisChartSlots
export type LineChartEmits = AxisChartEmits
export type LineChartSlots = AxisChartSlots
export type AreaChartEmits = AxisChartEmits
export type AreaChartSlots = AxisChartSlots
export type DonutChartEmits = {
/**
* A slice was selected, by click or by Enter on the keyboard cursor. The
* "Others" slice carries every row it grouped, so a caller can drill into
* the tail as well as into a named slice.
*/
select: [event: DonutSliceEvent]
}
export type DonutChartSlots = ChartActionsSlot &
ChartStateSlots & {
/**
* Replaces the readout in the middle of the ring. Reads the total, or the
* hovered slice while one is hovered.
*/
center?: (props: {
value: string
label: string
/** Only set while a slice is hovered. */
percent?: string
}) => unknown
/** Replaces the tooltip body. `items` holds the hovered slice alone. */
tooltip?: (props: { items: ChartTooltipItem[] }) => unknown
}
export type FunnelChartEmits = {
/**
* A stage was selected, by click or by Enter on the keyboard cursor. Carries
* its position in the funnel and the row behind it; the whole column is the
* hit area, not just the shape it draws.
*/
select: [event: FunnelStageEvent]
}
export type FunnelChartSlots = ChartActionsSlot &
ChartStateSlots & {
/**
* Replaces the tooltip body. `stage` carries the two conversion rates the
* default body prints under the value.
*/
tooltip?: (props: {
label?: string
items: ChartTooltipItem[]
stage?: FunnelStage
}) => unknown
}
export type HeatmapChartEmits = {
/**
* A cell was selected, by click or by Enter on the keyboard cursor. Carries
* both its categories and the row behind it.
*/
select: [event: HeatmapCellEvent]
}
export type HeatmapChartSlots = ChartActionsSlot &
ChartStateSlots & {
/** Replaces the tooltip body. `items` holds the hovered cell alone. */
tooltip?: (props: { label?: string; items: ChartTooltipItem[] }) => unknown
}
export type SankeyChartEmits = {
/**
* A band was selected, by click or by Enter on the keyboard cursor. Carries
* its two nodes and the row behind it. A node emits nothing: it stands for
* every row that passes through it, not one.
*/
select: [event: SankeyLinkEvent]
}
export type SankeyChartSlots = ChartActionsSlot &
ChartStateSlots & {
/** Replaces the tooltip body. `items` holds the hovered band or node alone. */
tooltip?: (props: { label?: string; items: ChartTooltipItem[] }) => unknown
}
export type ScatterChartEmits = {
/**
* A point was selected, by click or by Enter on the keyboard cursor. Carries
* both measures and the row behind it.
*/
select: [event: ScatterPointEvent]
}
export type ScatterChartSlots = ChartActionsSlot &
ChartStateSlots & {
/**
* Replaces the tooltip body. `items` holds the point's two measures, and
* its size when the chart draws one.
*/
tooltip?: (props: { label?: string; items: ChartTooltipItem[] }) => unknown
}
/** No tooltip slot: a card with no plot has nothing to hover. */
export type NumberCardSlots = ChartActionsSlot &
ChartStateSlots & {
/** Replaces `deltaCaption`, e.g. with a Dropdown that changes the period. */
caption?: (props: { caption?: string }) => unknown
}
export type ChartCardSlots = {
/** The card's contents. The card supplies the surface and clips them. */
default: () => unknown
}
export type ChartContainerSlots = ChartActionsSlot &
ChartStateSlots & {
/** The plot itself, drawn into a box the container sizes and states. */
default: () => unknown
/** The row under the plot, e.g. a `ChartLegend` or a ramp scale. */
legend?: () => unknown
}
export type ChartLegendEmits = {
/** An entry was pressed: the named series' visibility flipped. */
change: [name: string]
/** The highlighted series, or null when the highlight clears. */
highlight: [name: string | null]
}
export type ChartTooltipSlots = {
/** Replaces the whole tooltip body, headline row included. */
default: (props: { label?: string; items: ChartTooltipItem[] }) => unknown
}Heads the card. Left out, the chart draws no header row at all.
A second line under the title, e.g. the period the numbers cover.
Forces layout direction; defaults to document.documentElement.dir
Draws the placeholder in place of the plot, for data still on its way.
Puts the chart in its error state and prints this message under it. A chart that fails to draw sets its own; this is for a failed request.
One row per flow, i.e. one band from a source node to a target node.
Row key holding the node a flow leaves.
Row key holding the node a flow arrives at.
Row key holding how much flows along the link.
Defaults to `'horizontal'`: the flow runs left to right.
Where a node sits along the flow. Defaults to `'justify'`.
Prints every number the flow shows, i.e. what a band or node carries.
Defaults to `'categorical'`: nodes are unrelated categories.
Escape hatch: deep-merged into the echarts option the props built.
| Slot | Payload |
|---|---|
loading | — Replaces the whole placeholder, e.g. with a skeleton of the app's own. |
error | { error?: string | null | undefined; } Replaces the message, e.g. to put a retry button beside it. |
empty | — Replaces the "no data" line, e.g. with a hint about the filters. |
actions | — |
tooltip | { label?: string | undefined; items: ChartTooltipItem[]; } Replaces the tooltip body. `items` holds the hovered band or node alone. |
Replaces the whole placeholder, e.g. with a skeleton of the app's own.
Replaces the message, e.g. to put a retry button beside it.
Replaces the "no data" line, e.g. with a hint about the filters.
Replaces the tooltip body. `items` holds the hovered band or node alone.
| Event | Payload |
|---|---|
select | [event: SankeyLinkEvent] A band was selected, by click or by Enter on the keyboard cursor. Carries its two nodes and the row behind it. A node emits nothing: it stands for every row that passes through it, not one. |
A band was selected, by click or by Enter on the keyboard cursor. Carries its two nodes and the row behind it. A node emits nothing: it stands for every row that passes through it, not one.