Skip to content

Commit a0048b3

Browse files
committed
feat(ep/msca): ajout des graphs d'évolution
1 parent d257452 commit a0048b3

16 files changed

Lines changed: 1114 additions & 6 deletions

File tree

‎client/src/boards/european-projects/colors.scss‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@
4040
--erc-avg-top10-color: #d75521; // moyenne top 10
4141
--erc-avg-all-color: #1f8d49; // moyenne tous pays
4242

43+
// MSCA domain colors
44+
--msca-panel-che-color: var(--scale-1-color);
45+
--msca-panel-eco-color: var(--scale-2-color);
46+
--msca-panel-eng-color: var(--scale-3-color);
47+
--msca-panel-env-color: var(--scale-4-color);
48+
--msca-panel-lif-color: var(--scale-5-color);
49+
--msca-panel-mat-color: var(--scale-7-color);
50+
--msca-panel-phy-color: var(--scale-8-color);
51+
--msca-panel-soc-color: var(--scale-9-color);
52+
53+
// MSCA destination/grant type colors
54+
--msca-destination-cofund-color: var(--scale-1-color);
55+
--msca-destination-dn-color: var(--scale-2-color);
56+
--msca-destination-pf-color: var(--scale-3-color);
57+
--msca-destination-se-color: var(--scale-4-color);
58+
--msca-destination-citizens-color: var(--scale-5-color);
59+
4360

4461
--evaluated-project-color: #4dd4db; // projets évalués (plus clair)
4562
--successful-project-color: #6dd897; // projets lauréats (vert lumineux)

