# Line Custom Dots

> A line chart with custom dual-ring dots tracking software deployment release frequency built with TanStack Charts.

A shadcn/ui block from the [@paceui](https://paceui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/paceui/tanstack-charts-line-custom-dots).

- Page, with a live preview: https://shadcn.exchange/r/paceui/tanstack-charts-line-custom-dots
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/paceui/tanstack-charts-line-custom-dots-light-5e1c7919e10f8296.png
- Registry item: https://paceui.com/r/tanstack-charts-line-custom-dots.json
- Listed: 2026-10-21, source last changed 2026-10-21

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/paceui/tanstack-charts-line-custom-dots.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@tanstack/charts`, `@tanstack/react-charts`, `d3-array`, `d3-scale`, `d3-shape`
- Registry items: `card`

## Files

### `components/tanstack-charts/line/line-custom-dots.tsx`

```tsx
import { defineChart, dot, lineY } from "@tanstack/charts";
import { d3Curve } from "@tanstack/charts/d3/shape";
import { motion } from "@tanstack/charts/motion";
import { tooltip } from "@tanstack/charts/tooltip";
import { Chart } from "@tanstack/react-charts/core";
import { useMemo } from "react";

import { max } from "d3-array";
import { scaleLinear, scalePoint } from "d3-scale";
import { curveNatural } from "d3-shape";

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";

const motionRenderer = motion({
    initial: "always",
    transition: { type: "spring", stiffness: 170, damping: 18, mass: 1 },
});

const chartData = [
    { month: "January", deployments: 12 },
    { month: "February", deployments: 18 },
    { month: "March", deployments: 15 },
    { month: "April", deployments: 24 },
    { month: "May", deployments: 28 },
    { month: "June", deployments: 22 },
    { month: "July", deployments: 35 },
    { month: "August", deployments: 41 },
    { month: "September", deployments: 38 },
];

export function LineCustomDots() {
    const definition = useMemo(() => {
        const yMax = max(chartData, (d) => d.deployments) ?? 0;

        return defineChart({
            renderer: motionRenderer,
            motion: {
                transition: { type: "spring", stiffness: 170, damping: 18, mass: 1 },
            },
            focus: "group-x",
            tooltip: {
                use: tooltip,
                motion: { type: "spring", stiffness: 170, damping: 18 },
                className: "font-sans! shadow-none! capitalize!",
                content: (points) => {
                    const datum = points[0]?.datum as (typeof chartData)[number];
                    if (!datum) return { rows: [] };
                    return {
                        title: String(points[0].xValue),
                        rows: [
                            {
                                label: "Deployments",
                                value: String(datum.deployments),
                                color: "var(--chart-1)",
                            },
                        ],
                    };
                },
            },
            marks: [
                lineY(chartData, {
                    id: "deployments",
                    x: (d) => d.month.slice(0, 3),
                    y: (d) => d.deployments,
                    stroke: "var(--chart-1)",
                    strokeWidth: 2,
                    curve: d3Curve(curveNatural),
                    points: false,
                }),
                dot(chartData, {
                    id: "deployments-ring",
                    x: (d) => d.month.slice(0, 3),
                    y: (d) => d.deployments,
                    r: 6,
                    fill: "var(--background)",
                    stroke: "var(--chart-1)",
                    strokeWidth: 2,
                }),
                dot(chartData, {
                    id: "deployments-dot",
                    x: (d) => d.month.slice(0, 3),
                    y: (d) => d.deployments,
                    r: 2.5,
                    fill: "var(--chart-1)",
                }),
            ],
            scales: {
                x: {
                    scale: scalePoint().domain(chartData.map((d) => d.month.slice(0, 3))),
                },
                y: {
                    scale: scaleLinear().domain([0, yMax]).nice(),
                    grid: true,
                },
            },
        });
    }, []);

    return (
        <Card className="gap-2">
            <CardHeader>
                <CardTitle className="text-lg">Continuous Integration Pipeline</CardTitle>
                <CardDescription>Production Release Frequency (2024)</CardDescription>
            </CardHeader>
            <CardContent>
                <Chart
                    definition={definition}
                    renderer={motionRenderer}
                    height={250}
                    ariaLabel="Line chart with custom styled dots tracking software deployment frequency"
                />
            </CardContent>
        </Card>
    );
}
```
