Pricing

ChartGroup handbook

v0.4.3 · @snowui-design-system/components

License: Before reading component documentation, source, or npm usage instructions for the first time, read the full LICENSE. Reuse that review for the same task and version. Public API configuration and internal implementation changes have different license implications. All components use the unified runtime package; source rights remain governed by the license.

Overview

Combine charts in one area with shared category hover, overlaid drawing, and coordinated axes to compare related data.

When to use it

Use ChartGroup to coordinate charts under shared settings. Each child chart still owns its public data and visual props; do not modify private marks from the page.

  • Provide shared axis and interaction settings only. ChartGroup does not create charts, titles, or legends.

  • Compose BarChart and LineChart explicitly and coordinate their width, height, and position to share one canvas.

  • The default root accessible name follows LocaleProvider through chartGroup.name; an explicit aria-label takes priority. The data-cell group uses chartGroup.cells. When no corresponding xLabels entry is provided, chartGroup.cell uses the complete {{index}} template with one-based numbering; explicit category names remain unchanged. Business names, data values, and numeric formatting remain controlled by the caller.

Quick start

This example shows the recommended basic composition. See the sections below for more cases and composition limits.

<script lang="ts">	import { Button, Frame } from '@snowui-design-system/components';	import { BarChart, ChartGroup, LineChart } from '@snowui-design-system/components';	const weeks = Array.from({ length: 12 }, (_, index) => `W${index + 1}`);	const previousQuarterVisits = [4000, 6000, 5200, 7600, 3600, 6600, 4500, 7200, 4800, 8000, 3400, 6400];	const currentQuarterVisits = [3250, 6875, 4125, 8750, 2625, 5750, 3250, 6875, 4125, 8750, 2625, 5750];	// Compare the first 12 weeks of each quarter in the same week order; the line has two samples per week, with odd indexes centered on bars.	const sampleLabels = Array.from({ length: 25 }, (_, index) =>	    index % 2 === 1 ? weeks[(index - 1) / 2] : `Week ${index / 2 + 1} start`	);	const organicSearch = [3510, 3990, 4440, 1360, 2020, 2680, 1750, 3190, 4500, 4290, 3980, 5970, 7910, 7700, 7550, 8630, 9690, 6380, 6950, 7550, 5070, 6080, 7100, 7310, 7550];	const referralForecast = [1660, 2630, 3550, 3490, 4590, 3780, 2920, 5710, 6100, 6470, 4040, 1650, 1680, 1730, 3220, 4680, 4120, 3560, 6150, 8740, 8270, 7820, 9030, 8810, 8550];	// Shared hover follows the week index; the line uses each week’s center sample.	let activeWeek: number | null = $state(null);	function selectSample(index: number | null) {	    activeWeek = index === null ? null : Math.min(weeks.length - 1, Math.floor(index / 2));	}	let trafficBars: { replay: () => void; reverseAppearance: () => void } | undefined;	let trafficLines: { replay: () => void; reverseAppearance: () => void } | undefined;</script> <Frame column style="align-items: center; width: 100%; gap: var(--gap-4) var(--gap-3);">    <ChartGroup        width="100%"        height={264}        xLabels={weeks}        yMin={0}        yMax={10000}        hoverPanelPlacement="pointer"        hoverPanelVariant="dark"        activeCellIndex={activeWeek}        onCellHoverChange={(index) => activeWeek = index}        showYAxis={false}        showGrid={false}        showGuides={false}        aria-label="SnowUI website traffic: direct visits in the first 12 weeks of the previous and current quarters, with current-quarter organic search and referral forecast"    >        <BarChart            bind:this={trafficBars}            style="position: absolute; inset: 0; --snowui-bar-chart-axis-line-color-override: transparent;"            width="100%"            height={264}            valueFormat={{ fractionDigits: 0 }}            mode="overlay"            barWidth={16}            groupGap={4}            cornerRadius={20}            data={currentQuarterVisits.map((value, index) => ({                name: weeks[index],                bars: [                    { name: 'Previous quarter', value: previousQuarterVisits[index] },                    { name: 'Current quarter', value }                ],                color: 'var(--secondary-blue)'            }))}        />        <LineChart            bind:this={trafficLines}            style="position: absolute; top: 0; left: 0; z-index: 1;"            width="100%"            height={240}            showXAxis={false}            xAxisMode="edge"            xLabels={sampleLabels}            activeCellIndex={activeWeek === null ? null : activeWeek * 2 + 1}            onCellHoverChange={selectSample}            valueFormat={{ fractionDigits: 0 }}            series={[                {                    name: 'Organic search',                    data: organicSearch.map((value, index) => ({                        value,                        marker: index % 2 === 1                    })),                    color: 'var(--secondary-purple)',                    strokeWidth: 1,                    markers: true,                    area: true                },                {                    name: 'Referral traffic forecast',                    data: referralForecast,                    color: 'var(--secondary-indigo)',                    strokeWidth: 1,                    strokeStyle: 'dashed',                    markers: false,                    area: true                }            ]}        />    </ChartGroup>    <div style="display: flex; justify-content: center; flex-wrap: wrap; gap: var(--gap-4) var(--gap-3); width: 100%;">        <Button onclick={() => { trafficBars?.replay(); trafficLines?.replay(); }}>Replay animation</Button>        <Button onclick={() => { trafficBars?.reverseAppearance(); trafficLines?.reverseAppearance(); }}>Reverse animation</Button>    </div></Frame>

