> ## 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.

# Reference lines

export const GraphEmbed = ({src, story, aspectRatio, borderRadius = '14px', args}) => {
  const STORYBOOK_BASE_URL = typeof window !== 'undefined' && 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&globals=mode:readonly${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 reference line marks a constant value — a sales target, a capacity ceiling, a zero baseline — that the rest of the chart is read against. It's a geom like any other: `geom.rule()`, layered into the spec with its own value and style.

## A horizontal goal line

Bind the rule to a constant `y` with `aes: { y: { value } }`. The `{ value }` form is a [constant mapping](/sdk-next/concepts/mappings#variables-and-constants) — it pins the aesthetic to a literal instead of a column:

<GraphEmbed story="features-reference-lines--horizontal-goal-line" />

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

const data = {
  columns: [{ key: 'month' }, { key: 'revenue' }],
  rows: [
    { month: 'Jan', revenue: 1200 },
    { month: 'Feb', revenue: 1800 },
    { month: 'Mar', revenue: 2400 },
    { month: 'Apr', revenue: 1600 },
    { month: 'May', revenue: 3200 },
    { month: 'Jun', revenue: 2800 },
  ],
};

export function RevenueWithTarget() {
  const spec = useMemo(
    () =>
      pipe(
        createSpec({ x: 'month', y: 'revenue' }),
        geom.bar(),
        geom.rule({
          aes: { y: { value: 2500 } },
          params: { label: 'Target' },
        }),
        scale.x(),
        scale.y()
      ),
    []
  );

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

The rule value participates in the y-scale's auto-domain — set a target above the tallest bar and the axis grows to fit it, so the line is never clipped off the top.

## The label

A rule's `params` carry its label and nothing else:

<ParamField path="label" type="string">
  Optional inline text drawn alongside the line. Omit to draw a bare line.
</ParamField>

<ParamField path="labelPosition" type="'start' | 'end'" default="start">
  Anchors the label at the start or end of the line.
</ParamField>

Under [`coord.flip()`](/sdk-next/concepts/coordinate-systems) a rule runs vertically, where `'end'`
puts the label at the top of the line rather than at its foot. `ToggleGoalLineCommand` and
`SetStatLineCommand` pick that way round for you when they build a line under a flipped coord.

## Painting the line

Stroke and label typography come from the [stylesheet](/sdk-next/config/styling), the same as every
other geom. `style.geom.rule()` takes `stroke`, `strokeWidth` and `dashArray`; `style.geom.rule.label()`
takes `fontFamily`, `fontSize`, `fontWeight`, `lineHeight` and `textColor`. Both take a `{ layer }` option, so a
single rule can be painted without touching the others. Leave `textColor` undeclared and the label ink is
contrast-picked against the pill.

```tsx theme={null}
import {
  createSpec,
  pipe,
  geom,
  scale,
  styles,
  style,
  token,
} from '@graphysdk/react';

pipe(
  createSpec({ x: 'month', y: 'revenue' }),
  geom.bar(),
  geom.rule({
    id: 'floor',
    aes: { y: { value: 1000 } },
    params: { label: 'Floor' },
  }),
  scale.x(),
  scale.y(),
  styles({
    overrides: [
      style.geom.rule(
        { stroke: token('ruleLine'), strokeWidth: 2, dashArray: [] },
        { layer: 'floor' }
      ),
      style.geom.rule.label(
        { fontSize: 11, fontWeight: 600, textColor: '#FFFFFF' },
        { layer: 'floor' }
      ),
    ],
  })
);
```

The built-in stylesheet paints every rule **dashed** at 1px in the `ruleLine` token, so a bare
`geom.rule()` draws a dashed line. Redefine `ruleLine` through `styles({ tokens: { ruleLine } })`
to recolour every rule, goal line and average line at once.

## Vertical lines

Mark a constant along the x-axis with `aes: { x: { value } }` instead:

<GraphEmbed story="features-reference-lines--vertical-threshold" />

```tsx theme={null}
geom.rule({ aes: { x: { value: 40 } }, params: { label: 'Threshold' } });
```

<Note>
  A vertical rule requires a **numeric** x-axis — it marks a constant x-value,
  which only has meaning when x is continuous. Categorical and date x-axes
  support horizontal rules only. A rule reads a scalar from exactly one axis, so
  supplying both `x` and `y` is an `INVALID_RULE_MAPPING` error, as is supplying
  neither.
</Note>

## Multiple rules

Rules compose like any other layer, and paint in spec order — a rule added after `geom.bar()` draws in front of the bars. Stack as many as you need:

<GraphEmbed story="features-reference-lines--multiple-rules" />

```tsx theme={null}
pipe(
  createSpec({ x: 'month', y: 'revenue' }),
  geom.bar(),
  geom.rule({
    id: 'floor',
    aes: { y: { value: 1000 } },
    params: { label: 'Floor' },
  }),
  geom.rule({ aes: { y: { value: 2500 } }, params: { label: 'Target' } }),
  geom.rule({
    id: 'ceiling',
    aes: { y: { value: 3500 } },
    params: { label: 'Ceiling' },
  }),
  scale.x(),
  scale.y(),
  styles({
    overrides: [
      style.geom.rule({ dashArray: [0, 2] }, { layer: 'floor' }),
      style.geom.rule({ dashArray: [0, 2] }, { layer: 'ceiling' }),
    ],
  })
);
```

Name the rules you want painted apart — an `id` on the layer is what a `{ layer }`-scoped entry
addresses.

## Flipped and secondary axes

A rule tracks the data space, not the screen. Under [`coord.flip()`](/sdk-next/concepts/coordinate-systems) a `{ y: { value } }` rule still marks a constant y — the renderer paints it as a vertical line because the y-axis now runs horizontally. To pin a rule to a [secondary axis](/sdk-next/graph-types/combo), add `yScaleType: 'secondary'`:

```tsx theme={null}
geom.rule({
  aes: { y: { value: 7 } },
  yScaleType: 'secondary',
  params: { label: 'SLA' },
});
```

## An average line

A rule's value can also be computed. Give it [`stat.mean()`](/sdk-next/advanced/statistics) instead
of a constant and the line lands at the layer's average:

```tsx theme={null}
import { createSpec, pipe, geom, scale, stat } from '@graphysdk/react';

pipe(
  createSpec({ x: 'month', y: 'revenue' }),
  geom.bar(),
  geom.rule({ stat: stat.mean() }),
  scale.x(),
  scale.y()
);
```

## Editing a rule at runtime

Four [commands](/sdk-next/advanced/commands-and-history) cover reference lines in an editing UI:

| Command | Params | What it does |
| - | - | - |
| `ToggleGoalLineCommand` | `showGoalLine`, `layerId?`, `value?`, `label?`, `removedLayer?` | Adds or removes the goal line. `value` and `label` are read on the way on; `removedLayer` is what makes undo bring the old one back intact |
| `SetRuleValueCommand` | `layerId?`, `value` | Moves an existing rule along its axis |
| `SetRuleLabelCommand` | `layerId?`, `label` | Sets the inline text; `null` removes it |
| `SetStatLineCommand` | `line: 'none' \| 'trend' \| 'average'`, `layerId?`, `method?`, `group?`, `label?`, `removedLayer?` | The single switch behind "show a trendline" and "show an average" — swapping one for the other is one undo entry |

```tsx theme={null}
import { ToggleGoalLineCommand } from '@graphysdk/viz-engine';

dispatch(
  new ToggleGoalLineCommand({
    showGoalLine: true,
    value: 2500,
    label: 'Target',
  })
);
```

## Related

* [Styling](/sdk-next/config/styling) — the stylesheet entries that paint a rule and its label
* [Statistics](/sdk-next/advanced/statistics) — `stat.mean()` and the fitted trends a rule can carry
* [Mappings & aesthetics](/sdk-next/concepts/mappings#variables-and-constants) — the `{ value }` constant form
* [Scales](/sdk-next/concepts/scales) — how the rule value extends the auto-domain
* [Coordinate systems](/sdk-next/concepts/coordinate-systems) — rules under `coord.flip()`


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.