Bar Chart

Flexible bar chart supporting grouped, stacked, vertical, and horizontal layouts

Installation

pnpm dlx shadcn@latest add "https://vritti.thesatyajit.com/r/bar-chart"

Dependencies

pnpm add @visx/responsive @visx/scale @visx/shape @visx/group @visx/event @visx/grid d3-array motion react-use-measure @base-ui/react

Examples

Custom Tooltip

"use client"
import BarChart, { Bar, BarXAxis, ChartTooltip, Grid } from "@/components/vritti/bar-chart"

const data = [
  { month: "Jan", revenue: 12000, profit: 4500 },
  { month: "Feb", revenue: 15500, profit: 5200 },
  { month: "Mar", revenue: 11000, profit: 3800 },
  { month: "Apr", revenue: 18500, profit: 7100 },
  { month: "May", revenue: 16800, profit: 5400 },
  { month: "Jun", revenue: 21200, profit: 8800 },
]

export function BarChartCustomTooltipDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={data} xDataKey="month">
        <Grid horizontal />
        <Bar dataKey="revenue" fill="hsl(217, 91%, 60%)" lineCap={4} />
        <Bar dataKey="profit" fill="hsl(280, 87%, 65%)" lineCap={4} />
        <BarXAxis />
        <ChartTooltip
          rows={(point) => [
            {
              color: "hsl(217, 91%, 60%)",
              label: "Revenue",
              value: `$${(point.revenue as number)?.toLocaleString()}`,
            },
            {
              color: "hsl(280, 87%, 65%)",
              label: "Profit",
              value: `$${(point.profit as number)?.toLocaleString()}`,
            },
          ]}
        />
      </BarChart>
    </div>
  )
}

Gradient

"use client"
import BarChart, { Bar, BarXAxis, ChartTooltip, Grid, LinearGradient } from "@/components/vritti/bar-chart"

const data = [
  { month: "Jan", revenue: 12000 },
  { month: "Feb", revenue: 15500 },
  { month: "Mar", revenue: 11000 },
  { month: "Apr", revenue: 18500 },
  { month: "May", revenue: 16800 },
  { month: "Jun", revenue: 21200 },
]

export function BarChartGradientDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={data} xDataKey="month">
        <LinearGradient
          from="hsl(217, 91%, 60%)"
          id="barGradient"
          to="hsl(280, 87%, 65%)"
        />
        <Grid horizontal />
        <Bar
          dataKey="revenue"
          fill="url(#barGradient)"
          lineCap={4}
          stroke="hsl(217, 91%, 60%)"
        />
        <BarXAxis />
        <ChartTooltip />
      </BarChart>
    </div>
  )
}

Horizontal

"use client"
import BarChart, { Bar, BarYAxis, Grid, ChartTooltip } from "@/components/vritti/bar-chart"

const data = [
  { browser: "Chrome", users: 275 },
  { browser: "Safari", users: 200 },
  { browser: "Firefox", users: 187 },
  { browser: "Edge", users: 173 },
  { browser: "Other", users: 90 },
]

export function BarChartHorizontalDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={data} xDataKey="browser" orientation="horizontal" aspectRatio="4 / 3" margin={{ left: 80 }}>
        <Grid fadeVertical horizontal={false} vertical />
        <Bar dataKey="users" fill="hsl(217, 91%, 60%)" lineCap={4} />
        <BarYAxis />
        <ChartTooltip showCrosshair={false} />
      </BarChart>
    </div>
  )
}

Interactive

"use client"
import BarChart, { Bar, BarXAxis, Grid, ChartTooltip } from "@/components/vritti/bar-chart"

const data = [
  { category: "Electronics", sales: 42500 },
  { category: "Clothing", sales: 31200 },
  { category: "Groceries", sales: 28900 },
  { category: "Furniture", sales: 19400 },
  { category: "Books", sales: 15800 },
  { category: "Sports", sales: 12300 },
  { category: "Toys", sales: 9700 },
]