API

Keyword Type Default Description
width number | string 800 Root canvas width. Numbers use px; CSS length strings may use percentages. Shared hit and panel bounds use the final measured size. Each overlaid child chart still needs its own width, usually 100%.
height number | string 400 Root canvas height as a number or CSS length. Percentage height requires a definite parent height. Child chart heights are not set automatically; an overlaid chart without the X axis must account for the 24px occupied by the single visible X axis.
xLabels string[] [] Complete category labels. Array order defines the shared zero-based activeCellIndex; it also provides a fallback when a child chart omits xLabels. Label thinning does not remove categories.
yMin number — Pass the Y-axis lower bound to child charts. Without it, each child derives its own range; set a shared bound for an overlay chart.
yMax number — Pass the Y-axis upper bound to child charts. Without it, each child derives its own maximum, which can misalign overlaid charts.
yTicks number[] — Pass explicit Y-axis tick values to children instead of automatic ticks. Displayed labels may still be thinned; a child’s own yTicks takes precedence.
yTickCount number 6 Target count for automatic Y ticks, not an exact count. Child domain, nice steps, and label thinning determine the final count. Prefer implicit yTicks.
yTickFormatter (value: number) => string — Format each Y tick for display without changing its value, domain, or hover value. A child formatter takes precedence; otherwise its default format applies.
showXAxis boolean true Default visibility of the X axis and labels passed to child charts. ChartGroup draws no axis itself. In an overlay, enable the X axis on only one child.
showYAxis boolean false Default Y-axis label visibility passed to children and included in the group’s top safe-area calculation. Child settings take precedence. Each child measures its own left/right label space; ChartGroup does not equalize widths.
showGrid boolean false Default grid visibility passed to children. Children draw grid lines at visible Y ticks; use only one grid renderer in an overlay. Explicit child settings take precedence.
showGuides boolean false Issing auxiliary line switches; LineChart can draw data point auxiliary lines, and BarChart currently has no additional guide graphics. The explicit configuration of the sub-diagram is prioritized.
xAxisMode ChartGroupXAxisMode "edge" Pass LineChart point placement: edge puts endpoints at the plot edges; cell centers points in category cells. Use cell when overlaying a line on bars. Shared hit areas always follow category cells.
xLabelAutoCull boolean true The X-axis label automatically dilutes the switch, and the visible label is reduced according to the actual measured text and available width of the sub-diagram; do not delete data, path, classification grid or shared index. The explicit value of the sub-diagram is prioritized.
xMaxLabelCount number — Maximum visible X-axis labels. A child may show fewer when space is tight; the complete categories and hover data remain. Explicit child settings take precedence.
yLabelAutoCull boolean true The Y-axis label is automatically thinned, and the visible label is reduced when there is not enough space; the actual layout is processed by each sub-diagram, and the explicit value of the sub-diagram is given priority.
yMaxLabelCount number — Maximum visible Y-axis labels. Without yTicks, this also informs each child’s automatic tick count. Explicit child settings take precedence.
plotBounds ChartGroupPlotBounds {} The drawing area of the relative root canvas is reduced, and the fields top/right/bottom/left are all px numbers. It is used to share hit and panel bounds; top can only increase the maximum safe area of all sub-drates. The rest of the edges do not rewrite the geometry of the sub-dragram itself. When closing all X axes, you can explicitly set bottom: 0.
hoverMode ChartGroupHoverMode "cell" cell coordinates hover for the same category. item leaves hit testing with each child chart and creates no group-level cell hit layer.
activeCellIndex number | null — Controlled category index, zero-based in xLabels. null explicitly means no active category; undefined uses internal uncontrolled state.
defaultActiveCellIndex number | null null Initial uncontrolled index only. Later changes do not reset state; it does not control selection when activeCellIndex is supplied.
onCellHoverChange (index: number | null) => void — Called with a new index or null when the shared active category changes through pointer, keyboard focus, or a child chart. Internal interaction clamps finite indexes to valid categories. Controlled mode only notifies; the caller owns the value, and the callback return value does not cancel the change.
showHitLayer boolean true Enable the group SVG category hit layer only when hoverMode="cell" and showHoverPanel=false. With the shared panel, root pointer coordinates and child hits handle hover. false does not disable child-chart interaction.
adaptiveContrast boolean true Adaptive black/white contrast responds to a solid host background and same-origin overlaid bars, changing default black/white nodes and black lines. Explicit child settings win; palette nodes do not change. The light/dark threshold is sRGB gray 0.5 and follows current bar shape and opacity. Images and cross-chart coordinate transforms are unsupported. Enabled by default; may be disabled explicitly.
showHoverPanel boolean true Merge child reports into one shared hover panel. In cell mode the root pointer selects the current category. Enabled by default. When disabled, children may show their own panels according to their settings. For number labels, hide a child panel or explicitly enable showHoverLabel. Empty data shows no panel.
showHoverLabel boolean — Control child numeric hover labels together. If omitted, follow each child setting and the shared-panel default. Explicit true/false overrides the corresponding child setting; true permits numeric labels alongside the shared panel.
alwaysShowHoverLabel boolean false Turn on the numerical label for the sub-diagram to be often displayed; alwaysShowHoverLabel and the sub-diagram itself alwaysShowHoverLabel are logically or merged, so the group-level false does not turn off the explicit display of the sub-gragram. It is still under the control of the final showHoverLabel.
hoverPanelVariant ChartHoverPanelVariant "light" The appearance of the only shared panel: light is light, dark is dark, and surface is a surface with a blurred background; do not modify the color of the column, fold line or sub-figure numerical label.
hoverPanelPlacement ChartHoverPanelPlacement "pointer" Shared panel positioning: anchor-side follows the side of the data graphic, anchor-top aligns the highest data anchor, pointer follows the pointer; pin-* is fixed in the corresponding direction of the drawing area. Panel collision processing is completed by shared ChartHoverPanel.
showHoverPanelLabel boolean false Show the active category name above the shared panel, using xLabels first and falling back to the first child-panel label.
children Snippet — Parameterless Snippet for explicitly composed charts. ChartGroup does not generate BarChart, LineChart, titles, or legends.
class string "" The class attached to the root Frame is merged with the internal root class name; it is not sent to the sub-chart.
style string "" The additional inline style of the root Frame is located after the declaration of the canvas size variable; it is used for public coverage such as host positioning, and is not sent to the sub-drater.
enableResponsive boolean true Whether to turn on the response ability of the root Frame; the actual measurement size of the chart canvas exists independently, and the percentage width/height does not depend on this switch.
enableContext boolean true Enable the root Frame’s general Context capability; this does not disable ChartGroup’s shared category, axis, and hover protocol for children.
enableScaleObserver boolean false The compatibility switch of the root Frame scale observer is turned off by default; it does not control the actual measurement size of the chart.
enableDefaultMotion boolean true Whether to enable the default dynamic effect of the root Frame; the appearance dynamic effect of the sub-diagram itself will not be uniformly turned on or off, and the sub-diagram will use their own public dynamic effect Props and instance methods.

