Nuage.tsx 5,25 ko
Newer Older
antoH's avatar
antoH a validé
"use client";

import { useMemo, useState } from "react";
import * as d3 from "d3";

type CommuneData = {
  commune: string;
  departement: string;
  year: number;
  taxType: string;
  taxRate: number;
  volume: number;
};

const data: CommuneData[] = [
  { commune: "Rouen", departement: "76", year: 2022, taxType: "Foncière", taxRate: 18.2, volume: 320000 },
  { commune: "Le Havre", departement: "76", year: 2022, taxType: "Foncière", taxRate: 17.5, volume: 410000 },
  { commune: "Dieppe", departement: "76", year: 2022, taxType: "Foncière", taxRate: 19.1, volume: 150000 },
  { commune: "Fécamp", departement: "76", year: 2022, taxType: "Foncière", taxRate: 18.8, volume: 120000 },
  { commune: "Elbeuf", departement: "76", year: 2022, taxType: "Foncière", taxRate: 20.3, volume: 98000 },

  { commune: "Rouen", departement: "76", year: 2022, taxType: "Habitation", taxRate: 12.1, volume: 180000 },
  { commune: "Le Havre", departement: "76", year: 2022, taxType: "Habitation", taxRate: 11.8, volume: 240000 },
];


export default function ScatterDepartement() {
  const [year, setYear] = useState(2022);
  const [departement, setDepartement] = useState("76");
  const [taxType, setTaxType] = useState("Foncière");

  const width = 700;
  const height = 420;
  const margin = { top: 40, right: 30, bottom: 50, left: 60 };

  /**
   * === DATA FILTER =====================================
   */
  const filteredData = useMemo(() => {
    return data.filter(d =>
      d.year === year &&
      d.departement === departement &&
      d.taxType === taxType
    );
  }, [year, departement, taxType]);

  /**
   * === SCALES ==========================================
   */
  const xScale = useMemo(() => {
    return d3.scaleLinear()
      .domain(d3.extent(filteredData, d => d.taxRate) as [number, number])
      .nice()
      .range([margin.left, width - margin.right]);
  }, [filteredData]);

  const yScale = useMemo(() => {
    return d3.scaleLinear()
      .domain(d3.extent(filteredData, d => d.volume) as [number, number])
      .nice()
      .range([height - margin.bottom, margin.top]);
  }, [filteredData]);

  const xTicks = xScale.ticks(5);
  const yTicks = yScale.ticks(5);

  return (
    <section className="w-full max-w-4xl">
      <div className="rounded-xl bg-white shadow-sm p-6">
        <h2 className="text-2xl font-semibold text-gray-800 mb-1">
          Corrélation taux / volume collecté
        </h2>
        <p className="text-gray-500 mb-6">
          Communes du département {departement}{year}
        </p>

        {/* === CONTROLS ================================= */}
        <div className="mb-6 flex flex-wrap gap-4">
          <select
            value={taxType}
            onChange={e => setTaxType(e.target.value)}
            className="rounded-md border px-3 py-2 text-sm"
          >
            <option>Foncière</option>
            <option>Habitation</option>
          </select>

          <select
            value={year}
            onChange={e => setYear(+e.target.value)}
            className="rounded-md border px-3 py-2 text-sm"
          >
            <option value={2022}>2022</option>
          </select>

          <select
            value={departement}
            onChange={e => setDepartement(e.target.value)}
            className="rounded-md border px-3 py-2 text-sm"
          >
            <option value="76">76</option>
          </select>
        </div>

        {/* === SVG ====================================== */}
        <svg width={width} height={height} className="mx-auto block">

          {/* Grid */}
          {yTicks.map(t => (
            <line
              key={t}
              x1={margin.left}
              x2={width - margin.right}
              y1={yScale(t)}
              y2={yScale(t)}
              stroke="#e5e7eb"
              strokeDasharray="2 2"
            />
          ))}

          {/* Axes */}
          <line
            x1={margin.left}
            x2={margin.left}
            y1={margin.top}
            y2={height - margin.bottom}
            stroke="#9ca3af"
          />
          <line
            x1={margin.left}
            x2={width - margin.right}
            y1={height - margin.bottom}
            y2={height - margin.bottom}
            stroke="#9ca3af"
          />

          {/* X ticks */}
          {xTicks.map(t => (
            <text
              key={t}
              x={xScale(t)}
              y={height - margin.bottom + 20}
              textAnchor="middle"
              fontSize="10"
              fill="#6b7280"
            >
              {t} %
            </text>
          ))}

          {/* Y ticks */}
          {yTicks.map(t => (
            <text
              key={t}
              x={margin.left - 10}
              y={yScale(t)}
              textAnchor="end"
              alignmentBaseline="middle"
              fontSize="10"
              fill="#6b7280"
            >
              {t / 1000}k €
            </text>
          ))}

          {/* Points */}
          {filteredData.map((d, i) => (
            <circle
              key={i}
              cx={xScale(d.taxRate)}
              cy={yScale(d.volume)}
              r={6}
              fill="#3b82f6"
              className="opacity-80 hover:opacity-100 transition"
            />
          ))}

        </svg>
      </div>
    </section>
  );
}