export function BarChartInteractiveDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={data} xDataKey="category">
        <Grid horizontal />
        <Bar
          dataKey="sales"
          fadedOpacity={0.15}
          fill="hsl(217, 91%, 60%)"
          lineCap={4}
        />
        <BarXAxis />
        <ChartTooltip />
      </BarChart>
    </div>
  )
}

Label

"use client"
import BarChart, { Bar, BarXAxis, Grid, ChartTooltip, useChart } from "@/components/vritti/bar-chart"

const data = [
  { product: "Widget A", units: 1240 },
  { product: "Widget B", units: 980 },
  { product: "Widget C", units: 1560 },
  { product: "Widget D", units: 720 },
  { product: "Widget E", units: 1890 },
  { product: "Widget F", units: 1100 },
]

function BarLabels({ dataKey }: { dataKey: string }) {
  const { data, barScale, yScale, bandWidth, barXAccessor } = useChart()

  if (!barScale || !bandWidth || !barXAccessor) return null

  return (
    <g>
      {data.map((d, i) => {
        const value = d[dataKey]
        if (typeof value !== "number") return null

        const label = barXAccessor(d)
        const x = (barScale(label) ?? 0) + bandWidth / 2
        const y = (yScale(value) ?? 0) - 10

        return (
          <text
            className="fill-chart-foreground-muted"
            dominantBaseline="middle"
            fontSize="11"
            fontWeight="500"
            key={i}
            textAnchor="middle"
            x={x}
            y={y}
          >
            {value.toLocaleString()}
          </text>
        )
      })}
    </g>
  )
}

export function BarChartLabelDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={data} xDataKey="product" margin={{ top: 50 }}>
        <Grid horizontal />
        <Bar dataKey="units" fill="hsl(280, 87%, 65%)" lineCap={4} />
        <BarLabels dataKey="units" />
        <BarXAxis />
        <ChartTooltip />
      </BarChart>
    </div>
  )
}

Legend

"use client"
import BarChart, {
  Bar,
  BarXAxis,
  ChartTooltip,
  Grid,
  Legend,
  LegendItem,
  LegendLabel,
  LegendMarker,
  type LegendItemData,
} from "@/components/vritti/bar-chart"

const stackedData = [
  { month: "Jan", desktop: 4000, mobile: 2400 },
  { month: "Feb", desktop: 5000, mobile: 3000 },
  { month: "Mar", desktop: 3500, mobile: 2800 },
  { month: "Apr", desktop: 4200, mobile: 3200 },
  { month: "May", desktop: 3800, mobile: 2600 },
  { month: "Jun", desktop: 5500, mobile: 3800 },
]

const legendItems: LegendItemData[] = [
  { label: "Desktop", value: 0, color: "hsl(217, 91%, 60%)" },
  { label: "Mobile", value: 0, color: "hsl(217, 91%, 75%)" },
]

export function BarChartLegendDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={stackedData} stacked stackGap={3} xDataKey="month">
        <Grid horizontal />
        <Bar
          dataKey="desktop"
          fill="hsl(217, 91%, 60%)"
          lineCap={4}
          stackGap={3}
        />
        <Bar
          dataKey="mobile"
          fill="hsl(217, 91%, 75%)"
          lineCap={4}
          stackGap={3}
        />
        <BarXAxis />
        <ChartTooltip />
      </BarChart>
      <Legend
        className="flex-row justify-center gap-6"
        items={legendItems}
      >
        <LegendItem className="flex items-center gap-2">
          <LegendMarker />
          <LegendLabel />
        </LegendItem>
      </Legend>
    </div>
  )
}

Mixed

"use client"
import BarChart, { Bar, BarXAxis, Grid, ChartTooltip } from "@/components/vritti/bar-chart"

