Skip to content

Chart

New
Visualize data.

Anatomy

<Chart>
<DonutChart />
<Legend>
<LegendItem />
<LegendItem />
</Legend>
</Chart>
  • Chart: Wraps and provides shared context (colors, currently hovered item, etc.) to child components. Not required if using a display-only chart without user interactions.
  • DonutChart: Displays data as parts of a whole, with an optional center value and label. Currently the only chart type available in Stylus.
  • Legend: Displays a list of labels and values related to the chart.

Usage

import { Chart, DonutChart, Legend, LegendItem } from "stylus-ui/Chart";
const data = [
{ name: "Notion", amount: 281 },
{ name: "VS Code", amount: 142 },
{ name: "Slack", amount: 50 },
];
export default () => (
<Chart className="flex items-center gap-8" data={data} category="name">
<DonutChart value="amount" />
<Legend className="w-48">
{data.map((item) => (
<LegendItem
key={item.name}
dataKey={item.name}
label={item.name}
value={item.amount}
/>
))}
</Legend>
</Chart>
);

Donut chart

Basic

For the simplest display, you can use DonutChart standalone, without the Chart wrapper.

import { DonutChart } from "stylus-ui/Chart";
const data = [
{ name: "Notion", amount: 281 },
{ name: "VS Code", amount: 142 },
{ name: "Slack", amount: 50 },
];
export default () => (
<DonutChart category="name" value="amount" data={data} className="size-16" />
);

Colors

Each segment will automatically take on a color from the default palette. The available colors are brand, blue, green, teal, yellow, pink, fuchsia, purple, red, cyan, orange, and slate.

Specific colors only

By default, the chart cycles through all available colors in order. To use a specific subset of colors, pass an array of names to the colors prop:

import { DonutChart } from "stylus-ui/Chart";
const data = [
{ name: "Primary", value: 40 },
{ name: "Secondary", value: 30 },
{ name: "Tertiary", value: 20 },
{ name: "Other", value: 10 },
];
export default () => (
<DonutChart
category="name"
value="value"
data={data}
colors={["brand", "pink", "cyan", "slate"]}
/>
);

Display value

Display a sum of all chart segments in the center by setting showValue to true.

import { DonutChart } from "stylus-ui/Chart";
import { formatIntlNumber } from "scribe-web-shared/functions";
const data = [
{ name: "Notion", amount: 2814 },
{ name: "VS Code", amount: 142 },
{ name: "Slack", amount: 503 },
];
export default () => (
<DonutChart category="name" value="amount" data={data} showValue />
);

Long values will automaically shrink to fit.

import { DonutChart } from "stylus-ui/Chart";
import { formatIntlNumber } from "scribe-web-shared/functions";
const data = [
{ name: "Notion", revenue: 4500600 },
{ name: "VS Code", revenue: 3200800 },
{ name: "Slack", revenue: 1800900 },
];
export default () => (
<DonutChart category="name" value="revenue" data={data} showValue />
);

Formatting values

Use the valueFormatter prop to format the display value. For example, you can use the formatIntlNumber helper to display locale-appropriate thousands separators for long numbers.

import { DonutChart } from "stylus-ui/Chart";
import { formatIntlNumber } from "scribe-web-shared/functions";
const data = [
{ name: "Notion", amount: 2814 },
{ name: "VS Code", amount: 142 },
{ name: "Slack", amount: 503 },
];
export default () => (
<DonutChart
category="name"
value="amount"
data={data}
showValue
valueFormatter={(v) => formatIntlNumber(v)}
/>
);

With label

To display text above the value, set label.

import { DonutChart } from "stylus-ui/Chart";
import { formatIntlNumber } from "scribe-web-shared/functions";
const data = [
{ name: "Notion", hours: 281 },
{ name: "VS Code", hours: 142 },
{ name: "Slack", hours: 50 },
];
export default () => (
<DonutChart
category="name"
value="hours"
data={data}
showValue
valueFormatter={(v) => `${formatIntlNumber(v)} hrs`}
label="Yearly average"
/>
);

No data

When there’s no data or all values are zero, the chart displays a full ring with a - in the center:

import { DonutChart } from "stylus-ui/Chart";
export default () => (
<DonutChart
category="name"
value="amount"
data={[]}
showValue
label="Yearly average"
/>
);

With legend

