diff --git a/config.json b/config.json index 0ca3e4783..c29938774 100644 --- a/config.json +++ b/config.json @@ -8,6 +8,7 @@ "all-documents", "classif-by", "close-by", + "clustered-precomputed", "count-all", "count-by-fields", "cross-by", diff --git a/src/app/custom/precomputers/precomputers-catalog.json b/src/app/custom/precomputers/precomputers-catalog.json index 48db0298b..68ef0d101 100644 --- a/src/app/custom/precomputers/precomputers-catalog.json +++ b/src/app/custom/precomputers/precomputers-catalog.json @@ -13,5 +13,10 @@ "id": "post-v1-lda", "url": "https://data-computer.services.istex.fr/v1/lda", "doc": "https://gitbucket.inist.fr/tdm/web-services/tree/master/data-computer#v1%252flda" + }, + { + "id": "post-v1-lda-segment", + "url": "https://data-computer.services.istex.fr/v1/lda-segment", + "doc": "https://gitbucket.inist.fr/tdm/web-services/tree/master/data-computer#v1%252flda" } ] diff --git a/src/app/custom/routines/routines-precomputed-catalog.json b/src/app/custom/routines/routines-precomputed-catalog.json index 74d96a151..db1a28b7a 100644 --- a/src/app/custom/routines/routines-precomputed-catalog.json +++ b/src/app/custom/routines/routines-precomputed-catalog.json @@ -6,10 +6,10 @@ "recommendedWith": ["Network", "HeatMap"] }, { - "id": "r-lda", - "url": "/api/run/lda-precomputed/", - "doc": "https://user-doc.lodex.inist.fr/lodex-user-documentation/configuration/routines/lda", - "recommendedWith": [] + "id": "r-clustered-precomputed", + "url": "/api/run/clustered-precomputed/", + "doc": "https://user-doc.lodex.inist.fr/lodex-user-documentation/configuration/routines/clustered-precomputed", + "recommendedWith": ["ClusteredChart"] }, { "id": "r-tree-by", diff --git a/src/app/custom/translations.tsv b/src/app/custom/translations.tsv index 07d4fe2c6..d67643c73 100644 --- a/src/app/custom/translations.tsv +++ b/src/app/custom/translations.tsv @@ -1017,4 +1017,12 @@ vega_variable_list You can use these variables to set the size dynamically: Vous vega_variable_width Replace with the width of the chart container Remplacée par la largeur du conteneur du graphique vega_variable_height Replace with the height of the chart container Remplacée par la hauteur du conteneur du graphique theme Application theme Thème de l'application -"error_precomputed_data_empty" "Precomputed data is empty or unfinished. You might want to relaunch the action" "Les données précalculées sont vides ou non terminées. Vous pouvez relancer l'action" \ No newline at end of file +"error_precomputed_data_empty" "Precomputed data is empty or unfinished. You might want to relaunch the action" "Les données précalculées sont vides ou non terminées. Vous pouvez relancer l'action" +"pc_post-v1-lda-segment_title" "Lda-Segment" "Lda-Segment" +"pc_post-v1-lda-segment_description" "Create a «lda» field consisting of 5 topics from the set of documents. Each topic contains a «word» field, which is composed of a list of 10 words that are most characteristic of the topic, as well as a «weight» field that corresponds to the weight associated with the topic in the document. The text must be in English." "Créer à partir de l'ensemble des documents un champ «lda» constitué de 5 topics. Chaque topic contient un champ «word», qui est composé d'une liste de 10 mots qui sont les plus caractéristiques du topic, ainsi que d'un champ «weight» qui correspond au poids associé au sujet dans le document. Le texte doit être en anglais." +r-clustered-precomputed_title clustered-precomputed clustered-precomputed +r-clustered-precomputed_description Retrieve precomputed data from precomputes like 'Lda-Segment' and display it in the Clustered chart Récupère les données précalculées des précalculs comme 'Lda-Segment' et les affiche dans un Diagramme à barres groupées +formatClusteredChart Graph - Clustered chart Graphique - Diagramme à barres groupées +formatClusteredChartDescription Displays several bar charts depending on the category of the element and its weight Affiche plusieurs diagrammes à barres en fonction de la catégorie de l'élément et de son poids +format_x_axis_title Title of x axis Titre de l'axe x +format_y_axis_title Title of y axis Titre de l'axe y diff --git a/src/app/js/formats/formats.js b/src/app/js/formats/formats.js index 976453446..3586c8f3c 100644 --- a/src/app/js/formats/formats.js +++ b/src/app/js/formats/formats.js @@ -41,6 +41,7 @@ import istexCitation from './istexCitation'; import istexRefbibs from './istexRefbibs'; import bubblePlot from './vega-lite/component/bubble-plot'; import flowMap from './vega/component/flow-map'; +import clusteredChart from './vega-lite/component/clustered-chart'; export const FORMATS_CATALOG = [ { @@ -346,4 +347,11 @@ export const FORMATS_CATALOG = [ component: flowMap, type: 'chart', }, + { + name: 'formatClusteredChart', + description: 'formatClusteredChartDescription', + componentName: 'clusteredChart', + component: clusteredChart, + type: 'chart', + }, ]; diff --git a/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChart.js b/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChart.js new file mode 100644 index 000000000..19e0fad36 --- /dev/null +++ b/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChart.js @@ -0,0 +1,82 @@ +import { CustomActionVegaLite } from '../vega-lite-component'; +import { VEGA_LITE_DATA_INJECT_TYPE_A } from '../../../chartsUtils'; +import PropTypes from 'prop-types'; +import React, { useMemo } from 'react'; + +const ClusteredChart = ({ data, topic, params }) => { + const values = useMemo(() => { + return data.filter(value => value.source === topic); + }, [data, topic]); + + const spec = useMemo(() => { + const { colors, xTitle, yTitle } = params; + const specToReturn = { + $schema: 'https://vega.github.io/schema/vega-lite/v5.json', + config: { legend: { disable: true } }, + title: topic, + encoding: { + y: { field: 'target', type: 'nominal', sort: null }, + x: { field: 'weight', type: 'quantitative', sort: null }, + }, + layer: [ + { + mark: 'bar', + encoding: { + color: { + field: 'weight', + scale: { range: colors.split(' ') }, + }, + }, + }, + { + mark: { + type: 'text', + align: 'left', + baseline: 'middle', + dx: 3, + }, + encoding: { + text: { + field: 'weight', + type: 'quantitative', + format: '.4f', + }, + }, + }, + ], + width: 'container', + height: { step: 20 }, + }; + + if (xTitle && xTitle !== '') { + specToReturn.encoding.x.title = xTitle; + } + + if (yTitle && yTitle !== '') { + specToReturn.encoding.y.title = yTitle; + } + + return specToReturn; + }, [values, topic, params]); + return ( + + ); +}; + +ClusteredChart.propTypes = { + data: PropTypes.array.isRequired, + topic: PropTypes.string.isRequired, + params: PropTypes.shape({ + colors: PropTypes.string.isRequired, + xTitle: PropTypes.string, + yTitle: PropTypes.string, + }), +}; + +export default ClusteredChart; diff --git a/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChartAdmin.js b/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChartAdmin.js new file mode 100644 index 000000000..5620f25c3 --- /dev/null +++ b/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChartAdmin.js @@ -0,0 +1,73 @@ +import React from 'react'; +import translate from 'redux-polyglot/translate'; +import PropTypes from 'prop-types'; +import { Box, TextField } from '@mui/material'; + +import { polyglot as polyglotPropTypes } from '../../../../propTypes'; +import ColorPickerParamsAdmin from '../../../shared/ColorPickerParamsAdmin'; +import { MONOCHROMATIC_DEFAULT_COLORSET } from '../../../colorUtils'; +import updateAdminArgs from '../../../shared/updateAdminArgs'; + +export const defaultArgs = { + colors: MONOCHROMATIC_DEFAULT_COLORSET, +}; + +const ClusteredChartAdmin = props => { + const { args, p } = props; + const { colors, xTitle, yTitle } = args; + + const handleColors = colors => { + updateAdminArgs('colors', colors || defaultArgs.colors, props); + }; + + const handleXAxisTitle = event => { + updateAdminArgs('xTitle', event.target.value, props); + }; + + const handleYAxisTitle = event => { + updateAdminArgs('yTitle', event.target.value, props); + }; + + return ( + + + + + + ); +}; + +ClusteredChartAdmin.defaultProps = { + args: defaultArgs, +}; + +ClusteredChartAdmin.propTypes = { + args: PropTypes.shape({ + colors: PropTypes.string, + xTitle: PropTypes.string, + yTitle: PropTypes.string, + }), + onChange: PropTypes.func.isRequired, + p: polyglotPropTypes.isRequired, +}; + +export default translate(ClusteredChartAdmin); diff --git a/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChartView.js b/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChartView.js new file mode 100644 index 000000000..69c22ade3 --- /dev/null +++ b/src/app/js/formats/vega-lite/component/clustered-chart/ClusteredChartView.js @@ -0,0 +1,85 @@ +import React, { useMemo } from 'react'; +import compose from 'recompose/compose'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; +import { Grid, Paper } from '@mui/material'; + +import injectData from '../../../injectData'; +import { field as fieldPropTypes } from '../../../../propTypes'; +import ClusteredChart from './ClusteredChart'; + +const ClusteredChartView = ({ data, colors, xTitle, yTitle }) => { + const { values } = data; + + const topics = useMemo(() => { + return _.chain(values) + .map(value => value.source) + .uniq() + .sort((a, b) => + a.localeCompare(b, 'fr', { + sensitivity: 'accent', + numeric: true, + usage: 'sort', + ignorePunctuation: true, + }), + ) + .value(); + }, [values]); + + return ( +
+ + {topics.map(topic => ( + + + + + + ))} + +
+ ); +}; + +const mapStateToProps = (state, { formatData }) => { + if (!formatData) { + return { + data: { + values: [], + }, + }; + } + return { + data: { + values: formatData, + }, + }; +}; + +ClusteredChartView.propTypes = { + field: fieldPropTypes.isRequired, + resource: PropTypes.object.isRequired, + data: PropTypes.any, + colors: PropTypes.string.isRequired, + xTitle: PropTypes.string, + yTitle: PropTypes.string, +}; + +export default compose( + injectData(), + connect(mapStateToProps), +)(ClusteredChartView); diff --git a/src/app/js/formats/vega-lite/component/clustered-chart/index.js b/src/app/js/formats/vega-lite/component/clustered-chart/index.js new file mode 100644 index 000000000..f99e9e27d --- /dev/null +++ b/src/app/js/formats/vega-lite/component/clustered-chart/index.js @@ -0,0 +1,12 @@ +import DefaultFormat from '../../../DefaultFormat'; +import Icon from '../../VegaLiteIcon'; +import Component from './ClusteredChartView'; +import AdminComponent, { defaultArgs } from './ClusteredChartAdmin'; + +export default { + ...DefaultFormat, + Component, + AdminComponent, + defaultArgs, + Icon, +}; diff --git a/workers/routines/clustered-precomputed.ini b/workers/routines/clustered-precomputed.ini new file mode 100644 index 000000000..70fefe7c5 --- /dev/null +++ b/workers/routines/clustered-precomputed.ini @@ -0,0 +1,57 @@ +; suppress inspection "DuplicateSectionInFile" for whole file +; suppress inspection "DuplicateKeyInSection" for whole file +; Export and format clustered precompute data into an valid format for vega-lite +prepend = delegate?file=../worker.ini +mimeType = application/json +label = clustered-precomputed + +[use] +plugin = basics +plugin = lodex +plugin = analytics + +[buildContext] +connectionStringURI = get('connectionStringURI') +precomputedName = get('precomputedName') + +[env] +path = connectionStringURI +value = get('connectionStringURI') + +path = precomputedName +value = get('precomputedName') + +[LodexRunQuery] + +[LodexFilterPrecomputed] + +[assign] +path = id +value = fix(`["${self.source}","${self.target}"]`) + +path = value +value = get('weight') + +[reducing] + +[assign] +path = source +value = fix(JSON.parse(self.id)[1]) + +path = target +value = fix(JSON.parse(self.id)[0]) + +path = weight +value = fix(self.value[0]) + +[keep] +path = source +path = target +path = weight + +[sort] +path = weight +reverse = true + +[LodexOutput] +indent = true