See the base component documentation for inherited capabilities not redeclared here. This table lists only named public Props declared by this component.

Usage boundaries and details

HoverPanel shares a three-column layout. The dot-to-name gap prefers --gap-1 and the name-to-value gap prefers --gap-2; both retain a 4 CSS px floor in LTR and RTL. At a 16px root font size the defaults remain 4px / 8px, and larger tokens are preserved. This floor does not apply to vertical panel sections or chart center annotations.

Design principle

  • Chart composition host: ChartGroup coordinates axes and interaction state across chart components. It does not replace each chart’s public data and visual API.

  • Child charts remain independent: Pass each child its own data, colors, axes, and motion settings. ChartGroup does not copy private geometry.

  • Cell hover: When hoverMode="cell", the container draws the classification cell hit layer on the top layer; each cell is based on the classification center, and the left and right half of the cell width, and the mouse enters the area to trigger the sharing activeCellIndex.

  • Coordinate axis control: ChartGroup can uniformly issue coordinate axis display, grid, auxiliary line, X/Y label thinning, Y axis domain / ticks / formatter and LineChart's xAxisMode. When the sub-chart explicitly enters the prop of the same name, the sub-chart's own configuration is given priority.

  • Responsive canvas: Root layer observes the final display size; width="100%" / height="100%" share the hit layer and hover panel bounds to use the real canvas size.

  • Complete classification semantics: ChartGroup always sends the complete xLabels and the complete classification cell to the subchart; the extraction is only performed by the subchart that actually draws the coordinate axis, without reducing the shared hit cell or active index range.

  • Shared vertical boundary: The top uses the content-aware safe area; ChartGroup collects the graphics, marker and Y-axis requirements of the BarChart / LineChart sub-chart and takes the maximum value, so that all overlay layers and shared hit layers use the same top edge. The hover value does not increase the height of the shared top, and remains at 4px above the graphic. If necessary, it can overflow the top edge of the drawing area. Only the X-axis 8px spacing and 16px label height are reserved at the bottom.

  • Overlay canvas: The root layer is a fixed-size canvas of position: relative, and the sub-chart can be superimposed by class="absolute top-0 left-0".

  • Single axis renderer: In an overlay, let one child render axes and grid; disable duplicate axes, grid, and labels on the others.

  • No built-in business shell: No built-in title, card background, legend or toolbar; these are combined by external Frame / Card.


