From 749c421364b192d375f94e9132ad77f4695aaec6 Mon Sep 17 00:00:00 2001
From: victor barbier <victor.barbier@recherche.gouv.fr>
Date: Fri, 13 Oct 2023 16:11:35 +0200
Subject: [PATCH] feat(graph): Add absolute and relative toggle for
 retractations by publisher

---
 .../others/retractions/chart-by-publisher.js  | 15 ++++++++-
 .../retractions/get-data-by-publisher.js      | 31 +++++++++--------
 src/translations/en.json                      |  4 +++
 src/translations/fr.json                      |  4 +++
 src/utils/chartFetchOptions.js                |  1 +
 src/utils/chartOptions.js                     | 16 ++++++---
 src/utils/helpers.js                          | 33 +++++++++++++++++++
 7 files changed, 84 insertions(+), 20 deletions(-)

diff --git a/src/components/Charts/publications/others/retractions/chart-by-publisher.js b/src/components/Charts/publications/others/retractions/chart-by-publisher.js
index 0456bd9b..25754555 100644
--- a/src/components/Charts/publications/others/retractions/chart-by-publisher.js
+++ b/src/components/Charts/publications/others/retractions/chart-by-publisher.js
@@ -1,3 +1,4 @@
+import { Toggle } from '@dataesr/react-dsfr';
 import Highcharts from 'highcharts';
 import HCExportingData from 'highcharts/modules/export-data';
 import HCExporting from 'highcharts/modules/exporting';
