> ## Documentation Index
> Fetch the complete documentation index at: https://docs.graphy.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Combo

export const GraphEmbed = ({src, story, aspectRatio, borderRadius = '14px', args}) => {
  const STORYBOOK_BASE_URL = window.location.hostname === 'localhost' ? 'http://localhost:6006' : 'https://storybook-sdk.vercel.app';
  function buildArgsString(args) {
    if (!args) return '';
    const encodeValue = value => {
      if (value === null) return '!null';
      if (value === undefined) return '!undefined';
      if (typeof value === 'boolean') return `!${value}`;
      if (typeof value === 'number') return String(value);
      return encodeURIComponent(String(value));
    };
    const properties = Object.entries(args).map(([key, value]) => `${key}:${encodeValue(value)}`).join(';');
    return `&args=${properties}`;
  }
  const resolvedSrc = story ? `${STORYBOOK_BASE_URL}/iframe.html?id=${story}&viewMode=story&embed=1${buildArgsString(args)}` : src;
  const resolvedAspectRatio = aspectRatio ?? (story ? '16 / 9' : '10 / 6');
  return <iframe src={resolvedSrc} loading="lazy" allowfullscreen="true" style={{
    width: '100%',
    aspectRatio: resolvedAspectRatio,
    border: 'none',
    borderRadius,
    colorScheme: 'light'
  }} />;
};

A combo chart layers two geoms with different y-scales — typically bars for one metric and a line for another on a second axis. It's how you show two series whose units or magnitudes don't share a scale.

## Dual-axis example

Give each geom its own `y` through a [layer-local mapping](/sdk-next/concepts/mappings#where-a-mapping-lives), and bind the line to the secondary axis with `yScaleType: 'secondary'`:

<GraphEmbed story="chart-types-combo-graph--revenue-bars-and-growth-line" />

```tsx theme={null}
import { useMemo } from 'react';
import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine';
import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer';

const data = {
  columns: [
    { key: 'quarter', label: 'Quarter' },
    { key: 'revenue', label: 'Revenue' },
    { key: 'growth', label: 'Growth' },
  ],
  rows: [
    { quarter: 'Q1', revenue: 12000, growth: 5 },
    { quarter: 'Q2', revenue: 15000, growth: 25 },
    { quarter: 'Q3', revenue: 14000, growth: -7 },
    { quarter: 'Q4', revenue: 18500, growth: 32 },
    { quarter: 'Q5', revenue: 21000, growth: 14 },
    { quarter: 'Q6', revenue: 19500, growth: -7 },
  ],
};

export function RevenueAndGrowth() {
  const spec = useMemo(
    () =>
      pipe(
        createSpec({ x: 'quarter' }), // shared x
        geom.bar({ aes: { y: 'revenue' } }), // primary axis
        geom.line({ aes: { y: 'growth' }, yScaleType: 'secondary' }), // secondary axis
        scale.x(),
        scale.y(),
        scale.ySecondary(),
      ),
    [],
  );

  return (
    <GraphProvider input={spec} data={data}>
      <GraphRenderer />
    </GraphProvider>
  );
}
```

Each scale trains only on the layers bound to it, so the two metrics keep independent ranges.

## Related

* [Mappings & aesthetics](/sdk-next/concepts/mappings) — layer-local mappings
* [Scales](/sdk-next/concepts/scales) — the secondary y-scale
* [Geoms & layers](/sdk-next/concepts/geoms) — how layers stack
