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

# Lollipop

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 lollipop compares one value per category, like a bar, as a head at the value and a stem dropped to zero. It uses less ink per category than a bar, so it holds up when there are many categories of similar size, such as a ranking across countries.

<Warning>
  The lollipop ships as its own package, `@graphysdk/geom-lollipop`, on a `0.x`
  version line. While it is on `0.x`, a minor release may change its aesthetics,
  style parts or look; pin a caret range (`^0.1.0`) to take only patches. Tell
  us what works and what doesn't on [Discord](https://discord.gg/yGmYCjkSr).
</Warning>

## Install

```bash theme={null}
npm install @graphysdk/geom-lollipop
```

It peer-depends on `@graphysdk/react` and nothing else. Register the plugin once with `createGraphyKit`, which adds `kit.geom.lollipop()` and binds `kit.GraphProvider` to the same plugin:

```tsx theme={null}
import {
  createGraphyKit,
  GraphRenderer,
  highlight,
  styles,
} from '@graphysdk/react';
import { lollipop } from '@graphysdk/geom-lollipop';

const kit = createGraphyKit({ plugins: [lollipop] });
```

## One value per category

Map the category to `x` and the value to `y`:

<GraphEmbed story="chart-types-lollipop--reading" args={{ orientation: 'vertical' }} />

```tsx theme={null}
const data = {
  columns: [{ key: 'country' }, { key: 'share' }],
  rows: [
    { country: 'Norway', share: 89 },
    { country: 'Sweden', share: 85 },
    // …
  ],
};

const spec = kit.pipe(
  kit.createSpec({ x: 'country', y: 'share' }),
  kit.geom.lollipop(),
  kit.scale.x.discrete(),
  kit.scale.y.continuous()
);

export function Reading() {
  return (
    <kit.GraphProvider spec={spec} data={data}>
      <GraphRenderer />
    </kit.GraphProvider>
  );
}
```

A stem encodes length, as a bar does, so the value axis always holds zero. A negative value hangs its stem below the baseline:

<GraphEmbed story="chart-types-lollipop--negative" />

## Grouped lollipops

Map a group to `color`, with one row per lollipop. Each category's lollipops spread across its band, as a grouped bar's do, and every stem takes its head's colour from the scale:

<GraphEmbed story="chart-types-lollipop--grouped" />

```tsx theme={null}
const spec = kit.pipe(
  kit.createSpec({ x: 'quarter', y: 'revenue', color: 'channel' }),
  kit.geom.lollipop(),
  kit.scale.x.discrete(),
  kit.scale.y.continuous(),
  kit.scale.color.palette()
);
```

Pass `position: 'identity'` to stand a category's lollipops on one line instead. A stack is not supported: a stacked stem would start on the head below it. When each group is its own column, reshape it to long with `kit.transform.reshape()`, as for a [dumbbell](/sdk-next/graph-types/dumbbell#wide-data).

## Horizontal

Add `kit.coord.flip()`, as for a horizontal bar. The categories move to the vertical axis and the stems lie across.

<GraphEmbed story="chart-types-lollipop--reading" args={{ orientation: 'horizontal' }} />

## Styling

The heads are the layer's observations and style through the kind's own builder. The stem is a part beside them, with a builder of its own:

| Builder | Properties | Default |
| - | - | - |
| `kit.style.geom.lollipop()` | `fill`, `stroke`, `alpha`, `fillAlpha`, `strokeAlpha`, `saturation`, `size`, `strokeWidth` | Fill from the scale, 10px, white border |
| `kit.style.geom.lollipop.stem()` | `stroke`, `alpha`, `strokeWidth`, `dashArray` | Stroke from the scale, 2px, solid |

<GraphEmbed story="chart-types-lollipop--styled" />

```tsx theme={null}
const spec = kit.pipe(
  // …
  styles({
    defaults: [
      kit.style.geom.lollipop({ size: 14, strokeWidth: 2 }),
      kit.style.geom.lollipop.stem({
        strokeWidth: 1,
        stroke: '#b5b0a3',
        dashArray: [2, 3],
      }),
    ],
  })
);
```

An entry that names no part, such as `style.geom({ fill: 'navy' })`, styles the heads like any other geom's observations and never the stems. A stem entry's `stroke` wins over the scale, so one entry gives every stem a neutral colour under coloured heads. A stem entry's `where` is tested against the lollipop it holds up.

## Hover and highlight

Hovering a category emphasises all its lollipops. The tooltip lists every group in legend order, with the head nearest the cursor marked in place. A press anywhere along a stem takes its category.

A highlight raises each matched lollipop whole, stem and head together:

<GraphEmbed story="chart-types-lollipop--highlight" args={{ target: 'one channel' }} />

```tsx theme={null}
const spec = kit.pipe(
  // …
  highlight({ variable: 'channel', eq: 'Online' })
);
```

## Data labels

`dataLabels.showDataLabels` prints every head's value beside it. Under the default `position: 'auto'` each label continues the stem past its head: above a rising lollipop, below a hanging one. An explicit `position` puts every label on the side `justify` names.

```tsx theme={null}
const spec = kit.pipe(
  // …
  kit.geom.lollipop({ dataLabels: { showDataLabels: true } })
);
```

The label prints the head's `y`, formatted as the value axis formats it; map `label` to print another column instead. `showStackTotals` and `showCategoryLabels` are bar settings and a lollipop places neither.

## Not yet supported

* **The editor's chart-type picker and the chart agent.** In editable mode the lollipop renders, hovers, selects and takes annotations on its heads, but it is authored in code.
* **Polar lollipops.** A radial lollipop is not drawn yet.
* **Aggregation.** Duplicate rows for one category and group all draw; aggregate them first with `transform.aggregate()`.