‎client/src/boards/european-projects/components/chart-ep.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export function CreateChartOptions(type: NonNullable<HighchartsInstance.Options[
1313
legend: { enabled: false },
1414
exporting: { enabled: false },
1515
credits: { enabled: false },
16+
accessibility: { enabled: true },
1617
};
1718

1819
if (Array.isArray(options.xAxis) && options.xAxis.length > 0) {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { Title } from "@dataesr/dsfr-plus";
3+
4+
import { getData } from "./query";
5+
import Options from "./options";
6+
import { useGetParams, processEvolutionData, renderDataTable } from "./utils";
7+
import { getI18nLabel } from "../../../../../../utils";
8+
import { EPChartsSources } from "../../../../config.js";
9+
10+
import ChartWrapper from "../../../../../../components/chart-wrapper";
11+
import DefaultSkeleton from "../../../../../../components/charts-skeletons/default";
12+
13+
import i18n from "../../i18n.json";
14+
15+
interface EvolutionChartsProps {
16+
countryAdjective?: string;
17+
currentLang?: string;
18+
}
19+
20+
const configWeight = {
21+
id: "mscaEvolutionWeight",
22+
idQuery: "mscaEvolution",
23+
comment: {
24+
fr: (
25+
<>
26+
Ce graphique présente l'évolution du poids des projets lauréats du pays sélectionné par rapport au total européen, ventilé par type de
27+
financement MSCA.
28+
</>
29+
),
30+
en: (
31+
<>
32+
This chart shows the evolution of the share of successful projects from the selected country compared to the European total, broken down by
33+
MSCA funding type.
34+
</>
35+
),
36+
},
37+
readingKey: {
38+
fr: <>Un poids de 10% signifie que le pays représente 10% des porteurs de projets lauréats au niveau européen pour ce type de financement.</>,
39+
en: <>A share of 10% means that the country represents 10% of successful project PIs at the European level for this funding type.</>,
40+
},
41+
sources: EPChartsSources,
42+
integrationURL: "/european-projects/components/pages/msca/charts/evolution-weight",
43+
};
44+
45+
const configSuccessRate = {
46+
id: "mscaEvolutionSuccessRate",
47+
idQuery: "mscaEvolution",
48+
comment: {
49+
fr: (
50+
<>
51+
Ce graphique présente l'évolution du taux de succès des projets du pays sélectionné, calculé comme le ratio entre projets lauréats et projets
52+
évalués, ventilé par type de financement MSCA.
53+
</>
54+
),
55+
en: (
56+
<>
57+
This chart shows the evolution of the success rate of projects from the selected country, calculated as the ratio of successful to evaluated
58+
projects, broken down by MSCA funding type.
59+
</>
60+
),
61+
},
62+
readingKey: {
63+
fr: <>Un taux de succès de 20% signifie que 20% des projets soumis et évalués ont été retenus pour ce type de financement.</>,
64+
en: <>A success rate of 20% means that 20% of submitted and evaluated projects were selected for this funding type.</>,
65+
},
66+
sources: EPChartsSources,
67+
integrationURL: "/european-projects/components/pages/msca/charts/evolution-success-rate",
68+
};
69+
70+
export default function EvolutionFundingsCharts({ countryAdjective = "français", currentLang: propLang }: EvolutionChartsProps) {
71+
const { params, currentLang: urlLang } = useGetParams();
72+
const currentLang = propLang || urlLang;
73+
74+
const { data, isLoading } = useQuery({
75+
queryKey: ["mscaEvolution", params],
76+
queryFn: () => getData(params),
77+
enabled: params !== "",
78+
});
79+
80+
if (isLoading || !data) return <DefaultSkeleton />;
81+
82+
const processedData = processEvolutionData(data, currentLang);
83+
84+
if (processedData.years.length === 0) {
85+
return (
86+
<div className="fr-alert fr-alert--info">
87+
<p>{getI18nLabel(i18n, "evolution.noDataAvailable", currentLang)}</p>
88+
</div>
89+
);
90+
}
91+
92+
const weightOptions = Options({
93+
processedData,
94+
chartType: "weight",
95+
countryAdjective,
96+
currentLang,
97+
});
98+
99+
const successRateOptions = Options({
100+
processedData,
101+
chartType: "successRate",
102+
countryAdjective,
103+
currentLang,
104+
});
105+
106+
return (
107+
<div className="evolution-charts">
108+
<Title as="h2" look="h5">
109+
{getI18nLabel(i18n, "evolution.sectionTitle", currentLang)} {countryAdjective} {getI18nLabel(i18n, "evolution.byFundingType", currentLang)}
110+
</Title>
111+
112+
{/* Graphique 1: Poids des projets */}
113+
<div className="fr-mb-4w chart-container--default">
114+
<ChartWrapper config={configWeight} options={weightOptions} renderData={() => renderDataTable(processedData, "weight", currentLang)} />
115+
</div>
116+
117+
{/* Graphique 2: Taux de succès */}
118+
<div className="fr-mb-4w chart-container--default">
119+
<ChartWrapper
120+
config={configSuccessRate}
121+
options={successRateOptions}
122+
renderData={() => renderDataTable(processedData, "successRate", currentLang)}
123+
/>
124+
</div>
125+
</div>
126+
);
127+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import type HighchartsInstance from "highcharts/es-modules/masters/highcharts.src.js";
2+
import { CreateChartOptions } from "../../../../components/chart-ep";
3+
import type { ProcessedEvolutionData } from "./utils";
4+
5+
interface OptionsParams {
6+
processedData: ProcessedEvolutionData;
7+
chartType: "weight" | "successRate";
8+
countryAdjective?: string;
9+
currentLang?: string;
10+
startYear?: string;
11+
}
12+
13+
export default function Options({
14+
processedData,
15+
chartType,
16+
countryAdjective = "français",
17+
currentLang = "fr",
18+
startYear,
19+
}: OptionsParams): HighchartsInstance.Options | null {
20+
if (!processedData || processedData.years.length === 0) return null;
21+
22+
const series = chartType === "weight" ? processedData.weightSeries : processedData.successRateSeries;
23+
24+
// Déterminer l'année de départ pour le titre
25+
const firstYear = startYear || processedData.years[0] || "2014";
26+
27+
// Titre dynamique selon le type de graphique
28+
const titleText =
29+
chartType === "weight"
30+
? currentLang === "fr"
31+
? `Poids des projets ${countryAdjective} lauréats depuis ${firstYear}`
32+
: `Share of ${countryAdjective} successful projects since ${firstYear}`
33+
: currentLang === "fr"
34+
? `Taux de succès sur les projets ${countryAdjective} depuis ${firstYear}`
35+
: `Success rate on ${countryAdjective} projects since ${firstYear}`;
36+
37+
const yAxisLabel = chartType === "weight" ? "%" : "%";
38+
39+
const highchartsSeries: HighchartsInstance.SeriesOptionsType[] = series.map((s) => ({
40+
type: "line" as const,
41+
name: s.name,
42+
color: s.color,
43+
data: s.data,
44+
marker: {
45+
enabled: true,
46+
radius: 4,
47+
},
48+
dataLabels: {
49+
enabled: true,
50+
format: "{y}%",
51+
style: {
52+
fontSize: "10px",
53+
fontWeight: "normal",
54+
textOutline: "1px white",
55+
},
56+
y: -8,
57+
},
58+
connectNulls: false,
59+
}));
60+
61+
const newOptions: HighchartsInstance.Options = {
62+
chart: {
63+
height: 400,
64+
},
65+
title: {
66+
text: titleText,
67+
style: {
68+
fontSize: "14px",
69+
fontWeight: "600",
70+
},
71+
},
72+
xAxis: {
73+
categories: processedData.years,
74+
title: {
75+
text: "",
76+
},
77+
labels: {
78+
style: {
79+
fontSize: "12px",
80+
},
81+
},
82+
},
83+
yAxis: {
84+
title: {
85+
text: "",
86+
},
87+
labels: {
88+
format: `{value}${yAxisLabel}`,
89+
style: {
90+
fontSize: "12px",
91+
},
92+
},
93+
min: 0,
94+
gridLineWidth: 0.5,
95+
gridLineColor: "#e0e0e0",
96+
},
97+
tooltip: {
98+
shared: true,
99+
useHTML: true,
100+
headerFormat: "<b>{point.key}</b><br/>",
101+
pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: <b>{point.y}%</b><br/>',
102+
},
103+
legend: {
104+
enabled: true,
105+
layout: "horizontal",
106+
align: "center",
107+
verticalAlign: "bottom",
108+
itemStyle: {
109+
fontSize: "11px",
110+
},
111+
},
112+
plotOptions: {
113+
line: {
114+
lineWidth: 2,
115+
states: {
116+
hover: {
117+
lineWidth: 3,
118+
},
119+
},
120+
},
121+
},
122+
series: highchartsSeries,
123+
};
124+
125+
return CreateChartOptions("line", newOptions);
126+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const { VITE_APP_SERVER_URL } = import.meta.env;
2+
3+
export interface EvolutionItem {
4+
call_year: string;
5+
destination_code: string;
6+
destination_name_en: string;
7+
total_funding_project: number;
8+
total_involved: number;
9+
// total_pi: number;
10+
}
11+
12+
export interface EvolutionData {
13+
country: {
14+
successful: EvolutionItem[];
15+
evaluated: EvolutionItem[];
16+
};
17+
total: {
18+
successful: EvolutionItem[];
19+
evaluated: EvolutionItem[];
20+
};
21+
}
22+
23+
export async function getData(params: string): Promise<EvolutionData> {
24+
if (params === "") {
25+
return {
26+
country: { successful: [], evaluated: [] },
27+
total: { successful: [], evaluated: [] },
28+
};
29+
}
30+
31+
return fetch(`${VITE_APP_SERVER_URL}/european-projects/msca/evolution?${params}`).then((response) => response.json());
32+
}

0 commit comments

Comments
 (0)