Skip to main content

ComboChart

ComboChart

Bars, lines and areas over shared categories, on one or two value axes

Tags: chart combo dual-axis comparison reporting

View Demo

The ComboChart draws bars, lines and areas over the same categories, optionally on a second value axis on the right. Use it to show a volume next to a rate, such as monthly closings with average cycle time, or revenue bars with a margin line.

Svelte only (@classic-homes/charts-svelte); React support is pending.

Import​

<script>
  import { ComboChart } from '@classic-homes/charts-svelte';
</script>

Basic Usage​

<script>
  import { ComboChart } from '@classic-homes/charts-svelte';

  const data = {
    categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
    series: [
      { name: 'Closings', type: 'bar', data: [42, 38, 51, 47, 55, 61] },
      {
        name: 'Avg cycle time',
        type: 'line',
        axis: 'secondary',
        data: [118, 121, 114, 109, 112, 104],
      },
    ],
  };
</script>

<ComboChart
  title="Closings and Cycle Time"
  {data}
  valueFormat="integer"
  secondaryValueFormat="days"
  height={400}
/>

Props​

Chart-Specific Props​

Prop Type Default Description
data ComboChartData required Categories and series; each series has a type and an axis
stacked boolean false Stack all bar series (series with a stack id stack regardless)
smooth boolean false Use smooth curved lines for line and area series
showLabels boolean false Show data labels
connectNulls boolean false Draw lines across missing (null) values instead of leaving a gap
secondaryValueFormat ValueFormat - Value format for series on the secondary axis (defaults to valueFormat)
labelFormatter string | ((params: LabelFormatterParams) => string) - Data label template or function
xAxisConfig AxisConfig - Category axis name, tick formatter, rotation and range
yAxisConfig AxisConfig - Left (primary) value axis customisation
secondaryYAxisConfig AxisConfig - Right (secondary) value axis customisation
referenceLines ReferenceLine[] - Lines marking targets, thresholds or statistics ('average', 'median', 'min', 'max')
referenceBands ReferenceBand[] - Shaded value ranges
dataZoom boolean | 'inside' | 'slider' - Let users zoom and pan the category axis
legend ChartLegendConfig - Built-in legend position, alignment, icon and formatter
onLegendChange (visible: Record<string, boolean>) => void - Called when legend items are shown or hidden
renderLegend Snippet<[LegendRenderProps]> - Custom HTML legend (e.g. ChartLegend) instead of the built-in one
onClick (params: DataPointEventParams) => void - Callback when a bar or point is clicked
onHover (params: DataPointEventParams) => void - Callback when hovering over a bar or point

Inherited Base Props​

Prop Type Default Description
title string required Chart title for accessibility (aria-label)
description string - Description for screen readers (generated from the data when omitted)
height number | string 400 Chart height in pixels or CSS value
minHeight number - Minimum height in pixels; also bounds an 'auto' height
maxHeight number - Maximum height in pixels; also bounds an 'auto' height
loading boolean false Show loading skeleton (an overlay when data is already shown)
error string | null - Error message to display
onRetry () => void - Show a Retry button on the error state, calling this
emptyMessage string - Message shown when data is empty
theme 'light' | 'dark' | 'auto' 'auto' Theme preference
animation boolean true Enable/disable animations (respects prefers-reduced-motion)
showLegend boolean true Show chart legend
showTooltip boolean true Show tooltip on hover
showGrid boolean true Show grid lines
valueFormat ValueFormat - Preset ('number', 'integer', 'compact', 'currency', 'currencyCents', 'percent', 'percentValue', 'days'), Intl.NumberFormatOptions or a function; drives the value axis, tooltip, labels and data table
locale string 'en-US' Locale for valueFormat
tooltipFormatter string | ((items: TooltipFormatterParams[]) => string) - Custom tooltip template or HTML function
tooltipValueFormatter (value: number) => string - Format values in the default tooltip (overrides valueFormat there)
option EChartsOption - Raw ECharts option deep-merged over the generated one
onReady (chart: EChartsType) => void - Called with the ECharts instance when it is created
events ChartEventHandlers - Raw ECharts event handlers keyed by event name ('datazoom', 'legendselectchanged', ...)
showDataTable boolean | 'sr-only' 'sr-only' Data table for screen readers only, a visible "Show data" toggle (true) or none (false)
exportable boolean | ChartExportConfig - Show PNG and CSV download buttons, or { image, csv, filename }
patterns boolean false Fill series with decal patterns as well as colour
class string - Custom CSS class

Data Structure​

interface ComboChartData {
  categories: string[]; // Category labels
  series: Array<{
    name: string; // Series name for legend
    data: (number | null)[]; // Values by category; null leaves a gap
    type?: 'bar' | 'line' | 'area'; // How the series is drawn (default 'bar')
    axis?: 'primary' | 'secondary'; // Left or right value axis (default 'primary')
    stack?: string; // Stack group id
    color?: string; // Optional custom color
    labels?: (string | null | undefined)[]; // Per-point label text
  }>;
}

Examples​

Dual Axis with Different Formats​

Each axis, tooltip row and data label uses the format of the axis its series is on.

<ComboChart
  title="Revenue and Gross Margin"
  data={{
    categories: ['Q1', 'Q2', 'Q3', 'Q4'],
    series: [
      { name: 'Revenue', data: [18_400_000, 21_200_000, 19_800_000, 24_600_000] },
      { name: 'Gross margin', type: 'line', axis: 'secondary', data: [0.212, 0.224, 0.208, 0.231] },
    ],
  }}
  valueFormat="compact"
  secondaryValueFormat="percent"
  secondaryYAxisConfig={{ name: 'Margin' }}
/>

Stacked Bars with a Total Line​

<ComboChart
  title="Starts by Product Line"
  data={{
    categories: ['Jan', 'Feb', 'Mar', 'Apr'],
    series: [
      { name: 'Townhomes', stack: 'starts', data: [12, 15, 11, 18] },
      { name: 'Single family', stack: 'starts', data: [28, 31, 26, 34] },
      { name: 'Target', type: 'line', data: [40, 42, 42, 45] },
    ],
  }}
/>

From Records​

Pivot API rows into categories and series with fromRecords, then set each series' type.

<script>
  import { ComboChart, fromRecords } from '@classic-homes/charts-svelte';

  let { rows } = $props(); // [{ month: 'Jan', closings: 42, cycleDays: 118 }, ...]

  const pivot = $derived(
    fromRecords(rows, {
      category: 'month',
      measures: { Closings: 'closings', 'Cycle time': 'cycleDays' },
    })
  );
  const data = $derived({
    categories: pivot.categories,
    series: pivot.series.map((s) =>
      s.name === 'Cycle time' ? { ...s, type: 'line', axis: 'secondary' } : s
    ),
  });
</script>

<ComboChart title="Closings and Cycle Time" {data} secondaryValueFormat="days" />

Target Line and Zoom​

<ComboChart
  title="Weekly Closings"
  {data}
  referenceLines={[{ value: 50, label: 'Goal' }]}
  dataZoom="slider"
/>

Accessibility​

  • The title prop is used as aria-label; a description of each series trend is generated when description is omitted
  • The chart data is rendered as a screen-reader table (showDataTable), with each axis' format
  • Animations respect prefers-reduced-motion
  • patterns adds decal fills for colour-blind readers
On this page