@geo2france/api-dashboard 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +192 -0
- package/dist/components/Attributions/Attribution.test.d.ts +1 -0
- package/dist/components/Attributions/Attribution.test.js +19 -0
- package/dist/components/Attributions/Attributions.d.ts +17 -0
- package/dist/components/Attributions/Attributions.js +30 -0
- package/dist/components/Charts/ChartEcharts.d.ts +7 -0
- package/dist/components/Charts/ChartEcharts.js +35 -0
- package/dist/components/Charts/Pie.d.ts +10 -0
- package/dist/components/Charts/Pie.js +35 -0
- package/dist/components/Charts/YearSerie.d.ts +16 -0
- package/dist/components/Charts/YearSerie.js +70 -0
- package/dist/components/Control/Control.d.ts +21 -0
- package/dist/components/Control/Control.js +84 -0
- package/dist/components/Control/Radio.d.ts +19 -0
- package/dist/components/Control/Radio.js +27 -0
- package/dist/components/Control/Select.d.ts +15 -0
- package/dist/components/Control/Select.js +25 -0
- package/dist/components/DashboardChart/DashboardChart.d.ts +11 -0
- package/dist/components/DashboardChart/DashboardChart.js +42 -0
- package/dist/components/DashboardElement/DashboardElement.d.ts +21 -0
- package/dist/components/DashboardElement/DashboardElement.js +108 -0
- package/dist/components/DashboardElement/hooks.d.ts +28 -0
- package/dist/components/DashboardElement/hooks.js +29 -0
- package/dist/components/DashboardPage/Block.d.ts +15 -0
- package/dist/components/DashboardPage/Block.js +40 -0
- package/dist/components/DashboardPage/DashboardPage.test.d.ts +1 -0
- package/dist/components/DashboardPage/DashboardPage.test.js +40 -0
- package/dist/components/DashboardPage/Page.d.ts +42 -0
- package/dist/components/DashboardPage/Page.js +80 -0
- package/dist/components/Dataset/DataPreview.d.ts +12 -0
- package/dist/components/Dataset/DataPreview.js +21 -0
- package/dist/components/Dataset/Dataset.d.ts +17 -0
- package/dist/components/Dataset/Dataset.js +94 -0
- package/dist/components/Dataset/Filter.d.ts +9 -0
- package/dist/components/Dataset/Filter.js +7 -0
- package/dist/components/Dataset/Join.d.ts +8 -0
- package/dist/components/Dataset/Join.js +7 -0
- package/dist/components/Dataset/Producer.d.ts +18 -0
- package/dist/components/Dataset/Producer.js +27 -0
- package/dist/components/Dataset/Provider.d.ts +21 -0
- package/dist/components/Dataset/Provider.js +22 -0
- package/dist/components/Dataset/Transform.d.ts +6 -0
- package/dist/components/Dataset/Transform.js +7 -0
- package/dist/components/Dataset/hooks.d.ts +24 -0
- package/dist/components/Dataset/hooks.js +19 -0
- package/dist/components/Debug/Debug.d.ts +1 -0
- package/dist/components/Debug/Debug.js +24 -0
- package/dist/components/FlipCard/FlipCard.d.ts +12 -0
- package/dist/components/FlipCard/FlipCard.js +38 -0
- package/dist/components/FlipCard/FlipCard.test.d.ts +1 -0
- package/dist/components/FlipCard/FlipCard.test.js +36 -0
- package/dist/components/KeyFigure/KeyFigure.d.ts +18 -0
- package/dist/components/KeyFigure/KeyFigure.js +13 -0
- package/dist/components/Layout/DashboardApp.d.ts +18 -0
- package/dist/components/Layout/DashboardApp.js +46 -0
- package/dist/components/Layout/Error.d.ts +2 -0
- package/dist/components/Layout/Error.js +6 -0
- package/dist/components/Layout/Footer.d.ts +6 -0
- package/dist/components/Layout/Footer.js +47 -0
- package/dist/components/Layout/Sider.d.ts +9 -0
- package/dist/components/Layout/Sider.js +48 -0
- package/dist/components/LoadingContainer/LoadingContainer.d.ts +17 -0
- package/dist/components/LoadingContainer/LoadingContainer.js +33 -0
- package/dist/components/MapLegend/MapLegend.d.ts +12 -0
- package/dist/components/MapLegend/MapLegend.js +19 -0
- package/dist/components/NextPrevSelect/NextPrevSelect.d.ts +17 -0
- package/dist/components/NextPrevSelect/NextPrevSelect.js +49 -0
- package/dist/components/Palette/Palette.d.ts +18 -0
- package/dist/components/Palette/Palette.js +29 -0
- package/dist/data_providers/datafair/datafair.test.d.ts +1 -0
- package/dist/data_providers/datafair/datafair.test.js +39 -0
- package/dist/data_providers/datafair/index.d.ts +15 -0
- package/dist/data_providers/datafair/index.js +33 -0
- package/dist/data_providers/datafair/utils/axios.d.ts +2 -0
- package/dist/data_providers/datafair/utils/axios.js +13 -0
- package/dist/data_providers/datafair/utils/generateFilter.d.ts +1 -0
- package/dist/data_providers/datafair/utils/generateFilter.js +40 -0
- package/dist/data_providers/datafair/utils/generateSort.d.ts +1 -0
- package/dist/data_providers/datafair/utils/generateSort.js +19 -0
- package/dist/data_providers/datafair/utils/index.d.ts +4 -0
- package/dist/data_providers/datafair/utils/index.js +4 -0
- package/dist/data_providers/datafair/utils/mapOperator.d.ts +1 -0
- package/dist/data_providers/datafair/utils/mapOperator.js +13 -0
- package/dist/data_providers/file/index.d.ts +6 -0
- package/dist/data_providers/file/index.js +25 -0
- package/dist/data_providers/file/utils/axios.d.ts +2 -0
- package/dist/data_providers/file/utils/axios.js +26 -0
- package/dist/data_providers/types.d.ts +52 -0
- package/dist/data_providers/types.js +2 -0
- package/dist/data_providers/wfs/index.d.ts +3 -0
- package/dist/data_providers/wfs/index.js +43 -0
- package/dist/data_providers/wfs/utils/axios.d.ts +2 -0
- package/dist/data_providers/wfs/utils/axios.js +26 -0
- package/dist/data_providers/wfs/utils/generateFilter.d.ts +4 -0
- package/dist/data_providers/wfs/utils/generateFilter.js +45 -0
- package/dist/data_providers/wfs/utils/generateSort.d.ts +1 -0
- package/dist/data_providers/wfs/utils/generateSort.js +20 -0
- package/dist/data_providers/wfs/utils/index.d.ts +4 -0
- package/dist/data_providers/wfs/utils/index.js +4 -0
- package/dist/data_providers/wfs/utils/mapOperator.d.ts +1 -0
- package/dist/data_providers/wfs/utils/mapOperator.js +36 -0
- package/dist/data_providers/wfs/wfs.test.d.ts +1 -0
- package/dist/data_providers/wfs/wfs.test.js +60 -0
- package/dist/dsl/index.d.ts +19 -0
- package/dist/dsl/index.js +19 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +33 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.js +1 -0
- package/dist/utils/baserecordtogeojsonpoint.d.ts +13 -0
- package/dist/utils/baserecordtogeojsonpoint.js +17 -0
- package/dist/utils/cardStyles.d.ts +2 -0
- package/dist/utils/cardStyles.js +12 -0
- package/dist/utils/deepmerge.d.ts +2 -0
- package/dist/utils/deepmerge.js +24 -0
- package/dist/utils/route_utils.d.ts +6 -0
- package/dist/utils/route_utils.js +14 -0
- package/dist/utils/useApi.d.ts +12 -0
- package/dist/utils/useApi.js +14 -0
- package/dist/utils/useMapControl.d.ts +8 -0
- package/dist/utils/useMapControl.js +24 -0
- package/dist/utils/useSearchParamsState.d.ts +11 -0
- package/dist/utils/useSearchParamsState.js +18 -0
- package/dist/utils/usechartexports.d.ts +17 -0
- package/dist/utils/usechartexports.js +39 -0
- package/dist/utils/usecharthightlight.d.ts +25 -0
- package/dist/utils/usecharthightlight.js +44 -0
- package/package.json +98 -0
package/README.MD
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# API-Dashboard
|
|
2
|
+
|
|
3
|
+
Collection de composants React pour faciliter la création de **tableaux de bords territoriaux**.
|
|
4
|
+
|
|
5
|
+
Le projet permet la mise en place d'un tableau de bord facile à déployer sur une **infrastucture légère**.
|
|
6
|
+
Le tableau de bord, une fois compilé, peut-être mis à disposition des utilisateurs via un **simple server web** (HTTP/HTTPS) sans configuration particulière.
|
|
7
|
+
Il s'agit d'une application React (Javascript) s'executant dans le navigateur des utilisateurs. Il n'y a pas **backend à installer**,
|
|
8
|
+
l'application récupère les données via API **auprès d'un partenaire** (plateforme régionale, portail open-data, etc.) ou sur **votre serveur de données**.
|
|
9
|
+
Les données sont ensuites traitées par le client et présentées à l'utilisateur via des graphiques ou cartes.
|
|
10
|
+
|
|
11
|
+
Si des données sensibles alimentent un tableau de bord, l'authentification et la sécurité sont gérées au niveau du serveur de données. C'est donc
|
|
12
|
+
lui qui va s'assurer que l'utilisateur a un droit d'accès aux données. L'application peut donc tout être utilisée pour présenter
|
|
13
|
+
des données sensibles ou même des graphiques dont les données diffèrent selon les droits de l'utilisateur.
|
|
14
|
+
|
|
15
|
+
Les API suivantes sont actuellement supportées (interrogation, filtre, pagination, etc. ) :
|
|
16
|
+
- [WFS](src/data_providers/wfs/) : API proposée par la plupart des serveurs geographiques (QGIS Server, GeoServer, ArcGIS server, etc.)
|
|
17
|
+
- [Data Fair](src/data_providers/datafair/) : API de la solution open source Data Fair.
|
|
18
|
+
- En développement : OGC API Features, TJS
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
En bref :
|
|
23
|
+
|
|
24
|
+
- ✅ Déploiement facile et rapide (_client side_)
|
|
25
|
+
- ✅ Présentez au même endroit des données tierces, vos données et celles de vos partenaires
|
|
26
|
+
- ✅ Possibilité de visualiser des données sensibles
|
|
27
|
+
- ✅ Flexibilité
|
|
28
|
+
|
|
29
|
+
Les composants sont actuellement utilisés pour le [tableau de bord de l'Odema](https://github.com/geo2france/odema-dashboard).
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
`npm install https://github.com/geo2france/api-dashboard/releases/latest/download/api-dashboard.tgz`
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Utilisation
|
|
37
|
+
|
|
38
|
+
Le tableau de bord est construit de manière déclarative **JSX** (accronyme de JavaScript XML).
|
|
39
|
+
Comme son nom l'indique, il permet de combiner la clarté et l'efficacité du XML, avec la souplesse et la puissance du JavaScript.
|
|
40
|
+
|
|
41
|
+
La construction du tableau de bord repose sur l'articulation de 3 grands concepts :
|
|
42
|
+
|
|
43
|
+
- Les `<Dataset>` permettent de rappatrier, filter et traiter laes données depuis des sources distantes : [documentation](./src/components/Dataset/README.md)
|
|
44
|
+
- Les graphiques `<ChartXXX>` et `<MapXXX>` constituent la partie essentielle du tableau de bord
|
|
45
|
+
- Les `<Control>` pour que les utilisateurs puissent interragir avec le tableau de bord (filter, activer/désactiver une option, etc.) : [documentation](./src/components/Control/README.md)
|
|
46
|
+
|
|
47
|
+
Le JSX permet aussi d'ajouter des éléments dynamiques en Javascript.
|
|
48
|
+
|
|
49
|
+
```jsx
|
|
50
|
+
|
|
51
|
+
import { Transform, Dashboard, Dataset, Filter, Producer, Control, ChartPie, useControl } from "api-dashboard/dsl"
|
|
52
|
+
|
|
53
|
+
export const MaPremierePage = () => (
|
|
54
|
+
|
|
55
|
+
<Dashboard>
|
|
56
|
+
|
|
57
|
+
<Dataset
|
|
58
|
+
id="dma_collecte_traitement"
|
|
59
|
+
resource="sinoe-(r)-destination-des-dma-collectes-par-type-de-traitement/lines"
|
|
60
|
+
url="https://data.ademe.fr/data-fair/api/v1/datasets"
|
|
61
|
+
type="datafair"
|
|
62
|
+
pageSize={5000}>
|
|
63
|
+
<Filter field="L_REGION">Hauts-de-France</Filter>
|
|
64
|
+
{/* Un filtre statique appliqué à l'API qui fournie les données */}
|
|
65
|
+
|
|
66
|
+
<Filter field="ANNEE">{useControl('annee')}</Filter>
|
|
67
|
+
{/* Un second filtre : l'année choisie par l'utilisateur */}
|
|
68
|
+
|
|
69
|
+
<Transform>SELECT [ANNEE], [L_TYP_REG_DECHET], SUM([TONNAGE_DMA]) as [TONNAGE_DMA] FROM ? GROUP BY [ANNEE], [L_TYP_REG_DECHET]</Transform>
|
|
70
|
+
{/* Transformation local des données */}
|
|
71
|
+
|
|
72
|
+
<Producer url="https://odema-hautsdefrance.org/">Odema</Producer>
|
|
73
|
+
{/* Pour créditer les graphiques */}
|
|
74
|
+
</Dataset>
|
|
75
|
+
|
|
76
|
+
<Control>
|
|
77
|
+
<Select name="annee" options={[2021,2019,2017]} initial_value={2019} arrows={true} />
|
|
78
|
+
</Control>
|
|
79
|
+
{/* Un control permettant à mon utilisateur de choisir l'année */}
|
|
80
|
+
|
|
81
|
+
<ChartPie title={`Tonnages de déchets en ${useControl('annee')}`} dataset='dma_collecte_traitement' nameKey='L_TYP_REG_DECHET' dataKey='TONNAGE_DMA' />
|
|
82
|
+
{/* Un graphique camembert standard. J'indique mon jeu de données et les colonnes à utiliser */}
|
|
83
|
+
|
|
84
|
+
</Dashboard>
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Bien débuter
|
|
89
|
+
|
|
90
|
+
1. Mise en place du projet template (_en cours_)
|
|
91
|
+
2. Créer une nouvelle page
|
|
92
|
+
3. Ajouter des [jeux de données](./src/components/Dataset/README.md)
|
|
93
|
+
4. Ajouter un [graphique](./src/components/Charts/README.md)
|
|
94
|
+
5. Personnaliser son tableau : ajouter une [Palette](./src/components/Palette/README.md), configurer le theme [theme AntDesign](https://ant.design/docs/react/customize-theme#customize-design-token) (_TODO_)
|
|
95
|
+
6. Ajouter de l'interactivité : les [contrôles utilisateur](./src/components/Control/README.md)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## Aller plus loin (développeur)
|
|
99
|
+
|
|
100
|
+
### Développement de composants dataviz
|
|
101
|
+
|
|
102
|
+
La bibliothèque propose des graphiques de bases, mais il possible de développer ses propres composants React.
|
|
103
|
+
Des fonctions sont proposés afin d'aider le développeur dans cette tâche : accéder facilement aux données ou aux options utilisateurs, gestion des erreurs, etc.
|
|
104
|
+
|
|
105
|
+
Le développeur est libre d'utiliser bibliothèque dataviz ou carto de son choix, à partir du moment où le composant retourne un élément visuelle.
|
|
106
|
+
Nous préconisons [Echarts](https://echarts.apache.org), mais d'autres sont utilisables ([Recharts](https://recharts.org/), [Chart.js](https://www.chartjs.org/), etc.)
|
|
107
|
+
|
|
108
|
+
`TODO : guide développement composant`
|
|
109
|
+
|
|
110
|
+
### Développement de contrôles
|
|
111
|
+
|
|
112
|
+
Il est également possible de développer des contrôle utilisateurs (éléments de formulaires) personalisés.
|
|
113
|
+
|
|
114
|
+
`TODO : guide développement control. Le composant doit retourner un <Form.Item />`
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## Documentation (ancienne)
|
|
120
|
+
|
|
121
|
+
⭐ Essentiel
|
|
122
|
+
👨💻 Utilisateur avancé
|
|
123
|
+
|
|
124
|
+
### Mise en page et structure
|
|
125
|
+
|
|
126
|
+
- [DashboardApp](/src/components/Layout/) ⭐
|
|
127
|
+
- [DashboardPage](/src/components/DashboardPage/) ⭐
|
|
128
|
+
- [DashboardElement](/src/components/DashboardElement/) ⭐
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
### Composants
|
|
132
|
+
|
|
133
|
+
- [KeyFigure](/src/components/KeyFigure/) ⭐
|
|
134
|
+
- [NextPrevSelect](/src/components/NextPrevSelect/) ⭐
|
|
135
|
+
- [MapLegend](/src/components/MapLegend/) ⭐
|
|
136
|
+
- [FlipCard](/src/components/FlipCard/)
|
|
137
|
+
- [LoadingContainer](/src/components/LoadingContainer/)
|
|
138
|
+
|
|
139
|
+
### Hooks et fonctions
|
|
140
|
+
|
|
141
|
+
- [useApi](/src/utils/README.MD) ⭐
|
|
142
|
+
- [useSearchParamsState](/src/utils/README.MD) ⭐
|
|
143
|
+
- [useChartEvents](/src/utils/README.MD) 👨💻
|
|
144
|
+
- [useChartActionHightlight](/src/utils/README.MD) 👨💻
|
|
145
|
+
- [useMapControl](/src/utils/README.MD) 👨💻
|
|
146
|
+
- [useChartExport](/src/utils/README.MD)
|
|
147
|
+
|
|
148
|
+
### Fournisseur de données
|
|
149
|
+
|
|
150
|
+
- [WFS](/src/data_providers/wfs/) ⭐
|
|
151
|
+
- [Datafair](/src/data_providers/datafair/) ⭐
|
|
152
|
+
- [Filte](/src/data_providers/file/) ⭐
|
|
153
|
+
|
|
154
|
+

|
|
155
|
+
|
|
156
|
+
<!---
|
|
157
|
+
```mermaid
|
|
158
|
+
graph TD;
|
|
159
|
+
|
|
160
|
+
subgraph "<DashboardApp>"
|
|
161
|
+
subgraph "<DashboardPage>"
|
|
162
|
+
subgraph "<DashboardElement>"
|
|
163
|
+
subgraph "Chart(Echart)"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
subgraph "<DashboardElement>"
|
|
167
|
+
subgraph "Chart(Echart)"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
subgraph "<DashboardElement>"
|
|
171
|
+
subgraph "Chart(Echart)"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
subgraph "<DashboardPage>"
|
|
177
|
+
subgraph "<DashboardElement>"
|
|
178
|
+
subgraph "Chart(Echart)"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
subgraph "<DashboardElement>"
|
|
182
|
+
subgraph "Chart(Echart)"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
subgraph "<DashboardElement>"
|
|
186
|
+
subgraph "Chart(Echart)"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
--->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import Attribution from './Attributions';
|
|
5
|
+
describe("Attribution Component", () => {
|
|
6
|
+
test("initial state", async () => {
|
|
7
|
+
// Génération DOM virtuel et sélection des noeuds
|
|
8
|
+
render(_jsx(Attribution, { licenses: ['CC', 'SA'], data: [{ url: "http://monurl.fr", name: "MyDataSourceName" }] }));
|
|
9
|
+
// Assertions
|
|
10
|
+
expect(screen.getByText("MyDataSourceName")).toBeInTheDocument();
|
|
11
|
+
expect(screen.getByText("Source des données:")).toBeInTheDocument();
|
|
12
|
+
const linkElement = screen.getByText("MyDataSourceName");
|
|
13
|
+
expect(linkElement).toHaveAttribute('href', "http://monurl.fr");
|
|
14
|
+
expect(screen.queryByLabelText('CC')).toBeInTheDocument();
|
|
15
|
+
expect(screen.queryByLabelText('SA')).toBeInTheDocument();
|
|
16
|
+
expect(screen.queryByLabelText('NC')).not.toBeInTheDocument();
|
|
17
|
+
expect(screen.queryByLabelText('BY')).not.toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React, { CSSProperties } from 'react';
|
|
2
|
+
import { License } from '../../types';
|
|
3
|
+
export interface SourceProps {
|
|
4
|
+
name: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SourceMakerProps {
|
|
8
|
+
maker?: SourceProps;
|
|
9
|
+
sources?: SourceProps[];
|
|
10
|
+
}
|
|
11
|
+
interface AttributionProps {
|
|
12
|
+
data: SourceMakerProps | SourceProps[];
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
licenses?: License[];
|
|
15
|
+
}
|
|
16
|
+
declare const Attribution: React.FC<AttributionProps>;
|
|
17
|
+
export default Attribution;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Tooltip, Typography } from 'antd';
|
|
3
|
+
import { FaCreativeCommons, FaCreativeCommonsBy, FaCreativeCommonsNc, FaCreativeCommonsPd, FaCreativeCommonsSa, FaCreativeCommonsZero } from 'react-icons/fa';
|
|
4
|
+
const { Text, Link } = Typography;
|
|
5
|
+
const LogoLicence = ({ license, style }) => {
|
|
6
|
+
switch (license) {
|
|
7
|
+
case "CC":
|
|
8
|
+
return _jsx(FaCreativeCommons, { style: style });
|
|
9
|
+
case "BY":
|
|
10
|
+
return _jsx(FaCreativeCommonsBy, { style: style });
|
|
11
|
+
case "NC":
|
|
12
|
+
return _jsx(FaCreativeCommonsNc, { style: style });
|
|
13
|
+
case "PD":
|
|
14
|
+
return _jsx(FaCreativeCommonsPd, { style: style });
|
|
15
|
+
case "SA":
|
|
16
|
+
return _jsx(FaCreativeCommonsSa, { style: style });
|
|
17
|
+
case "ZERO":
|
|
18
|
+
return _jsx(FaCreativeCommonsZero, { style: style });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const Attribution = ({ data, style, licenses }) => {
|
|
22
|
+
const licence_logo_style = { height: '12px', width: '12px' };
|
|
23
|
+
const sources = Array.isArray(data) ? data : data.sources;
|
|
24
|
+
const maker = Array.isArray(data) ? undefined : data.maker;
|
|
25
|
+
const plural = (sources?.length ?? 0) > 1 ? 's' : '';
|
|
26
|
+
return (_jsx("div", { style: { paddingLeft: 4, paddingBottom: 4, ...style }, children: _jsxs(Text, { type: "secondary", children: [`Source${plural} des données : `, sources?.map((e, i) => (_jsxs("span", { children: [_jsx(Link, { href: e.url, children: e.name }), i < sources.length - 1 ? ", " : ""] }, i))), maker && _jsxs("span", { children: [' ', "| R\u00E9alisation : ", _jsx(Link, { href: maker.url, children: maker.name })] }), _jsx("span", { style: { marginLeft: 5 }, children: _jsx(Tooltip, { title: licenses?.join(' '), placement: "bottom", children: licenses?.map((license, index) => {
|
|
27
|
+
return (_jsx("span", { style: { marginLeft: 2 }, children: _jsx(LogoLicence, { license: license, style: licence_logo_style, "aria-label": license }) }, index));
|
|
28
|
+
}) }) })] }) }));
|
|
29
|
+
};
|
|
30
|
+
export default Attribution;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { EChartsOption } from "echarts";
|
|
2
|
+
import EChartsReact from "echarts-for-react";
|
|
3
|
+
interface ChartEchartsProps {
|
|
4
|
+
option?: EChartsOption;
|
|
5
|
+
}
|
|
6
|
+
export declare const ChartEcharts: import("react").ForwardRefExoticComponent<ChartEchartsProps & import("react").RefAttributes<EChartsReact>>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
3
|
+
import { theme } from 'antd';
|
|
4
|
+
import EChartsReact from "echarts-for-react";
|
|
5
|
+
import { usePalette } from "../Palette/Palette";
|
|
6
|
+
import deepMerge from "../../utils/deepmerge";
|
|
7
|
+
const { useToken } = theme;
|
|
8
|
+
/*
|
|
9
|
+
* Ce composant peut servir de base aux développements d'autres composants ou être utilisé directement dans une page (non conseillé).
|
|
10
|
+
* - Applique la palette utilisateur
|
|
11
|
+
* - Utilise le style de texte de l'application
|
|
12
|
+
* devnote : A partir de React 19, ne plus utiliser forwardRef https://react.dev/reference/react/forwardRef
|
|
13
|
+
*/
|
|
14
|
+
export const ChartEcharts = forwardRef(({ option = {} }, ref) => {
|
|
15
|
+
const innerRef = useRef(null);
|
|
16
|
+
useImperativeHandle(ref, () => innerRef.current, []); // Pour exposer le innerref au parent
|
|
17
|
+
const { token } = useToken();
|
|
18
|
+
const n_series = Array.isArray(option.series) ? option.series?.length : 1;
|
|
19
|
+
// Récupérer et traduire les éléments depuis le theme antdesign : police, couleurs, etc..
|
|
20
|
+
// Ceci peut-être surchargé par les options de l'utilisateur
|
|
21
|
+
const default_option = {
|
|
22
|
+
color: usePalette({ nColors: n_series }),
|
|
23
|
+
textStyle: {
|
|
24
|
+
fontFamily: token.fontFamily,
|
|
25
|
+
color: token.colorTextSecondary
|
|
26
|
+
},
|
|
27
|
+
xAxis: {
|
|
28
|
+
axisLine: { lineStyle: { color: token.colorTextSecondary } },
|
|
29
|
+
},
|
|
30
|
+
yAxis: {
|
|
31
|
+
axisLine: { lineStyle: { color: token.colorTextSecondary } },
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
return (_jsx(EChartsReact, { option: deepMerge({}, default_option, option), ref: innerRef }));
|
|
35
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext, useEffect } from "react";
|
|
3
|
+
import { useDataset } from "../Dataset/hooks";
|
|
4
|
+
import { ChartBlockContext } from "../DashboardPage/Block";
|
|
5
|
+
import { usePalette } from "../Palette/Palette";
|
|
6
|
+
import { from, op } from "arquero";
|
|
7
|
+
import { ChartEcharts } from "./ChartEcharts";
|
|
8
|
+
export const ChartPie = ({ dataset: dataset_id, nameKey, dataKey, unit, title, donut = false }) => {
|
|
9
|
+
const dataset = useDataset(dataset_id);
|
|
10
|
+
const blockConfig = useContext(ChartBlockContext);
|
|
11
|
+
const data = dataset?.data;
|
|
12
|
+
const block_config = {
|
|
13
|
+
title: title,
|
|
14
|
+
dataExport: data
|
|
15
|
+
};
|
|
16
|
+
useEffect(() => blockConfig?.setConfig(block_config), [data]);
|
|
17
|
+
const chart_data = data && from(data).groupby(nameKey).rollup({ value: op.sum(dataKey) }).objects().map((d) => ({
|
|
18
|
+
name: d[nameKey],
|
|
19
|
+
value: d.value,
|
|
20
|
+
}));
|
|
21
|
+
const option = {
|
|
22
|
+
color: usePalette({ nColors: chart_data?.length }),
|
|
23
|
+
xAxis: { show: false }, yAxis: { show: false },
|
|
24
|
+
series: [{
|
|
25
|
+
type: 'pie',
|
|
26
|
+
data: chart_data,
|
|
27
|
+
radius: donut ? ['40%', '75%'] : [0, '75%']
|
|
28
|
+
}],
|
|
29
|
+
tooltip: {
|
|
30
|
+
show: true,
|
|
31
|
+
valueFormatter: v => `${v?.toLocaleString()} ${unit || ''} `
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return _jsx(ChartEcharts, { option: option });
|
|
35
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphique standard pour afficher des données annuelles
|
|
3
|
+
*/
|
|
4
|
+
interface IYearSerieProps {
|
|
5
|
+
dataset: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
yearControl?: string;
|
|
8
|
+
yearKey: string;
|
|
9
|
+
valueKey: string;
|
|
10
|
+
categoryKey?: string;
|
|
11
|
+
stack?: boolean;
|
|
12
|
+
yearMark?: number | string;
|
|
13
|
+
type?: 'bar' | 'line' | 'area';
|
|
14
|
+
}
|
|
15
|
+
export declare const ChartYearSerie: React.FC<IYearSerieProps>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Graphique standard pour afficher des données annuelles
|
|
4
|
+
*/
|
|
5
|
+
import { from, op } from "arquero";
|
|
6
|
+
import { useDataset } from "../Dataset/hooks";
|
|
7
|
+
import { usePalette } from "../Palette/Palette";
|
|
8
|
+
import { ChartEcharts } from "./ChartEcharts";
|
|
9
|
+
export const ChartYearSerie = ({ dataset: dataset_id, categoryKey, valueKey, yearKey, yearMark, stack: stack_input, type: chart_type = 'bar', yearControl = 'year' }) => {
|
|
10
|
+
const stack = stack_input || chart_type == 'line' ? false : true; // Pas de stack par défaut pour le type line
|
|
11
|
+
const dataset = useDataset(dataset_id);
|
|
12
|
+
const data = dataset?.data;
|
|
13
|
+
const chart_data = categoryKey ? data && from(data).groupby(yearKey, categoryKey) //Somme par année et categorykey
|
|
14
|
+
.rollup({ [valueKey]: op.sum(valueKey) })
|
|
15
|
+
.groupby(yearKey).orderby(yearKey).objects()
|
|
16
|
+
:
|
|
17
|
+
data && from(data).groupby(yearKey) //Somme par année seulement
|
|
18
|
+
.rollup({ [valueKey]: op.sum(valueKey) }).orderby(yearKey)
|
|
19
|
+
.objects();
|
|
20
|
+
const distinct_cat = categoryKey ?
|
|
21
|
+
data && from(data).rollup({ cat: op.array_agg_distinct(categoryKey) }).object().cat
|
|
22
|
+
:
|
|
23
|
+
[valueKey];
|
|
24
|
+
const COLORS = usePalette({ nColors: distinct_cat?.length }) || [];
|
|
25
|
+
const series = distinct_cat ? distinct_cat.map((cat, idx) => ({
|
|
26
|
+
name: cat,
|
|
27
|
+
type: chart_type === 'area' ? 'line' : chart_type,
|
|
28
|
+
data: categoryKey ? chart_data?.filter((row) => row[categoryKey] === cat).map((row) => ([String(row[yearKey]), row[valueKey]]))
|
|
29
|
+
: chart_data?.map((row) => ([String(row[yearKey]), row[valueKey]])),
|
|
30
|
+
itemStyle: {
|
|
31
|
+
color: COLORS && COLORS[idx % COLORS.length],
|
|
32
|
+
},
|
|
33
|
+
stack: stack ? 'total' : undefined,
|
|
34
|
+
areaStyle: chart_type === 'area' ? {} : undefined,
|
|
35
|
+
markLine: idx === 0 && yearMark ? {
|
|
36
|
+
symbol: 'none',
|
|
37
|
+
data: [
|
|
38
|
+
{ xAxis: String(yearMark) }
|
|
39
|
+
]
|
|
40
|
+
} : undefined
|
|
41
|
+
})) : [];
|
|
42
|
+
function tooltipFormatter(params) {
|
|
43
|
+
if (!params || params.length === 0)
|
|
44
|
+
return '';
|
|
45
|
+
const year = params[0].value[0];
|
|
46
|
+
const lines = params.map((p) => {
|
|
47
|
+
const value = Number(p.value[1]).toLocaleString();
|
|
48
|
+
return `${p.marker} ${p.seriesName} <b><span style="display:inline-block; min-width:80px; text-align:right;">${value}</span></b>`;
|
|
49
|
+
});
|
|
50
|
+
return `<div>${year}</div>` + lines.join('<br/>');
|
|
51
|
+
}
|
|
52
|
+
const option = {
|
|
53
|
+
series: series,
|
|
54
|
+
legend: {
|
|
55
|
+
show: true,
|
|
56
|
+
bottom: 0
|
|
57
|
+
},
|
|
58
|
+
tooltip: {
|
|
59
|
+
trigger: 'axis',
|
|
60
|
+
formatter: tooltipFormatter
|
|
61
|
+
},
|
|
62
|
+
xAxis: {
|
|
63
|
+
type: 'time',
|
|
64
|
+
},
|
|
65
|
+
yAxis: {
|
|
66
|
+
type: 'value',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
return (_jsx(ChartEcharts, { option: option }));
|
|
70
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { CSSProperties, ReactElement } from "react";
|
|
2
|
+
interface IControlProps {
|
|
3
|
+
children: ReactElement | ReactElement[];
|
|
4
|
+
style?: CSSProperties;
|
|
5
|
+
}
|
|
6
|
+
declare const Control: React.FC<IControlProps>;
|
|
7
|
+
export default Control;
|
|
8
|
+
export declare const useControl: (name: string) => string | undefined;
|
|
9
|
+
export declare const useAllControls: () => Record<string, any>;
|
|
10
|
+
export declare const list_to_options: (input?: string[] | number[] | {
|
|
11
|
+
label: string | number;
|
|
12
|
+
value: string | number;
|
|
13
|
+
}[]) => {
|
|
14
|
+
label: string | number;
|
|
15
|
+
value: string | number;
|
|
16
|
+
}[];
|
|
17
|
+
interface IControlProps {
|
|
18
|
+
children: ReactElement | ReactElement[];
|
|
19
|
+
}
|
|
20
|
+
export declare const DSL_Control: React.FC<IControlProps>;
|
|
21
|
+
export declare const ControlPreview: React.FC;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Descriptions, Form, Layout } from "antd";
|
|
3
|
+
import React, { useContext, useEffect } from "react";
|
|
4
|
+
import { ControlContext } from "../DashboardPage/Page";
|
|
5
|
+
const { Header } = Layout;
|
|
6
|
+
/*
|
|
7
|
+
* Composant destiné à recevoir un Form avec les contrôles de la page
|
|
8
|
+
*/
|
|
9
|
+
const Control = ({ children, style = {} }) => {
|
|
10
|
+
return (_jsx(Header, { style: {
|
|
11
|
+
padding: 12,
|
|
12
|
+
position: "sticky",
|
|
13
|
+
top: 0,
|
|
14
|
+
zIndex: 600, // maplibre top zIndex if 500
|
|
15
|
+
backgroundColor: "#fff",
|
|
16
|
+
height: "auto",
|
|
17
|
+
width: "100%",
|
|
18
|
+
...style,
|
|
19
|
+
}, children: children }));
|
|
20
|
+
};
|
|
21
|
+
export default Control;
|
|
22
|
+
/*
|
|
23
|
+
* Hook pour accéder à un control spécifique de la page
|
|
24
|
+
*/
|
|
25
|
+
export const useControl = (name) => {
|
|
26
|
+
const context_controls = useContext(ControlContext);
|
|
27
|
+
if (!context_controls) {
|
|
28
|
+
throw new Error("useControl must be used within a ControlProvider");
|
|
29
|
+
}
|
|
30
|
+
const { values } = context_controls;
|
|
31
|
+
const value = values[name];
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
/*
|
|
35
|
+
* Hook pour accéder à tous les controls utilisateur
|
|
36
|
+
*/
|
|
37
|
+
export const useAllControls = () => {
|
|
38
|
+
const context_controls = useContext(ControlContext);
|
|
39
|
+
if (!context_controls) {
|
|
40
|
+
throw new Error("useControl must be used within a ControlProvider");
|
|
41
|
+
}
|
|
42
|
+
const { values } = context_controls;
|
|
43
|
+
return values;
|
|
44
|
+
};
|
|
45
|
+
/* Convenient function to return Options from list or Options */
|
|
46
|
+
export const list_to_options = (input = []) => {
|
|
47
|
+
if (input === undefined) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return input.map((o) => {
|
|
51
|
+
if (typeof o == "string" || typeof o == "number") {
|
|
52
|
+
return { label: String(o), value: o };
|
|
53
|
+
}
|
|
54
|
+
return o;
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
export const DSL_Control = ({ children }) => {
|
|
58
|
+
const context_controls = useContext(ControlContext);
|
|
59
|
+
const [form] = Form.useForm();
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
handleChange(form?.getFieldsValue(true)); // Appliquer les valeurs par défaut au contexte lors de l'initialisation du composant
|
|
62
|
+
}, []);
|
|
63
|
+
if (!context_controls) { //Le contexte peut être nul ?
|
|
64
|
+
throw new Error("useControl must be used within a ControlProvider");
|
|
65
|
+
}
|
|
66
|
+
const { values: _control, pushValue: pushControl } = context_controls;
|
|
67
|
+
const childrenArray = React.Children.toArray(children).filter((child) => React.isValidElement(child));
|
|
68
|
+
//Ajout des nouvelles valeurs de controles dans le contexte de la page
|
|
69
|
+
const handleChange = (changed_value) => {
|
|
70
|
+
pushControl(changed_value);
|
|
71
|
+
};
|
|
72
|
+
const initialValues = Object.fromEntries(childrenArray.filter((child) => child.props.options && child.props.options.length > 0).map((child) => [child.props.name, child.props.options[0].value])); // Initialisé avec la première valeur de chaque option
|
|
73
|
+
return (_jsx(Form, { onValuesChange: handleChange, layout: "inline", initialValues: initialValues, form: form, children: children }));
|
|
74
|
+
};
|
|
75
|
+
//TODO : ajouter la gestion des useSearchParameters (ici au dans la Page ?)
|
|
76
|
+
export const ControlPreview = ({}) => {
|
|
77
|
+
const controlValues = useAllControls();
|
|
78
|
+
const items = Object.entries(controlValues).map(([key, value]) => ({
|
|
79
|
+
key: key,
|
|
80
|
+
label: key,
|
|
81
|
+
children: _jsx("p", { children: value }),
|
|
82
|
+
}));
|
|
83
|
+
return (_jsx(Descriptions, { items: items }));
|
|
84
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RadioGroupProps } from 'antd';
|
|
2
|
+
import { SimpleRecord } from '../../types';
|
|
3
|
+
export declare const buildOptionsFromData: (data: SimpleRecord[], labelField?: string, valueField?: string) => {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string | number;
|
|
6
|
+
}[];
|
|
7
|
+
type ExtendedRadioGroupProps = RadioGroupProps & {
|
|
8
|
+
name?: string;
|
|
9
|
+
dataset?: string;
|
|
10
|
+
options?: {
|
|
11
|
+
label: string;
|
|
12
|
+
value: string | number;
|
|
13
|
+
}[] | string[] | number[];
|
|
14
|
+
labelField?: string;
|
|
15
|
+
valueField?: string;
|
|
16
|
+
initalValue?: string | number;
|
|
17
|
+
};
|
|
18
|
+
export declare const Radio: React.FC<ExtendedRadioGroupProps>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Radio as AntRadio, Form } from 'antd';
|
|
3
|
+
import { useDataset } from '../Dataset/hooks';
|
|
4
|
+
import { from } from 'arquero';
|
|
5
|
+
import { list_to_options } from './Control';
|
|
6
|
+
// On construit les options depuis le tableau de données, utiliser pour Radio et Select
|
|
7
|
+
export const buildOptionsFromData = (data, labelField = 'label', valueField = 'value') => {
|
|
8
|
+
const t = from(data);
|
|
9
|
+
return (t.select(labelField, valueField)
|
|
10
|
+
.dedupe(valueField)
|
|
11
|
+
.objects()
|
|
12
|
+
.map((row) => ({
|
|
13
|
+
label: row[labelField],
|
|
14
|
+
value: row[valueField],
|
|
15
|
+
})));
|
|
16
|
+
};
|
|
17
|
+
export const Radio = ({ dataset: datasetSource, options: input_options = [], labelField = 'label', valueField = 'value', name, initalValue: initalValue_input, ...rest }) => {
|
|
18
|
+
// Ici tu pourrais fetcher les données depuis un contexte/dataset si datasetSource est présent
|
|
19
|
+
const data = useDataset(datasetSource)?.data;
|
|
20
|
+
const options = list_to_options(input_options);
|
|
21
|
+
const data_options = options || data && buildOptionsFromData(data, labelField, valueField);
|
|
22
|
+
const initial_value = initalValue_input ? initalValue_input : data_options && data_options?.length > 0 && data_options[0].value;
|
|
23
|
+
if (data === undefined && options === undefined) {
|
|
24
|
+
return _jsx(_Fragment, {});
|
|
25
|
+
}
|
|
26
|
+
return (_jsx(Form.Item, { name: name, label: name, initialValue: initial_value, children: _jsx(AntRadio.Group, { options: data_options, value: initial_value, ...rest }) }));
|
|
27
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SelectProps } from 'antd';
|
|
2
|
+
type ExtendedSelectProps = Omit<SelectProps<any>, 'options'> & {
|
|
3
|
+
dataset?: string;
|
|
4
|
+
options?: {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string | number;
|
|
7
|
+
}[] | string[] | number[];
|
|
8
|
+
initial_value?: string | number;
|
|
9
|
+
labelField?: string;
|
|
10
|
+
valueField?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
arrows?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare const Select: React.FC<ExtendedSelectProps>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useDataset } from '../Dataset/hooks';
|
|
3
|
+
import { buildOptionsFromData } from './Radio';
|
|
4
|
+
import NextPrevSelect from '../NextPrevSelect/NextPrevSelect';
|
|
5
|
+
import { list_to_options } from './Control';
|
|
6
|
+
// TODO : a fusionner avec NextPrevSelect pour n'avoir qu'un seul composant
|
|
7
|
+
// Actuellement, Select apporte seulement le fait de choisir les valeurs depuis un
|
|
8
|
+
export const Select = ({ name, dataset: datasetSource, options: input_options = [], labelField = 'label', valueField = 'value', arrows = false, initial_value: initial_value_in, ...rest }) => {
|
|
9
|
+
const options = list_to_options(input_options);
|
|
10
|
+
const data = useDataset(datasetSource)?.data;
|
|
11
|
+
const data_options = datasetSource ? (data && buildOptionsFromData(data, labelField, valueField)) : options;
|
|
12
|
+
const myOptions = data_options && data_options.map((o) => {
|
|
13
|
+
if (typeof o == "string" || typeof o == "number") {
|
|
14
|
+
return { label: o, value: o };
|
|
15
|
+
}
|
|
16
|
+
return o;
|
|
17
|
+
});
|
|
18
|
+
const initial_value = initial_value_in || myOptions && myOptions?.length > 0 && myOptions[0].value;
|
|
19
|
+
if (data_options === undefined) {
|
|
20
|
+
return _jsx(_Fragment, {});
|
|
21
|
+
}
|
|
22
|
+
return (_jsx(NextPrevSelect, { name: name, options: data_options, defaultValue: initial_value == null || initial_value == false
|
|
23
|
+
? undefined
|
|
24
|
+
: initial_value, arrows: arrows, optionFilterProp: "label", ...rest }));
|
|
25
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { EChartsOption } from "echarts";
|
|
2
|
+
type EChartsSeriesTypes = ('line' | 'bar' | 'pie' | 'scatter');
|
|
3
|
+
interface IDashboardChartProps {
|
|
4
|
+
data?: any[];
|
|
5
|
+
chart_type?: EChartsSeriesTypes;
|
|
6
|
+
sql?: string;
|
|
7
|
+
echarts_option?: EChartsOption;
|
|
8
|
+
reverse_axies?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare const DashboardChart: React.FC<IDashboardChartProps>;
|
|
11
|
+
export default DashboardChart;
|