Core concepts
Stories
Scrolly keeps one graphic in place and changes its state as the reader scrolls, swipes or steps through a list of text steps.
Scrolly takes a list of steps and a render function. Each step has its text in content, plus any other keys you choose. Those keys hold the state the graphic should be in at that step. Because the same chart instance receives each new state, ECharts morphs between them.
Scroll mode
The graphic pins while the steps scroll past. Scroll this page slowly.
Renewable generation, TWh
2020, 2022 and 2024 combined
The step crossing the middle of the viewport is the active one. In a container narrower than 768 pixels, like this column, the text cards pass over the graphic instead of sitting beside it; the home page shows the side-by-side layout. The source of this demo:
import { BarChart, Scrolly } from "insightcharts";
// Each step changes one thing: the grouping field or the highlight.
const steps = [
{ id: "sources", x: "source", highlight: undefined, content: "Renewable generation in three regions, grouped by source. Hydro generated 11,520 TWh, 53% of the total." },
{ id: "solar", x: "source", highlight: "Solar", content: "Solar generated 3,870 TWh, 18% of the total." },
{ id: "regions", x: "region", highlight: undefined, content: "The same rows grouped by region." },
{ id: "asia", x: "region", highlight: "Asia", content: "Asia generated 10,590 TWh, 49% of the total." },
];
export default function ScrollStory({ stickyTop = 72, stepHeight = "70vh" }: { stickyTop?: number; stepHeight?: string }) {
return (
<Scrolly steps={steps} mode="scroll" stickyTop={stickyTop} stepHeight={stepHeight}>
{({ step }) => (
<BarChart
data="/data/energy.csv"
x={step.x}
y="twh"
transform={{ type: "aggregate", groupBy: step.x, measures: { twh: { op: "sum", field: "twh" } } }}
highlight={step.highlight}
sort="desc"
orientation="horizontal"
format="compact"
height={280}
title="Renewable generation, TWh"
subtitle="2020, 2022 and 2024 combined"
note="Illustrative data"
/>
)}
</Scrolly>
);
}
Steps and swipe modes
mode="steps" shows one card with previous and next buttons, and needs no scrolling room: use it inside dashboards, slides and cards. mode="swipe" turns the steps into cards that swipe horizontally under the graphic. In both, the left and right arrow keys move between steps when focus is inside the story.
Monthly visits by channel
Thousands of visits per month
import { useState } from "react";
import { LineChart, Scrolly } from "insightcharts";
const visits = [
{ month: "Jan", Search: 42, Social: 30, Email: 12 },
{ month: "Feb", Search: 44, Social: 34, Email: 13 },
{ month: "Mar", Search: 47, Social: 41, Email: 13 },
{ month: "Apr", Search: 49, Social: 52, Email: 14 },
{ month: "May", Search: 50, Social: 61, Email: 15 },
{ month: "Jun", Search: 53, Social: 58, Email: 21 },
];
const steps = [
{ id: "search", highlight: "Search", content: "Search visits rose from 42,000 in January to 53,000 in June." },
{ id: "social", highlight: "Social", content: "Social visits rose from 30,000 in January to 58,000 in June." },
{ id: "email", highlight: "Email", content: "Email visits rose from 15,000 in May to 21,000 in June." },
];
export default function StoryModes() {
const [mode, setMode] = useState<"steps" | "swipe">("steps");
return (
<div style={{ display: "grid", gap: 16 }}>
<div role="group" aria-label="Story mode" style={{ display: "flex", gap: 8, fontSize: 13 }}>
{(["steps", "swipe"] as const).map((m) => (
<button key={m} type="button" aria-pressed={mode === m} onClick={() => setMode(m)} style={{ padding: "4px 12px", borderRadius: 999, border: "1px solid var(--border)", background: mode === m ? "var(--accent)" : "transparent" }}>
mode="{m}"
</button>
))}
</div>
<Scrolly steps={steps} mode={mode}>
{({ step }) => <LineChart data={visits} x="month" y={["Search", "Social", "Email"]} highlight={step.highlight} title="Monthly visits by channel" subtitle="Thousands of visits per month" note="Illustrative data" height={240} />}
</Scrolly>
</div>
);
}
| Mode | Behaviour |
|---|---|
"auto" (default) | "swipe" on touch devices in a container under 640 pixels wide, "scroll" otherwise. |
"scroll" | Sticky graphic, steps scroll past. |
"swipe" | Horizontal scroll-snapped cards, with previous and next buttons. |
"steps" | One card at a time, buttons only. |
Writing steps
const steps = [
{ id: "all", content: "Hydro generated 11,520 TWh, 53% of the total.", x: "source" },
{ id: "solar", content: "Solar generated 3,870 TWh, 18% of the total.", x: "source", highlight: "Solar" },
{ id: "region", content: "Asia generated 10,590 TWh, 49% of the total.", x: "region", highlight: "Asia" },
];
<Scrolly steps={steps}>
{({ step, index, progress, count }) => <BarChart x={step.x} highlight={step.highlight} ... />}
</Scrolly>- Change one thing per step: the data, the highlight, or the encoding.
contentis one or two plain sentences that state what the chart shows at that step, with the number from the data. It follows the same rules as a title; see writing titles. It accepts any React node.idis rendered as the step's element id, so#solardeep-links to it.- Return the same component from the render function at every step where you want a morph. Swapping to a different component remounts the chart.
The render function receives step, index, count and progress, a number from 0 to 1 through the active step. progress updates in scroll mode only and is 0 elsewhere.
Props
| Prop | Default | Purpose |
|---|---|---|
steps | required | The steps. Each needs content; other keys are yours. |
children | required | (state) => ReactNode, the graphic for the active step. |
mode | "auto" | See above. |
side | "left" | Which side the text column sits on in scroll mode. |
stickyTop | 0 | Distance from the top of the viewport at which the graphic pins. Set it to the height of a sticky header. |
stepHeight | "80vh" | Scroll distance per step, as a CSS length. |
index | uncontrolled | Controlled active step. |
onStepChange | Called with (index, step). | |
theme, colorMode | Same as theme and mode on charts. |
Accessibility and fallbacks
All of the step text is in the document. In scroll mode every step is a <section> in reading order, so the text can be read with scripting or animation off. Steps mode announces the active card through a polite live region. Charts skip their transitions when the reader prefers reduced motion.