@@ -22,15 +23,22 @@ const Chart = ({ domain, hasComments, hasFooter, id }) => {
   const chartRef = useRef();
   const intl = useIntl();
   const [chartComments, setChartComments] = useState('');
+  const [isPercent, setPercent] = useState(false);
   const { observationSnaps } = useGlobals();
-  const { data, isError, isLoading } = useGetData(observationSnaps, domain);
+  const { data, isError, isLoading } = useGetData(
+    observationSnaps,
+    domain,
+    isPercent,
+  );
   const { categories, dataGraph } = data;
   const idWithDomain = withDomain(id, domain);
+
   const optionsGraph = chartOptions[id].getOptions(
     idWithDomain,
     intl,
     categories,
     dataGraph,
+    isPercent,
   );
 
   useEffect(() => {
@@ -47,6 +55,11 @@ const Chart = ({ domain, hasComments, hasFooter, id }) => {
       isError={isError}
       isLoading={isLoading || !dataGraph}
     >
+      <Toggle
+        checked={isPercent}
+        label={intl.formatMessage({ id: 'app.proportion' })}
+        onChange={() => setPercent(!isPercent)}
+      />
       <HighchartsReact
         highcharts={Highcharts}
         id={idWithDomain}
diff --git a/src/components/Charts/publications/others/retractions/get-data-by-publisher.js b/src/components/Charts/publications/others/retractions/get-data-by-publisher.js
index 23dc2c50..984f0c63 100644
--- a/src/components/Charts/publications/others/retractions/get-data-by-publisher.js
+++ b/src/components/Charts/publications/others/retractions/get-data-by-publisher.js
@@ -18,27 +18,30 @@ function useGetData(observationSnaps, domain = '', isPercent = false) {
       parameters: [lastObservationSnap],
       objectType: ['publications'],
     });
+    const numberOfRetracted = (item) => item.by_retraction.buckets.find((i2) => i2.key === 1)?.doc_count ?? 0;
     const response = await Axios.post(ES_API_URL, query, HEADERS);
-    const buckets = response?.data?.aggregations?.by_publisher?.buckets?.sort(
-      (a, b) => b.doc_count - a.doc_count,
-    );
+    const buckets = response?.data?.aggregations?.by_publisher?.buckets
+      ?.sort((a, b) => numberOfRetracted(b) - numberOfRetracted(a))
+      .slice(0, 20);
     const categories = buckets.map((item) => item.key);
-    const dataGraph = [
-      {
-        data: buckets.map(
-          (item) => ((item.by_retraction.buckets.find((i2) => i2.key === 1)
-            ?.doc_count ?? 0)
-              / item.doc_count)
-            * 100,
-        ),
-      },
-    ];
+    const dataGraph = {
+      data: buckets.map((item, catIndex) => ({
+        y_tot: item.doc_count ?? 0,
+        y_abs: numberOfRetracted(item),
+        y_rel: (numberOfRetracted(item) / item.doc_count) * 10000,
+        y: isPercent
+          ? (numberOfRetracted(item) / item.doc_count) * 10000
+          : numberOfRetracted(item),
+        x: catIndex,
+        publisher: categories[catIndex],
+      })),
+    };
 
     return {
       categories,
       dataGraph,
     };
-  }, [domain, lastObservationSnap]);
+  }, [domain, isPercent, lastObservationSnap]);
 
   useEffect(() => {
     async function getData() {
diff --git a/src/translations/en.json b/src/translations/en.json
index 5a539c66..b2bdd93d 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -906,6 +906,7 @@
   "app.observational.end": "End of<br>the observational<br>study",
   "app.observationals": "observational studies",
   "app.project": "The project",
+  "app.proportion": "Proportion",
   "app.promoteurs.impact.chart-classement-pays.tooltip": "<b>Trials completed, in collaboration with {point.country}</b><br>{point.y:.2f}% share scientific results or publication ({point.y_abs} / {point.y_tot})",
   "app.promoteurs.impact.chart-repartition.tooltip": "<b>Trials completed in {point.year}, {series.name}</b><br>Trials contribution with {point.name}: {point.y:.2f}% of the trials ({point.y_abs} / {point.y_tot})",
   "app.publi.affiliations": "Affiliations",
@@ -943,7 +944,10 @@
   "app.publi.navigation.langues": "The languages of publication",
   "app.publi.navigation.voies": "The opening routes",
   "app.publi.nb-publications": "Number of publications",
+  "app.publi.nb-publications-retracted": "Number of retracted publications",
+  "app.national-publi.others.retractions.chart-by-publisher.tooltip": "<b>{point.publisher}</b><br>• Share of retracted publications :<br>{point.y_rel:.1f} ‱ ({point.y_abs} / {point.y_tot})",
   "app.publi.percent-publications-retracted": "Percent of retracted publications",
+  "app.publi.pertenthousand-publications-retracted": "Share of retracted publications (per ten thousand)",
   "app.publi.part-publications-archive": "Share of publications in open access on archive",
   "app.publi.percentage-publi": "Proportion of publications",
   "app.publi.percentage-publi-bealls": "Share of publications whose journal or<br>publisher is in the Beall's list",
diff --git a/src/translations/fr.json b/src/translations/fr.json
index e1412d8c..7b589e2b 100644
--- a/src/translations/fr.json
+++ b/src/translations/fr.json
@@ -1068,6 +1068,7 @@
   "app.observational.end": "Fin de l'étude<br>observationnelle",
   "app.observationals": "études observationnelles",
   "app.project": "Le projet",
+  "app.proportion": "Proportion",
   "app.promoteurs.impact.chart-classement-pays.tooltip": "<b>Essais terminés, en collaboration avec {point.country}</b><br>{point.y:.2f} % partagent des résultats ou une publication scientifique ({point.y_abs} / {point.y_tot})",
   "app.promoteurs.impact.chart-repartition.tooltip": "<b>Essais terminés en {point.year}, {series.name}</b><br>Contribution des essais avec {point.name} : {point.y:.2f} % des essais ({point.y_abs} / {point.y_tot})",
   "app.publi.affiliations": "Affiliations",
@@ -1105,7 +1106,10 @@
   "app.publi.navigation.langues": "Les langues de publication",
   "app.publi.navigation.voies": "Les voies d'ouverture",
   "app.publi.nb-publications": "Nombre de publications",
+  "app.publi.nb-publications-retracted": "Nombre de publications retirées",
+  "app.national-publi.others.retractions.chart-by-publisher.tooltip": "<b>{point.publisher}</b><br>• Part de publications retirées :<br>{point.y_rel:.1f} ‱ ({point.y_abs} / {point.y_tot})",
   "app.publi.percent-publications-retracted": "Pourcentage de publications retirées",
+  "app.publi.pertenthousand-publications-retracted": "Part des publications retirées (pour dix mille)",
   "app.publi.part-publications-archive": "Part des publications en accès ouvert sur archive",
   "app.publi.percentage-publi": "Proportion de publications",
   "app.publi.percentage-publi-bealls": "Proportion de publications dont la revue ou<br>l'éditeur est dans la liste de Beall",
diff --git a/src/utils/chartFetchOptions.js b/src/utils/chartFetchOptions.js
index 6a4f8453..cb2d1fd3 100644
--- a/src/utils/chartFetchOptions.js
+++ b/src/utils/chartFetchOptions.js
@@ -2148,6 +2148,7 @@ export default function getFetchOptions({
         by_publisher: {
           terms: {
             field: 'publisher_normalized.keyword',
+            size: 50,
           },
           aggs: {
             by_retraction: {
diff --git a/src/utils/chartOptions.js b/src/utils/chartOptions.js
index 9e826c47..4729c53b 100644
--- a/src/utils/chartOptions.js
+++ b/src/utils/chartOptions.js
@@ -3,6 +3,7 @@ import {
   cleanNumber,
   getCSSValue,
   getPercentageYAxis,
+  getPertenthousandYAxis,
   getSource,
   getURLSearchParams,
   withtStudyType,
@@ -4195,16 +4196,18 @@ export const chartOptions = {
     },
   },
   'publi.others.retractions.chart-by-publisher': {
-    getOptions: (id, intl, categories, data, dataTitle) => {
+    getOptions: (id, intl, categories, graph, isPercent, dataTitle) => {
       const options = getGraphOptions({ id, intl, dataTitle });
       options.chart.type = 'column';
       options.xAxis = {
         title: { text: intl.formatMessage({ id: 'app.publishers' }) },
         categories,
       };
-      options.yAxis = getPercentageYAxis();
+      options.yAxis = getPertenthousandYAxis(false, null, !isPercent);
       options.yAxis.title.text = intl.formatMessage({
-        id: 'app.publi.percent-publications-retracted',
+        id: isPercent
+          ? 'app.publi.pertenthousand-publications-retracted'
+          : 'app.publi.nb-publications-retracted',
       });
       options.legend.enabled = false;
       options.plotOptions = {
@@ -4212,12 +4215,15 @@ export const chartOptions = {
           dataLabels: {
             enabled: true,
             formatter() {
-              return this.y === 0 ? '' : this.y.toFixed(3).concat(' %');
+              if (isPercent) {
+                return this.y === 0 ? '' : this.y.toFixed(1).concat(' ‱');
+              }
+              return this.y === 0 ? '' : this.y.toFixed();
             },
           },
         },
       };
-      options.series = data;
+      options.series = [graph];
       options.exporting.chartOptions.legend.enabled = false;
       return options;
     },
diff --git a/src/utils/helpers.js b/src/utils/helpers.js
index 07b9f0d5..16f1518b 100644
--- a/src/utils/helpers.js
+++ b/src/utils/helpers.js
@@ -125,6 +125,39 @@ export function getPercentageYAxis(
   return axis;
 }
 
+export function getPertenthousandYAxis(
+  showTotal = true,
+  max = null,
+  absolute = false,
+  precision = 0,
+) {
+  const suffix = absolute ? '' : ' ‱';
+  const axis = {
+    title: { text: '' },
+    stackLabels: {
+      enabled: true,
+      formatter() {
+        return showTotal && this.total
+          ? this.total.toFixed(precision).concat(suffix)
+          : '';
+      },
+      style: {
+        fontWeight: 'bold',
+        fontSize: '15px',
+      },
+    },
+    labels: {
+      formatter() {
+        return this.axis.defaultLabelFormatter.call(this).concat(suffix);
+      },
+    },
+  };
+  if (max) {
+    axis.max = max;
+  }
+  return axis;
+}
+
 /**
  *
  * @param keys