PricingChart.jsx 6.52 KB
// PricingChart.jsx
import React, { useState, useEffect, useRef } from 'react';
import { useScrollToTop } from '../hooks/useScrollToTop';
import '../styles/PricingChart.css';

const ChartLegend = ({ onLegendClick, highlightedBarTypes, showPricing = true }) => {
  if (!showPricing) return null;
  
  return (
    <div className="legend">
      <div
        className="legend-item"
        onClick={() => onLegendClick('input')}
        style={{ cursor: 'pointer', opacity: highlightedBarTypes.input ? 1 : 0.5 }}
      >
        <div className="legend-color input-color"></div>
        <span>Input Price</span>
      </div>
      <div
        className="legend-item"
        onClick={() => onLegendClick('output')}
        style={{ cursor: 'pointer', opacity: highlightedBarTypes.output ? 1 : 0.5 }}
      >
        <div className="legend-color output-color"></div>
        <span>Output Price</span>
      </div>
    </div>
  );
};

const ChartBar = ({ price, type, maxPrice, highlighted, score }) => {
  const getBarHeight = () => {
    if (score !== undefined) {
      return (score / maxPrice) * 200;
    }
    return (price / maxPrice) * 200;
  };

  return (
    <div
      className={`bar ${score !== undefined ? 'score-bar' : `${type}-bar`}`}
      style={{
        height: `${getBarHeight()}px`,
        opacity: highlighted ? 1 : 0.3,
      }}
    >
      <span className="price-label">{score !== undefined ? score : price}</span>
    </div>
  );
};

const ProviderColumn = ({ provider, maxPrice, highlightedBarTypes, showPricing = true }) => (
  <div className="chart-column">
    <div className="bars-container">
      {showPricing ? (
        <>
          <ChartBar
            price={provider.inputPrice}
            type="input"
            maxPrice={maxPrice}
            highlighted={highlightedBarTypes.input}
          />
          <ChartBar
            price={provider.outputPrice}
            type="output"
            maxPrice={maxPrice}
            highlighted={highlightedBarTypes.output}
          />
        </>
      ) : (
        <ChartBar
          score={provider.score}
          maxPrice={maxPrice}
          highlighted={true}
        />
      )}
    </div>
    <div className="provider-info">
      <img
        src={`${provider.logo}`}
        alt={`${provider.name} logo`}
        className="provider-logo"
      />
      <span className="provider-name">{provider.name}</span>
    </div>
  </div>
);

const YAxis = ({ maxPrice }) => {
  const numberOfTicks = 5;
  const tickValues = [];

  for (let i = 0; i <= numberOfTicks; i++) {
    const value = ((maxPrice / numberOfTicks) * i).toFixed(2);
    tickValues.push(value);
  }

  return (
    <div className="y-axis">
      {tickValues.reverse().map((value, index) => (
        <div key={index} className="y-axis-label">
          {value}
        </div>
      ))}
    </div>
  );
};

const GridLines = () => (
  <div className="grid-lines">
    {[...Array(5)].map((_, index) => (
      <div key={index} className="grid-line" style={{ bottom: `${(index / 4) * 100}%` }}></div>
    ))}
  </div>
);

const PricingChart = ({ data, showPricing = true }) => {
  useScrollToTop();
  const [highlightedBarTypes, setHighlightedBarTypes] = useState({
    input: true,
    output: true,
  });
  const chartAreaRef = useRef(null);
  const [hasScroll, setHasScroll] = useState(false);
  const [showScrollHint, setShowScrollHint] = useState(false);

  useEffect(() => {
    const checkScroll = () => {
      if (chartAreaRef.current) {
        const { scrollWidth, clientWidth, scrollLeft } = chartAreaRef.current;
        setHasScroll(scrollWidth > clientWidth);
        // 只在滚动到最左侧时显示提示
        setShowScrollHint(scrollWidth > clientWidth && scrollLeft === 0);
      }
    };

    const handleScroll = () => {
      if (chartAreaRef.current) {
        const { scrollLeft } = chartAreaRef.current;
        // 当用户开始滚动时隐藏提示
        if (scrollLeft > 0) {
          setShowScrollHint(false);
        }
      }
    };

    checkScroll();
    window.addEventListener('resize', checkScroll);
    if (chartAreaRef.current) {
      chartAreaRef.current.addEventListener('scroll', handleScroll);
    }

    return () => {
      window.removeEventListener('resize', checkScroll);
      if (chartAreaRef.current) {
        chartAreaRef.current.removeEventListener('scroll', handleScroll);
      }
    };
  }, [data]);

  const handleScrollHintClick = () => {
    if (chartAreaRef.current) {
      const { scrollWidth, clientWidth } = chartAreaRef.current;
      chartAreaRef.current.scrollTo({
        left: scrollWidth - clientWidth,
        behavior: 'smooth'
      });
      setShowScrollHint(false);
    }
  };

  const handleLegendClick = (barType) => {
    setHighlightedBarTypes((prevState) => ({
      ...prevState,
      [barType]: !prevState[barType],
    }));
  };

  const getMaxPrice = () => {
    if (!showPricing) {
      return Math.max(...data.providers.map(provider => provider.score));
    }
    const prices = data.providers.flatMap((provider) => [
      provider.inputPrice,
      provider.outputPrice,
    ]);
    return Math.max(...prices);
  };

  const maxPrice = getMaxPrice();

  return (
    <div className="pricing-chart">
      <h1 className="chart-title">{data.title}</h1>
      <h2 className="chart-subtitle">{data.subtitle}</h2>

      <ChartLegend 
        onLegendClick={handleLegendClick} 
        highlightedBarTypes={highlightedBarTypes}
        showPricing={showPricing}
      />

      <div className={`chart-area ${hasScroll ? 'has-scroll' : ''}`} ref={chartAreaRef}>
        <YAxis maxPrice={maxPrice} />
        <div className="chart-container">
          <GridLines />
          {data.providers.map((provider) => (
            <ProviderColumn
              key={provider.name}
              provider={provider}
              maxPrice={maxPrice}
              highlightedBarTypes={highlightedBarTypes}
              showPricing={showPricing}
            />
          ))}
        </div>
        {showScrollHint && (
          <div 
            className="scroll-hint-container"
            onClick={handleScrollHintClick}
            style={{ cursor: 'pointer' }}
          >
            <div className="scroll-hint">
              <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
                <path d="M9.29 15.88L13.17 12 9.29 8.12c-.39-.39-.39-1.02 0-1.41.39-.39 1.02-.39 1.41 0l4.59 4.59c.39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0-.38-.39-.39-1.03 0-1.42z"/>
              </svg>
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

export default PricingChart;