Combine the DonutChart with the Legend component for interactive data visualization. The Chart wrapper automatically synchronizes hover and click states between components with zero boilerplate:

import React from "react";
import { Chart, DonutChart, Legend, LegendItem } from "stylus-ui/Chart";
const data = [
{ name: "Notion", amount: 281 },
{ name: "VS Code", amount: 142 },
{ name: "GitHub", amount: 87 },
{ name: "Slack", amount: 50 },
{ name: "Figma", amount: 43 },
];
export default () => {
const total = data.reduce((sum, item) => sum + item.amount, 0);
return (
<Chart className="flex items-center gap-8" data={data} category="name">
<DonutChart
value="amount"
label="Yearly average"
valueFormatter={(v) => `${v} hrs`}
showValue
/>
<Legend>
{data.map((item) => (
<LegendItem
key={item.name}
dataKey={item.name}
label={item.name}
value={`${item.amount} hrs (${((item.amount / total) * 100).toFixed(1)}%)`}
/>
))}
</Legend>
</Chart>
);
};

With click interactions

Add click handling to toggle selection by providing an onClick callback to the Chart component:

import React from "react";
import { Chart, DonutChart, Legend, LegendItem } from "stylus-ui/Chart";
const data = [
{ name: "Notion", amount: 281 },
{ name: "VS Code", amount: 142 },
{ name: "GitHub", amount: 87 },
{ name: "Slack", amount: 50 },
{ name: "Figma", amount: 43 },
];
export default () => {
const [selected, setSelected] = React.useState(null);
const total = data.reduce((sum, item) => sum + item.amount, 0);
return (
<Chart
className="flex items-center gap-8"
data={data}
category="name"
selectedDataKey={selected}
onClick={setSelected}
>
<DonutChart
value="amount"
label="Yearly average"
valueFormatter={(v) => `${v} hrs`}
showValue
/>
<Legend>
{data.map((item) => (
<LegendItem
key={item.name}
dataKey={item.name}
label={item.name}
value={`${item.amount} hrs (${((item.amount / total) * 100).toFixed(1)}%)`}
/>
))}
</Legend>
</Chart>
);
};

With icons and logos

Each legend item accepts an icon prop for FontAwesome icons or a logo prop (with name and src) to display a favicon.

import React from "react";
import { Chart, DonutChart, Legend, LegendItem } from "stylus-ui/Chart";
import { getFavicon } from "stylus-ui/utils/getFavicon";
import { faGrid2 } from "@fortawesome/pro-regular-svg-icons";
const data = [
{ name: "Notion", url: "https://notion.so", amount: 281 },
{ name: "Slack", url: "https://slack.com", amount: 142 },
{ name: "Figma", url: "https://figma.com", amount: 87 },
{ name: "Others", amount: 50 },
];
export default () => {
const total = data.reduce((sum, item) => sum + item.amount, 0);
return (
<Chart
className="flex items-center gap-8"
data={data}
category="name"
colors={["brand", "pink", "cyan", "slate"]}
>
<DonutChart
value="amount"
label="Yearly average"
valueFormatter={(v) => `${v} hrs`}
showValue
/>
<Legend>
{data.map((item) => {
const logoObj = item.url && {
name: item.name,
src: getFavicon({ url: item.url }),
};
return (
<LegendItem
key={item.name}
dataKey={item.name}
logo={logoObj}
icon={!item.url ? faGrid2 : undefined}
label={item.name}
value={`${((item.amount / total) * 100).toFixed(1)}%`}
/>
);
})}
</Legend>
</Chart>
);
};

API Reference

Chart

Prop
Type
Default
children React.ReactNode
category string
className string
colors AvailableChartColorsKeys[]
data Record<string, any>[]
hoveredDataKey string | null
onClick (dataKey: string | null) => void
onHover (dataKey: string | null) => void
selectedDataKey string | null

DonutChart

Prop
Type
Default
value string
category string
className string
colors AvailableChartColorsKeys[]
data Record<string, any>[]
label string
onValueChange (value: DonutChartEventProps) => void
showValue boolean false
valueFormatter (value: number) => string

Legend

Prop
Type
Default
children React.ReactNode
className string

LegendItem

Prop
Type
Default
dataKey string
label string
value string
className string
icon IconDefinition
logo { name: string; src: string }
onClick (dataKey: string | null) => void