Bloom Logo

Chart

A responsive data visualization chart component built on Recharts supporting line and bar series with customizable height, colors, and tooltips.

importimport { Chart } from "@/components/ui/chart/chart";
$npx @bloomui-react/cli add chart
chart.tsx
"use client";

import { Icon } from "@iconify/react";
import * as React from "react";
import {
  Bar,
  Brush,
  CartesianGrid,
  Line,
  BarChart as ReBarChart,
  LineChart as ReLineChart,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";
import { Button } from "@/components/ui/button/button";
import { cn } from "@/lib/utils";

export interface ChartProps {
  type?: "line" | "bar";
  data: Array<Record<string, any>>;
  xKey: string;
  yKey: string;
  height?: number;
  className?: string;
  color?: string;
  title?: string;
  valueFormatter?: (value: number) => string;
  enableZoomPan?: boolean;
  enableExport?: boolean;
  onExportSVG?: () => void;
  onExportPNG?: () => void;
}

export function Chart({
  type = "line",
  data,
  xKey,
  yKey,
  height = 300,
  className,
  color = "#0284c7",
  title,
  valueFormatter,
  enableZoomPan = false,
  enableExport = false,
  onExportSVG,
  onExportPNG,
}: ChartProps) {
  const containerRef = React.useRef<HTMLDivElement>(null);
  const [zoomStartIndex, setZoomStartIndex] = React.useState<number>(0);
  const [zoomEndIndex, setZoomEndIndex] = React.useState<number>(
    data.length > 0 ? data.length - 1 : 0,
  );

  React.useEffect(() => {
    if (data.length > 0) {
      setZoomEndIndex(data.length - 1);
    }
  }, [data]);

  const handleExportSVG = React.useCallback(() => {
    if (onExportSVG) {
      onExportSVG();
    } else {
      alert("Exporting SVG chart...");
    }
  }, [onExportSVG]);

  const handleExportPNG = React.useCallback(() => {
    if (onExportPNG) {
      onExportPNG();
    } else {
      alert("Exporting PNG chart...");
    }
  }, [onExportPNG]);

  const formatTooltipValue = (value: any) => {
    if (typeof value === "number" && valueFormatter) {
      return valueFormatter(value);
    }
    return value;
  };

  return (
    <div
      ref={containerRef}
      className={cn(
        "w-full rounded-2xl border border-zinc-200 dark:border-zinc-800 p-6 bg-white dark:bg-zinc-900 shadow-xs text-zinc-900 dark:text-zinc-100 flex flex-col space-y-4",
        className,
      )}
    >
      {(title || enableExport || enableZoomPan) && (
        <div className="flex items-center justify-between pb-2 border-b border-zinc-100 dark:border-zinc-800/80">
          <div>
            {title && (
              <h4 className="font-semibold text-sm text-zinc-900 dark:text-zinc-100">
                {title}
              </h4>
            )}
          </div>
          <div className="flex items-center gap-2">
            {enableZoomPan && (
              <Button
                variant="flat"
                size="xs"
                onClick={() => {
                  setZoomStartIndex(0);
                  setZoomEndIndex(data.length - 1);
                }}
                className="text-xs text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100"
              >
                Reset Zoom
              </Button>
            )}
            {enableExport && (
              <div className="flex items-center gap-2">
                <Button
                  variant="bordered"
                  size="xs"
                  startContent={
                    <Icon icon="hugeicons:download-02" className="size-3.5" />
                  }
                  onClick={handleExportSVG}
                >
                  SVG
                </Button>
                <Button
                  variant="bordered"
                  size="xs"
                  startContent={
                    <Icon icon="hugeicons:image-01" className="size-3.5" />
                  }
                  onClick={handleExportPNG}
                >
                  PNG
                </Button>
              </div>
            )}
          </div>
        </div>
      )}

      <ResponsiveContainer width="100%" height={height}>
        {type === "bar" ? (
          <ReBarChart data={data}>
            <CartesianGrid
              strokeDasharray="3 3"
              opacity={0.15}
              stroke="#a1a1aa"
            />
            <XAxis
              dataKey={xKey}
              stroke="#71717a"
              fontSize={12}
              tickLine={false}
              axisLine={false}
            />
            <YAxis
              stroke="#71717a"
              fontSize={12}
              tickLine={false}
              axisLine={false}
              tickFormatter={(val) =>
                valueFormatter ? valueFormatter(val) : val
              }
            />
            <Tooltip
              formatter={formatTooltipValue}
              contentStyle={{
                backgroundColor: "rgba(24, 24, 27, 0.95)",
                borderColor: "#27272a",
                borderRadius: "0.75rem",
                color: "#f4f4f5",
                fontSize: "12px",
                boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.3)",
              }}
              itemStyle={{ color: "#f4f4f5" }}
            />
            <Bar dataKey={yKey} fill={color} radius={[6, 6, 0, 0]} />
            {enableZoomPan && (
              <Brush
                dataKey={xKey}
                height={26}
                stroke={color}
                fill="rgba(244, 244, 245, 0.05)"
                startIndex={zoomStartIndex}
                endIndex={zoomEndIndex}
                onChange={(range) => {
                  if (range.startIndex !== undefined)
                    setZoomStartIndex(range.startIndex);
                  if (range.endIndex !== undefined)
                    setZoomEndIndex(range.endIndex);
                }}
              />
            )}
          </ReBarChart>
        ) : (
          <ReLineChart data={data}>
            <CartesianGrid
              strokeDasharray="3 3"
              opacity={0.15}
              stroke="#a1a1aa"
            />
            <XAxis
              dataKey={xKey}
              stroke="#71717a"
              fontSize={12}
              tickLine={false}
              axisLine={false}
            />
            <YAxis
              stroke="#71717a"
              fontSize={12}
              tickLine={false}
              axisLine={false}
              tickFormatter={(val) =>
                valueFormatter ? valueFormatter(val) : val
              }
            />
            <Tooltip
              formatter={formatTooltipValue}
              contentStyle={{
                backgroundColor: "rgba(24, 24, 27, 0.95)",
                borderColor: "#27272a",
                borderRadius: "0.75rem",
                color: "#f4f4f5",
                fontSize: "12px",
                boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.3)",
              }}
              itemStyle={{ color: "#f4f4f5" }}
            />
            <Line
              type="monotone"
              dataKey={yKey}
              stroke={color}
              strokeWidth={3}
              dot={{ r: 4, fill: color }}
              activeDot={{ r: 6, fill: color }}
            />
            {enableZoomPan && (
              <Brush
                dataKey={xKey}
                height={26}
                stroke={color}
                fill="rgba(244, 244, 245, 0.05)"
                startIndex={zoomStartIndex}
                endIndex={zoomEndIndex}
                onChange={(range) => {
                  if (range.startIndex !== undefined)
                    setZoomStartIndex(range.startIndex);
                  if (range.endIndex !== undefined)
                    setZoomEndIndex(range.endIndex);
                }}
              />
            )}
          </ReLineChart>
        )}
      </ResponsiveContainer>
    </div>
  );
}

