棒グラフ - Vibe Coding Showcase

棒グラフ

データを視覚的に比較できる棒グラフ。アニメーション、ツールチップ、レスポンシブ対応

デザインプレビュー

棒グラフの特徴

インタラクティブ

ホバー時のツールチップとアニメーション効果

カスタマイズ可能

カラー、グリッド、値表示など柔軟にカスタマイズ

レスポンシブ対応

あらゆる画面サイズで美しく表示

AI活用ガイド

ダッシュボード用チャート

プロンプト例:

ダッシュボード用の棒グラフを実装してください。月別・四半期別の切り替え、前年比表示、エクスポート機能(PNG/CSV)、リアルタイムデータ更新機能を含めてください。

比較分析チャート

プロンプト例:

複数データセットの比較棒グラフを作成してください。積み上げ棒グラフ、グループ化表示、凡例表示、データフィルタリング、パーセンテージ表示モードを実装してください。

アニメーション付きチャート

プロンプト例:

高度なアニメーション効果を持つ棒グラフを実装してください。データ更新時のトランジション、ソート機能、ズーム・パン操作、タッチジェスチャー対応を含めてください。

実装コード

ファイルサイズ: 8.2KB TypeScript

コンポーネント実装

import React, { useState, useRef, useEffect } from 'react';

interface DataPoint {
  label: string;
  value: number;
  color?: string;
}

interface BarChartProps {
  data: DataPoint[];
  height?: number;
  showGrid?: boolean;
  showValues?: boolean;
  animated?: boolean;
  interactive?: boolean;
  className?: string;
}

export const BarChart: React.FC<BarChartProps> = ({
  data,
  height = 300,
  showGrid = true,
  showValues = true,
  animated = true,
  interactive = true,
  className = ''
}) => {
  const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
  const [animationProgress, setAnimationProgress] = useState(0);
  const chartRef = useRef<HTMLDivElement>(null);

  // 最大値を計算
  const maxValue = Math.max(...data.map(d => d.value));
  const roundedMax = Math.ceil(maxValue / 10) * 10;

  // Y軸の目盛りを生成
  const yAxisSteps = 5;
  const yAxisValues = Array.from(
    { length: yAxisSteps + 1 }, 
    (_, i) => (roundedMax / yAxisSteps) * i
  );

  // デフォルトカラー
  const defaultColors = [
    '#3B82F6', // blue
    '#10B981', // green
    '#F59E0B', // yellow
    '#EF4444', // red
    '#8B5CF6', // purple
    '#EC4899', // pink
  ];

  // アニメーション
  useEffect(() => {
    if (animated) {
      const timer = setTimeout(() => {
        const interval = setInterval(() => {
          setAnimationProgress(prev => {
            if (prev >= 1) {
              clearInterval(interval);
              return 1;
            }
            return prev + 0.05;
          });
        }, 20);
      }, 100);
      
      return () => {
        clearTimeout(timer);
      };
    } else {
      setAnimationProgress(1);
    }
  }, [animated]);

  // バーの高さを計算
  const getBarHeight = (value: number) => {
    const percentage = (value / roundedMax) * animationProgress;
    return percentage * (height - 60); // 軸とラベルのスペースを確保
  };

  return (
    <div className={`w-full ${className}`} ref={chartRef}>
      <div className="relative" style={{ height: `${height}px` }}>
        {/* Y軸とグリッド */}
        <div className="absolute left-0 top-0 bottom-12 w-12 flex flex-col justify-between">
          {yAxisValues.reverse().map((value, index) => (
            <div key={index} className="relative flex items-center">
              <span className="text-xs text-gray-500 pr-2">{value}</span>
              {showGrid && index !== yAxisValues.length - 1 && (
                <div 
                  className="absolute left-12 w-screen h-px bg-gray-200"
                  style={{ width: `calc(100vw - 3rem)` }}
                />
              )}
            </div>
          ))}
        </div>

        {/* チャート本体 */}
        <div className="ml-12 h-full relative">
          <div className="flex items-end justify-around h-full pb-12">
            {data.map((item, index) => {
              const barHeight = getBarHeight(item.value);
              const color = item.color || defaultColors[index % defaultColors.length];
              const isHovered = hoveredIndex === index;

              return (
                <div
                  key={index}
                  className="relative flex-1 mx-1 flex flex-col items-center justify-end"
                  onMouseEnter={() => interactive && setHoveredIndex(index)}
                  onMouseLeave={() => interactive && setHoveredIndex(null)}
                >
                  {/* 値表示 */}
                  {showValues && (
                    <div
                      className={`
                        absolute bottom-full mb-2 text-sm font-semibold
                        transition-all duration-200
                        ${isHovered ? 'text-gray-900' : 'text-gray-600'}
                      `}
                      style={{ 
                        bottom: `${barHeight}px`,
                        opacity: animationProgress
                      }}
                    >
                      {item.value}
                    </div>
                  )}

                  {/* バー */}
                  <div
                    className={`
                      w-full rounded-t-md transition-all duration-200
                      ${interactive ? 'cursor-pointer' : ''}
                      ${isHovered ? 'opacity-80' : ''}
                    `}
                    style={{
                      backgroundColor: color,
                      height: `${barHeight}px`,
                      transform: isHovered ? 'translateY(-2px)' : 'translateY(0)'
                    }}
                  />

                  {/* ツールチップ */}
                  {interactive && isHovered && (
                    <div className="absolute bottom-full mb-8 px-3 py-2 bg-gray-900 text-white text-xs rounded shadow-lg whitespace-nowrap z-10">
                      <div className="font-semibold">{item.label}</div>
                      <div>値: {item.value}</div>
                      <div className="absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-full">
                        <div className="w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900" />
                      </div>
                    </div>
                  )}

                  {/* X軸ラベル */}
                  <div className="absolute top-full mt-2 text-xs text-gray-600 whitespace-nowrap">
                    {item.label}
                  </div>
                </div>
              );
            })}
          </div>

          {/* X軸 */}
          <div className="absolute bottom-12 left-0 right-0 h-px bg-gray-300" />
        </div>
      </div>
    </div>
  );
};

