Charts & Stats chartsvgzero-dependencylollipopdottargetscore
Lollipop Chart
Thin stems with value dots and an optional goal line; a lighter alternative to bar charts.
Live Preview
Score per page template
View data
| Item | Value | Goal |
|---|---|---|
| Home | 98 | Met |
| Docs | 96 | Met |
| Blog | 91 | Met |
| Pricing | 88 | Below |
| Dashboard | 74 | Below |
| Checkout | 81 | Below |
| Search | 69 | Below |
Usage
Copy and paste this code into your Astro page:
astro
---
import { LollipopChart } from 'astro-component-kit/charts';
---
<LollipopChart
items={[
{ label: 'Home', value: 98 },
{ label: 'Search', value: 69 },
]}
target={90}
targetLabel="Goal"
/> ---
import { LollipopChart } from 'astro-component-kit/charts';
---
<LollipopChart
items={[
{ label: 'Home', value: 98 },
{ label: 'Search', value: 69 },
]}
target={90}
targetLabel="Goal"
/> Manual Installation
If you are not using the npm package, create a new file and paste the following code: src/components/lib/LollipopChart.astro:
astro
---
/**
* LollipopChart — Lightweight alternative to a bar chart: a thin stem with a dot
* at the value. Optionally draws a target/benchmark line.
*
* @param {Array<{label: string, value: number}>} items - Values to plot.
* @param {number} target - Optional benchmark value.
* @param {string} targetLabel - Label for the benchmark line.
* @param {string} prefix - Value prefix.
* @param {string} suffix - Value suffix.
* @param {string} color - Stem/dot color.
* @param {string} variant - Card style (glass|solid|ghost).
* @param {string} size - Density (sm|md|lg).
*/
import ChartFrame from './ChartFrame.astro';
import { scaleLinear, niceTicks, formatValue } from './chart-utils';
interface Props {
title?: string;
subtitle?: string;
items?: Array<{ label: string; value: number }>;
target?: number;
targetLabel?: string;
prefix?: string;
suffix?: string;
height?: number;
color?: string;
variant?: 'glass' | 'solid' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const {
title = 'Lighthouse performance',
subtitle = 'Score per page template',
items = [
{ label: 'Home', value: 98 },
{ label: 'Docs', value: 96 },
{ label: 'Blog', value: 91 },
{ label: 'Pricing', value: 88 },
{ label: 'Dashboard', value: 74 },
{ label: 'Checkout', value: 81 },
{ label: 'Search', value: 69 },
],
target = 90,
targetLabel = 'Goal',
prefix = '',
suffix = '',
height = 270,
color = 'var(--chart-1, #3987e5)',
variant = 'glass',
size = 'md',
className = '',
} = Astro.props;
const W = 640;
const H = height;
const m = { t: 16, r: 12, b: 30, l: 40 };
const ticks = niceTicks(0, Math.max(...items.map((i) => i.value), target ?? 0), 4);
const y = scaleLinear(0, ticks[ticks.length - 1], H - m.b, m.t);
const band = (W - m.l - m.r) / items.length;
const fmt = (v: number) => formatValue(v, prefix, suffix);
---
<ChartFrame
title={title}
subtitle={subtitle}
variant={variant}
size={size}
className={className}
table={{ headers: ['Item', 'Value', targetLabel], rows: items.map((i) => [i.label, fmt(i.value), target !== undefined ? (i.value >= target ? 'Met' : 'Below') : '—']) }}
>
<svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: lollipop chart`}>
{ticks.map((t) => (
<g>
<line class={t === 0 ? 'cf-axis' : 'cf-grid'} x1={m.l} x2={W - m.r} y1={y(t)} y2={y(t)} />
<text class="cf-label" x={m.l - 8} y={y(t) + 4} text-anchor="end">{fmt(t)}</text>
</g>
))}
{target !== undefined && (
<g aria-hidden="true">
<line x1={m.l} x2={W - m.r} y1={y(target)} y2={y(target)} stroke="var(--c-text-1, #fff)" stroke-opacity="0.55" stroke-width="1" stroke-dasharray="4 4" />
<text class="cf-label" x={W - m.r} y={y(target) - 6} text-anchor="end">{targetLabel} {fmt(target)}</text>
</g>
)}
{items.map((it, i) => {
const cx = m.l + i * band + band / 2;
const below = target !== undefined && target > it.value;
return (
<g class="cf-mark cf-dim" tabindex="0" role="img" aria-label={`${it.label}: ${fmt(it.value)}${below ? ', below goal' : ''}`}
data-tip-title={it.label} data-tip={`${color}::${fmt(it.value)}${target !== undefined ? `|${below ? 'Below' : 'Meets'} ${targetLabel.toLowerCase()}` : ''}`}>
<line x1={cx} x2={cx} y1={y(0)} y2={y(it.value)} stroke={color} stroke-width="2" stroke-opacity={below ? 0.5 : 1} />
<circle cx={cx} cy={y(it.value)} r="7" fill={below ? 'var(--chart-surface, #0d0d0f)' : color} stroke={color} stroke-width="2.5" />
<rect x={cx - band / 2} y={m.t} width={band} height={H - m.t - m.b} fill="transparent" />
<text class="cf-label" x={cx} y={H - 10} text-anchor="middle">{it.label}</text>
</g>
);
})}
</svg>
</ChartFrame> ---
/**
* LollipopChart — Lightweight alternative to a bar chart: a thin stem with a dot
* at the value. Optionally draws a target/benchmark line.
*
* @param {Array<{label: string, value: number}>} items - Values to plot.
* @param {number} target - Optional benchmark value.
* @param {string} targetLabel - Label for the benchmark line.
* @param {string} prefix - Value prefix.
* @param {string} suffix - Value suffix.
* @param {string} color - Stem/dot color.
* @param {string} variant - Card style (glass|solid|ghost).
* @param {string} size - Density (sm|md|lg).
*/
import ChartFrame from './ChartFrame.astro';
import { scaleLinear, niceTicks, formatValue } from './chart-utils';
interface Props {
title?: string;
subtitle?: string;
items?: Array<{ label: string; value: number }>;
target?: number;
targetLabel?: string;
prefix?: string;
suffix?: string;
height?: number;
color?: string;
variant?: 'glass' | 'solid' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const {
title = 'Lighthouse performance',
subtitle = 'Score per page template',
items = [
{ label: 'Home', value: 98 },
{ label: 'Docs', value: 96 },
{ label: 'Blog', value: 91 },
{ label: 'Pricing', value: 88 },
{ label: 'Dashboard', value: 74 },
{ label: 'Checkout', value: 81 },
{ label: 'Search', value: 69 },
],
target = 90,
targetLabel = 'Goal',
prefix = '',
suffix = '',
height = 270,
color = 'var(--chart-1, #3987e5)',
variant = 'glass',
size = 'md',
className = '',
} = Astro.props;
const W = 640;
const H = height;
const m = { t: 16, r: 12, b: 30, l: 40 };
const ticks = niceTicks(0, Math.max(...items.map((i) => i.value), target ?? 0), 4);
const y = scaleLinear(0, ticks[ticks.length - 1], H - m.b, m.t);
const band = (W - m.l - m.r) / items.length;
const fmt = (v: number) => formatValue(v, prefix, suffix);
---
<ChartFrame
title={title}
subtitle={subtitle}
variant={variant}
size={size}
className={className}
table={{ headers: ['Item', 'Value', targetLabel], rows: items.map((i) => [i.label, fmt(i.value), target !== undefined ? (i.value >= target ? 'Met' : 'Below') : '—']) }}
>
<svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: lollipop chart`}>
{ticks.map((t) => (
<g>
<line class={t === 0 ? 'cf-axis' : 'cf-grid'} x1={m.l} x2={W - m.r} y1={y(t)} y2={y(t)} />
<text class="cf-label" x={m.l - 8} y={y(t) + 4} text-anchor="end">{fmt(t)}</text>
</g>
))}
{target !== undefined && (
<g aria-hidden="true">
<line x1={m.l} x2={W - m.r} y1={y(target)} y2={y(target)} stroke="var(--c-text-1, #fff)" stroke-opacity="0.55" stroke-width="1" stroke-dasharray="4 4" />
<text class="cf-label" x={W - m.r} y={y(target) - 6} text-anchor="end">{targetLabel} {fmt(target)}</text>
</g>
)}
{items.map((it, i) => {
const cx = m.l + i * band + band / 2;
const below = target !== undefined && target > it.value;
return (
<g class="cf-mark cf-dim" tabindex="0" role="img" aria-label={`${it.label}: ${fmt(it.value)}${below ? ', below goal' : ''}`}
data-tip-title={it.label} data-tip={`${color}::${fmt(it.value)}${target !== undefined ? `|${below ? 'Below' : 'Meets'} ${targetLabel.toLowerCase()}` : ''}`}>
<line x1={cx} x2={cx} y1={y(0)} y2={y(it.value)} stroke={color} stroke-width="2" stroke-opacity={below ? 0.5 : 1} />
<circle cx={cx} cy={y(it.value)} r="7" fill={below ? 'var(--chart-surface, #0d0d0f)' : color} stroke={color} stroke-width="2.5" />
<rect x={cx - band / 2} y={m.t} width={band} height={H - m.t - m.b} fill="transparent" />
<text class="cf-label" x={cx} y={H - 10} text-anchor="middle">{it.label}</text>
</g>
);
})}
</svg>
</ChartFrame>
Quick Info
- Category
- Charts & Stats
- Filename
LollipopChart.astro- Dependencies
- apexcharts (peer)
- Tags
- chartsvgzero-dependencylollipopdottargetscore