const data = [
  { month: "Jan", profit: 8200, loss: 3100 },
  { month: "Feb", profit: 5400, loss: 7800 },
  { month: "Mar", profit: 11200, loss: 2400 },
  { month: "Apr", profit: 3600, loss: 9500 },
  { month: "May", profit: 14800, loss: 1200 },
  { month: "Jun", profit: 7100, loss: 5600 },
  { month: "Jul", profit: 2300, loss: 11400 },
  { month: "Aug", profit: 16500, loss: 800 },
]

export function BarChartMixedDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={data} xDataKey="month">
        <Grid horizontal />
        <Bar dataKey="profit" fill="hsl(142, 71%, 45%)" lineCap={4} />
        <Bar dataKey="loss" fill="hsl(0, 91%, 65%)" lineCap={4} />
        <BarXAxis />
        <ChartTooltip />
      </BarChart>
    </div>
  )
}

Narrow

"use client"
import BarChart, { Bar, BarXAxis, ChartTooltip, Grid } from "@/components/vritti/bar-chart"

const data = [
  { month: "Jan", revenue: 12000 },
  { month: "Feb", revenue: 15500 },
  { month: "Mar", revenue: 11000 },
  { month: "Apr", revenue: 18500 },
  { month: "May", revenue: 16800 },
  { month: "Jun", revenue: 21200 },
]

export function BarChartNarrowDemo() {
  return (
    <div className="w-full p-4">
      <BarChart barGap={0.1} data={data} xDataKey="month">
        <Grid horizontal />
        <Bar
          dataKey="revenue"
          fill="var(--chart-line-primary)"
          lineCap="round"
        />
        <BarXAxis />
        <ChartTooltip />
      </BarChart>
    </div>
  )
}

No Gap

"use client"
import { useEffect, useState } from "react"
import { createPortal } from "react-dom"
import { motion, useSpring } from "motion/react"
import BarChart, {
  Bar,
  BarXAxis,
  ChartTooltip,
  Grid,
  LinearGradient,
  useChart,
} from "@/components/vritti/bar-chart"

const data = [
  { month: "Jan", revenue: 12000 },
  { month: "Feb", revenue: 15500 },
  { month: "Mar", revenue: 11000 },
  { month: "Apr", revenue: 18500 },
  { month: "May", revenue: 16800 },
  { month: "Jun", revenue: 21200 },
]

function AnimatedBarLine({
  barX,
  barTopY,
  barBottomY,
  width,
  isHovered,
}: {
  barX: number
  barTopY: number
  barBottomY: number
  width: number
  isHovered: boolean
}) {
  const animatedY = useSpring(barBottomY, { stiffness: 300, damping: 30 })
  const animatedOpacity = useSpring(0, { stiffness: 300, damping: 30 })

  useEffect(() => {
    animatedY.set(isHovered ? barTopY : barBottomY)
    animatedOpacity.set(isHovered ? 1 : 0)
  }, [isHovered, barTopY, barBottomY, animatedY, animatedOpacity])

  return (
    <motion.rect
      fill="var(--chart-indicator-color)"
      height={2}
      style={{ opacity: animatedOpacity, y: animatedY }}
      width={width}
      x={barX}
    />
  )
}

function BarHorizontalLineIndicator() {
  const { barScale, bandWidth, innerHeight, margin, containerRef, hoveredBarIndex, yScale } =
    useChart()
  const [mounted, setMounted] = useState(false)

  useEffect(() => {
    setMounted(true)
  }, [])

  const container = containerRef.current
  if (!(mounted && container && bandWidth && barScale)) {
    return null
  }

  return createPortal(
    <svg
      aria-hidden="true"
      className="pointer-events-none absolute inset-0 z-50"
      height="100%"
      width="100%"
    >
      <g transform={`translate(${margin.left},${margin.top})`}>
        {data.map((d, i) => {
          const barX = (barScale as (v: string) => number)(d.month) ?? 0
          const barTopY = (yScale as (v: number) => number)(d.revenue) ?? innerHeight
          const isHovered = hoveredBarIndex === i

          return (
            <AnimatedBarLine
              barBottomY={innerHeight}
              barTopY={barTopY}
              barX={barX}
              isHovered={isHovered}
              key={d.month}
              width={bandWidth}
            />
          )
        })}
      </g>
    </svg>,
    container
  )
}