2.1 Props

Keyword Type Default Description
loading boolean — Loading state. Keep the final canvas size, shared coordinates, and child chart nodes while each child reads loading context and holds its starting visual state.

The rest of the undeclared attributes will be passed to the root layer Frame.

2.2 Sub-chart protocol

ChartGroup exposes Chart to future generations through context:

type ChartGroupHoverMode = "item" | "cell"; interface ChartGroupContext {  hoverMode: () => ChartGroupHoverMode;  activeCellIndex: () => number | null;  setActiveCellIndex: (index: number | null) => void;  xLabels: () => string[];  yMin: () => number | undefined;  yMax: () => number | undefined;  yTicks: () => number[] | undefined;  yTickCount: () => number;  yTickFormatter: () => ((value: number) => string) | undefined;  showXAxis: () => boolean;  showYAxis: () => boolean;  showGrid: () => boolean;  showGuides: () => boolean;  xAxisMode: () => "edge" | "cell";  xLabelAutoCull: () => boolean;  xMaxLabelCount: () => number | undefined;  yLabelAutoCull: () => boolean;  yMaxLabelCount: () => number | undefined;  plotTopInset: () => number;  registerPlotTopInset?: (source: string, resolveInset: () => number) => () => void;  showHoverPanel?: () => boolean;  showHoverLabel?: () => boolean | undefined;  setHoverPanelData?: (source: string, data: ChartHoverPanelData | null) => void;}

Current access components:

  • BarChart: bars, segments, overlays, or grouped bars with the same groupIndex respond to a shared cell hover.

  • LineChart: markers with the same pointIndex respond to cell hover; their own transparent hit areas remain available.

  • When showHoverPanel is turned on, the sub-chart only reports the current hover panel data, and ChartGroup renders a shared panel uniformly; the sub-chart hides its own hover label by default, but it can still be displayed at the same time when explicitly showHoverLabel={true}.


