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

# Highlights

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 highlight emphasises the observations that match a predicate by de-emphasising the ones that don't. You describe *what* to match — not which pixels to paint — so the highlight tracks the data through re-sorts, filters, and updates.

<GraphEmbed story="features-highlights--regional-sales" />

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

const data = {
  columns: [{ key: 'quarter' }, { key: 'region' }, { key: 'sales' }],
  rows: [
    { quarter: 'Q1', region: 'EU', sales: 1200 },
    { quarter: 'Q1', region: 'US', sales: 1500 },
    { quarter: 'Q2', region: 'EU', sales: 1800 },
    { quarter: 'Q2', region: 'US', sales: 1600 },
    { quarter: 'Q3', region: 'EU', sales: 2400 },
    { quarter: 'Q3', region: 'US', sales: 2200 },
    { quarter: 'Q4', region: 'EU', sales: 2800 },
    { quarter: 'Q4', region: 'US', sales: 3100 },
  ],
};

export function RegionalSales() {
  const spec = useMemo(
    () =>
      pipe(
        createSpec({ x: 'quarter', y: 'sales', color: 'region' }),
        geom.bar({ position: 'dodge' }),
        scale.x(),
        scale.y(),
        scale.color.palette(),
        highlight({ variable: 'region', eq: 'EU' }) // EU bars stay vivid, the rest fade
      ),
    []
  );

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

## Predicates

The first argument to `highlight()` is a predicate — a test against your post-transform columns. Field predicates name a `variable` and a comparison:

| Predicate                                      | Matches                                          |
| ---------------------------------------------- | ------------------------------------------------ |
| `{ variable: 'region', eq: 'EU' }`             | exact value                                      |
| `{ variable: 'quarter', oneOf: ['Q3', 'Q4'] }` | any of a set                                     |
| `{ variable: 'sales', gt: 2000 }`              | `gt` / `gte` / `lt` / `lte` — ordered comparison |
| `{ variable: 'sales', range: [1500, 2500] }`   | inclusive numeric range                          |

<Note>
  Ordered operators (`gt`, `gte`, `lt`, `lte`, `range`) need a numeric or date
  column. Using one against a categorical field is a compile-time validation
  error, surfaced as a [diagnostic](/sdk-next/concepts/how-a-chart-is-built)
  rather than a silent no-op.
</Note>

Combine predicates with the logical forms `and`, `or`, and `not`:

```tsx theme={null}
highlight({
  and: [
    { variable: 'region', eq: 'EU' },
    { variable: 'quarter', oneOf: ['Q3', 'Q4'] },
  ],
}); // EU, but only in the second half
```

## Scope

By default a highlight matches individual observations. The `scope` option expands each match to a larger visual unit:

| Scope                      | Effect                                                                                                 |
| -------------------------- | ------------------------------------------------------------------------------------------------------ |
| `'data-point'` *(default)* | Only the rows that satisfy the predicate; their series siblings stay dimmed                            |
| `'series'`                 | Any matching row expands to its whole series — highlight an entire line, area, or bar group            |
| `'x-value'`                | Any matching row expands to every observation sharing its x — highlight a vertical slice across series |

```tsx theme={null}
highlight({ variable: 'region', eq: 'EU' }, { scope: 'series' });
```

## Scoping to one layer

In a multi-layer chart (say bars plus a trend line), pass `layerIndex` to bind the highlight to a single layer. Observations in other layers are never tested, so a Q4 highlight on the bars leaves the trend line untouched:

<GraphEmbed story="features-highlights--combo-layer-scoped" />

```tsx theme={null}
pipe(
  createSpec({ x: 'month' }),
  geom.bar({ aes: { y: 'sales' } }),
  geom.line({ aes: { y: 'trend' } }),
  // ...scales
  highlight(
    { variable: 'month', oneOf: ['Oct', 'Nov', 'Dec'] },
    { layerIndex: 0 }
  )
);
```

Omit `layerIndex` to evaluate the predicate against every layer.

## Combining highlights

Multiple `highlight()` calls accumulate, and the engine **unions** their matches — an observation matched by any highlight is emphasised. So two separate highlights behave like an `or`:

<GraphEmbed
  story="features-highlights--playground"
  args={{
predicate: 'variable-gte-2000',
secondPredicate: 'variable-eq-region-us',
combineWith: 'separate-highlight',
}}
/>

```tsx theme={null}
pipe(
  // ...
  highlight({ variable: 'sales', gte: 2000 }),
  highlight({ variable: 'region', eq: 'US' }) // rows ≥ 2000 OR any US row
);
```

Because the union is exactly an `or`, this is equivalent to a single highlight with an `or` predicate — same matched observations, same result:

```tsx theme={null}
pipe(
  // ...
  highlight({
    or: [
      { variable: 'sales', gte: 2000 },
      { variable: 'region', eq: 'US' },
    ],
  })
);
```

Reach for the single `or` predicate when the two conditions are one idea; reach for two separate highlights when they're independent emphases you might toggle separately.

## Styling the de-emphasis

How the *un-matched* observations recede is a rendering concern, set once through [`config.appearance.highlightStyle`](/sdk-next/config/appearance#highlight-style):

```tsx theme={null}
config({ appearance: { highlightStyle: 'desaturate' } });
```

`'dim'` (the default) lowers the opacity of non-matched observations; `'desaturate'` drains their color toward grey.

## Related

* [Mappings & aesthetics](/sdk-next/concepts/mappings) — the variables a predicate can name
* [Transforms](/sdk-next/advanced/transforms) — predicates run against post-transform columns
* [Appearance](/sdk-next/config/appearance#highlight-style) — the `highlightStyle` treatment