export function BarChartNoGapDemo() {
  return (
    <div className="w-full p-4">
      <BarChart barGap={0} data={data} xDataKey="month">
        <LinearGradient
          from="var(--chart-3)"
          id="noGapGradient"
          to="transparent"
        />
        <Grid horizontal />
        <Bar
          dataKey="revenue"
          fill="url(#noGapGradient)"
          lineCap="butt"
          stroke="var(--chart-3)"
        />
        <BarXAxis />
        <ChartTooltip showCrosshair={false} showDots={false} />
        <BarHorizontalLineIndicator />
      </BarChart>
    </div>
  )
}

Pattern

"use client"
import BarChart, { Bar, BarXAxis, ChartTooltip, Grid, PatternLines } from "@/components/vritti/bar-chart"

const data = [
  { month: "Jan", revenue: 12000 },
  { month: "Feb", revenue: 15500 },
  { month: "Mar", revenue: 11000 },
  { month: "Apr", revenue: 18500 },
  { month: "May", revenue: 16800 },
  { month: "Jun", revenue: 21200 },
]

export function BarChartPatternDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={data} xDataKey="month">
        <PatternLines
          height={8}
          id="barPattern"
          orientation={["diagonal"]}
          stroke="hsl(217, 91%, 60%)"
          strokeWidth={2}
          width={8}
        />
        <Grid horizontal />
        <Bar
          dataKey="revenue"
          fill="url(#barPattern)"
          lineCap={4}
          stroke="hsl(217, 91%, 60%)"
        />
        <BarXAxis />
        <ChartTooltip />
      </BarChart>
    </div>
  )
}

Radial

"use client"
import BarChart, { Bar, BarYAxis, Grid, ChartTooltip } from "@/components/vritti/bar-chart"

const data = [
  { age: "18-24", male: 320, female: 290 },
  { age: "25-34", male: 480, female: 510 },
  { age: "35-44", male: 410, female: 390 },
  { age: "45-54", male: 350, female: 370 },
  { age: "55-64", male: 270, female: 250 },
  { age: "65+", male: 180, female: 210 },
]

export function BarChartDivergingDemo() {
  return (
    <div className="w-full p-4">
      <BarChart
        data={data}
        xDataKey="age"
        orientation="horizontal"
        aspectRatio="4 / 3"
        margin={{ left: 60 }}
      >
        <Grid fadeVertical horizontal={false} vertical />
        <Bar dataKey="male" fill="hsl(217, 91%, 60%)" lineCap={4} />
        <Bar dataKey="female" fill="hsl(330, 80%, 60%)" lineCap={4} />
        <BarYAxis />
        <ChartTooltip showCrosshair={false} />
      </BarChart>
    </div>
  )
}

Stacked

"use client"
import BarChart, { Bar, BarXAxis, Grid, ChartTooltip } from "@/components/vritti/bar-chart"

const data = [
  { month: "Jan", desktop: 4000, mobile: 2400 },
  { month: "Feb", desktop: 5000, mobile: 3000 },
  { month: "Mar", desktop: 3500, mobile: 2800 },
  { month: "Apr", desktop: 4200, mobile: 3200 },
  { month: "May", desktop: 3800, mobile: 2600 },
  { month: "Jun", desktop: 5500, mobile: 3800 },
]

export function BarChartStackedDemo() {
  return (
    <div className="w-full p-4">
      <BarChart data={data} xDataKey="month" stacked stackGap={3}>
        <Grid horizontal />
        <Bar dataKey="desktop" fill="hsl(217, 91%, 60%)" lineCap={4} stackGap={3} />
        <Bar dataKey="mobile" fill="hsl(217, 91%, 75%)" lineCap={4} stackGap={3} />
        <BarXAxis />
        <ChartTooltip />
      </BarChart>
    </div>
  )
}