GrChartBar
Overview
When to take it
- how much every category has — orders by region, errors by service, revenue by product: columns are compared by eye more precisely than in any other type;
- two or three metrics per category side by side — the series stand as a group, and
groupPaddingseparates the groups; - the composition of every category —
stackedfolds the series into one column; - the shares inside a category —
stacked: '100%'normalises the column to one, and the proportions are compared rather than the quantities; - long names of categories —
orientation: 'horizontal'lays the bars sideways, and a label is read as a line rather than as a slanted tail; - there are few categories — the vertical layout holds fifteen or twenty, beyond that the labels of the axis run into one another; the horizontal one counts height rather than width and calmly takes thirty or forty rows.
When to take something else
| Need | Take |
|---|---|
| Show the course of a value over time | GrChartLine |
| Show the whole and the contribution of its parts over time | GrChartArea |
| Show the composition of one whole | GrChartPie |
| Compare the shape of a profile across several axes | GrChartRadar |
| Compare the rows of a table with one another | GrDataTable + GrSparkline |
Hundreds of categories are not a chart but a list: the room runs out before the reader’s patience does (in the vertical layout it is the width, in the horizontal one the height of the page). Such data is shown as a table with sorting, and the top of it with columns.
Examples 4 each with a live demo and its code
Plan against fact, side by side
Grouped bars answer «how much each»: both start at zero and the difference is read by height — stacking them would make plan plus fact, a number that means nothing.
group-paddingseparates the regions,bar-radiusrounds the far end only, andpoint-clickopens the region in the panel below.Q3 sales: plan against factThousands of euros by region · click a region Chart data X Plan Fact North 120 132 South 95 88 East 80 84 West 110 97 Central 70 76 NorthPlan €120kFact €132k+10.0%What every week is made of
stackedmakes the height of a column the week’s total and its segments the channels — the shift from email to chat is visible without a second chart. A new team is fetched again, andloadingkeeps the frame instead of showing the old picture;show-grid="y"keeps only the lines that are read.Support tickets per weekChat grows while email and phone shrinkChart data X Email Chat Phone W33 212 140 64 W34 198 156 58 W35 230 171 61 W36 245 168 55 W37 221 190 52 W38 205 214 49 W39 190 236 44 W40 184 251 41 Long names lie on their side
orientation="horizontal"gives «Delivered after the promised date» a whole line instead of a tilted, clipped tail, and keeps the ranking top to bottom. In a ranking every bar matters, sodim-inactiveis off; one series needs no legend, and thetooltipslot adds the share of all refunds.Refund reasons, September188 refunds, most frequent first Chart data X Refunds Ordered by mistake 64 Arrived damaged in transit 41 Customer changed their mind 37 Colour differs from the photo 22 Missing parts or screws 15 Delivered after the promised date 9 Money and units on two axes
Thousands of euros and thousands of orders cannot share a scale.
dual-axiswithy-domain-rightandy-tick-format-rightputs orders on the right; it is deliberate because two axes can make any pair look correlated, so both domains start at zero. Vertical bars only — a horizontal layout has no room for a second value axis.Revenue and ordersOrders grow faster than revenue: the average basket is getting smaller Chart data X Revenue, € thousands (left axis) Orders (right axis) Apr 312 1,840 May 338 1,910 Jun 351 1,875 Jul 372 2,050 Aug 386 2,190 Sep 399 2,240
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
xScale | GrChartScaleKind | undefined | undefined | Type of the X axis. Not set — inferred from the data; for bars that is usually categories. |
stacked | boolean | "100%" | undefined | false | `true` — a stack, `'100%'` — a stack with the column normalized to one. |
height | number | undefined | undefined | The canvas height in pixels; 256 by default. The data, the loading state and the empty state all take up the same height. |
width | number | undefined | 640 | Declared width: the first render goes from it, after that the width is measured. |
yDomain | readonly [number | null, number | null] | undefined | undefined | The bounds of the value axis `[min, max]`; `null` in place of a bound — it is taken from the data. |
references | readonly GrChartReference[] | undefined | undefined | Reference lines and bands: a threshold, a plan, an acceptable corridor. Not a series and never counted as one: it does not get into the legend, does not spend a palette index, does not join the stack and goes into the hidden table as a note, not as a data row. |
includeReferencesInDomain | boolean | undefined | false | Include the references in the axis domain. Off by default, and deliberately so: a threshold of `1.0` with data around `0.03` would stretch the axis so far that the data itself would collapse into a line. |
dualAxis | boolean | undefined | false | Show a second value axis on the right. Without it, series with `axis: 'right'` land on the left one. Turning it on is deliberate: two axes let you fit any two series to a visible correlation, and that must be a decision of the chart author, not a side effect of a field in the data. |
yDomainRight | readonly [number | null, number | null] | undefined | undefined | The bounds of the right value axis with `dualAxis`; `null` in place of a bound — it is taken from the data. |
yTickFormatRight | ((value: number) => string) | undefined | undefined | The tick label of the right axis. Unset — a number formatted by the locale. |
valueFormatRight | GrChartNumberFormat | undefined | undefined | The value format of the right-axis series in the tooltip, the hidden table and announcements. |
xTickCount | number | undefined | undefined | The desired number of ticks on the X axis; 6 by default. For categories — the cap on the number of labels (unset — 12). Labels that do not fit the width are thinned out. |
yTickCount | number | undefined | 5 | The desired number of ticks on the value axis; 5 by default. The step stays "round", so there may be one tick fewer. |
xTickFormat | ChartTickFormat | undefined | undefined | The tick label of the X axis: `(value, kind) => string`. Unset — the category, or a date or number formatted by the locale. |
yTickFormat | ((value: number) => string) | undefined | undefined | The tick label of the value axis, for example `value => value + '%'`. The space for the axis is measured from the formatted label. |
valueFormat | GrChartNumberFormat | undefined | undefined | The value format in the tooltip, the hidden table and announcements: decimal places and separators. |
showGrid | "y" | "both" | "x" | "none" | undefined | undefined | Grid lines: `y` (the default) — along the value ticks, `x`, `both` or `none`. |
showLegend | boolean | "auto" | undefined | undefined | The series legend: `auto` (the default) — when there is more than one series; `true` and `false` — always and never. |
legendPosition | "bottom" | "top" | undefined | undefined | Where the legend sits: `bottom` (the default) or `top`. |
tooltip | boolean | undefined | undefined | The panel with the values under the cursor; `true` by default. `false` hides only the panel: the cursor, the active marks, `update:activeIndex` and `pointHover` keep working while the chart is interactive. |
activeIndexv-model | number | null | undefined | undefined | Cursor — `v-model:activeIndex`. Keeps a pair of charts in sync. |
loading | boolean | undefined | false | Loading: a skeleton of the same height stands in place of the plot area, and the space for the axes is already reserved. |
empty | boolean | undefined | undefined | The empty state, set explicitly. Unset — the chart is empty when there is nothing to draw. |
emptyText | string | undefined | undefined | The placeholder text of an empty chart. Unset — "No data" from the package translations. |
dataTable | "hidden" | "visible" | "off" | undefined | undefined | The data table: `hidden` (the default) — for the screen reader only, `visible` — under the chart, `off` — no table. |
interactive | boolean | undefined | true | `false` — the chart becomes a picture: no focus, no tooltip, no keyboard. |
size | "md" | "xs" | "sm" | "lg" | undefined | undefined | The type size of the axis and legend labels on the core control scale; by default — the size from `GrConfigProvider`. |
locale | string | undefined | undefined | The BCP-47 locale for numbers and dates. Unset — the language of the application translations, otherwise `en`. |
ariaLabel | string | undefined | undefined | The accessible name of the chart. Unset — a summary the component assembles: the chart type and the amount of data. |
ariaDescription | string | undefined | undefined | A description for the screen reader — the conclusion the chart is shown for; it adds to the summary rather than replacing it. |
dataTableMaxRows | number | "auto" | undefined | undefined | The row ceiling of the hidden data table. `'auto'` (the default) — as many rows as can be read; above the ceiling the table goes by an even sample and says so with a note, while point-by-point completeness stays with the keyboard. A number sets its own ceiling, `Infinity` removes it altogether, `dataTable: 'off'` removes the table entirely — which of these is needed is up to the application. |
orientation | ChartOrientation | undefined | undefined | Bar layout. `'horizontal'` — categories run top to bottom, values to the right. It is taken for the labels: a long category name in the vertical layout either runs into its neighbour or stands askew. Even after the turn the axes are named **after the data**: `yDomain`, `yTickFormat` and `showGrid: 'y'` refer to the value axis wherever it lies. There are deliberately no aliases — two props with one meaning are worse than one imprecise name. |
groupPadding | number | undefined | undefined | The share of the slot width that goes into the gap between series inside a category. |
barRadius | number | undefined | undefined | Rounding of the bar end farthest from the baseline, pixels. |
dimInactive | boolean | undefined | undefined | Dim the bars of inactive categories on hover. `false` — the active category is not highlighted at all, only the tooltip speaks of it. That makes sense where the chart stands next to a legend or a table and extra movement of color gets in the way of reading the neighbours. |
seriesrequired | readonly number[] | readonly GrChartSeries[] | — | Series or a bare row of numbers — then the ordinal index becomes the category. |
Slots
| Slot | Type | Description |
|---|---|---|
tooltip | { active: GrChartActivePoint; formatValue: (value: number | null) => string; } | Custom content for the tooltip panel instead of the standard one; the data of the active position comes in the slot props. |
legend | { series: readonly NormalizedSeries[]; toggle: (id: string) => void; } | A custom legend instead of the standard one. It is not drawn for an empty chart. |
empty | any | A custom placeholder for an empty chart instead of "No data"; it takes up the same `height`. |
header | any | A header above the chart, inside its root — above a legend placed on top. |
Events
| Event | Signature | Description |
|---|---|---|
update:hiddenSeries | (event: "update:hiddenSeries", value: string[]): void | — |
update:activeIndex | (event: "update:activeIndex", value: number | null): void | — |
pointClick | (event: "pointClick", value: GrChartActivePoint): void | — |
pointHover | (event: "pointHover", value: GrChartActivePoint | null): void | — |
legendToggle | (event: "legendToggle", value: { seriesId: string; hidden: boolean; }): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
element | HTMLElement | null | The root element — for measurements and scrolling in the consumer. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrChartBarConfigurableProps, GrChartSize, GrChartBarProps } from '@feugene/granularity-charts/components/GrChartBar'Filedist/types/components/GrChartBar/config.d.ts
const grChartBarConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartBar">;Filedist/types/components/GrChartBar/defaults.d.ts
interface GrChartBarConfigurableProps {
size: GrChartSize;
orientation: ChartOrientation;
height: number;
barRadius: number;
groupPadding: number;
dimInactive: boolean;
showGrid: 'both' | 'x' | 'y' | 'none';
showLegend: boolean | 'auto';
legendPosition: 'top' | 'bottom';
tooltip: boolean;
dataTable: 'hidden' | 'visible' | 'off';
dataTableMaxRows: number | 'auto';
}Filedist/types/components/GrChartFrame/chartFrameStyles.d.ts
type GrChartSize = GrComponentSize;Filedist/types/components/GrChartBar/safelist.d.ts
const grChartBarSafelist: string[];Filedist/types/components/GrChartBar/GrChartBar.vue.d.ts
interface GrChartBarProps {
series: readonly GrChartSeries[] | readonly number[];
xScale?: GrChartScaleKind;
orientation?: ChartOrientation;
stacked?: boolean | '100%';
groupPadding?: number;
barRadius?: number;
dimInactive?: boolean;
height?: number;
width?: number;
yDomain?: readonly [number | null, number | null];
references?: readonly GrChartReference[];
includeReferencesInDomain?: boolean;
dualAxis?: boolean;
yDomainRight?: readonly [number | null, number | null];
yTickFormatRight?: (value: number) => string;interface GrChartBarEmits {
(e: 'update:hiddenSeries', value: string[]): void;
(e: 'update:activeIndex', value: number | null): void;
(e: 'pointClick', value: GrChartActivePoint): void;
(e: 'pointHover', value: GrChartActivePoint | null): void;
(e: 'legendToggle', value: {
seriesId: string;
hidden: boolean;
}): void;
}Accessibility
APG pattern
Серия читается той же парой стрелок; активная категория подсвечивается приглушением соседей
Full contract of the package: docs/keyboard.md ↗
Usage from the library prose
Horizontal: bars sideways, the axes named by the data
<GrChartBar :series="series" orientation="horizontal" aria-label="Revenue by product" />Only the layout changes: the stack, the group, '100%', the references, the legend, the tooltip and
the hidden table work in exactly the same way.
The axes are named by the data rather than by the screen. yDomain, yTickFormat and
yTickCount are always the value axis, wherever it lies; xTickFormat is always the labels of the
categories. With showGrid: 'y' the value lines in the horizontal layout run vertically: the grid
follows its own axis rather than the direction on the screen.
The alternative — introducing paired horizontalTickFormat and the rest — would give two props with
one meaning and the question “and which of them is in charge right now” on every page. One imprecise
name is cheaper than two precise ones.
The keyboard follows the eyes: ↓/↑ walk the categories from top to bottom, and ←/→ switch
the series being read inside a category.
A category name reads as a line. The label column grows to fit the longest formatted name — up
to 40% of the chart width, but no narrower than 96px. An ellipsis appears only beyond this ceiling,
and the full name stays in the label’s <title>, in the tooltip and in the hidden table.
What the horizontal layout cannot do
dualAxis with orientation: 'horizontal' is switched off: the second value axis would become the
top one, and the layout has no top margin. The prop does not fail — it simply does not apply, all of
the series sit on one axis, and a warning is printed in dev. If two scales are needed, take the
vertical layout.
Zero on the axis is not switchable
There is nothing to switch off in includeZero for columns: a bar is drawn from the baseline, and if
the base is not zero, the length of the bar stops being the quantity. In a line chart there is a
choice — there the course is read rather than the length.
Dimming instead of hiding
dimInactive dims the inactive series under the cursor instead of removing them. A removed series
would shift its neighbours and change the width of the bars — the drawing would “move” from a single
movement of the mouse.
A threshold is drawn as a reference, not as a series
A plan, a norm and the limit of the acceptable are the references prop rather than a series made of a constant: a
constant series would enter the legend, would stretch the axis and would travel into the table as data. The details —
../model.md, the section “A reference is not a series”.
A stack and a group are different questions
A group answers “how much each has”, a stack “what each consists of”. Mixing them in one chart is not allowed: the reader does not know whether to compare the heights of the columns or the heights of the segments. If both answers are needed, that is two charts.
Two axes are a deliberate decision
Series of different orders (money and units) are readable on one chart only with two axes, and two
axes let any pair be fitted to a visible correlation. That is why the axis: 'right' of a series
does not work until dualAxis is switched on. The invariants —
../model.md, the section “The second value axis”.
Props in this example