Skip to content

Commit

Permalink
Merge pull request #48 from geo2france/dev
Browse files Browse the repository at this point in the history
1.3
  • Loading branch information
jbdesbas authored Nov 21, 2024
2 parents 902460c + bb52f2e commit 87a2b12
Show file tree
Hide file tree
Showing 25 changed files with 717 additions and 54 deletions.
3 changes: 3 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ Version stable :

### Composants

- [DashboardLayout](/src/components/DashboardLayout/)
- [DashboardElement](/src/components/DashboardElement/)
- [KeyFigure](/src/components/KeyFigure/)
- [NextPrevSelect](/src/components/NextPrevSelect/)
- [MapLegend](/src/components/MapLegend/)
- [FlipCard](/src/components/FlipCard/)
- [LoadingContainer](/src/components/LoadingContainer/)

Expand All @@ -58,6 +60,7 @@ Version stable :
- [useChartEvents](/src/utils/README.MD)
- [useChartActionHightlight](/src/utils/README.MD)
- [useSearchParamsState](/src/utils/README.MD)
- [useMapControl](/src/utils/README.MD)
- [useChartExport](/src/utils/README.MD)

### Fournisseur de données
Expand Down
1 change: 1 addition & 0 deletions __mocks__/styleMock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ module.exports = {
testEnvironment: 'jsdom',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest' },
'^.+\\.(ts|tsx)$': 'ts-jest', },
//setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
testMatch: ['**/?(*.)+(test).(ts|tsx)'],
moduleNameMapper: {
'\\.svg\\?react$': '<rootDir>/__mocks__/svgReact.tsx', // Mock pour le composant SVG
'\\.svg\\?url$': '<rootDir>/__mocks__/svgUrl.tsx', // Mock pour l'URL de l'image SVG

"\\.(css|less|sass|scss)$": "<rootDir>/__mocks__/styleMock.tsx", // Mock por les CSS
},
};
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
},
"dependencies": {
"alasql": "^4.5.0",
"axios": "^1.7.2",
"axios": "^1.7.4",
"echarts": "^5.5.1",
"echarts-for-react": "^3.0.2",
"query-string": "^9.1.0",
"query-string": "~7.1.3",
"react-icons": "^5.2.1",
"react-map-gl": "^7.1.7",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
},
"devDependencies": {
Expand Down
70 changes: 70 additions & 0 deletions src/components/DashboardChart/DashboardChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import alasql from "alasql";
import { EChartsOption } from "echarts";
import ReactECharts from 'echarts-for-react';
import { useRef } from "react";
import { useChartData, useDashboardElement } from "../DashboardElement/hooks";
import deepMerge from "../../utils/deepmerge";


type EChartsSeriesTypes = ('line' | 'bar' | 'pie' | 'scatter');

interface IDashboardChartProps {
data?: any[];
chart_type?: EChartsSeriesTypes; // Utiliser directement le type de SeriesOption
sql? : string;
echarts_option?: EChartsOption;
reverse_axies?:boolean
}


// Les données doivent être de la forme {axeA : axeB : } ?

const DashboardChart: React.FC<IDashboardChartProps> = ({data, chart_type='line', reverse_axies=false, echarts_option = {}, sql}) => {
const chartRef = useRef<any>();
useDashboardElement({chartRef})

const data_xy = sql ? alasql(sql, [data]) : data;

useChartData({data:data_xy})

const keys = Object.keys(data_xy[0]);

const axis0 = {
type: typeof data_xy[0][keys[0]] === 'number' ? 'value' : 'category',
name: keys[0]
}

const axis1 = {
type: typeof data_xy[0][keys[1]] === 'number' ? 'value' : 'category',
name: keys[1]
}

const options:EChartsOption = {
series:[
{
type:chart_type,
data:data_xy.map((e:any) =>
reverse_axies ?
[e[keys[1]], e[keys[0]]]
: [e[keys[0]], e[keys[1]]]
)
}
],
//@ts-ignore
xAxis:{
...reverse_axies ? axis1 : axis0
},
//@ts-ignore
yAxis:{
...reverse_axies ? axis0 : axis1
}
}
return (
<ReactECharts
option={deepMerge(options,echarts_option)} ref={chartRef} />
)
}


export default DashboardChart;

38 changes: 38 additions & 0 deletions src/components/DashboardChart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# DashboardChart

Le composant DashboardChart facilite la création de graphique à deux dimensions.
Le type de graphique `chart_type` est à choisir parmis les suivants : `'bar' | 'pie' | 'line' | 'scatter' `.

## Les données et les axes

Les données `data` doivent être une liste d'objets ayant chacun _exactement_ deux propriétés. Le nom des propriétés est libre, et consitituerons les noms des axes.
Par défaut, la première clé correspond aux abscisses (X) et la seconde aux ordonnées (Y). Il est possible d'inverser ceci avec `reverse_axies=true`.
Le type d'axe (`value` ou `category`) est déterminé automatiquement à partir du contenu des données (numérique ou texte).

DashboardChart permet également d'appliquer une requête `sql` pour traiter les données à la volée (par exemple pour réaliser une aggrégation). Le nom de la table à utilisé est `?` (exemple : `SELECT x as commune, y as population FROM ?`). Pour plus d'information, voir la [documentation AlaSQL](https://github.com/AlaSQL/alasql/wiki/Select).
Si aucune requête n'est fournie, les données `data` sont directement utilisées.

## Intégration dans la page

Il est intéressant de placer _DashboardChart_ en tant qu'enfant de [_DashboardElement_](../DashboardElement/) : En plus de l'habillage du graphique (titre, crédits, etc.), ceci permettra de proposer automatiquement à l'utilisateur d'exporter les données en format tabulaire (CSV, ODS, etc.).


## Personnalisation avancée

La propriété `echarts_option` permet de personnaliser le graphique en éditant directement la configuration du graphique. Voir les paramètres disponibles dans la [documentation de Apache ECharts](https://echarts.apache.org/en/option.html).

## Exemple

```tsx
<DashboardElement title="Mon graphique de test">
<DashboardChart
data={[{mavar:'a', autrevar:2},{mavar:'b', autrevar:5},{mavar:'c', autrevar:4}]}
chart_type="bar"
echarts_option={{
xAxis:{show:false},
yAxis:{axisLabel:{color:'red'}},
tooltip:{show:true}
}}
/>
</DashboardElement>
```
74 changes: 42 additions & 32 deletions src/components/DashboardElement/DashboardElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,46 @@ const { Text } = Typography;
export const chartContext = createContext<any>({
setchartRef: () => {},
setData: () => {},
data: undefined,
setNodata: () => {}
});

type DataFileType = "csv" | "xlsx" | "ods";

interface IDashboardElementProps {
export interface IDashboardElementProps {
title: string;
children: ReactNode;
isFetching?: boolean;
header?: boolean;
attributions?: SourceProps[];
toolbox?: boolean;
fullscreen?: boolean;
exportPNG?: boolean;
exportData?: boolean;
description?: ReactElement | string;
licenses?:License[]
section?:string
virtual?:boolean
}

const DashboardElement: React.FC<IDashboardElementProps> = ({
children,
title,
header = true,
attributions,
isFetching = false,
toolbox = true,
fullscreen = true,
exportPNG = true,
exportData = true,
description,
licenses = ['CC', 'BY']
licenses = ['CC', 'BY'],
virtual = false,
}) => {
const { token } = useToken();
const [modalIsOpen, setModalIsOpen] = useState(false);
const [chartRef, setchartRef] = useState(undefined);
const [data, setData] = useState(undefined);
const [nodata, setNodata] = useState(false);
const [requestDlImage, setRequestDlImage] = useState(false);
const [requestDlData, setrequestDlData] = useState<DataFileType | null>(null);

Expand Down Expand Up @@ -169,12 +175,14 @@ const DashboardElement: React.FC<IDashboardElementProps> = ({
className="dashboard-element"
styles={cardStyles}
style={{
backgroundColor: virtual ? 'transparent' : undefined,
boxShadow : virtual ? 'none' : undefined,
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
}}
title={
bordered={!virtual}
title={ header && !virtual &&
<div
style={{
display: "flex",
Expand All @@ -189,33 +197,35 @@ const DashboardElement: React.FC<IDashboardElementProps> = ({
</div>
}
>
<chartContext.Provider value={{ chartRef, setchartRef, setData }}>
<LoadingContainer isFetching={isFetching}>
{children}
</LoadingContainer>
</chartContext.Provider>

<Flex justify="flex-end" align="flex-end" style={{ marginRight: 5 }}>
{attributions && (
<div style={{ marginTop: "auto" }}>
<Attribution licenses={licenses} data={attributions} />
</div>
)}
{description && (
<Popover content={
<div style={{ maxWidth: 800 }}>
{typeof description === "string" ?
<Text italic type="secondary"> {description} </Text>
: <>{description}</> }
</div>
}
>
<Button
type="link"
icon={<HiQuestionMarkCircle />}
style={{fontSize:"150%"}}/>
</Popover>
)}
<Flex vertical justify="space-between" style={{height:"100%"}}>
<chartContext.Provider value={{ chartRef, setchartRef, setData, setNodata }}>
<LoadingContainer isFetching={isFetching} noData={nodata}>
{children}
</LoadingContainer>
</chartContext.Provider>

<Flex justify="flex-end" align="flex-end" style={{ marginRight: 5}}>
{attributions && (
<div style={{ marginTop: "auto" }}>
<Attribution licenses={licenses} data={attributions} />
</div>
)}
{description && (
<Popover content={
<div style={{ maxWidth: 800 }}>
{typeof description === "string" ?
<Text italic type="secondary"> {description} </Text>
: <>{description}</> }
</div>
}
>
<Button
type="link"
icon={<HiQuestionMarkCircle />}
style={{fontSize:"150%"}}/>
</Popover>
)}
</Flex>
</Flex>
</Card>

Expand Down
16 changes: 11 additions & 5 deletions src/components/DashboardElement/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

Le composant [DashboardElement](src/components/dashboard_element/index.tsx) peut-être utilisé pour ajouter des fonctionnalités à un _element_ (graphique ou cartographique) de tableau de bord.
Il ajoute :
- Une _card_ servant de conteneur, avec titre et crédit.
- Une _card_ servant de conteneur, avec titre, crédit et licences.
- Un menu contextuel permettant à l'utilisateur de :
- Afficher le contenu en plein écran
- Exporter le contenu en format image (png)
- Un gestion du chargement de données
- Exporter les données (csv, xlsx, ods)
- Un gestion du chargement de données et des données non disponibles
- Une possibilité d'écrire une note méthodologique

Pour fonctionner correctement, le composant enfant doit exporter la référence de l'élément graphique (_echart_ ou _maplibre_) à l'aide du hook `useDashboardElement`.

![Capture d'écran du composant Dashboard Element](./dashboardelement_screen.png)

## Propriétés

- `title` : Titre de l'élément
Expand All @@ -21,20 +25,22 @@ Pour fonctionner correctement, le composant enfant doit exporter la référence
- `exportPNG` : Booléen pour autoriser l'export en format image
- `exportData` : Booléen pour autoriser l'export de données
- `Licenses` : Les licences appliquées au graphique (exemple : ['CC','SA']). Par défaut ['CC','BY'].
- `header` : Booléen pour afficher/masquer l'entête (titre + menu contextuel) (_true_)
- `virtual` : Booléen pour que le composant serve uniquement de conteneur logique, sans élément visible (_false_)



## Export des données
## Données du graphique

Les données proposées au téléchargement à l'utilisateur sont à définir dans le hook `useExportData`. Si, pour un graphique, les données sont susceptibles de changer, on passera les variables qui déclanchent ce changement dans le paramètre `dependencies`.
L'utilisateur peut télécharger les données en _csv_, _ods_ ou _xlsx_.

Il est possible de désactiver l'export de données en passant le paramètre `exportData=False` au composant `DashboardElement`.
Si le tableau passé en paramètre est vide, le composant affichera alors une information "Pas de données disponibles".

Exemple :
```typescript
// La sélection d'une autre année change les données a télécharger
useChartData({data:data_chart, dependencies:[year]})
useChartData({data:data_chart}) //Si datachart est un tableau vide, le graphique est masqué et remplacé par un affichage "Pas de données".
```


Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 19 additions & 5 deletions src/components/DashboardElement/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,29 @@ export const useDashboardElement = ({ chartRef }: useDashboardElementProps) => {
* @param {Array<any>} [props.dependencies=[]] - Les dépendances suspeptibles de modifier les données
*/
export interface useChartDataProps {
data?: any,
data?: any[],
dependencies?:any[]
}
export const useChartData = ({data, dependencies=[]}:useChartDataProps) => {
const { setData, data:contextdata } = useContext(chartContext);

const { setData, setNodata } = useContext(chartContext);
useEffect(() => {
if (data && contextdata != data) {
setData(data);
}
data === undefined || data.length < 1 ? setNodata(true) : setNodata(false)
}, dependencies);
}


/**
* Hook permettant d'indiquer au DashboardElement que aucune données n'est disponible
* Il est aussi possible d'avoir ce comportement en passant un tableau vide dans useChartData
* Deprécié (utilisez useChartData avec un tableau vide)
* @param {boolean} noData - True si aucune donnée n'est disponible.
*/
export const useNoData = (noData:boolean) => {
const { setNodata } = useContext(chartContext);

useEffect(() => {
setNodata(noData);
},[noData]) // !

}
Loading

0 comments on commit 87a2b12

Please sign in to comment.