chartLineDotsCustom
A Charts block/component from shadcn/ui — clean-room reimplemented for Domphy (see methodology). Call chartLineDotsCustom() with no arguments for a working demo, or edit the code below live.
Props
| Prop | Type | Description |
|---|---|---|
title | string | — |
description | string | — |
seriesLabel | string | — |
seriesColor | ThemeColor | — |
data | MonthlyPoint[] | — |
markerWidth | number | — |
markerHeight | number | — |
trendHeadline | string | — |
trendSubtitle | string | — |
trendDirection | "up" | "down" | — |
Implementation notes
Custom per-point icon markers aren't reachable through ChartOption (SymbolType accepts 'pin'/'diamond'/etc in the type surface, but the actual line-symbol renderer always draws a plain circle regardless of the requested symbol). Implemented the same way as chartLineDotsColors: native symbols disabled, a companion SVG overlay draws a small hollow rounded-bar 'pin' glyph (original hand-drawn artwork, outline = series color, fill = card background) at each point via the shared staticPointMarkersOverlay + public scale factories. Direct-source-diff fix (2026-07-05): Point marker was a rounded capsule bar — upstream uses a lucide GitCommitVertical glyph (hollow circle + short vertical tick above/below). Redrawn to match.
Status: ported · Reference: shadcn/ui original
// shadcn/ui "charts/line-dots-custom" block — clean-room reimplementation.
//
// The default six-month smooth single-line chart with plain circular dots
// replaced by a "git commit" glyph at every data point: a hollow circle
// (outline = line color, fill = card background) with a short vertical tick
// above and below it, mirroring the upstream block's lucide GitCommitVertical
// marker.
//
// @domphy/chart's built-in line-symbol renderer only ever draws a plain
// circle (see ./chart-line-shared.ts), so the custom glyph is drawn by a
// companion SVG overlay positioned with the exact same public scale
// factories the engine itself uses.
import type { ChartOption } from "@domphy/chart";
import type { DomphyElement } from "@domphy/core";
import { type ThemeColor, themeColor } from "@domphy/theme";
import {
chartCard,
chartLineSeriesColor,
chartPlot,
computeYDomain,
DEFAULT_LINE_GRID,
hiddenLabelYAxis,
lineSwatchLabelValueTooltipFormatter,
MONTHLY_VISITOR_DATA,
type MonthlyPoint,
monthCategoryXAxis,
staticPointMarkersOverlay,
trendFooter,
} from "./chart-line-shared.js";
const MARKER_WIDTH = 8;
const MARKER_HEIGHT = 20;
/** Props for {@link chartLineDotsCustom}. */
export interface ChartLineDotsCustomProps {
title?: string;
description?: string;
seriesLabel?: string;
seriesColor?: ThemeColor;
data?: MonthlyPoint[];
markerWidth?: number;
markerHeight?: number;
trendHeadline?: string;
trendSubtitle?: string;
trendDirection?: "up" | "down";
}
/**
* shadcn/ui "charts/line-dots-custom" — the default single-line chart with a
* custom hollow pin-glyph marker at every point instead of a plain circle.
* Call with no arguments for a fully working demo.
*/
function chartLineDotsCustom(
props: ChartLineDotsCustomProps = {},
): DomphyElement<"div"> {
const {
title = "Line Chart - Custom Dots",
description = "January - June 2026",
seriesLabel = "Desktop",
seriesColor = "primary",
data = MONTHLY_VISITOR_DATA,
markerWidth = MARKER_WIDTH,
markerHeight = MARKER_HEIGHT,
trendHeadline = "Trending up by 5.2% this month",
trendSubtitle = "Showing total visitors for the last 6 months",
trendDirection = "up",
} = props;
const categories = data.map((point) => point.month);
const values = data.map((point) => point.desktop);
const yDomain = computeYDomain(values);
// Upstream's stroke/markers are var(--chart-1) — the ramp's first step.
const ramp = chartLineSeriesColor(0);
const markerOutline = themeColor(null, ramp.tone, seriesColor);
const markerFill = themeColor(null, "shift-0", "neutral");
const option: ChartOption = {
grid: DEFAULT_LINE_GRID,
xAxis: monthCategoryXAxis(categories),
yAxis: hiddenLabelYAxis(yDomain),
tooltip: {
trigger: "axis",
axisPointer: { type: "none" },
// Upstream renders <ChartTooltipContent hideLabel />: hideLabel drops only
// the category-name header, still showing the color swatch + series label
// + value per row (same as chart-line-default, which is also hideLabel).
formatter: (params) =>
lineSwatchLabelValueTooltipFormatter(
params,
chartLineSeriesColor(0).css,
),
},
series: [
{
type: "line",
name: seriesLabel,
data: values,
smooth: true,
showSymbol: false,
lineStyle: { width: 2, opacity: ramp.strokeOpacity },
color: seriesColor,
},
],
};
return chartCard({
title,
description,
plot: chartPlot({
option,
overlays: [
staticPointMarkersOverlay({
categories,
values,
yDomain,
grid: DEFAULT_LINE_GRID,
renderMarker({ cx, cy, group }) {
const svgNamespace = "http://www.w3.org/2000/svg";
const radius = markerWidth / 2;
const halfHeight = markerHeight / 2;
// Short vertical tick above and below the node (the git-commit look).
for (const [y1, y2] of [
[cy - halfHeight, cy - radius],
[cy + radius, cy + halfHeight],
]) {
const tick = document.createElementNS(
svgNamespace,
"line",
) as SVGLineElement;
tick.setAttribute("x1", String(cx));
tick.setAttribute("y1", String(y1));
tick.setAttribute("x2", String(cx));
tick.setAttribute("y2", String(y2));
tick.setAttribute("stroke", markerOutline);
tick.setAttribute("stroke-width", "2");
tick.setAttribute("stroke-linecap", "round");
group.appendChild(tick);
}
const node = document.createElementNS(
svgNamespace,
"circle",
) as SVGCircleElement;
node.setAttribute("cx", String(cx));
node.setAttribute("cy", String(cy));
node.setAttribute("r", String(radius));
node.setAttribute("fill", markerFill);
node.setAttribute("stroke", markerOutline);
node.setAttribute("stroke-width", "2");
group.appendChild(node);
},
}),
],
}),
footer: trendFooter({
headline: trendHeadline,
subtitle: trendSubtitle,
direction: trendDirection,
}),
});
}
export { chartLineDotsCustom };