bigbullui logobigbullui
Componentcharts

Bubble Chart

Bubble chart with size-coded circles and hover magnify.

Preview

Live Preview
A

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add bubble-chart

Or install the full npm package:

npm install bigbullui

Usage

import { BubbleChart } from "@/components/ui/bubble-chart";

<BubbleChart />

Source code

bubble-chart.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface BubblePoint {
  id: string;
  x: number;
  y: number;
  size: number;
  label?: string;
}

export interface BubbleChartProps extends React.HTMLAttributes<SVGSVGElement> {
  points: BubblePoint[];
  height?: number;
}

/** Bubble chart: x/y positioning + sized circles with hover focus. */
export function BubbleChart({ points, height = 240, className, ...props }: BubbleChartProps) {
  const [hover, setHover] = React.useState<string | null>(null);
  const maxSize = Math.max(...points.map((p) => p.size), 1);

  return (
    <svg viewBox="0 0 100 62" style={{ height }} className={cn("w-full", className)} role="img" aria-label="Bubble chart" onMouseLeave={() => setHover(null)} {...props}>
      <style>{`@keyframes bcPop { from { transform: scale(0); opacity: 0; } }`}</style>
      {/* Izgara */}
      {[20, 40, 60, 80].map((x) => <line key={x} x1={x} y1={4} x2={x} y2={58} stroke="var(--border)" strokeWidth="0.2" strokeDasharray="1 1" />)}
      {[20, 40].map((y) => <line key={y} x1={2} y1={y} x2={98} y2={y} stroke="var(--border)" strokeWidth="0.2" strokeDasharray="1 1" />)}
      <rect x="1" y="3" width="98" height="56" fill="none" stroke="var(--border)" strokeWidth="0.3" rx="1" />

      {points.map((p, idx) => {
        const r = 2 + (p.size / maxSize) * 6;
        const isHover = hover === p.id;
        return (
          <g
            key={p.id}
            onMouseEnter={() => setHover(p.id)}
            style={{ animation: "bcPop 0.4s cubic-bezier(0.34,1.56,0.64,1) both", animationDelay: `${idx * 70}ms`, transformOrigin: `${p.x}px ${p.y}px` }}
          >
            <circle
              cx={p.x} cy={p.y} r={r}
              fill="var(--accent)"
              opacity={isHover ? 0.55 : 0.25}
              stroke="var(--accent)"
              strokeWidth={isHover ? 0.6 : 0.3}
              className="cursor-pointer transition-all duration-200 motion-reduce:transition-none"
            />
            <text x={p.x} y={p.y + r + 3} textAnchor="middle" fontSize="2.4" fill="var(--muted-foreground)" className="pointer-events-none select-none font-mono">
              {p.label ?? ""}
            </text>
          </g>
        );
      })}
    </svg>
  );
}

Props

PropTypeDescription
pointsBubblePoint[]
heightnumber