// 使用例
export const BarChartExample = () => {
  const [chartType, setChartType] = useState<'quarterly' | 'monthly'>('quarterly');

  const quarterlyData: DataPoint[] = [
    { label: 'Q1', value: 50, color: '#3B82F6' },
    { label: 'Q2', value: 68, color: '#10B981' },
    { label: 'Q3', value: 38, color: '#F59E0B' },
    { label: 'Q4', value: 62, color: '#EF4444' },
  ];

  const monthlyData: DataPoint[] = [
    { label: '1月', value: 45 },
    { label: '2月', value: 52 },
    { label: '3月', value: 48 },
    { label: '4月', value: 61 },
    { label: '5月', value: 58 },
    { label: '6月', value: 65 },
  ];

  return (
    <div className="p-8 max-w-4xl mx-auto space-y-8">
      <div>
        <h3 className="text-lg font-semibold mb-4">四半期売上高</h3>
        <BarChart
          data={quarterlyData}
          height={300}
          showGrid={true}
          showValues={true}
          animated={true}
          interactive={true}
        />
      </div>

      <div>
        <div className="flex justify-between items-center mb-4">
          <h3 className="text-lg font-semibold">月別データ</h3>
          <div className="flex gap-2">
            <button
              onClick={() => setChartType('quarterly')}
              className={`px-4 py-2 text-sm rounded-md ${
                chartType === 'quarterly'
                  ? 'bg-blue-500 text-white'
                  : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
              }`}
            >
              四半期
            </button>
            <button
              onClick={() => setChartType('monthly')}
              className={`px-4 py-2 text-sm rounded-md ${
                chartType === 'monthly'
                  ? 'bg-blue-500 text-white'
                  : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
              }`}
            >
              月別
            </button>
          </div>
        </div>
        <BarChart
          data={chartType === 'quarterly' ? quarterlyData : monthlyData}
          height={250}
          showGrid={true}
          showValues={true}
          animated={true}
          interactive={true}
        />
      </div>

      <div>
        <h3 className="text-lg font-semibold mb-4">シンプルな棒グラフ(静的)</h3>
        <BarChart
          data={[
            { label: 'A', value: 30 },
            { label: 'B', value: 50 },
            { label: 'C', value: 20 },
            { label: 'D', value: 40 },
          ]}
          height={200}
          showGrid={false}
          showValues={false}
          animated={false}
          interactive={false}
        />
      </div>
    </div>
  );
};

使用例

// 基本的な使用例
import { BarChart } from './bar-chart';

function SalesChart() {
  const data = [
    { label: 'Q1', value: 50, color: '#3B82F6' },
    { label: 'Q2', value: 68, color: '#10B981' },
    { label: 'Q3', value: 38, color: '#F59E0B' },
    { label: 'Q4', value: 72, color: '#8B5CF6' },
  ];

  return (
    <BarChart
      data={data}
      height={300}
      showGrid={true}
      showValues={true}
      animated={true}
      interactive={true}
    />
  );
}

// カスタマイズ例
function SimpleChart() {
  const data = [
    { label: 'A', value: 30 },
    { label: 'B', value: 50 },
    { label: 'C', value: 20 },
  ];

  return (
    <BarChart
      data={data}
      height={200}
      showGrid={false}
      showValues={false}
      animated={false}
    />
  );
}