diff --git a/components/charts/axis-tick-by-date.js b/components/charts/axis-tick-by-date.js
new file mode 100644
index 000000000..dc28c804d
--- /dev/null
+++ b/components/charts/axis-tick-by-date.js
@@ -0,0 +1,25 @@
+import PropTypes from 'prop-types'
+
+function AxisTickByDate({x, y, payload}) {
+ const [year, month, day] = payload.value.split('-')
+
+ return (
+
+
+ {
+ day ?
+ `${day}/${month}/${year}` :
+ (new Date(year, month - 1, day || 1)).toLocaleDateString('fr-FR', {year: 'numeric', month: 'short'})
+ }
+
+
+ )
+}
+
+AxisTickByDate.propTypes = {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ payload: PropTypes.object,
+}
+
+export default AxisTickByDate
diff --git a/components/charts/chart.js b/components/charts/chart.js
new file mode 100644
index 000000000..9eb4e2339
--- /dev/null
+++ b/components/charts/chart.js
@@ -0,0 +1,140 @@
+import PropTypes from 'prop-types'
+
+import {
+ AreaChart,
+ Area,
+ BarChart,
+ Bar,
+ LineChart,
+ Line,
+ ScatterChart,
+ Scatter,
+ CartesianGrid,
+ XAxis,
+ YAxis,
+ LabelList,
+ ResponsiveContainer,
+ Tooltip,
+ Legend
+} from 'recharts'
+
+import {defaultTheme} from './color-theme'
+import AxisTickByDate from './axis-tick-by-date'
+
+const renderColorfulLegendText = (value, entry) => {
+ const {color} = entry
+ return {value}
+}
+
+const yAxisTickFormatter = value => {
+ return Number.isNaN(value) ?
+ value :
+ `${value.toLocaleString(
+ undefined, {minimumFractionDigits: 0}
+ ).replace(/000$/g, 'K')}`
+}
+
+const defaultArea = {
+ type: 'monotone',
+ dataKey: 'download BAL',
+ stackId: '1',
+ strokeWidth: 0.5,
+}
+
+const typeComponents = {
+ area: {
+ chart: AreaChart,
+ axis: Area,
+ },
+ bar: {
+ chart: BarChart,
+ axis: Bar,
+ },
+ line: {
+ chart: LineChart,
+ axis: Line,
+ },
+ scatter: {
+ chart: ScatterChart,
+ axis: Scatter,
+ },
+}
+
+function CartesianChart({type, data, axisDef, yAxisMaxKeyName, totalKeyName}) {
+ const dataList = Object.entries(axisDef).map(([dataKey, areaItem], index) => ({
+ ...defaultArea,
+ dataKey,
+ stroke: defaultTheme[index]?.[0],
+ fill: defaultTheme[index]?.[1],
+ ...(areaItem || {}),
+ }))
+
+ const {chart: Chart, axis: Axis} = typeComponents[type]
+
+ return (
+
+
+
+ }
+ />
+
+
+
+
+ <>
+ {dataList.map((areaItem, index, arr) => (
+
+ {totalKeyName && index === arr.length - 1 && }
+
+ ))}
+ >
+
+
+
+
+
+ )
+}
+
+CartesianChart.propTypes = {
+ type: PropTypes.oneOf(['area', 'bar', 'line', 'scatter']),
+ data: PropTypes.array,
+ axisDef: PropTypes.object,
+ yAxisMaxKeyName: PropTypes.string,
+ totalKeyName: PropTypes.string,
+}
+
+export default CartesianChart
diff --git a/components/charts/color-theme.js b/components/charts/color-theme.js
new file mode 100644
index 000000000..42384a011
--- /dev/null
+++ b/components/charts/color-theme.js
@@ -0,0 +1,51 @@
+export const defaultTheme = [
+ ['#6C6C6C', '#b3b3b3'],
+ ['#5C5C5C', '#a6a6a6'],
+ ['#4F4F4F', '#999999'],
+ ['#444444', '#8c8c8c'],
+]
+
+export const colorthemes = {
+ ecume: [
+ ['#869ECE', '#ced6ea'],
+ ['#465F9D', '#8b9eda'],
+ ['#273962', '#5576c2'],
+ ['#222940', '#536190'],
+ ],
+ wisteria: [
+ ['#A695E7', '#e2d8f5'],
+ ['#8C7CE5', '#c6bdf0'],
+ ['#6F5FD5', '#a99ee6'],
+ ['#4D3DBD', '#7d6fc6'],
+ ],
+ azure: [
+ ['#3C91E6', '#cbe0f7'],
+ ['#1D7ECC', '#8dc0f3'],
+ ['#0E6AA2', '#6aa8e9'],
+ ['#0A4C6A', '#4e8ad5'],
+ ],
+ forest: [
+ ['#6DBA8E', '#c8e5d6'],
+ ['#3FAE6B', '#9ed8b9'],
+ ['#2D8E53', '#6cc39b'],
+ ['#1E673C', '#4da77f'],
+ ],
+ emerald: [
+ ['#4DBA87', '#c8e5d6'],
+ ['#2FAE64', '#9ed8b9'],
+ ['#1D8E4F', '#6cc39b'],
+ ['#0E673A', '#4da77f'],
+ ],
+ glicyne: [
+ ['#F5C242', '#f9e7c7'],
+ ['#F3B824', '#f9d99f'],
+ ['#F1A806', '#f9cb77'],
+ ['#F09A00', '#f9c05e'],
+ ],
+ rubi: [
+ ['#E65A5A', '#f7c6c6'],
+ ['#CC3F3F', '#f3a8a8'],
+ ['#A22D2D', '#e98b8b'],
+ ['#6A1E1E', '#d56f6f'],
+ ],
+}
diff --git a/components/charts/index.js b/components/charts/index.js
new file mode 100644
index 000000000..153beab6e
--- /dev/null
+++ b/components/charts/index.js
@@ -0,0 +1,2 @@
+export {default as CartesianChart} from './chart'
+export {defaultTheme, colorthemes} from './color-theme'
diff --git a/components/color-line.js b/components/color-line.js
new file mode 100644
index 000000000..fcb8cabf0
--- /dev/null
+++ b/components/color-line.js
@@ -0,0 +1,34 @@
+import PropTypes from 'prop-types'
+
+function ColorLine({color, className}) {
+ return (
+ <>
+
+
+
+
+ >
+ )
+}
+
+ColorLine.propTypes = {
+ color: PropTypes.string,
+ className: PropTypes.string,
+}
+
+export default ColorLine
diff --git a/components/key-numbers-block/index.js b/components/key-numbers-block/index.js
new file mode 100644
index 000000000..6158e42f3
--- /dev/null
+++ b/components/key-numbers-block/index.js
@@ -0,0 +1,2 @@
+export {default} from './key-numbers-block'
+export {default as KeyNumberItem} from './key-number-item'
diff --git a/components/key-numbers-block/key-number-item.js b/components/key-numbers-block/key-number-item.js
new file mode 100644
index 000000000..29af8bf52
--- /dev/null
+++ b/components/key-numbers-block/key-number-item.js
@@ -0,0 +1,60 @@
+import PropTypes from 'prop-types'
+
+function KeyNumberItem({value = '', unit, label = '', description, className}) {
+ return (
+ <>
+
+ {value}
+ {label}
+ {description && {description} }
+
+
+
+ >
+ )
+}
+
+export const KeyNumberItemPropTypes = {
+ value: PropTypes.string.isRequired,
+ unit: PropTypes.string,
+ label: PropTypes.string.isRequired,
+ description: PropTypes.string,
+ className: PropTypes.string,
+}
+
+KeyNumberItem.propTypes = KeyNumberItemPropTypes
+
+export default KeyNumberItem
diff --git a/components/key-numbers-block/key-numbers-block.js b/components/key-numbers-block/key-numbers-block.js
new file mode 100644
index 000000000..aa85c8856
--- /dev/null
+++ b/components/key-numbers-block/key-numbers-block.js
@@ -0,0 +1,80 @@
+import PropTypes from 'prop-types'
+
+import theme from '@/styles/theme'
+
+import KeyNumberItem, {KeyNumberItemPropTypes} from './key-number-item'
+
+function KeyNumbersBlock({data = [], className, hasSeparator}) {
+ return (
+ <>
+
+ {data.map(
+ ({large, hasSeparator, className = '', ...keyNumberProps}) => (
+
+ ))}
+
+
+
+ >
+ )
+}
+
+KeyNumbersBlock.propTypes = {
+ data: PropTypes.arrayOf(PropTypes.shape({
+ ...KeyNumberItemPropTypes,
+ large: PropTypes.bool,
+ hasSeparator: PropTypes.bool,
+ })),
+ className: PropTypes.string,
+ hasSeparator: PropTypes.bool,
+}
+
+export default KeyNumbersBlock
diff --git a/components/section.js b/components/section.js
index e0fc57815..8cddc9c0b 100644
--- a/components/section.js
+++ b/components/section.js
@@ -2,9 +2,9 @@ import PropTypes from 'prop-types'
import Container from './container'
-function Section({title, subtitle, children, background, ...props}) {
+function Section({title, subtitle, children, background, className, ...props}) {
return (
-
+
{title && {title} }
{subtitle && {subtitle}
}
@@ -18,6 +18,7 @@ Section.propTypes = {
title: PropTypes.string,
subtitle: PropTypes.string,
children: PropTypes.node,
+ className: PropTypes.string,
style: PropTypes.object,
background: PropTypes.oneOf([
'white',
diff --git a/components/star-line.js b/components/star-line.js
new file mode 100644
index 000000000..1a9156844
--- /dev/null
+++ b/components/star-line.js
@@ -0,0 +1,48 @@
+/* eslint-disable react/no-array-index-key */
+import PropTypes from 'prop-types'
+import {fr} from '@codegouvfr/react-dsfr'
+
+function Star({isFill, isHalf}) {
+ const className = isFill ? 'ri-star-s-fill' : (isHalf ? 'ri-star-half-s-line' : 'ri-star-s-line')
+ return
+}
+
+Star.propTypes = {
+ isFill: PropTypes.bool,
+ isHalf: PropTypes.bool,
+}
+
+function StarLine({score = 0, color, className}) {
+ const fullScore = 5
+ const minScore = Math.floor(score)
+ const halfScore = (score - minScore) >= 0.25 && (score - minScore) < 0.75 ? 1 : 0
+ const intagerScore = (minScore + 0.75) < score ? minScore + 1 : minScore
+ const emptyScore = fullScore - intagerScore - halfScore
+ return (
+ <>
+
+ {(Array.from({length: intagerScore})).map((_, i) => )}
+ {(Array.from({length: halfScore})).map((_, i) => )}
+ {(Array.from({length: emptyScore})).map((_, i) => )}
+
+
+ >
+ )
+}
+
+StarLine.propTypes = {
+ score: PropTypes.number,
+ color: PropTypes.string,
+ className: PropTypes.string,
+}
+
+export default StarLine
diff --git a/components/wip-section.js b/components/wip-section.js
new file mode 100644
index 000000000..fb91173e7
--- /dev/null
+++ b/components/wip-section.js
@@ -0,0 +1,37 @@
+import PropTypes from 'prop-types'
+import {fr} from '@codegouvfr/react-dsfr'
+
+import Section from '@/components/section'
+
+function WipSection({children}) {
+ return (
+ <>
+
+
+
+
+ >
+ )
+}
+
+WipSection.propTypes = {
+ children: PropTypes.node.isRequired,
+}
+
+export default WipSection
diff --git a/package.json b/package.json
index b19dac33f..9f16db041 100644
--- a/package.json
+++ b/package.json
@@ -54,9 +54,11 @@
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-feather": "^2.0.10",
+ "recharts": "^2.8.0",
"send": "^0.18.0",
"sharp": "^0.29.3",
"styled-components": "^6.0.7",
+ "swr": "^2.2.4",
"underscore.string": "^3.3.6",
"vt-pbf": "^3.1.3"
},
diff --git a/pages/api/stats/[key-stat].js b/pages/api/stats/[key-stat].js
new file mode 100644
index 000000000..f3ddbea8f
--- /dev/null
+++ b/pages/api/stats/[key-stat].js
@@ -0,0 +1,170 @@
+import {
+ matomoToVisitData,
+ matomoDailyToData,
+ matomoMonthlyToData,
+ geocoderSearchToData,
+} from '@/views/stats/helper'
+
+const {
+ NEXT_PUBLIC_MATOMO_URL: MATOMO_URL,
+ NEXT_PUBLIC_MATOMO_SITE_ID: MATOMO_ID,
+ MATOMO_TOKEN_AUTH,
+} = process.env
+
+const fakeDataQualityScore = {
+ 0: 39,
+ 1: 9,
+ 2: 22,
+ 3: 19,
+ 4: 2,
+ 5: 9
+}
+
+const matomoToFakeGeocodeurData = (matomoData = {}) => Object.entries(matomoData)
+ .map(([dataDate, dataStat]) => {
+ const filtredDataStat = dataStat.filter((dataStatItem = {}) => /^download/i.test(dataStatItem.label))
+ return [dataDate, filtredDataStat]
+ })
+ .map(([dataDate, dataStat]) => {
+ const convertToFakeLabel = {
+ 'download BAL': ['nbSearch', 'nbReverseSearch'],
+ 'Download BAL': ['nbSearch', 'nbReverseSearch'],
+ 'download CSV historique adresses': ['nbSearchResult', 'nbReverseSearchResult'],
+ 'Download CSV historique adresses': ['nbSearchResult', 'nbReverseSearchResult'],
+ 'download CSV historique lieux-dits': ['nbSearchEmptyResult', 'nbReverseSearchEmptyResult'],
+ 'Download CSV historique lieux-dits': ['nbSearchEmptyResult', 'nbReverseSearchEmptyResult'],
+ }
+ const finalDataStat = dataStat
+ .map((dataStatItem = {}) => {
+ const {subtable} = dataStatItem
+ const otherFields = Object.fromEntries(
+ subtable.flatMap((subtableItem = {}) => {
+ const {label: subtableLabel, nb_events: subtableNbEvents} = subtableItem
+ return [
+ [convertToFakeLabel[subtableLabel]?.[0] || subtableLabel, (subtableNbEvents * 0)],
+ [convertToFakeLabel[subtableLabel]?.[1] || subtableLabel, (subtableNbEvents * 0)]
+ ]
+ })
+ )
+
+ return {
+ label: 'API Geocodeur Adresse',
+ period: dataDate,
+ ...otherFields,
+ }
+ })
+ return finalDataStat[0]
+ })
+
+const getQualityData = dataQuality => {
+ const StateBanDefs = [
+ {color: '#e2d8f5', description: 'Adresses composés à partir de sources multiples (autre que BAL)'},
+ {color: '#f9e7c7', description: 'Adresses issues des Bases Adresses Locales'},
+ {color: '#f7c6c6', description: 'Adresses certifiées'},
+ {color: '#cccccc', description: 'Total des adresses au sein de la Base Adresse National'},
+ ]
+ const QualityDefs = [
+ {color: '#869ECE', description: 'Adresses issues de l\'assemblage de sources non certifiable'},
+ {color: '#A695E7', description: 'Adresses issues de l\'assemblage de sources certifiées'},
+ {color: '#3C91E6', description: 'Adresses issues d\'une Base Adresse Locale'},
+ {color: '#0E6AA2', description: 'Adresses certifiées ou referençants une parcelle cadastrale'},
+ {color: '#F5C242', description: 'Adresses identifiables et suivies au sein d\'une Base Adresse Locale'},
+ {color: '#E65A5A', description: 'Adresses certifiées et referençants une parcelle cadastrale'},
+ ]
+ const DataQuality = [
+ Object
+ .entries(dataQuality)
+ .reduce((acc, [key, value]) => { // eslint-disable-line unicorn/no-array-reduce
+ let name
+ switch (key) {
+ case '0':
+ case '1':
+ name = '0'
+ break
+ case '2':
+ case '3':
+ case '4':
+ name = '1'
+ break
+ case '5':
+ name = '2'
+ break
+ default:
+ name = 0
+ }
+
+ acc[name] = (acc[name] || 0) + value
+ console.log('acc', acc)
+ return acc
+ }, [])
+ .map((value, index) => [`${index}`, value])
+ .map(
+ ([name, value]) => ({
+ name, value,
+ color: StateBanDefs[Number(name)].color,
+ description: StateBanDefs[Number(name)].description
+ })
+ ),
+ Object
+ .entries(fakeDataQualityScore)
+ .map(
+ ([name, value]) => ({
+ name, value,
+ color: QualityDefs[Number(name)].color,
+ description: QualityDefs[Number(name)].description
+ })
+ ),
+ ]
+ return DataQuality
+}
+
+const getMonthlyUsageData = dataMonthlyUsage => ({
+ period: dataMonthlyUsage.period,
+ value: dataMonthlyUsage ? [
+ {value: `${dataMonthlyUsage?.value?.nb_events || ''}`, label: 'Téléchargements', description: 'des données BAN sur nos serveurs.*', large: true},
+ {value: '??', label: 'Recherche', description: 'effectuées sur notre API Geocodage-BAN.**'},
+ {value: '??', label: 'Exploitants', description: 'utilisant les données de la BAN sur leurs outils.***', hasSeparator: true},
+ ] : []
+})
+
+const URL_GET_STATS_DAILY_DOWNLOAD = `${MATOMO_URL}/index.php?idSite=${MATOMO_ID}&module=API&format=JSON&period=day&date=previous30&method=Events.getCategory&expanded=1&filter_limit=10&token_auth=${MATOMO_TOKEN_AUTH}`
+const URL_GET_STATS_MONTHLY_DOWNLOAD = `${MATOMO_URL}/index.php?idSite=${MATOMO_ID}&module=API&format=JSON&period=month&date=previous30&method=Events.getCategory&label=download&format_metrics=1&expanded=1&token_auth=${MATOMO_TOKEN_AUTH}`
+const URL_GET_STATS_GEOCODER = `${MATOMO_URL}/index.php?idSite=${MATOMO_ID}&module=API&format=JSON&period=day&date=previous30&method=Events.getCategory&expanded=1&filter_limit=10&token_auth=${MATOMO_TOKEN_AUTH}`
+const URL_GET_STAT_VISIT = `${MATOMO_URL}/index.php?idSite=${MATOMO_ID}&module=API&format=JSON&period=month&date=previous12&method=API.get&filter_limit=100&format_metrics=1&expanded=1&token_auth=${MATOMO_TOKEN_AUTH}`
+
+const APIs = {
+ 'daily-download': {url: URL_GET_STATS_DAILY_DOWNLOAD, converter: matomoDailyToData},
+ 'monthly-usage': {url: URL_GET_STATS_MONTHLY_DOWNLOAD, converter: data => getMonthlyUsageData(matomoMonthlyToData(data))},
+ geocoder: {url: URL_GET_STATS_GEOCODER, converter: data => geocoderSearchToData(matomoToFakeGeocodeurData(data))},
+ visit: {url: URL_GET_STAT_VISIT, converter: matomoToVisitData},
+ quality: {data: fakeDataQualityScore, converter: getQualityData},
+}
+
+export default async function handler(req, res) {
+ const {'key-stat': keyStat} = req.query
+
+ try {
+ const {url, data: dataRaw, converter = d => d} = APIs?.[keyStat] || {}
+ if (!url && !dataRaw) {
+ throw new Error('API not found', {status: 404, cause: {details: 'API not found', status: 404}})
+ }
+
+ if (dataRaw) {
+ const data = converter(dataRaw)
+ return res.status(200).json(data)
+ }
+
+ const response = await fetch(url)
+ const {status} = response
+ if (status !== 200) {
+ throw new Error('API not found', {cause: {details: 'Error while fetching API', status}})
+ }
+
+ const data = converter(await response.json())
+ res.status(status).json(data)
+ } catch (error) {
+ const {message, cause} = error
+ console.error('Error on Front-end Stat API :', message, cause, error)
+ res.status(cause?.status || 500).send(message || 'Internal server error')
+ }
+}
diff --git a/pages/budget.js b/pages/budget.js
new file mode 100644
index 000000000..905b02292
--- /dev/null
+++ b/pages/budget.js
@@ -0,0 +1,255 @@
+import {Fragment} from 'react'
+import Link from 'next/link'
+
+import Page from '@/layouts/main'
+import Section from '@/components/section'
+import WipSection from '@/components/wip-section'
+
+const budgetFormatter = value => {
+ return Number.isNaN(value) ?
+ value :
+ `${value.toLocaleString(
+ undefined, {minimumFractionDigits: 0}
+ ).replace(/000$/g, 'K')}`
+}
+
+const keyLegend = {
+ BAN: 'Base Adresses Nationale (BAN)',
+ BAL: 'Base Adresses Locales (BAL)',
+}
+
+const dataBudget = {
+ 2023: {
+ BAN: {
+ info: 'En 2023 le budget de l’équipe est évalué à 1 192 000 €. L’IGN et la DINUM partagent le financement de cette équipe dédiée à la construction de la Base Adresse Nationale.',
+ values: {
+ Développement: [0, 0],
+ Déploiement: [0, 0],
+ 'Logiciel hébergement': [0, 0],
+ 'Total HT': [0, 0],
+ 'Total TTC': [0, 0],
+ },
+ },
+ BAL: {
+ info: 'En 2023 le budget de l’équipe est évalué à XXX €. L’ANCT et la DINUM partagent le financement de cette équipe dédiée au projet Base Adresse Local.',
+ values: {
+ Développement: [0, 0],
+ Déploiement: [0, 0],
+ 'Logiciel hébergement': [0, 0],
+ 'Total HT': [0, 0],
+ 'Total TTC': [0, 0],
+ },
+ }
+ }
+}
+
+function StatsPage() {
+ return (
+
+
+ La page que vous visitez est actuellement en cours de construction.
+ Les informations qui y sont présentées sont susceptibles d’évoluer, d’être incomplètes ou erronées.
+ Nous vous remercions de votre compréhension.
+
+
+
+ Budget
+
+ La Base Adresse Nationale (BAN) est un service public numérique, c’est pourquoi nous sommes transparents
+ sur les ressources allouées et la manière dont elles sont employées.
+
+
+
+ Ces données étant en cours de construction, elles peuvent, dans certains cas, être incomplètes.
+ Nous vous remercions de votre compréhension.
+
+
+
+
+ Principes
+
+ Nous suivons le manifeste beta.gouv dont
+ nous rappelons les principes ici :
+
+
+
+ Les besoins des utilisateurs sont prioritaires sur les besoins de l’administration
+ Le mode de gestion de l’équipe repose sur la confiance
+ L’équipe adopte une approche itérative et d’amélioration en continu
+
+
+
+
+
+ Budget consommé
+ ● Année : {2023}
+
+
+ {
+ Object.entries(dataBudget['2023']).map(([projectName, projectData]) => (
+
+
+ {projectData.info}
+
+
+
+
+
+ {keyLegend?.[projectName] || projectName}
+
+ 1er semestre
+ 2ème semestre
+ total
+
+
+
+ {
+ Object.entries(projectData.values)
+ .map(([label, values]) => {
+ return (
+
+ {label}
+
+ {budgetFormatter(values[0])}
+ {budgetFormatter(values[1])}
+ {budgetFormatter(values[0] + values[1])}
+
+ )
+ })
+ }
+
+
+
+ )
+ )
+ }
+
+
+
+
+ Description des catégories
+
+ Déploiement et développement
+
+ Les coûts de développement représentent la grande majorité de notre budget. Nous sommes une petite équipe
+ de développeurs composée d’agents publics et de freelances, pluridisciplinaires aussi bien sur les aspects techniques,
+ stratégiques et métiers. Les rémunérations des intervenants en freelance suivent
+ la grille
+ de beta.gouv.
+
+
+ Logiciel et hébergement
+
+ Notre modèle open-source nous permet d’accéder gratuitement à la majorité des outils que nous utilisons
+ (hébergement de code, serveurs de tests, etc.). Le site est hébergé
+ chez OVH.
+
+
+ Portage
+
+ La marge du porteur attributaire
+ du marché
+ public de la DINUM,
+ ainsi que les coûts liés à la société
+ spécialement créée pour effectuer le portage des indépendants qui travaillent pour le service (administration,
+ comptabilité, facturation, impôts, etc.).
+
+
+
+
+
+
A propos de la TVA
+
+ Contrairement aux entreprises du secteur privé, les administrations ne peuvent pas récupérer la TVA supportée sur
+ leurs achats dans le cadre de leur activité.
+ Le montant TTC inclut la TVA au taux de 20%.
+ La TVA est collectée et reversée à l’État et diminue donc le montant du budget utilisable sur le projet.
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default StatsPage
diff --git a/pages/stats.js b/pages/stats.js
new file mode 100644
index 000000000..2ac39bed4
--- /dev/null
+++ b/pages/stats.js
@@ -0,0 +1,251 @@
+import {useMemo, useEffect} from 'react'
+import useSWR from 'swr'
+import Link from 'next/link'
+import {fr} from '@codegouvfr/react-dsfr'
+
+import Page from '@/layouts/main'
+import Section from '@/components/section'
+import WipSection from '@/components/wip-section'
+import KeyNumbersBlock from '@/components/key-numbers-block'
+import {CartesianChart as Chart} from '@/components/charts'
+import QualityChartBlock from '@/views/stats/quality-chart-block'
+import {
+ getDataDef,
+ getBanStatsData,
+ fetcher,
+} from '@/views/stats/helper'
+import {
+ defDatadailyDownload,
+ defDataGeocoder,
+ defDataBanVisit,
+} from '@/views/stats/stats-config-data'
+
+const URL_GET_STATS_DOWNLOAD_DAY = './api/stats/daily-download'
+const URL_GET_STATS_DOWNLOAD_MONTH = './api/stats/monthly-usage'
+const URL_GET_STATS_GEOCODER = './api/stats/geocoder'
+const URL_GET_STATS_VISIT = './api/stats/visit'
+const URL_GET_STATS_QUALITY = './api/stats/quality'
+const QUALITY_CHART_WRAPPER_SIZE = 260
+
+function StatsPage() {
+ const {data: dataDailyDownload, error: errorDataDailyDownload} = useSWR(URL_GET_STATS_DOWNLOAD_DAY, fetcher)
+ const {data: dataMonthlyUsage, error: errorDataMonthlyUsage} = useSWR(URL_GET_STATS_DOWNLOAD_MONTH, fetcher)
+ const {data: dataGeocoder, error: errorDataGeocoder} = useSWR(URL_GET_STATS_GEOCODER, fetcher)
+ const {data: dataBanVisit, error: errorDataBanVisit} = useSWR(URL_GET_STATS_VISIT, fetcher)
+ const {data: dataBanQuality, error: errorDataBanQuality} = useSWR(URL_GET_STATS_QUALITY, fetcher)
+ const {data: dataStateBan, error: errorBanStats} = useSWR('BAN__GET_STATE__API', getBanStatsData)
+
+ const axisDefDailyDownload = useMemo(() => getDataDef(defDatadailyDownload, 'glicyne'), [])
+ const axisDefDataGeocoder = useMemo(() => getDataDef(defDataGeocoder), [])
+ const axisDefDataBanVisit = useMemo(() => getDataDef(defDataBanVisit), [])
+
+ useEffect(() => {
+ [
+ errorDataDailyDownload,
+ errorDataMonthlyUsage,
+ errorDataGeocoder,
+ errorDataBanVisit,
+ errorDataBanQuality,
+ errorBanStats,
+ ].filter(Boolean).forEach(err => console.error(`API CALL ERROR: ${err}`))
+ }, [
+ errorDataDailyDownload,
+ errorDataMonthlyUsage,
+ errorDataGeocoder,
+ errorDataBanVisit,
+ errorDataBanQuality,
+ errorBanStats,
+ ])
+
+ return (
+
+
+ La page que vous visitez est actuellement en cours de construction.
+ Les informations qui y sont présentées sont susceptibles d’évoluer, d’être incomplètes ou erronées.
+ Nous vous remercions de votre compréhension.
+
+
+
+ Statistiques
+
+ Vous trouverez ici les statistiques d’utilisation de la Base Adresse Nationale (BAN) .
+ Ces données ont pour objectif de vous permettre de suivre l’évolution de la BAN et
+ de vous aider à prendre des décisions sur son utilisation.
+ Elles nous permettent également de mieux comprendre vos besoins et
+ d’orienter nos actions pour améliorer la BAN.
+
+
+
+ Il est à noter que ces statistiques ne sont pas exhaustives et qu’elles sont en constante évolution.
+ De plus, elles ne prennent pas en compte les utilisations de la BAN par les organismes partenaires.
+
+
+
+ Ces données étant en cours de construction, elles peuvent, dans certains cas, être incomplètes.
+ Nous vous remercions de votre compréhension.
+
+
+
+
+ Les usages de la BAN en chiffre
+ {dataMonthlyUsage?.period && `● Periode : ${dataMonthlyUsage.period}`}
+
+ Les données affichées
+ ci-dessous sont incomplètes.
+
+
+
+
+
+ * Moyenne mensuelle sur la periode.
+
+
+ ** Anciennement “API Adresse”. Moyenne mensuelle sur la periode.
+
+
+ *** Total référencé sur la Base Adresses Nationale durant la periode.
+
+
+
+
+
+ Exploitation directe de la BAN : Nombre de recherches d’adresses
+ Consommation des données de la BAN depuis l’API Geocodage-BAN (anciennement “API Adresse”).
+
+ Ces données
+ ne sont pas encore disponibles.
+
+
+
+
+
+
+
+
+ Recherches quotidiennes sur les 30 derniers jours.
+
+
+ * L’API Geocodage-BAN (anciennement “API Adresse”) met à disposition les adresses présentes dans la BAN.
+ Les adresses retournées par l’API ne sont actuellement disponibles qu’en français.
+ L’API ne fournit pas encore les lieux-dits.
+
+
+
+
+
+ Exploitation indirecte de la BAN : Nombre de téléchargements des données BAN
+ Téléchargement des données de la BAN depuis nos serveurs, pour une utilisation gérée par l’exploitant.
+
+ Les données affichées
+ ci-dessous sont incomplètes.
+
+
+
+
+
+
+
+ Téléchargements quotidiens sur les 30 derniers jours.
+
+
+
+
+ État de la Base Adresses Nationale (BAN)
+
+ La Base Adresse Nationale (BAN) est constituée de plusieurs sources de données, de natures différentes.
+ La récente loi dite 3DS impose aux communes de mettre en place une Base Adresse Locale (BAL) ,
+ seule source certifiable et officielle, qui à terme constituera l’intégralité de la BAN.
+
+
+ {dataStateBan && (
+
+ )}
+
+
+ Plus d'informations sur l'état du déploiement des Bases Adresses Locales
+
+
+
+ {dataBanQuality && (
+
+ Répartition qualitative des données de la BAN
+
+
+ Afin de déterminer la qualité des adresses de la Base Adresse Nationale (BAN) , un système de notation a été mis en place.
+ Ce système est basé sur la qualité des sources qui ont permis de constituer la BAN.
+
+
+
+ Chaque adresse de la BAN est notée entre 0 et 5 étoiles selon des critères cumulatifs prédéfinis.
+ Le graphique ci-dessous représente la répartition actuelle des différentes sources de la BAN, et
+ la moyenne des notes de chaque adresse qui la constitue au niveau national.
+
+
+
+
+ )}
+
+
+ Attrait du sujet “Adresse”
+ Quantité de visites sur le site web.
+
+
+
+
+
+
+ Valeurs totales mensuelles des 12 derniers mois.
+
+
+
+
+ À propos des statistiques
+
+ Les données de statistiques sont issues de Matomo, un outil libre de mesure d’audience web.
+ Ces données sont anonymisées, hébergées en France par la société OVH, et conservées dans le but d’améliorer les services proposés par l’état.
+ En aucun cas, ces données ne sont partagées avec des tiers ou utilisées à d’autres fins que l’amélioration de la plateforme.
+
+
+ Pour plus d'informations, vous pouvez consulter
+ nos
+ « Conditions Générales d’Utilisation »
+ .
+
+
+
+
+
+
+ )
+}
+
+export default StatsPage
diff --git a/views/stats/helper.js b/views/stats/helper.js
new file mode 100644
index 000000000..f7ecb643c
--- /dev/null
+++ b/views/stats/helper.js
@@ -0,0 +1,129 @@
+import {
+ colorthemes as customColor,
+ defaultTheme,
+} from '@/components/charts'
+import {getStats} from '@/lib/api-ban'
+
+const colorName = 'glicyne'
+
+const legendTranslate = {
+ interopKeyTotal: 'Clé d\'interoperabilité conservée',
+ interopKeyRemove: 'Clé d\'interoperabilité supprimmé',
+ interopKeyNew: 'Nouvelle clé d\'interoperabilité',
+ addrTotal: 'Total des adresses',
+
+ 'download BAL': 'Fichiers BAL',
+ 'Download BAL': 'Fichiers BAL',
+ 'download CSV historique adresses': 'CSV historique adresses',
+ 'Download CSV historique adresses': 'CSV historique adresses',
+ 'download CSV historique lieux-dits': 'CSV historique lieux-dits',
+ 'Download CSV historique lieux-dits': 'CSV historique lieux-dits',
+
+ nbSearch: 'Recherche',
+ nbSearchResult: 'Résultats de recherche',
+ nbSearchEmptyResult: 'Recherche sans résultat',
+ nbReverseSearch: 'Recherche inversée',
+ nbReverseSearchEmptyResult: 'Recherche inversée sans résultat',
+ nbReverseSearchResult: 'Résultats de recherche inversée',
+
+ nbVisits: 'Visites',
+ nbUniqVisitors: 'Visiteurs uniques',
+}
+
+export const matomoToVisitData = (matomoData = {}) => Object.entries(matomoData)
+ .map(([dataDate, {nb_visits: nbVisits, nb_uniq_visitors: nbUniqVisitors}]) => {
+ return [dataDate, {nbVisits, nbUniqVisitors}]
+ })
+ .map(([dataDate, dataStat]) => {
+ return {
+ period: dataDate,
+ label: 'visit',
+ ...Object
+ .fromEntries(
+ Object
+ .entries(dataStat)
+ .map(([key, value]) => [legendTranslate[key] || key, value])
+ )
+ }
+ })
+
+export const matomoDailyToData = (matomoData = {}) => Object.entries(matomoData)
+ .map(([dataDate, dataStat]) => {
+ const filtredDataStat = dataStat.filter((dataStatItem = {}) => /^download/i.test(dataStatItem.label))
+ return [dataDate, filtredDataStat]
+ })
+ .map(([dataDate, dataStat]) => {
+ const finalDataStat = dataStat
+ .map((dataStatItem = {}) => {
+ const {label, nb_events: nbEvents, subtable} = dataStatItem
+ const otherFields = Object.fromEntries(
+ subtable.map((subtableItem = {}) => {
+ const {label: subtableLabel, nb_events: subtableNbEvents} = subtableItem
+ return [legendTranslate[subtableLabel] || subtableLabel, subtableNbEvents]
+ })
+ )
+
+ return {
+ label,
+ period: dataDate,
+ total: nbEvents,
+ ...otherFields,
+ }
+ })
+ return finalDataStat[0]
+ })
+
+export const matomoMonthlyToData = (matomoMonthlyData = {}) => {
+ const [date = '', value = []] = Object
+ .entries(matomoMonthlyData)
+ .reverse()?.[0] || []
+ const [year, month] = date.split('-')
+ const period = (new Date(year, month - 1, 1)).toLocaleDateString('fr-FR', {year: 'numeric', month: 'long'})
+ return {
+ period,
+ value: value?.[0] || {},
+ }
+}
+
+export const geocoderSearchToData = (geocoderData = []) => {
+ const data = geocoderData
+ .map(({period, ...value}) => {
+ const translatedValues = Object.fromEntries(
+ Object.entries(value)
+ .map(([key, value]) => {
+ return [legendTranslate[key] || key, value]
+ })
+ )
+ return {
+ period,
+ ...translatedValues,
+ }
+ }
+ )
+ return data
+}
+
+export const getDataDef = (dataDef = [], themeColor = null) => dataDef
+ .reduce((acc, {dataKey, colors, ...dataItem}, index) => { // eslint-disable-line unicorn/no-array-reduce
+ const theme = (themeColor ? customColor[colorName]?.[index] : defaultTheme?.[index]) || defaultTheme[0]
+ return {
+ ...acc,
+ [legendTranslate[dataKey] || dataKey]: {
+ stroke: colors?.[0] || theme?.[0],
+ fill: colors?.[1] || theme?.[1],
+ ...dataItem
+ },
+ }
+ }, {})
+
+export const getBanStatsData = async () => {
+ const {ban: dataBanStats, bal: dataBalStats} = await getStats() || {}
+ return dataBanStats && dataBalStats ? [
+ {value: `${(Number(((dataBanStats.nbAdresses) * 0.000001).toFixed(2)))}`, unit: 'M', label: 'Total des adresses au sein de la Base Adresse National'},
+ {value: `${(Number(((dataBalStats.nbAdresses) * 0.000001).toFixed(2)))}`, unit: 'M', label: 'Adresses issues des Bases Adresses Locales', hasSeparator: true},
+ {value: `${(Number(((dataBanStats.nbAdresses - dataBalStats.nbAdresses) * 0.000001).toFixed(2)))}`, unit: 'M', label: 'Adresses issues des autres sources'},
+ {value: `${(Number(((dataBalStats.nbAdressesCertifiees) * 0.000001).toFixed(2)))}`, unit: 'M', label: 'Adresses certifiées', hasSeparator: true},
+ ] : []
+}
+
+export const fetcher = async (...args) => (await fetch(...args))?.json()
diff --git a/views/stats/quality-chart-block/index.js b/views/stats/quality-chart-block/index.js
new file mode 100644
index 000000000..d9854353e
--- /dev/null
+++ b/views/stats/quality-chart-block/index.js
@@ -0,0 +1 @@
+export {default} from './quality-chart-block'
diff --git a/views/stats/quality-chart-block/quality-chart-block.js b/views/stats/quality-chart-block/quality-chart-block.js
new file mode 100644
index 000000000..59c88ea55
--- /dev/null
+++ b/views/stats/quality-chart-block/quality-chart-block.js
@@ -0,0 +1,124 @@
+import PropTypes from 'prop-types'
+import {numFormater} from '@/lib/format-numbers'
+import theme from '@/styles/theme'
+
+import QualityPieCharts from './quality-pie-charts'
+import StarLine from '@/components/star-line'
+import ColorLine from '@/components/color-line'
+
+function QualityChartBlock({data, chartSize = 200}) {
+ return (
+ <>
+
+
+
+
+
+
+
+ {data[1].map(({name, value, color, description}) => (
+
+
+ {numFormater(value)}%
+
+ {description}
+
+ ))}
+
+
+
+
+
+ {data[0].map(({name, value, color, description}) => (
+
+
+ {numFormater(value)}%
+
+ {description}
+
+ ))}
+
+
+
+
+
+
+
+ >
+ )
+}
+
+QualityChartBlock.propTypes = {
+ data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ value: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+ description: PropTypes.string.isRequired,
+ }))).isRequired,
+ chartSize: PropTypes.number,
+}
+
+export default QualityChartBlock
diff --git a/views/stats/quality-chart-block/quality-pie-charts.js b/views/stats/quality-chart-block/quality-pie-charts.js
new file mode 100644
index 000000000..a819e446a
--- /dev/null
+++ b/views/stats/quality-chart-block/quality-pie-charts.js
@@ -0,0 +1,55 @@
+import PropTypes from 'prop-types'
+import {ResponsiveContainer, PieChart, Pie, Cell} from 'recharts'
+
+function QualityPieCharts({data, size = 200, pieThicknesses = [16, 20], pieGap: propsPieGap = 4, color = '#aaa'}) {
+ const pieGap = propsPieGap * pieThicknesses.length
+ const pieOuterRadius = size / 2
+ const paddingAngle = propsPieGap / 2
+
+ return (
+
+
+
+ {data[0].map(({name, color}) => (
+ |
+ ))}
+
+
+
+ {data[1].map(({name, color}) => (
+ |
+ ))}
+
+
+
+ )
+}
+
+QualityPieCharts.propTypes = {
+ data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ value: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+ description: PropTypes.string,
+ }))).isRequired,
+ size: PropTypes.number,
+ pieThicknesses: PropTypes.arrayOf(PropTypes.number),
+ pieGap: PropTypes.number,
+ color: PropTypes.string,
+}
+
+export default QualityPieCharts
diff --git a/views/stats/stats-config-data.js b/views/stats/stats-config-data.js
new file mode 100644
index 000000000..966721a1e
--- /dev/null
+++ b/views/stats/stats-config-data.js
@@ -0,0 +1,57 @@
+import {colorthemes as customColor} from '@/components/charts'
+
+export const defDatadailyDownload = [
+ {
+ dataKey: 'download BAL',
+ },
+ {
+ dataKey: 'download CSV historique adresses',
+ strokeDasharray: 5,
+ },
+ {
+ dataKey: 'download CSV historique lieux-dits',
+ },
+]
+
+export const defDataGeocoder = [
+ {
+ dataKey: 'nbSearch',
+ colors: customColor.glicyne[0],
+ },
+ {
+ dataKey: 'nbSearchResult',
+ strokeDasharray: 3,
+ colors: customColor.glicyne[2],
+ },
+ {
+ dataKey: 'nbSearchEmptyResult',
+ strokeDasharray: 5,
+ colors: customColor.glicyne[3],
+ },
+ {
+ dataKey: 'nbReverseSearch',
+ colors: customColor.ecume[0],
+ },
+ {
+ dataKey: 'nbReverseSearchResult',
+ strokeDasharray: 3,
+ colors: customColor.ecume[3],
+ },
+ {
+ dataKey: 'nbReverseSearchEmptyResult',
+ strokeDasharray: 5,
+ colors: customColor.ecume[2],
+ },
+]
+
+export const defDataBanVisit = [
+ {
+ dataKey: 'Visites',
+ colors: customColor.glicyne[3],
+ },
+ {
+ dataKey: 'Visiteurs uniques',
+ strokeDasharray: 3,
+ colors: customColor.azure[0],
+ },
+]
diff --git a/yarn.lock b/yarn.lock
index d681419eb..06c796151 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1244,6 +1244,13 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
+"@babel/runtime@^7.1.2":
+ version "7.23.1"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d"
+ integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
"@babel/runtime@^7.20.13":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
@@ -1987,6 +1994,57 @@
"@types/keygrip" "*"
"@types/node" "*"
+"@types/d3-array@^3.0.3":
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.8.tgz#a5d0687a12b48142c6f124d5e3796054e91bcea5"
+ integrity sha512-2xAVyAUgaXHX9fubjcCbGAUOqYfRJN1em1EKR2HfzWBpObZhwfnZKvofTN4TplMqJdFQao61I+NVSai/vnBvDQ==
+
+"@types/d3-color@*":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.1.tgz#43a2aa7836fdae19ce32fabe97742e787f4b2e08"
+ integrity sha512-CSAVrHAtM9wfuLJ2tpvvwCU/F22sm7rMHNN+yh9D6O6hyAms3+O0cgMpC1pm6UEUMOntuZC8bMt74PteiDUdCg==
+
+"@types/d3-ease@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.0.tgz#c29926f8b596f9dadaeca062a32a45365681eae0"
+ integrity sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==
+
+"@types/d3-interpolate@^3.0.1":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.2.tgz#b5928cca26fc20dbfe689ff37d62f7bac434c74e"
+ integrity sha512-zAbCj9lTqW9J9PlF4FwnvEjXZUy75NQqPm7DMHZXuxCFTpuTrdK2NMYGQekf4hlasL78fCYOLu4EE3/tXElwow==
+ dependencies:
+ "@types/d3-color" "*"
+
+"@types/d3-path@*":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.0.tgz#939e3a784ae4f80b1fde8098b91af1776ff1312b"
+ integrity sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==
+
+"@types/d3-scale@^4.0.2":
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.5.tgz#daa4faa5438315a37a1f5eb1bcdc5aeb3d3e5a2d"
+ integrity sha512-w/C++3W394MHzcLKO2kdsIn5KKNTOqeQVzyPSGPLzQbkPw/jpeaGtSRlakcKevGgGsjJxGsbqS0fPrVFDbHrDA==
+ dependencies:
+ "@types/d3-time" "*"
+
+"@types/d3-shape@^3.1.0":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.3.tgz#20eee7aad70f2562041af18e305fec6b48fd511d"
+ integrity sha512-cHMdIq+rhF5IVwAV7t61pcEXfEHsEsrbBUPkFGBwTXuxtTAkBBrnrNA8++6OWm3jwVsXoZYQM8NEekg6CPJ3zw==
+ dependencies:
+ "@types/d3-path" "*"
+
+"@types/d3-time@*", "@types/d3-time@^3.0.0":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.1.tgz#f0c8f9037632cc4511ae55e7e1459dcb95fb3619"
+ integrity sha512-5j/AnefKAhCw4HpITmLDTPlf4vhi8o/dES+zbegfPb7LaGfNyqkLxBR6E+4yvTAgnJLmhe80EXFMzUs38fw4oA==
+
+"@types/d3-timer@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.0.tgz#e2505f1c21ec08bda8915238e397fb71d2fc54ce"
+ integrity sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==
+
"@types/eslint@^7.2.13":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78"
@@ -2834,6 +2892,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
+classnames@^2.2.5:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
+ integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
+
clean-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7"
@@ -2846,7 +2909,7 @@ cli-boxes@^2.2.1:
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
-client-only@0.0.1:
+client-only@0.0.1, client-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
@@ -3087,6 +3150,11 @@ css-to-react-native@^3.2.0:
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
+css-unit-converter@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21"
+ integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==
+
csscolorparser@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b"
@@ -3097,6 +3165,77 @@ csstype@^3.1.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
+"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
+ integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
+ dependencies:
+ internmap "1 - 2"
+
+"d3-color@1 - 3":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
+ integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
+
+d3-ease@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
+ integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
+
+"d3-format@1 - 3":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
+ integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
+
+"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
+ integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
+ dependencies:
+ d3-color "1 - 3"
+
+d3-path@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
+ integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
+
+d3-scale@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
+ integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
+ 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"
+
+d3-shape@^3.1.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
+ integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
+ dependencies:
+ d3-path "^3.1.0"
+
+"d3-time-format@2 - 4":
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
+ integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
+ dependencies:
+ d3-time "1 - 3"
+
+"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
+ integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
+ dependencies:
+ d3-array "2 - 3"
+
+d3-timer@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
+ integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
+
date-fns@^2.29.3:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
@@ -3136,6 +3275,11 @@ decamelize@^1.1.0, decamelize@^1.2.0:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
+decimal.js-light@^2.4.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
+ integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==
+
decode-uri-component@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
@@ -3265,6 +3409,13 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dom-helpers@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
+ integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+
dom-scroll-into-view@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dom-scroll-into-view/-/dom-scroll-into-view-1.0.1.tgz#32abb92f0d8feca6215162aef43e4b449ab8d99c"
@@ -3823,6 +3974,11 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+eventemitter3@^4.0.1:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+ integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
events@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
@@ -3937,6 +4093,11 @@ fast-diff@^1.1.2:
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
+fast-equals@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d"
+ integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==
+
fast-glob@^2.2.6:
version "2.2.7"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
@@ -4663,6 +4824,11 @@ internal-slot@^1.0.3, internal-slot@^1.0.4:
has "^1.0.3"
side-channel "^1.0.4"
+"internmap@1 - 2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
+ integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+
interpret@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
@@ -5278,7 +5444,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
-lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.20, lodash@^4.17.21:
+lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -6086,6 +6252,11 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==
+postcss-value-parser@^3.3.0:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
+ integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
+
postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
@@ -6322,11 +6493,41 @@ react-feather@^2.0.10:
dependencies:
prop-types "^15.7.2"
-react-is@^16.13.1, react-is@^16.7.0:
+react-is@^16.10.2, react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+react-lifecycles-compat@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+ integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+
+react-resize-detector@^8.0.4:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-8.1.0.tgz#1c7817db8bc886e2dbd3fbe3b26ea8e56be0524a"
+ integrity sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==
+ dependencies:
+ lodash "^4.17.21"
+
+react-smooth@^2.0.2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-2.0.4.tgz#95187126265970a1490e2aea5690365203ee555f"
+ integrity sha512-OkFsrrMBTvQUwEJthE1KXSOj79z57yvEWeFefeXPib+RmQEI9B1Ub1PgzlzzUyBOvl/TjXt5nF2hmD4NsgAh8A==
+ dependencies:
+ fast-equals "^5.0.0"
+ react-transition-group "2.9.0"
+
+react-transition-group@2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
+ integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==
+ dependencies:
+ dom-helpers "^3.4.0"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+ react-lifecycles-compat "^3.0.4"
+
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -6369,6 +6570,28 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
+recharts-scale@^0.4.4:
+ version "0.4.5"
+ resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9"
+ integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==
+ dependencies:
+ decimal.js-light "^2.4.1"
+
+recharts@^2.8.0:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.8.0.tgz#90c95136e2cb6930224c94a51adce607701284fc"
+ integrity sha512-nciXqQDh3aW8abhwUlA4EBOBusRHLNiKHfpRZiG/yjups1x+auHb2zWPuEcTn/IMiN47vVMMuF8Sr+vcQJtsmw==
+ dependencies:
+ classnames "^2.2.5"
+ eventemitter3 "^4.0.1"
+ lodash "^4.17.19"
+ react-is "^16.10.2"
+ react-resize-detector "^8.0.4"
+ react-smooth "^2.0.2"
+ recharts-scale "^0.4.4"
+ reduce-css-calc "^2.1.8"
+ victory-vendor "^36.6.8"
+
redent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
@@ -6377,6 +6600,14 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+reduce-css-calc@^2.1.8:
+ version "2.1.8"
+ resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03"
+ integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==
+ dependencies:
+ css-unit-converter "^1.1.1"
+ postcss-value-parser "^3.3.0"
+
regenerate-unicode-properties@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
@@ -7100,6 +7331,14 @@ svg-arc-to-cubic-bezier@^3.0.0, svg-arc-to-cubic-bezier@^3.2.0:
resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6"
integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==
+swr@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07"
+ integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==
+ dependencies:
+ client-only "^0.0.1"
+ use-sync-external-store "^1.2.0"
+
table@^6.0.9:
version "6.8.1"
resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf"
@@ -7494,6 +7733,11 @@ url-parse-lax@^3.0.0:
dependencies:
prepend-http "^2.0.0"
+use-sync-external-store@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
+ integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
+
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
@@ -7527,6 +7771,26 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+victory-vendor@^36.6.8:
+ version "36.6.11"
+ resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.6.11.tgz#acae770717c2dae541a54929c304ecab5ab6ac2a"
+ integrity sha512-nT8kCiJp8dQh8g991J/R5w5eE2KnO8EAIP0xocWlh9l2okngMWglOPoMZzJvek8Q1KUc4XE/mJxTZnvOB1sTYg==
+ dependencies:
+ "@types/d3-array" "^3.0.3"
+ "@types/d3-ease" "^3.0.0"
+ "@types/d3-interpolate" "^3.0.1"
+ "@types/d3-scale" "^4.0.2"
+ "@types/d3-shape" "^3.1.0"
+ "@types/d3-time" "^3.0.0"
+ "@types/d3-timer" "^3.0.0"
+ d3-array "^3.1.6"
+ d3-ease "^3.0.1"
+ d3-interpolate "^3.0.1"
+ d3-scale "^4.0.2"
+ d3-shape "^3.1.0"
+ d3-time "^3.0.0"
+ d3-timer "^3.0.1"
+
vite-compatible-readable-stream@^3.6.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz#27267aebbdc9893c0ddf65a421279cbb1e31d8cd"