Skip to content
Domphy

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

PropTypeDescription
titlestring
descriptionstring
seriesLabelstring
seriesColorThemeColor
dataMonthlyPoint[]
markerWidthnumber
markerHeightnumber
trendHeadlinestring
trendSubtitlestring
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 };

← Back to shadcn/ui catalog