diff --git a/front/package.json b/front/package.json index e521b3f..d7ccf80 100644 --- a/front/package.json +++ b/front/package.json @@ -9,6 +9,7 @@ "dependencies": { "@fontsource/nunito-sans": "^5.2.7", "@tailwindcss/postcss": "^4.1.17", + "d3": "^7.9.0", "next": "^15.4.1", "postcss": "^8.5.6", "react": "^19.1.0", diff --git a/front/src/app/embalse/[embalse]/page.tsx b/front/src/app/embalse/[embalse]/page.tsx index a50d99c..b3262d1 100644 --- a/front/src/app/embalse/[embalse]/page.tsx +++ b/front/src/app/embalse/[embalse]/page.tsx @@ -1,4 +1,5 @@ -import Link from "next/link"; +import { mockData } from "../../model/reservoir-data"; +import { ReservoirGauge } from "./reservoir-gauge"; interface Props { params: Promise<{ embalse: string }>; @@ -6,9 +7,17 @@ interface Props { export default async function EmbalseDetallePage({ params }: Props) { const { embalse } = await params; + + const reservoirData = mockData; + return (
-

Detalle del embalse: {embalse}

+
); } diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts new file mode 100644 index 0000000..07a779d --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts @@ -0,0 +1,40 @@ +import * as d3 from "d3"; +import { arcConfig } from "./model"; + +type ArcGroup = d3.Selection; + +interface DrawArcParams { + arcGroup: ArcGroup; + endAngle: number; + fillColor: string; +} + +const createArcGenerator = (endAngle: number) => { + return d3 + .arc() + .innerRadius(arcConfig.innerRadius) + .outerRadius(arcConfig.outerRadius) + .startAngle(arcConfig.startAngle) + .endAngle(endAngle) + .cornerRadius(arcConfig.cornerRadius); +}; + +// TODO: add unit tests for calculateFilledAngle + +export const calculateFilledAngle = (percentage: number): number => { + // Ensure percentage is within valid range [0, 1] + const normalized = Math.max(0, Math.min(1, percentage)); + // Total sweep of the arc (from start to end) + const totalAngle = arcConfig.endAngle - arcConfig.startAngle; + // Calculate where the filled arc should end based on percentage + return arcConfig.startAngle + normalized * totalAngle; +}; + +export const drawArc = ({ arcGroup, endAngle, fillColor }: DrawArcParams) => { + const arcGenerator = createArcGenerator(endAngle); + + arcGroup + .append("path") + .attr("d", arcGenerator as any) + .style("fill", fillColor); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx new file mode 100644 index 0000000..e9d4cb8 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx @@ -0,0 +1,53 @@ +"use client"; + +import * as d3 from "d3"; +import { useEffect, useRef } from "react"; +import { calculateFilledAngle, drawArc } from "./gauge-arcs.business"; +import { arcConfig, gaugeDimensions } from "./model"; + +interface GaugeArcsProps { + percentage: number; +} + +export const GaugeArcs = ({ percentage }: GaugeArcsProps) => { + const svgRef = useRef(null); + + useEffect(() => { + if (!svgRef.current) return; + + const svg = d3.select(svgRef.current); + svg.selectAll("*").remove(); + + // Center position + const centerX = gaugeDimensions.width / 2; + const centerY = arcConfig.outerRadius; + + // Create centered group + const arcGroup = svg + .append("g") + .attr("transform", `translate(${centerX}, ${centerY})`); + + // 1. Background arc (--color-total-water, full) + drawArc({ + arcGroup, + endAngle: arcConfig.endAngle, + fillColor: "var(--color-total-water)", + }); + + // 2. Filled arc (primary color, based on percentage prop) + const filledEndAngle = calculateFilledAngle(percentage); + drawArc({ + arcGroup, + endAngle: filledEndAngle, + fillColor: "var(--color-primary)", + }); + }, [percentage]); + + return ( + + ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx new file mode 100644 index 0000000..7479665 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx @@ -0,0 +1,20 @@ +interface Props { + percentage: number; + measurementDate: string; +} + +export const GaugeInformation = ({ percentage, measurementDate }: Props) => { + const displayPercentage = `${Math.round(percentage * 100)}`; + + return ( +
+ + {displayPercentage} + % + + + {measurementDate} + +
+ ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts new file mode 100644 index 0000000..e73c0af --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts @@ -0,0 +1,2 @@ +export * from "./gauge-arcs.component"; +export * from "./gauge-information.component"; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts new file mode 100644 index 0000000..fb87310 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts @@ -0,0 +1,25 @@ +interface GaugeDimensions { + width: number; + height: number; +} + +interface ArcConfig { + innerRadius: number; + outerRadius: number; + startAngle: number; + endAngle: number; + cornerRadius: number; +} + +export const gaugeDimensions: GaugeDimensions = { + width: 220, + height: 184, +}; + +export const arcConfig: ArcConfig = { + innerRadius: 90, + outerRadius: 110, + startAngle: -Math.PI * 0.75, + endAngle: Math.PI * 0.75, + cornerRadius: 12, +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx new file mode 100644 index 0000000..b28c872 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx @@ -0,0 +1,31 @@ +import { gaugeDimensions } from "./components/model"; +import { GaugeInformation } from "./components"; +import { GaugeArcs } from "./components/gauge-arcs.component"; + +interface Props { + percentage: number; + measurementDate: string; +} + +export const GaugeChart = ({ percentage, measurementDate }: Props) => { + return ( +
+ {/* The SVG arc */} + + + {/* Center text */} +
+ +
+
+ ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts new file mode 100644 index 0000000..1b2c7de --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts @@ -0,0 +1 @@ +export * from "./gauge-chart.component"; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx new file mode 100644 index 0000000..6cc484b --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx @@ -0,0 +1,26 @@ +interface Props { + currentVolume: number; + totalCapacity: number; +} + +export const GaugeLegend = ({ currentVolume, totalCapacity }: Props) => { + return ( +
+ {/* Embalsada (filled water) - uses primary color */} +
+ + + Embalsada: {currentVolume}m³ + +
+ + {/* Total capacity - uses total-water color */} +
+ + + Total: {totalCapacity}m³ + +
+
+ ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts new file mode 100644 index 0000000..315d231 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts @@ -0,0 +1 @@ +export * from "./reservoir-gauge"; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx new file mode 100644 index 0000000..568f6d7 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx @@ -0,0 +1,28 @@ +import { ReservoirData } from "../../../model/reservoir-data"; +import { GaugeChart } from "./gauge-chart"; +import { GaugeLegend } from "./gauge-legend.component"; + +interface Props extends ReservoirData { + name: string; +} + +export const ReservoirGauge = ({ + name, + currentVolume, + totalCapacity, + measurementDate, +}: Props) => { + // const percentage = currentVolume / totalCapacity; + // TODO: replace hardcoded % for real reservoir filled water percentage + + return ( +
+

Embalse de {name}

+ + +
+ ); +}; diff --git a/front/src/app/globals.css b/front/src/app/globals.css index 7025098..f0275c1 100644 --- a/front/src/app/globals.css +++ b/front/src/app/globals.css @@ -24,6 +24,9 @@ /* Title color */ --color-title: #051c1f; + /* Graphic total water */ + --color-total-water: #26d6ed; + /* Accesible visited link color */ --color-visited-link: #257782; diff --git a/front/src/app/model/reservoir-data.ts b/front/src/app/model/reservoir-data.ts new file mode 100644 index 0000000..6ace67b --- /dev/null +++ b/front/src/app/model/reservoir-data.ts @@ -0,0 +1,11 @@ +export interface ReservoirData { + currentVolume: number; + totalCapacity: number; + measurementDate: string; +} + +export const mockData: ReservoirData = { + currentVolume: 1500, + totalCapacity: 50000, + measurementDate: "25/12/2025", +}; diff --git a/package-lock.json b/package-lock.json index cc9078a..1397bce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "dependencies": { "@fontsource/nunito-sans": "^5.2.7", "@tailwindcss/postcss": "^4.1.17", + "d3": "^7.9.0", "next": "^15.4.1", "postcss": "^8.5.6", "react": "^19.1.0", @@ -1416,6 +1417,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -1474,6 +1484,407 @@ "dev": true, "license": "MIT" }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/daisyui": { "version": "5.5.5", "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.5.tgz", @@ -1496,6 +1907,15 @@ "node": ">=6" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "license": "MIT", @@ -1998,6 +2418,15 @@ "node": ">=0.10.0" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-arrayish": { "version": "0.3.2", "license": "MIT", @@ -3162,6 +3591,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.46.4", "dev": true, @@ -3200,6 +3635,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safer-buffer": { "version": "2.1.2", "license": "MIT"