Layout model

Main layout model: composite management layout.

  • The root layer is Frame, which is used as a fixed-size combination canvas.

  • The sub-chart is declared by the external explicit declaration level and positioning, and the common writing method is absolute top-0 left-0.

  • The shared cell hit layer is the SVG coverage layer of absolute positioning inside the container. By default, it is located above the sub-chart; each classification hit area is calculated according to the left and right half of the classification center.

  • Do not access the three-slot protocol, and do not undertake the title, description, legend or card layout.

See LayoutProtocol.md §12.6 for details (source code reference: specs/maintainers/LayoutProtocol.md).


Barrier-free integration with Frame

  • The root uses role="group" and a locale-aware default accessible name ("Chart group" in en-US); an explicit aria-label takes priority.

  • When the shared panel is not turned on and hoverMode="cell", showHitLayer=true, each cell hit rect uses role="button", tabindex="0" and classification label as aria-label.

  • loading retains the combination canvas, shared coordinates and sub-diagram nodes; the root Frame explicitly closes the Skeleton, and the sub-diagram synchronously restores to the starting point of the dynamic effect through the loading context.

  • The explicit input enableResponsive / enableContext / enableScaleObserver / enableDefaultMotion.

  • class, style and undeclared attributes are transmitted to the root Frame.


Give a demonstration

<ChartGroup width="100%" height={400} xLabels={labels} xAxisMode="cell" yMin={0} yMax={120}>  <BarChart style="position: absolute; inset: 0;" width="100%" height={400} data={bars} />  <LineChart    style="position: absolute; top: 0; left: 0; z-index: 1;"    width="100%"    height={376}    showXAxis={false}    showYAxis={false}    showGrid={false}    xAxisMode="cell"    xLabels={labels}    series={series}  /></ChartGroup>

ChartGroup style overlay

Read the full LICENSE first; reuse that review for the same task, version, and scope.

First use public component Props to match the desired appearance, preserving the existing look where possible. If changes are necessary, follow the style selection and override order: use your own class (including Tailwind already in the project) or an instance style. The names below are public overrides actually consumed by source; check their scope and do not modify private DOM. Choose the correct SnowUI token family. External apps need not install SnowUI website utilities.

Variable Purpose and default reference
--snowui-chart-group-height-override Overall height (default 400px)
--snowui-chart-group-width-override Overall width (default 800px)

Design Guide · Token · Combination boundary

ChartGroup type and status

Required Props: no named required Props.

Bindable Props: this component declares no named two-way binding; handle state updates through public callbacks or its host.

Inheritance statement: Omit<FrameProps, "children" | "skeleton" | "renderHints">. The inheritance ability is subject to the public document of the base class; the fields in Omit have been excluded and cannot be used as they are according to the base class.

Share Frame API · Return API

The following type declarations are actually referenced by public Props and come from source. A Svelte Snippet denotes a content fragment; ComponentProps denotes the Props of the corresponding component.

ChartGroupXAxisMode

Source: components/src/lib/chart/group-context.ts

export type ChartGroupXAxisMode = "edge" | "cell";

ChartGroupPlotBounds

Source: components/src/components/ChartGroup.svelte

export interface ChartGroupPlotBounds {        top?: number;        right?: number;        bottom?: number;        left?: number;    }

ChartGroupHoverMode

Source: components/src/lib/chart/group-context.ts

export type ChartGroupHoverMode = "item" | "cell";

ChartHoverPanelVariant

Source: components/src/lib/chart/hover-panel.ts

export type ChartHoverPanelVariant = "dark" | "light" | "surface";

ChartHoverPanelPlacement

Source: components/src/lib/chart/hover-panel.ts

export type ChartHoverPanelPlacement =    | "anchor-top"    | "anchor-side"    | "pointer"    | "pin-top-left"    | "pin-top"    | "pin-top-right"    | "pin-right"    | "pin-bottom-right"    | "pin-bottom"    | "pin-bottom-left"    | "pin-left"    | "pin-center";