Default

Standard smooth line chart visualizing quantitative time-series data over time.

Bar Chart

Categorical bar representation for comparing discrete data values.

type: line | bar

Color Themes

Customize the chart series color using the color prop (HEX or CSS color string). Stacked vertically for clear visual comparison.

color: string
color="#0284c7" (Sky Blue)
color="#8b5cf6" (Purple)
color="#10b981" (Emerald Green)
color="#f43f5e" (Rose Red)

Custom Formatted Tooltips & Axes

Pass a valueFormatter function to format values as currency, percentages, or custom localized units in tooltips and Y-axis.

valueFormatter: (value: number) => string

Quarterly Revenue ($)

Zoom & Pan Control

Set enableZoomPan to true to display a slider brush control for zooming and panning across dense datasets.

enableZoomPan: boolean

High-Density User Traffic

Export Chart Utility

Set enableExport to true to show instant SVG and PNG download buttons for saving chart figures.

enableExport: boolean

Sales Performance Overview

API Reference

Props — Chart

Properties for configuring the Chart component.

PropTypeDefaultDescription
type'line' | 'bar''line'Chart visual representation series style.
dataArray<Record<string, any>>requiredArray of data point objects to plot on axes.
xKeystringrequiredObject key for horizontal X-axis category labels.
yKeystringrequiredObject key for numerical Y-axis series values.
heightnumber300Container height scale in pixels.
colorstring'#0284c7'HEX or CSS color string for line stroke or bar fill.
titlestringOptional header title text rendered above the chart.
valueFormatter(value: number) => stringFormatter function for currency/locale values in tooltips & axes.
enableZoomPanbooleanfalseEnables slider brush control for zooming and panning.
enableExportbooleanfalseDisplays SVG and PNG export buttons in the header.
onExportSVG() => voidCustom callback triggered when clicking the SVG export button.
onExportPNG() => voidCustom callback triggered when clicking the PNG export button.