@twoux/ui 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +61 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/NOTICE.md +64 -0
- package/README.md +211 -2
- package/SHADCN-DIFF.md +228 -0
- package/agent/AGENTS.md +358 -0
- package/agent/docs-md/accessibility.md +49 -0
- package/agent/docs-md/action-catalogue.md +81 -0
- package/agent/docs-md/actions.md +74 -0
- package/agent/docs-md/agent-activity-log.md +70 -0
- package/agent/docs-md/agent-permissions.md +82 -0
- package/agent/docs-md/agent-run.md +90 -0
- package/agent/docs-md/agents-on-behalf.md +74 -0
- package/agent/docs-md/ai-column.md +85 -0
- package/agent/docs-md/ai-origin-label.md +70 -0
- package/agent/docs-md/announcing-features.md +73 -0
- package/agent/docs-md/answer-actions.md +81 -0
- package/agent/docs-md/app-shell.md +114 -0
- package/agent/docs-md/artifact-card.md +89 -0
- package/agent/docs-md/ask-on-screen.md +72 -0
- package/agent/docs-md/assistant-page.md +100 -0
- package/agent/docs-md/assistant-panel.md +142 -0
- package/agent/docs-md/avatar.md +82 -0
- package/agent/docs-md/banner.md +106 -0
- package/agent/docs-md/board.md +163 -0
- package/agent/docs-md/breadcrumb.md +89 -0
- package/agent/docs-md/breakdown-table.md +97 -0
- package/agent/docs-md/bulk-action-bar.md +112 -0
- package/agent/docs-md/button.md +293 -0
- package/agent/docs-md/card.md +115 -0
- package/agent/docs-md/change-list.md +99 -0
- package/agent/docs-md/change-request.md +166 -0
- package/agent/docs-md/chart-area.md +87 -0
- package/agent/docs-md/chart-bar.md +100 -0
- package/agent/docs-md/chart-box-plot.md +88 -0
- package/agent/docs-md/chart-bullet.md +89 -0
- package/agent/docs-md/chart-candlestick.md +112 -0
- package/agent/docs-md/chart-card-line.md +120 -0
- package/agent/docs-md/chart-donut.md +90 -0
- package/agent/docs-md/chart-drill-down.md +72 -0
- package/agent/docs-md/chart-funnel.md +71 -0
- package/agent/docs-md/chart-heatmap.md +76 -0
- package/agent/docs-md/chart-histogram.md +81 -0
- package/agent/docs-md/chart-map.md +165 -0
- package/agent/docs-md/chart-mixed.md +88 -0
- package/agent/docs-md/chart-price.md +92 -0
- package/agent/docs-md/chart-radial-bars.md +109 -0
- package/agent/docs-md/chart-radial-lines.md +111 -0
- package/agent/docs-md/chart-sankey.md +104 -0
- package/agent/docs-md/chart-scatter.md +87 -0
- package/agent/docs-md/chart-share-bar.md +89 -0
- package/agent/docs-md/chart-treemap.md +98 -0
- package/agent/docs-md/chart-waterfall.md +93 -0
- package/agent/docs-md/checkbox.md +131 -0
- package/agent/docs-md/choosing-selection-control.md +71 -0
- package/agent/docs-md/citations-sources.md +79 -0
- package/agent/docs-md/citing-sources.md +63 -0
- package/agent/docs-md/clarify-before-acting.md +69 -0
- package/agent/docs-md/clarifying-question.md +92 -0
- package/agent/docs-md/code-block.md +174 -0
- package/agent/docs-md/colours.md +87 -0
- package/agent/docs-md/columns-menu.md +140 -0
- package/agent/docs-md/combobox.md +161 -0
- package/agent/docs-md/command-palette.md +126 -0
- package/agent/docs-md/confidence-note.md +87 -0
- package/agent/docs-md/configurable-dashboard.md +76 -0
- package/agent/docs-md/confirmation-dialog.md +93 -0
- package/agent/docs-md/connected-tools.md +80 -0
- package/agent/docs-md/connector-picker.md +127 -0
- package/agent/docs-md/consistency.md +53 -0
- package/agent/docs-md/content-density.md +61 -0
- package/agent/docs-md/conversation-history.md +78 -0
- package/agent/docs-md/create-record.md +80 -0
- package/agent/docs-md/csv-import.md +88 -0
- package/agent/docs-md/dashboard-items.md +78 -0
- package/agent/docs-md/data-export.md +61 -0
- package/agent/docs-md/data-grid.md +137 -0
- package/agent/docs-md/data-vis-colours.md +76 -0
- package/agent/docs-md/data-visualisation.md +76 -0
- package/agent/docs-md/date-picker.md +178 -0
- package/agent/docs-md/delete-record.md +75 -0
- package/agent/docs-md/dense-table.md +495 -0
- package/agent/docs-md/design-tokens.md +53 -0
- package/agent/docs-md/disabled-read-only.md +75 -0
- package/agent/docs-md/drag-and-drop.md +74 -0
- package/agent/docs-md/dropdown-menu.md +137 -0
- package/agent/docs-md/edit-record.md +73 -0
- package/agent/docs-md/empty-loading-error.md +78 -0
- package/agent/docs-md/empty-state.md +128 -0
- package/agent/docs-md/error-messages.md +75 -0
- package/agent/docs-md/error-state.md +123 -0
- package/agent/docs-md/export-menu.md +86 -0
- package/agent/docs-md/feedback-prompt.md +88 -0
- package/agent/docs-md/field-mapping.md +78 -0
- package/agent/docs-md/file-drop.md +110 -0
- package/agent/docs-md/filter-bar.md +138 -0
- package/agent/docs-md/filter-panel.md +103 -0
- package/agent/docs-md/filtering-search.md +79 -0
- package/agent/docs-md/follow-ups-starters.md +79 -0
- package/agent/docs-md/form-validation.md +95 -0
- package/agent/docs-md/generated-columns.md +65 -0
- package/agent/docs-md/generated-drafts-files.md +71 -0
- package/agent/docs-md/grouped-list.md +89 -0
- package/agent/docs-md/guide-cue.md +170 -0
- package/agent/docs-md/help-panel.md +103 -0
- package/agent/docs-md/help-system.md +74 -0
- package/agent/docs-md/iconography.md +49 -0
- package/agent/docs-md/inbox.md +108 -0
- package/agent/docs-md/inline-editing-validation.md +80 -0
- package/agent/docs-md/inline-input-in-answer.md +70 -0
- package/agent/docs-md/items-palette.md +116 -0
- package/agent/docs-md/keyboard-model.md +47 -0
- package/agent/docs-md/keyboard-navigation.md +68 -0
- package/agent/docs-md/keyboard-shortcut-hint.md +87 -0
- package/agent/docs-md/keyboard-shortcut-sheet.md +90 -0
- package/agent/docs-md/labelling-ai-output.md +66 -0
- package/agent/docs-md/layout.md +47 -0
- package/agent/docs-md/list-table.md +254 -0
- package/agent/docs-md/loading-skeleton.md +91 -0
- package/agent/docs-md/managing-agents.md +71 -0
- package/agent/docs-md/message-composer.md +105 -0
- package/agent/docs-md/migrating.md +49 -0
- package/agent/docs-md/motion.md +93 -0
- package/agent/docs-md/navigation.md +76 -0
- package/agent/docs-md/notification-channels.md +65 -0
- package/agent/docs-md/notifications-decision.md +100 -0
- package/agent/docs-md/onboarding-step.md +114 -0
- package/agent/docs-md/order-book.md +108 -0
- package/agent/docs-md/page-header.md +96 -0
- package/agent/docs-md/pagination.md +88 -0
- package/agent/docs-md/pane-layout.md +105 -0
- package/agent/docs-md/period-comparison.md +66 -0
- package/agent/docs-md/plan-card.md +120 -0
- package/agent/docs-md/popover-rules.md +88 -0
- package/agent/docs-md/prompt-editor.md +90 -0
- package/agent/docs-md/propose-apply.md +65 -0
- package/agent/docs-md/provider-logo.md +97 -0
- package/agent/docs-md/radio.md +83 -0
- package/agent/docs-md/ranked-list.md +102 -0
- package/agent/docs-md/reasoning-progress.md +72 -0
- package/agent/docs-md/record-detail.md +71 -0
- package/agent/docs-md/regenerating-answers.md +70 -0
- package/agent/docs-md/run-status-bar.md +94 -0
- package/agent/docs-md/saved-tasks-schedules.md +63 -0
- package/agent/docs-md/saved-views.md +149 -0
- package/agent/docs-md/saving-views.md +68 -0
- package/agent/docs-md/scheduler.md +258 -0
- package/agent/docs-md/secondary-panels.md +72 -0
- package/agent/docs-md/segmented-control.md +88 -0
- package/agent/docs-md/selection-bulk-actions.md +70 -0
- package/agent/docs-md/separator.md +75 -0
- package/agent/docs-md/setup-checklist.md +92 -0
- package/agent/docs-md/shape-elevation.md +55 -0
- package/agent/docs-md/sheet-grid.md +211 -0
- package/agent/docs-md/side-panel.md +134 -0
- package/agent/docs-md/sign-in.md +114 -0
- package/agent/docs-md/spacing.md +55 -0
- package/agent/docs-md/split-button.md +100 -0
- package/agent/docs-md/stat-compare.md +82 -0
- package/agent/docs-md/static-dashboard.md +75 -0
- package/agent/docs-md/status-badge.md +140 -0
- package/agent/docs-md/step-card.md +75 -0
- package/agent/docs-md/stepper.md +92 -0
- package/agent/docs-md/suggested-changes.md +91 -0
- package/agent/docs-md/switch.md +78 -0
- package/agent/docs-md/table-settings.md +81 -0
- package/agent/docs-md/tabs.md +85 -0
- package/agent/docs-md/tag-input.md +108 -0
- package/agent/docs-md/tag.md +79 -0
- package/agent/docs-md/task-builder.md +87 -0
- package/agent/docs-md/team-instructions.md +72 -0
- package/agent/docs-md/templates-gallery.md +85 -0
- package/agent/docs-md/text-input.md +160 -0
- package/agent/docs-md/thinking-steps.md +89 -0
- package/agent/docs-md/timeline.md +189 -0
- package/agent/docs-md/timestamp.md +84 -0
- package/agent/docs-md/timestamps.md +70 -0
- package/agent/docs-md/toast.md +117 -0
- package/agent/docs-md/tooltip.md +90 -0
- package/agent/docs-md/typography.md +62 -0
- package/agent/docs-md/undo-destructive.md +66 -0
- package/agent/docs-md/unsaved-changes.md +71 -0
- package/agent/docs-md/usage-meter.md +80 -0
- package/agent/docs-md/user-feedback.md +71 -0
- package/agent/docs-md/voice-and-tone.md +60 -0
- package/agent/docs-md/why-choice.md +93 -0
- package/agent/docs-md/workspace-onboarding.md +74 -0
- package/agent/docs-md/writing.md +48 -0
- package/agent/llms-full.txt +18369 -0
- package/agent/llms.txt +198 -0
- package/agent/skills/twoux-audit/SKILL.md +49 -0
- package/agent/skills/twoux-build/SKILL.md +39 -0
- package/agent/skills/twoux-design/SKILL.md +54 -0
- package/agent/skills/twoux-write/SKILL.md +32 -0
- package/bin/check.mjs +123 -0
- package/bin/docs.mjs +24 -0
- package/bin/guard.mjs +27 -0
- package/bin/init.mjs +113 -0
- package/bin/knowledge.mjs +87 -0
- package/bin/mcp.mjs +116 -0
- package/bin/theme.mjs +168 -0
- package/bin/twoux.mjs +25 -0
- package/package.json +80 -3
- package/registry/jobs.json +1028 -0
- package/registry/palette.json +770 -0
- package/registry/rules.json +913 -0
- package/src/components/agent-review.tsx +364 -0
- package/src/components/ai-basics.tsx +212 -0
- package/src/components/ai-work.tsx +304 -0
- package/src/components/ai.tsx +227 -0
- package/src/components/alert-dialog.tsx +203 -0
- package/src/components/alert.tsx +86 -0
- package/src/components/avatar.tsx +114 -0
- package/src/components/badge.tsx +59 -0
- package/src/components/board.tsx +397 -0
- package/src/components/breadcrumb.tsx +114 -0
- package/src/components/button-group.tsx +90 -0
- package/src/components/button.tsx +109 -0
- package/src/components/calendar.tsx +247 -0
- package/src/components/card.tsx +102 -0
- package/src/components/chart-map.tsx +336 -0
- package/src/components/charts-more.tsx +1331 -0
- package/src/components/charts-radial.tsx +328 -0
- package/src/components/charts.tsx +390 -0
- package/src/components/checkbox.tsx +40 -0
- package/src/components/code.tsx +139 -0
- package/src/components/collapsible.tsx +37 -0
- package/src/components/combobox.tsx +339 -0
- package/src/components/command-extras.tsx +11 -0
- package/src/components/command.tsx +196 -0
- package/src/components/data-grid.tsx +213 -0
- package/src/components/data-table.tsx +334 -0
- package/src/components/data-views.tsx +740 -0
- package/src/components/date-picker.tsx +686 -0
- package/src/components/dense-table.tsx +207 -0
- package/src/components/dialog.tsx +162 -0
- package/src/components/dropdown-menu.tsx +264 -0
- package/src/components/empty.tsx +114 -0
- package/src/components/feedback-prompt.tsx +80 -0
- package/src/components/field.tsx +254 -0
- package/src/components/fields.tsx +112 -0
- package/src/components/flow.tsx +840 -0
- package/src/components/guide-cue.tsx +273 -0
- package/src/components/inbox.tsx +113 -0
- package/src/components/input-group.tsx +176 -0
- package/src/components/input.tsx +26 -0
- package/src/components/kbd.tsx +34 -0
- package/src/components/label.tsx +27 -0
- package/src/components/labels.tsx +176 -0
- package/src/components/motion.tsx +136 -0
- package/src/components/onboarding.tsx +454 -0
- package/src/components/order-book.tsx +185 -0
- package/src/components/pane-layout.tsx +417 -0
- package/src/components/popover.tsx +96 -0
- package/src/components/provider-logos.data.ts +49 -0
- package/src/components/radio-group.tsx +52 -0
- package/src/components/scheduler.tsx +997 -0
- package/src/components/segmented-control.tsx +28 -0
- package/src/components/select.tsx +195 -0
- package/src/components/separator.tsx +31 -0
- package/src/components/sheet.tsx +148 -0
- package/src/components/shell.tsx +426 -0
- package/src/components/shortcut-sheet.tsx +83 -0
- package/src/components/skeleton.tsx +18 -0
- package/src/components/sonner.tsx +107 -0
- package/src/components/spinner.tsx +13 -0
- package/src/components/split-button.tsx +58 -0
- package/src/components/stepper.tsx +78 -0
- package/src/components/switch.tsx +43 -0
- package/src/components/table.tsx +126 -0
- package/src/components/tabs.tsx +118 -0
- package/src/components/tag-input.tsx +253 -0
- package/src/components/textarea.tsx +21 -0
- package/src/components/theme.tsx +80 -0
- package/src/components/timeline.tsx +408 -0
- package/src/components/toggle-group.tsx +87 -0
- package/src/components/toggle.tsx +54 -0
- package/src/components/tooltip.tsx +65 -0
- package/src/components/workspace-actions.tsx +294 -0
- package/src/components/workspace-import.tsx +465 -0
- package/src/components/workspace.tsx +1884 -0
- package/src/index.ts +80 -0
- package/src/lib/active-indicator.tsx +53 -0
- package/src/lib/chart-kit.tsx +72 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/shortcuts.tsx +61 -0
- package/src/lib/workspace.tsx +10 -0
- package/src/shadcn/alert-dialog.tsx +196 -0
- package/src/shadcn/alert.tsx +65 -0
- package/src/shadcn/avatar.tsx +108 -0
- package/src/shadcn/badge.tsx +47 -0
- package/src/shadcn/breadcrumb.tsx +108 -0
- package/src/shadcn/button-group.tsx +83 -0
- package/src/shadcn/button.tsx +63 -0
- package/src/shadcn/calendar.tsx +220 -0
- package/src/shadcn/card.tsx +91 -0
- package/src/shadcn/checkbox.tsx +31 -0
- package/src/shadcn/collapsible.tsx +33 -0
- package/src/shadcn/command.tsx +184 -0
- package/src/shadcn/dialog.tsx +158 -0
- package/src/shadcn/dropdown-menu.tsx +256 -0
- package/src/shadcn/empty.tsx +103 -0
- package/src/shadcn/field.tsx +248 -0
- package/src/shadcn/input-group.tsx +170 -0
- package/src/shadcn/input.tsx +20 -0
- package/src/shadcn/kbd.tsx +28 -0
- package/src/shadcn/label.tsx +23 -0
- package/src/shadcn/popover.tsx +88 -0
- package/src/shadcn/radio-group.tsx +44 -0
- package/src/shadcn/select.tsx +189 -0
- package/src/shadcn/separator.tsx +27 -0
- package/src/shadcn/sheet.tsx +142 -0
- package/src/shadcn/skeleton.tsx +13 -0
- package/src/shadcn/sonner.tsx +40 -0
- package/src/shadcn/switch.tsx +34 -0
- package/src/shadcn/table.tsx +115 -0
- package/src/shadcn/tabs.tsx +90 -0
- package/src/shadcn/textarea.tsx +17 -0
- package/src/shadcn/toggle-group.tsx +83 -0
- package/src/shadcn/toggle.tsx +46 -0
- package/src/shadcn/tooltip.tsx +56 -0
- package/src/styles/palette.css +127 -0
- package/src/styles/theme.css +35 -0
- package/src/styles/twoux.css +236 -0
- package/theme.json +14 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Stacked area chart
|
|
2
|
+
|
|
3
|
+
Two or more series stacked over time, so the top edge is the total and each band is one part of it, such as applications per week split by source.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- When the total over time matters and so does how it splits, such as weekly applications by source (LinkedIn, Indeed, Careers page, Referral).
|
|
10
|
+
- When the parts always add up to a meaningful whole, such as candidates in the pipeline by stage at the end of each week.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- For one series. Use the Line chart card; a single filled area only adds ink.
|
|
15
|
+
- For values that can go negative, such as net change in open roles. Use the Line chart card or Bar chart.
|
|
16
|
+
- For series that do not add up to a whole, such as applications for two unrelated listings. Overlapping areas hide each other; use lines.
|
|
17
|
+
- When people need to compare the middle bands exactly. Only the bottom band sits on a flat baseline; use the Line chart card or a Breakdown table.
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
1. Title (what is measured) and subtitle (period)
|
|
22
|
+
2. Controls row: time range, Chart or Table
|
|
23
|
+
3. Plot: stacked bands from a zero baseline, a thin surface-colour line between bands, one y-axis
|
|
24
|
+
4. Legend: one item per band, in stack order, each a toggle
|
|
25
|
+
5. Crosshair tooltip: every band's value and the total at the hovered date
|
|
26
|
+
6. Optional threshold: dashed line with a text label
|
|
27
|
+
7. Footer: source and updated time
|
|
28
|
+
|
|
29
|
+
## Guidelines
|
|
30
|
+
|
|
31
|
+
- Always stack, always from zero. The top edge must read as the true total.
|
|
32
|
+
- Use two to five bands. Fold the smallest sources into "Other" at the top of the stack rather than adding a sixth colour.
|
|
33
|
+
- Put the largest or steadiest band at the bottom so the most important part has a flat baseline, and keep the stack order the same on every chart.
|
|
34
|
+
- Use categorical colours in fixed order from colour 1, separated by a thin line in the surface colour so neighbouring bands stay distinct.
|
|
35
|
+
- The tooltip lists bands in the same order as the stack, top to bottom, then the total.
|
|
36
|
+
- Show missing weeks as a gap, never as a drop to zero.
|
|
37
|
+
- Hiding a band from the legend restacks the rest and updates the total in the tooltip. Colours do not move.
|
|
38
|
+
|
|
39
|
+
## Content
|
|
40
|
+
|
|
41
|
+
- Title names the total and the split: "Applications by source". Subtitle gives the period: "Last 12 weeks".
|
|
42
|
+
- Legend items use the names people use in the product: "Careers page", not "careers_site". Never truncate a name if the end is what tells two apart.
|
|
43
|
+
- Skip x-axis labels at a regular interval (every other week) instead of crowding them, and abbreviate months: "3 Nov".
|
|
44
|
+
|
|
45
|
+
## Accessibility
|
|
46
|
+
|
|
47
|
+
- The chart has an accessible name matching its title and a description such as "Stacked area chart showing applications by source, last 12 weeks."
|
|
48
|
+
- The Table view lists every band per week plus the total, and is the long description.
|
|
49
|
+
- Bands are named in the tooltip and legend, so colour is never the only way to tell them apart.
|
|
50
|
+
- Legend items are toggle buttons with a pressed state; hiding one is announced politely: "Referral hidden".
|
|
51
|
+
|
|
52
|
+
## Keyboard
|
|
53
|
+
|
|
54
|
+
| Keys | Action |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Tab | Focus the plot, then each legend item |
|
|
57
|
+
| ← → | Move the crosshair to the previous or next week |
|
|
58
|
+
| Enter | Pin the tooltip so its links can be reached with Tab |
|
|
59
|
+
| Esc | Close the tooltip and return focus to the plot |
|
|
60
|
+
| Space (legend item) | Show or hide that band |
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
|
|
64
|
+
### pipeline
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { CartesianChart, ChartFrame, ChartLegend, chartTable, seriesColor } from "@twoux/ui";
|
|
68
|
+
import { APPLICATIONS, WEEKS } from "../chart-card-line/data";
|
|
69
|
+
|
|
70
|
+
export const meta = { title: "Applications, stacked by team", description: "Stacked from zero, so the top edge is the total and each band is one team. Use it when the total matters as much as the parts; for comparing teams, use lines.", order: 1 };
|
|
71
|
+
|
|
72
|
+
export default function Example() {
|
|
73
|
+
return (
|
|
74
|
+
<ChartFrame className="w-full" title="Applications per week" description="Stacked by team" table={chartTable("Week", WEEKS, APPLICATIONS)} legend={<ChartLegend items={APPLICATIONS.map((s, i) => ({ id: s.id, label: s.label, color: seriesColor(i) }))} />}>
|
|
75
|
+
<CartesianChart kind="area" stacked x={WEEKS} series={APPLICATIONS} aria-label="Applications per week, stacked by team" />
|
|
76
|
+
</ChartFrame>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Related
|
|
82
|
+
|
|
83
|
+
- Line chart card
|
|
84
|
+
- Bar chart
|
|
85
|
+
- Data visualisation colours
|
|
86
|
+
- Breakdown table
|
|
87
|
+
- Data visualisation
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Bar chart
|
|
2
|
+
|
|
3
|
+
Vertical bars from a zero baseline for comparing amounts per period or category, with the latest bar labelled and an incomplete period shown lighter.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Comparing amounts across items or periods; simple bars are quickly processed and understood.
|
|
10
|
+
- Emphasising the total of each data point.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- Showing a trend over time where shape matters more than totals. Use the TwoUX Line chart card.
|
|
15
|
+
- Two dimensions at once. Use the TwoUX Heatmap.
|
|
16
|
+
- Breaking down task outcomes as stacks; stacked bars are hard to read. Use separate simple bars.
|
|
17
|
+
|
|
18
|
+
## Structure
|
|
19
|
+
|
|
20
|
+
1. Title and subtitle
|
|
21
|
+
2. Zero baseline
|
|
22
|
+
3. Bars (4px rounded top, gap between)
|
|
23
|
+
4. Latest-bar label
|
|
24
|
+
5. Incomplete period bar (lighter, labelled)
|
|
25
|
+
6. Hover tooltip
|
|
26
|
+
|
|
27
|
+
## Guidelines
|
|
28
|
+
|
|
29
|
+
- Bars always start at zero.
|
|
30
|
+
- Use horizontal bars when category names are long.
|
|
31
|
+
- Label only the latest bar; hover shows the rest.
|
|
32
|
+
- The current, incomplete period is lighter and says so in a label.
|
|
33
|
+
- A single series uses colour 1 and no legend.
|
|
34
|
+
- Bar charts have the same four states as line charts: skeleton plot while loading, empty, no match with Clear filters, and error with Try again.
|
|
35
|
+
- Use horizontal bars when there are many categories or long names, such as applications by listing title; vertical bars for periods.
|
|
36
|
+
- With several series, group bars to compare the parts. Stack only when the total is the main reading and there are four parts or fewer, with a 2px surface-colour gap between segments.
|
|
37
|
+
- A single series uses one colour for every bar. Never colour bars by month or by category for variety.
|
|
38
|
+
- A goal or limit is a dashed threshold line with a text label, such as "Target 120".
|
|
39
|
+
- The pinned popover can break a bar down into up to seven parts, with a link on the name or the value, not both.
|
|
40
|
+
|
|
41
|
+
## Content
|
|
42
|
+
|
|
43
|
+
- Title names the measure; subtitle gives the period and scope.
|
|
44
|
+
- Keep tick increments fixed and match time labels to the data's period.
|
|
45
|
+
- Label the incomplete period plainly, "This week so far".
|
|
46
|
+
|
|
47
|
+
## Accessibility
|
|
48
|
+
|
|
49
|
+
- 1.1.1 and WAI complex images: provide a short alternative and a table with the values.
|
|
50
|
+
- 1.4.11 Non-text Contrast: bars reach 3:1 against the background.
|
|
51
|
+
- 1.4.1 Use of Color: the incomplete period is marked in text, not lightness alone.
|
|
52
|
+
- 2.1.1 Keyboard: arrow keys move between bars and show each value.
|
|
53
|
+
- Description: "Bar chart showing applications per week, last 12 weeks." It matches the visible title and period.
|
|
54
|
+
- Grouped and stacked bars name each part in the tooltip and table, so colour is not the only cue.
|
|
55
|
+
|
|
56
|
+
## Keyboard
|
|
57
|
+
|
|
58
|
+
| Keys | Action |
|
|
59
|
+
|---|---|
|
|
60
|
+
| Tab | Focus the chart |
|
|
61
|
+
| ← → | Move to the previous or next bar |
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
|
|
65
|
+
### hires
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { CartesianChart, ChartFrame, ChartLegend, SegmentedControl, Segment, chartTable, seriesColor } from "@twoux/ui";
|
|
69
|
+
import { useState } from "react";
|
|
70
|
+
import { HIRES, WEEKS } from "../chart-card-line/data";
|
|
71
|
+
|
|
72
|
+
export const meta = { title: "Hires per week", description: "Grouped bars compare teams week by week; stacked shows the total. Bars start at zero with 4px rounded ends and 2px gaps.", order: 1 };
|
|
73
|
+
|
|
74
|
+
export default function Example() {
|
|
75
|
+
const [mode, setMode] = useState("grouped");
|
|
76
|
+
return (
|
|
77
|
+
<ChartFrame
|
|
78
|
+
className="w-full"
|
|
79
|
+
title="Hires per week"
|
|
80
|
+
description="Offers accepted, by team"
|
|
81
|
+
table={chartTable("Week", WEEKS, HIRES)}
|
|
82
|
+
actions={
|
|
83
|
+
<SegmentedControl value={mode} onValueChange={setMode} size="sm" aria-label="Bar layout">
|
|
84
|
+
<Segment value="grouped">Grouped</Segment>
|
|
85
|
+
<Segment value="stacked">Stacked</Segment>
|
|
86
|
+
</SegmentedControl>
|
|
87
|
+
}
|
|
88
|
+
legend={<ChartLegend items={HIRES.map((s, i) => ({ id: s.id, label: s.label, color: seriesColor(i) }))} />}
|
|
89
|
+
>
|
|
90
|
+
<CartesianChart kind="bar" stacked={mode === "stacked"} x={WEEKS} series={HIRES} aria-label="Hires per week by team" />
|
|
91
|
+
</ChartFrame>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Related
|
|
97
|
+
|
|
98
|
+
- Line chart card
|
|
99
|
+
- Breakdown table
|
|
100
|
+
- Data visualisation colours
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Box plot
|
|
2
|
+
|
|
3
|
+
Distributions of several groups on one axis: a box for the middle half, a line for the median, whiskers to 1.5 times the IQR and dots for outliers.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Comparing how spread out several groups are, such as response times by team.
|
|
10
|
+
- Finding outliers worth opening, such as the slowest tickets.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- For one group's shape. Use the TwoUX Histogram.
|
|
15
|
+
- For audiences new to statistics; show median and range in a table or a range plot instead.
|
|
16
|
+
|
|
17
|
+
## Structure
|
|
18
|
+
|
|
19
|
+
1. Title and subtitle
|
|
20
|
+
2. Group labels
|
|
21
|
+
3. Shared value axis with title and unit
|
|
22
|
+
4. Box from Q1 to Q3
|
|
23
|
+
5. Median line
|
|
24
|
+
6. Whiskers with caps
|
|
25
|
+
7. Outlier dots
|
|
26
|
+
8. Legend explaining the parts
|
|
27
|
+
9. Tooltip: median, middle half, whiskers, outliers
|
|
28
|
+
|
|
29
|
+
## Guidelines
|
|
30
|
+
|
|
31
|
+
- Put every group on one shared axis.
|
|
32
|
+
- Whiskers reach the furthest value within 1.5 times the IQR; values beyond are outlier dots.
|
|
33
|
+
- Always show the legend explaining box, median, whiskers and outliers.
|
|
34
|
+
- Order groups by meaning or by median, not alphabetically by default.
|
|
35
|
+
- Use ChartFrame for the four states: loading keeps the frame, an error says what failed with Try again, an empty period says so, and no match offers Clear filters.
|
|
36
|
+
|
|
37
|
+
## Content
|
|
38
|
+
|
|
39
|
+
- Say Middle half (Q1 to Q3) rather than only IQR.
|
|
40
|
+
- Axis title gives the measure and unit, such as Hours to first response.
|
|
41
|
+
|
|
42
|
+
## Accessibility
|
|
43
|
+
|
|
44
|
+
- 1.1.1 and WAI complex images: the chart has a short generated summary as its accessible name and a Table view with every value.
|
|
45
|
+
- 2.1.1 Keyboard: the chart is one tab stop; arrow keys move between marks and show the same tooltip as hover.
|
|
46
|
+
- 1.4.13 Content on Hover or Focus: the tooltip never covers the mark it describes, stays while the pointer is over the chart and closes with Esc.
|
|
47
|
+
- Accessible description: "Box plot showing first response time by team, last 40 tickets."
|
|
48
|
+
|
|
49
|
+
## Keyboard
|
|
50
|
+
|
|
51
|
+
| Keys | Action |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Tab | Focus the chart |
|
|
54
|
+
| ← → ↑ ↓ | Move to the previous or next mark and show its values |
|
|
55
|
+
| Home End | Jump to the first or last mark |
|
|
56
|
+
| Esc | Hide the tooltip |
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
### response-times
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { BoxPlot, boxTable, ChartFrame } from "@twoux/ui";
|
|
64
|
+
|
|
65
|
+
export const meta = { title: "First response time by team", description: "Four teams on one axis: the box is the middle half of tickets, the line the median, dots the slow outliers worth opening.", order: 1 };
|
|
66
|
+
|
|
67
|
+
const TEAMS = [
|
|
68
|
+
{ id: "billing", label: "Billing", values: [4.7, 1.3, 2.9, 3.3, 4.0, 4.2, 3.4, 2.1, 3.4, 5.1, 2.6, 1.9, 2.4, 5.4, 2.1, 3.7, 2.2, 0.9, 2.4, 3.3, 1.7, 10.8, 3.2, 2.4, 2.7, 5.0, 2.8, 1.3, 4.3, 5.4, 1.6, 2.1, 6.8, 2.9, 2.9, 2.7, 3.0, 1.8, 1.6, 7.0] },
|
|
69
|
+
{ id: "onboarding", label: "Onboarding", values: [9.5, 3.2, 3.3, 11.4, 7.0, 2.4, 4.1, 9.9, 3.3, 18.5, 2.0, 15.3, 5.2, 11.0, 9.5, 6.5, 9.4, 6.5, 8.4, 7.9, 4.3, 2.1, 5.7, 4.8, 4.1, 4.7, 6.5, 2.3, 3.5, 4.4, 5.6, 5.3, 31.2, 8.4, 11.4, 4.3, 6.2, 5.9, 2.1, 5.7] },
|
|
70
|
+
{ id: "integrations", label: "Integrations", values: [27.7, 7.3, 6.3, 18.5, 10.2, 22.2, 8.0, 22.1, 6.1, 3.5, 8.3, 12.8, 6.9, 10.9, 12.7, 11.7, 12.8, 9.4, 6.0, 8.5, 15.8, 5.0, 5.4, 20.1, 7.7, 15.4, 7.4, 7.5, 13.6, 2.9, 12.4, 3.6, 8.6, 9.0, 4.1, 2.2, 20.4, 11.0, 13.6, 11.3] },
|
|
71
|
+
{ id: "enterprise", label: "Enterprise", values: [0.9, 1.8, 2.3, 1.1, 2.6, 3.0, 1.8, 2.4, 3.9, 0.8, 2.1, 2.3, 1.2, 0.8, 2.0, 1.3, 2.6, 3.6, 2.2, 4.0, 0.9, 1.5, 2.8, 2.4, 1.4, 2.4, 1.8, 4.9, 3.3, 4.0, 2.5, 2.9, 4.1, 1.5, 2.4, 1.9, 1.4, 1.6, 3.1, 1.5] },
|
|
72
|
+
];
|
|
73
|
+
const hours = (n: number) => `${Math.round(n * 10) / 10}h`;
|
|
74
|
+
|
|
75
|
+
export default function Example() {
|
|
76
|
+
return (
|
|
77
|
+
<ChartFrame className="w-full" title="First response time by team" description="Last 40 tickets per team" table={boxTable(TEAMS, hours, "Team")}>
|
|
78
|
+
<BoxPlot groups={TEAMS} format={hours} xTitle="Hours to first response" aria-label="First response time by team, last 40 tickets" />
|
|
79
|
+
</ChartFrame>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Related
|
|
85
|
+
|
|
86
|
+
- Histogram
|
|
87
|
+
- Scatter chart
|
|
88
|
+
- Breakdown table
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Bullet chart
|
|
2
|
+
|
|
3
|
+
Actual against target for a few measures: a thin bar over grey qualitative bands with the target as a dark tick, in a fraction of a gauge's space.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Showing progress against a target for several measures in one card, such as bookings this quarter.
|
|
10
|
+
- Where a gauge or dial was asked for: a bullet chart says the same in a line.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- For a single number with no target. Use the TwoUX Stat compare.
|
|
15
|
+
- For usage against a hard limit. Use the TwoUX Usage meter.
|
|
16
|
+
|
|
17
|
+
## Structure
|
|
18
|
+
|
|
19
|
+
1. Title and subtitle
|
|
20
|
+
2. Row: measure name, actual and target as text
|
|
21
|
+
3. Qualitative bands in neutral grey steps, darkest for the worst
|
|
22
|
+
4. Actual: thin bar in c1
|
|
23
|
+
5. Target: dark tick
|
|
24
|
+
6. Scale under each bar
|
|
25
|
+
7. Legend: actual, target, bands
|
|
26
|
+
|
|
27
|
+
## Guidelines
|
|
28
|
+
|
|
29
|
+
- Use a bullet chart, never a gauge or dial.
|
|
30
|
+
- Bands are neutral grey steps, darkest for the worst range; they never use status colours.
|
|
31
|
+
- The actual is a thin bar in the first series colour, the target a dark tick across it.
|
|
32
|
+
- Print actual and target as text on each row, so the bar is never the only way to read them.
|
|
33
|
+
- Each row has its own scale; never compare bar lengths between rows with different scales.
|
|
34
|
+
- Use ChartFrame for the four states: loading keeps the frame, an error says what failed with Try again, an empty period says so, and no match offers Clear filters.
|
|
35
|
+
|
|
36
|
+
## Content
|
|
37
|
+
|
|
38
|
+
- Name the bands by meaning (Poor, Satisfactory, Good), worst first.
|
|
39
|
+
- Write actual of target, such as £412k of £450k.
|
|
40
|
+
|
|
41
|
+
## Accessibility
|
|
42
|
+
|
|
43
|
+
- 1.4.1 Use of Color: values are printed; bands are named in the legend and in each row's name.
|
|
44
|
+
- 1.1.1 and WAI complex images: each row's accessible name gives the actual, target, percentage of target and band.
|
|
45
|
+
- 2.1.1 Keyboard: each row is focusable and shows the same tooltip as hover.
|
|
46
|
+
- 1.4.11 Non-text Contrast: the actual bar and target tick meet 3:1 against every band.
|
|
47
|
+
|
|
48
|
+
## Keyboard
|
|
49
|
+
|
|
50
|
+
| Keys | Action |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Tab | Move to the next measure |
|
|
53
|
+
| Esc | Hide the tooltip |
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
### quarter-targets
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { BulletChart, ChartFrame } from "@twoux/ui";
|
|
61
|
+
|
|
62
|
+
export const meta = { title: "Quarter to date against target", description: "Each measure is a thin bar over grey bands, with the target as a dark tick. It replaces a row of gauges in a third of the space.", order: 1 };
|
|
63
|
+
|
|
64
|
+
const ROWS = [
|
|
65
|
+
{ id: "new", label: "New business", value: 412_000, target: 450_000, bands: [300_000, 400_000, 500_000] },
|
|
66
|
+
{ id: "expansion", label: "Expansion", value: 188_000, target: 160_000, bands: [100_000, 150_000, 220_000] },
|
|
67
|
+
{ id: "renewals", label: "Renewals", value: 731_000, target: 820_000, bands: [700_000, 800_000, 900_000] },
|
|
68
|
+
];
|
|
69
|
+
const gbp = (n: number) => (n === 0 ? "£0" : `£${Math.round(n / 1000)}k`);
|
|
70
|
+
|
|
71
|
+
export default function Example() {
|
|
72
|
+
return (
|
|
73
|
+
<ChartFrame
|
|
74
|
+
className="w-full"
|
|
75
|
+
title="Bookings against target"
|
|
76
|
+
description="Quarter to date, Q4 2026"
|
|
77
|
+
table={{ columns: ["Measure", "Actual", "Target", "Of target"], rows: ROWS.map((r) => [r.label, gbp(r.value), gbp(r.target), `${Math.round((r.value / r.target) * 100)}%`]) }}
|
|
78
|
+
>
|
|
79
|
+
<BulletChart rows={ROWS} format={gbp} aria-label="Bookings against target, quarter to date" />
|
|
80
|
+
</ChartFrame>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Related
|
|
86
|
+
|
|
87
|
+
- Stat card with comparison
|
|
88
|
+
- Waterfall chart
|
|
89
|
+
- Bar chart
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Candlestick chart
|
|
2
|
+
|
|
3
|
+
Open, high, low and close per period for a price. Up candles are hollow and down candles filled, in two categorical colours, or green and red on trading screens; volume is its own chart below.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Showing how a traded price moved each day, including its range, for people who read markets.
|
|
10
|
+
- Spotting days with wide ranges or large volume.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- For a general audience or one price over time. Use the TwoUX Price chart or Line chart card.
|
|
15
|
+
- For anything that is not open, high, low and close.
|
|
16
|
+
|
|
17
|
+
## Structure
|
|
18
|
+
|
|
19
|
+
1. Title and subtitle
|
|
20
|
+
2. Price axis on the right, not from zero, with a note
|
|
21
|
+
3. Candles: wick for high and low, body for open and close
|
|
22
|
+
4. Optional last-price tag on the axis, with a dashed line
|
|
23
|
+
5. Up: hollow c1; down: filled c2 (green and red with colors="market")
|
|
24
|
+
6. Volume chart below with its own axis, sharing the crosshair
|
|
25
|
+
7. Legend: up and down
|
|
26
|
+
8. Tooltip: change, open, high, low, close, volume
|
|
27
|
+
|
|
28
|
+
## Guidelines
|
|
29
|
+
|
|
30
|
+
- Up and down take two categorical colours and differ in fill too: up hollow, down filled.
|
|
31
|
+
- Use two categorical colours by default. On trading screens, where people expect it, use green for up and red for down (colors="market"); the hollow and filled bodies stay, so colour is never the only cue.
|
|
32
|
+
- Put volume in its own chart below with its own axis; never a second y axis.
|
|
33
|
+
- The price axis need not start at zero, and says so.
|
|
34
|
+
- One crosshair for both charts, so a day's price and volume read together.
|
|
35
|
+
- Use ChartFrame for the four states: loading keeps the frame, an error says what failed with Try again, an empty period says so, and no match offers Clear filters.
|
|
36
|
+
- On a live market, tag the last price on the axis (showLast) and move only the forming candle as prices arrive.
|
|
37
|
+
|
|
38
|
+
## Content
|
|
39
|
+
|
|
40
|
+
- Dates as day and short month (5 Nov); the unit in the subtitle.
|
|
41
|
+
- Legend says what up means: Up (close above open).
|
|
42
|
+
|
|
43
|
+
## Accessibility
|
|
44
|
+
|
|
45
|
+
- 1.4.1 Use of Color: direction is carried by hollow or filled bodies, the tooltip and the table.
|
|
46
|
+
- 1.1.1 and WAI complex images: the chart has a short generated summary as its accessible name and a Table view with every value.
|
|
47
|
+
- 2.1.1 Keyboard: the chart is one tab stop; arrow keys move between marks and show the same tooltip as hover.
|
|
48
|
+
- 1.4.13 Content on Hover or Focus: the tooltip never covers the mark it describes, stays while the pointer is over the chart and closes with Esc.
|
|
49
|
+
- Accessible description: "Candlestick chart showing Ashdown plc daily prices, last 30 trading days."
|
|
50
|
+
|
|
51
|
+
## Keyboard
|
|
52
|
+
|
|
53
|
+
| Keys | Action |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Tab | Focus the chart |
|
|
56
|
+
| ← → ↑ ↓ | Move to the previous or next mark and show its values |
|
|
57
|
+
| Home End | Jump to the first or last mark |
|
|
58
|
+
| Esc | Hide the tooltip |
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### daily
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { type Candle, CandlestickChart, candleTable, ChartFrame } from "@twoux/ui";
|
|
66
|
+
|
|
67
|
+
export const meta = { title: "Daily prices with volume", description: "Ninety trading days. Up days are hollow, down days filled, and volume sits in its own panel under the prices with the same crosshair.", order: 1 };
|
|
68
|
+
|
|
69
|
+
// 90 trading days from a seeded random walk, so the example looks like a real price series and stays the same on every load.
|
|
70
|
+
function tradingDays(count: number, endDate: Date) {
|
|
71
|
+
const days: Date[] = [];
|
|
72
|
+
const d = new Date(endDate);
|
|
73
|
+
while (days.length < count) {
|
|
74
|
+
if (d.getDay() !== 0 && d.getDay() !== 6) days.unshift(new Date(d));
|
|
75
|
+
d.setDate(d.getDate() - 1);
|
|
76
|
+
}
|
|
77
|
+
return days;
|
|
78
|
+
}
|
|
79
|
+
function makeCandles(): Candle[] {
|
|
80
|
+
let seed = 7;
|
|
81
|
+
const rand = () => ((seed = (seed * 16807) % 2147483647) / 2147483647);
|
|
82
|
+
let close = 742;
|
|
83
|
+
return tradingDays(90, new Date(2026, 10, 5)).map((day) => {
|
|
84
|
+
const open = close + (rand() - 0.5) * 4;
|
|
85
|
+
const pull = (742 - open) * 0.06; // drifts back towards a middle price, like a stock trading in a range
|
|
86
|
+
close = open + pull + (rand() - 0.5) * 22 + (rand() > 0.93 ? (rand() - 0.5) * 34 : 0);
|
|
87
|
+
const high = Math.max(open, close) + rand() * 9;
|
|
88
|
+
const low = Math.min(open, close) - rand() * 9;
|
|
89
|
+
const big = Math.abs(close - open) > 12;
|
|
90
|
+
const v = Math.round((1.4 + rand() * 1.4 + (big ? 1.2 : 0)) * 1_000_000);
|
|
91
|
+
const r = (n: number) => Math.round(n * 10) / 10;
|
|
92
|
+
return { t: day.toLocaleDateString("en-GB", { day: "numeric", month: "short" }), o: r(open), h: r(high), l: r(low), c: r(close), v };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const DAYS: Candle[] = makeCandles();
|
|
96
|
+
const pence = (n: number) => `${n.toLocaleString("en-GB", { minimumFractionDigits: 1, maximumFractionDigits: 1 })}p`;
|
|
97
|
+
|
|
98
|
+
export default function Example() {
|
|
99
|
+
return (
|
|
100
|
+
<ChartFrame className="w-full" title="Ashdown plc, daily" description="Last 90 trading days, in pence" table={candleTable(DAYS, pence)} height={320}>
|
|
101
|
+
<CandlestickChart candles={DAYS} format={pence} aria-label="Ashdown plc daily prices, last 90 trading days" />
|
|
102
|
+
</ChartFrame>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Related
|
|
108
|
+
|
|
109
|
+
- Price chart
|
|
110
|
+
- Line chart card
|
|
111
|
+
- Bar chart
|
|
112
|
+
- Order book
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Line chart card
|
|
2
|
+
|
|
3
|
+
A card that shows how one or more series change over time, with a title, period controls, a Chart or Table switch, direct labels, a shared crosshair tooltip and a source footer.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Showing how a value changes over time.
|
|
10
|
+
- Comparing trends across a few series, such as applies per day by team.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- Comparing amounts between categories. Use the TwoUX Bar chart.
|
|
15
|
+
- Two measures with different units. Use two charts side by side, never two y-axes.
|
|
16
|
+
- One number that matters. Use the TwoUX Stat card with comparison.
|
|
17
|
+
- More than 8 series. Fold extras into "Other" or filter.
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
1. Title (what is measured)
|
|
22
|
+
2. Subtitle (period)
|
|
23
|
+
3. Controls row: time range (7d, 30d, 90d), Chart or Table
|
|
24
|
+
4. Plot: 2px lines, faint grid, one y-axis
|
|
25
|
+
5. Direct labels at line ends
|
|
26
|
+
6. Legend (2 or more series)
|
|
27
|
+
7. Crosshair tooltip
|
|
28
|
+
8. Footer: source, updated time, Export CSV, Add to dashboard
|
|
29
|
+
|
|
30
|
+
## Guidelines
|
|
31
|
+
|
|
32
|
+
- Title says what is measured; the subtitle gives the period.
|
|
33
|
+
- Label line ends directly, and add a legend only for two or more series.
|
|
34
|
+
- One tooltip lists every series at the hovered day, sorted by value, with both axis values.
|
|
35
|
+
- Keep the grid faint and lines 2px; remove anything that does not carry data.
|
|
36
|
+
- A line chart may start above zero, unlike a bar chart.
|
|
37
|
+
- Every chart can switch to a table, which is also its accessible version.
|
|
38
|
+
- Every line chart has four states in the plot area, never blank: Loading: skeleton of the plot (axes and faint grid) with "Loading applications" as the region label. Empty: "No applications in this period" with one next step. No match: "No data matches these filters" with Clear filters. Error: "Couldn't load applications. Your filters are kept." with Try again.
|
|
39
|
+
- Missing days show as a break in the line, not a drop to zero.
|
|
40
|
+
- Legend items are toggles that show or hide a series; hidden series keep their colour slot. With three or more series, add a Series filter (all selected by default); with one, no filter and no legend.
|
|
41
|
+
- Selecting a point or pressing Enter pins the tooltip as a popover. A pinned popover can hold a breakdown of up to seven items and up to two footer buttons; see Chart drill-down.
|
|
42
|
+
- A target or limit is a threshold: a dashed line at a fixed value with a text label ("Target 40 a day"), not an extra series.
|
|
43
|
+
- Skip x-axis date labels at a regular interval rather than crowding or rotating them. The y-axis never shows time units.
|
|
44
|
+
- A forecast after today continues in the same colour but dashed, with a vertical "Today" marker, so the split is not shown by colour alone. Keep grey dashed for the previous period only.
|
|
45
|
+
|
|
46
|
+
## Content
|
|
47
|
+
|
|
48
|
+
- Use nouns in titles and keep axis titles to one to three words with units.
|
|
49
|
+
- Labels, values and legends use text colours; the series colour sits beside them as a swatch.
|
|
50
|
+
- The footer names the data source and when it updated.
|
|
51
|
+
|
|
52
|
+
## Accessibility
|
|
53
|
+
|
|
54
|
+
- 1.1.1 and WAI complex images: a short alternative names the chart type, data and period; the table view is the long description.
|
|
55
|
+
- 1.4.1 Use of Color: direct labels identify series, so colour is not the only cue.
|
|
56
|
+
- 1.4.11 Non-text Contrast: lines needed to read the data reach 3:1 against the background.
|
|
57
|
+
- 2.1.1 Keyboard: the plot is focusable and arrow keys move the crosshair day by day.
|
|
58
|
+
- The accessible name matches the visible title; the description reads "Line chart showing applications per day by team, last 30 days."
|
|
59
|
+
- Legend toggles are buttons with aria-pressed, and hiding a series is announced politely.
|
|
60
|
+
- Enter pins the tooltip, Tab moves into its content, Esc closes it and returns focus to the same day.
|
|
61
|
+
|
|
62
|
+
## Keyboard
|
|
63
|
+
|
|
64
|
+
| Keys | Action |
|
|
65
|
+
|---|---|
|
|
66
|
+
| Tab | Focus the plot |
|
|
67
|
+
| ← → | Move the crosshair to the previous or next day |
|
|
68
|
+
| Esc | Hide the tooltip |
|
|
69
|
+
|
|
70
|
+
## Examples
|
|
71
|
+
|
|
72
|
+
### applications
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { CartesianChart, ChartFrame, ChartLegend, type ChartStatus, SegmentedControl, Segment, chartTable, seriesColor } from "@twoux/ui";
|
|
76
|
+
import { useState } from "react";
|
|
77
|
+
import { APPLICATIONS, WEEKS } from "./data";
|
|
78
|
+
|
|
79
|
+
export const meta = { title: "Applications by team", description: "Hover, or focus the chart and use the arrow keys, to read every value. Click a legend key to hide a series. The table button shows the same numbers as a table.", order: 1 };
|
|
80
|
+
|
|
81
|
+
export default function Example() {
|
|
82
|
+
const [hidden, setHidden] = useState<string[]>([]);
|
|
83
|
+
const [state, setState] = useState<ChartStatus>("ready");
|
|
84
|
+
const [period, setPeriod] = useState("8w");
|
|
85
|
+
const n = period === "4w" ? 4 : 8;
|
|
86
|
+
const x = WEEKS.slice(-n);
|
|
87
|
+
const series = APPLICATIONS.map((s) => ({ ...s, data: s.data.slice(-n) }));
|
|
88
|
+
return (
|
|
89
|
+
<div className="grid w-full gap-3">
|
|
90
|
+
<ChartFrame
|
|
91
|
+
title="Applications per week"
|
|
92
|
+
description={`All open listings, last ${n} weeks`}
|
|
93
|
+
status={state}
|
|
94
|
+
onRetry={() => setState("ready")}
|
|
95
|
+
table={chartTable("Week", x, series)}
|
|
96
|
+
actions={
|
|
97
|
+
<SegmentedControl value={period} onValueChange={setPeriod} size="sm" aria-label="Period">
|
|
98
|
+
<Segment value="4w">4 weeks</Segment>
|
|
99
|
+
<Segment value="8w">8 weeks</Segment>
|
|
100
|
+
</SegmentedControl>
|
|
101
|
+
}
|
|
102
|
+
legend={<ChartLegend items={APPLICATIONS.map((s, i) => ({ id: s.id, label: s.label, color: seriesColor(i), shape: "line" }))} hidden={hidden} onToggle={(id) => setHidden((h) => (h.includes(id) ? h.filter((x) => x !== id) : [...h, id]))} />}
|
|
103
|
+
>
|
|
104
|
+
<CartesianChart kind="line" x={x} series={series} hidden={hidden} aria-label="Applications per week by team" />
|
|
105
|
+
</ChartFrame>
|
|
106
|
+
<SegmentedControl value={state} onValueChange={(v) => setState(v as ChartStatus)} size="sm" aria-label="Preview state" className="justify-self-start">
|
|
107
|
+
{(["ready", "loading", "empty", "error"] as const).map((s) => <Segment key={s} value={s}>{s[0].toUpperCase() + s.slice(1)}</Segment>)}
|
|
108
|
+
</SegmentedControl>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Related
|
|
115
|
+
|
|
116
|
+
- Bar chart
|
|
117
|
+
- Stat card with comparison
|
|
118
|
+
- Breakdown table
|
|
119
|
+
- Data visualisation colours
|
|
120
|
+
- Export menu
|