diff --git a/api-platform/api/src/Entity/Commune.php b/api-platform/api/src/Entity/Commune.php
index ee12563ad4dd3b21fbb41589b4c07166be2b28cd..8c5ba42b9a942cfd900b5e521945aaf246878cc3 100644
--- a/api-platform/api/src/Entity/Commune.php
+++ b/api-platform/api/src/Entity/Commune.php
@@ -9,6 +9,7 @@ use App\Repository\CommuneRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: CommuneRepository::class)]
#[ApiResource(
@@ -17,6 +18,9 @@ use Doctrine\ORM\Mapping as ORM;
new GetCollection()
],
paginationClientEnabled: true,
+ normalizationContext: [
+ 'groups' => ['read']
+ ],
)]
class Commune
{
@@ -29,6 +33,7 @@ class Commune
private ?int $code = null;
#[ORM\Column(length: 255)]
+ #[Groups(['read'])]
private ?string $nom = null;
#[ORM\ManyToOne()]
diff --git a/api-platform/api/src/Entity/Taxe.php b/api-platform/api/src/Entity/Taxe.php
index 3653e67618ecd356febcf170d6fb9d8e54c0777c..508554c9e4c3803717d7fe4a848b614787c9195c 100644
--- a/api-platform/api/src/Entity/Taxe.php
+++ b/api-platform/api/src/Entity/Taxe.php
@@ -10,6 +10,7 @@ use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
use App\Repository\TaxeRepository;
use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: TaxeRepository::class)]
#[ApiResource(
@@ -19,6 +20,9 @@ use Doctrine\ORM\Mapping as ORM;
],
paginationClientEnabled: true,
paginationClientItemsPerPage: true,
+ normalizationContext: [
+ 'groups' => ['read']
+ ],
)]
#[ApiFilter(SearchFilter::class, properties: [
'type' => 'exact',
@@ -32,23 +36,29 @@ class Taxe
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
+ #[Groups('read')]
private ?int $id = null;
#[ORM\ManyToOne(cascade: ['persist'])]
#[ORM\JoinColumn(nullable: false)]
+ #[Groups('read')]
private ?Commune $commune = null;
#[ORM\Column]
+ #[Groups('read')]
private ?int $annee = null;
#[ORM\ManyToOne()]
#[ORM\JoinColumn(nullable: false)]
+ #[Groups('read')]
private ?TypeTaxe $type = null;
#[ORM\Column]
+ #[Groups('read')]
private ?float $taux = null;
#[ORM\Column]
+ #[Groups('read')]
private ?float $volume = null;
public function getId(): ?int
diff --git a/front/package-lock.json b/front/package-lock.json
index 2a16d488f270014b7fc9d752e13e4081e3809b3b..5762d300532c84542745507c7b561e491454e529 100644
--- a/front/package-lock.json
+++ b/front/package-lock.json
@@ -13,10 +13,12 @@
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/postcss": "^4.1.18",
+ "chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
"react": "^19.2.0",
+ "react-chartjs-2": "^5.3.1",
"react-dom": "^19.2.0",
"react-router-dom": "^7.13.0",
"recharts": "^2.15.4",
@@ -1591,6 +1593,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "license": "MIT"
+ },
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.26.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
@@ -3987,6 +3995,18 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@@ -7419,6 +7439,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-chartjs-2": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz",
+ "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
diff --git a/front/package.json b/front/package.json
index eb767ef1a6caf7d59261c33dc86f6e14ae4e9359..8286e4cebfa2f97c19f24cee208750f0ae1cd566 100644
--- a/front/package.json
+++ b/front/package.json
@@ -15,16 +15,18 @@
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/postcss": "^4.1.18",
+ "chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
"react": "^19.2.0",
+ "react-chartjs-2": "^5.3.1",
"react-dom": "^19.2.0",
"react-router-dom": "^7.13.0",
"recharts": "^2.15.4",
"tailwind-merge": "^3.4.0",
- "tailwindcss-animate": "^1.0.7",
"tailwindcss": "^4.1.18",
+ "tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
diff --git a/front/src/components/ScatterPlot.tsx b/front/src/components/ScatterPlot.tsx
index 845b0691b434c41c636425c4a9cbccbee8bb5c92..1580a39898eab5128f6165da695e85f8be68c748 100644
--- a/front/src/components/ScatterPlot.tsx
+++ b/front/src/components/ScatterPlot.tsx
@@ -1,33 +1,55 @@
-import {
- ScatterChart,
- Scatter,
- XAxis,
- YAxis,
- Tooltip,
- CartesianGrid,
-} from 'recharts';
-import {
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from '@/components/ui/chart';
+import { Chart as ChartJS, LinearScale, PointElement, Tooltip as ChartJSTooltip, Legend } from 'chart.js';
+import type { ChartData, ChartOptions } from 'chart.js';
+import { Scatter } from 'react-chartjs-2';
type Point = { commune: string; taux: number; volume: number };
export default function ScatterPlot({ data }: { data: Point[] }) {
- const config = {
- Communes: { label: 'Communes', color: '#0B5FFF' },
- } as const;
+
+ ChartJS.register(LinearScale, PointElement, ChartJSTooltip, Legend);
+
+ const chartData: ChartData<'scatter'> = {
+ datasets: [
+ {
+ label: 'Communes',
+ data: data.map((p) => ({ x: p.taux, y: p.volume, commune: p.commune } as any)),
+ backgroundColor: 'var(--color-Communes)',
+ pointRadius: 4,
+ },
+ ],
+ };
+
+ const options: ChartOptions<'scatter'> = {
+ responsive: true,
+ maintainAspectRatio: false,
+ animation: false,
+ scales: {
+ x: {
+ type: 'linear',
+ title: { display: true, text: 'Taux (%)' },
+ },
+ y: {
+ title: { display: true, text: 'Volume (€)' },
+ },
+ },
+ plugins: {
+ tooltip: {
+ callbacks: {
+ title: () => '',
+ label: (ctx) => {
+ const r: any = ctx.raw;
+ const commune = r?.commune ?? '';
+ return `${commune} — Taux: ${r.x}%, Volume: €${r.y}`;
+ },
+ },
+ },
+ legend: { display: false },
+ },
+ };
return (
-