Charts

Exercise Minutes

Card with a two-line chart comparing this week's exercise minutes against the average by day, both drawn in primary ink with the average at half strength. Client component on recharts 3.8.

Free, MIT licenseOpen in Browse
"use client"

import { CartesianGrid, Line, LineChart, XAxis } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Exercise minutes",
  description: "Your exercise minutes are ahead of where you normally are.",
} as const

const EXERCISE_MINUTES = [
  { day: "Monday", today: 240, average: 400 },
  { day: "Tuesday", today: 139, average: 300 },
  { day: "Wednesday", today: 980, average: 200 },
  { day: "Thursday", today: 390, average: 278 },
  { day: "Friday", today: 480, average: 189 },
  { day: "Saturday", today: 380, average: 239 },
  { day: "Sunday", today: 430, average: 349 },
]

// Both series take the primary ink; the average is drawn at half strength.
const CHART_CONFIG = {
  today: { label: "Today", color: "var(--primary)" },
  average: { label: "Average", color: "var(--primary)" },
} satisfies ChartConfig

export default function ExerciseMinutes() {
  return (
    <Card className="mx-auto w-full max-w-md">
      <CardHeader>
        <CardTitle>
          <h2>{COPY.title}</h2>
        </CardTitle>
        <CardDescription>{COPY.description}</CardDescription>
      </CardHeader>
      <CardContent>
        <ChartContainer config={CHART_CONFIG} className="w-full md:h-[200px]">
          <LineChart
            accessibilityLayer
            aria-label={COPY.title}
            data={EXERCISE_MINUTES}
            margin={{ top: 5, right: 10, left: 16, bottom: 0 }}
          >
            <CartesianGrid vertical={false} />
            {/* Stock ChartContainer's label color misses recharts 3's label layer. */}
            <XAxis
              tick={{ fill: "var(--muted-foreground)" }}
              dataKey="day"
              tickLine={false}
              axisLine={false}
              tickMargin={8}
              tickFormatter={(value: string) => value.slice(0, 3)}
            />
            <Line
              type="monotone"
              dataKey="today"
              strokeWidth={2}
              stroke="var(--color-today)"
              dot={{ fill: "var(--color-today)" }}
              activeDot={{ r: 5 }}
            />
            <Line
              type="monotone"
              dataKey="average"
              strokeWidth={2}
              stroke="var(--color-average)"
              strokeOpacity={0.5}
              dot={{ fill: "var(--color-average)", opacity: 0.5 }}
              activeDot={{ r: 5 }}
            />
            <ChartTooltip content={<ChartTooltipContent />} />
          </LineChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}

Installation

pnpm dlx shadcn add @01orbital/exercise-minutes
npx shadcn add @01orbital/exercise-minutes
yarn shadcn add @01orbital/exercise-minutes
bunx --bun shadcn add @01orbital/exercise-minutes

Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.

It lands at components/blocks/charts/exercise-minutes/index.tsx, as source you own and edit.

Usage

import ExerciseMinutes from "@/components/blocks/charts/exercise-minutes"

export default function Example() {
  return <ExerciseMinutes />
}

Dependencies

npm: recharts@3.8.0. The CLI installs them for you.

Registry pieces it installs with it:

  • card
  • chart