From 6e0187acca92e244c36e73e0ba10f9df2bbb481e Mon Sep 17 00:00:00 2001 From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:17:21 +0100 Subject: [PATCH 1/5] feat: add an options to open a chart in fullscreen --- .../utils/components/ZoomableFormat.js | 70 +++++++++++++++++++ .../vega-component/VegaComponent.js | 23 +++--- .../vega-lite-component/VegaLiteComponent.js | 21 +++--- 3 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 src/app/js/formats/utils/components/ZoomableFormat.js diff --git a/src/app/js/formats/utils/components/ZoomableFormat.js b/src/app/js/formats/utils/components/ZoomableFormat.js new file mode 100644 index 000000000..3a0778ce0 --- /dev/null +++ b/src/app/js/formats/utils/components/ZoomableFormat.js @@ -0,0 +1,70 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import IconButton from '@mui/material/IconButton'; +import CloseIcon from '@mui/icons-material/Close'; +import DialogContent from '@mui/material/DialogContent'; +import OpenInFullIcon from '@mui/icons-material/OpenInFull'; + +const ZoomableFormat = ({ children }) => { + const [open, setOpen] = useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + const handleClose = () => { + setOpen(false); + }; + + return ( + <> +
+ {children} + +
+ + + + + + +
+ {children} +
+
+
+ + ); +}; + +ZoomableFormat.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default ZoomableFormat; diff --git a/src/app/js/formats/utils/components/vega-component/VegaComponent.js b/src/app/js/formats/utils/components/vega-component/VegaComponent.js index 808277b92..9afc5b3a4 100644 --- a/src/app/js/formats/utils/components/vega-component/VegaComponent.js +++ b/src/app/js/formats/utils/components/vega-component/VegaComponent.js @@ -9,6 +9,7 @@ import { VEGA_DATA_INJECT_TYPE_B, } from '../../chartsUtils'; import { ASPECT_RATIO_16_9, ASPECT_RATIOS } from '../../aspectRatio'; +import ZoomableFormat from '../ZoomableFormat'; /** * small component use to handle vega lite display @@ -43,7 +44,7 @@ function CustomActionVega(props) { switch (props.injectType) { case VEGA_DATA_INJECT_TYPE_A: - spec.data.forEach(e => { + spec.data.forEach((e) => { if (e.name === 'table') { e.values = props.data.values; } @@ -54,14 +55,14 @@ function CustomActionVega(props) { let data = { values: [], }; - props.data.values.forEach(e => { + props.data.values.forEach((e) => { data.values.push({ origin: e.source, destination: e.target, count: e.weight, }); }); - spec.data.forEach(e => { + spec.data.forEach((e) => { if (e.name === 'routes' || e.name === 'link_data') { e.values = data.values; } @@ -73,12 +74,14 @@ function CustomActionVega(props) { } return ( - + + + ); } @@ -103,7 +106,7 @@ CustomActionVega.propTypes = { * @param state application state * @returns {{user: *}} user state */ -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { user: state.user, }; diff --git a/src/app/js/formats/utils/components/vega-lite-component/VegaLiteComponent.js b/src/app/js/formats/utils/components/vega-lite-component/VegaLiteComponent.js index 778578ab5..c99133bc1 100644 --- a/src/app/js/formats/utils/components/vega-lite-component/VegaLiteComponent.js +++ b/src/app/js/formats/utils/components/vega-lite-component/VegaLiteComponent.js @@ -10,6 +10,7 @@ import { VEGA_LITE_DATA_INJECT_TYPE_C, } from '../../chartsUtils'; import { ASPECT_RATIO_16_9, ASPECT_RATIOS } from '../../aspectRatio'; +import ZoomableFormat from '../ZoomableFormat'; /** * small component use to handle vega lite display @@ -47,14 +48,14 @@ function CustomActionVegaLite({ aspectRatio, user, spec, data, injectType }) { specWithData.data = data; break; case VEGA_LITE_DATA_INJECT_TYPE_B: - specWithData.transform.forEach(e => { + specWithData.transform.forEach((e) => { if (e.lookup === 'id') { e.from.data.values = data.values; } }); break; case VEGA_LITE_DATA_INJECT_TYPE_C: - specWithData.transform.forEach(e => { + specWithData.transform.forEach((e) => { if (e.lookup === 'properties.HASC_2') { e.from.data.values = data.values; } @@ -65,12 +66,14 @@ function CustomActionVegaLite({ aspectRatio, user, spec, data, injectType }) { } return ( - + + + ); } @@ -95,7 +98,7 @@ CustomActionVegaLite.propTypes = { * @param state application state * @returns {{user: *}} user state */ -const mapStateToProps = state => { +const mapStateToProps = (state) => { return { user: state.user, }; From a640ce0bcf25fbd82f693d82b1410de6722b5dca Mon Sep 17 00:00:00 2001 From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:28:25 +0100 Subject: [PATCH 2/5] feat: add a tooltip --- src/app/custom/translations.tsv | 1 + .../utils/components/ZoomableFormat.js | 31 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/app/custom/translations.tsv b/src/app/custom/translations.tsv index f74d0346c..3051880ab 100644 --- a/src/app/custom/translations.tsv +++ b/src/app/custom/translations.tsv @@ -1072,3 +1072,4 @@ "import" "Import" "Importer" "import_successful" "Import completed successfully" "Import réalisé avec succès" "import_error" "Error during import" "Erreur lors de l'import" +"fullscreen" "View in full screen" "Mettre en plein écran" diff --git a/src/app/js/formats/utils/components/ZoomableFormat.js b/src/app/js/formats/utils/components/ZoomableFormat.js index 3a0778ce0..92d31669d 100644 --- a/src/app/js/formats/utils/components/ZoomableFormat.js +++ b/src/app/js/formats/utils/components/ZoomableFormat.js @@ -1,13 +1,15 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; import IconButton from '@mui/material/IconButton'; import CloseIcon from '@mui/icons-material/Close'; import DialogContent from '@mui/material/DialogContent'; import OpenInFullIcon from '@mui/icons-material/OpenInFull'; +import Tooltip from '@mui/material/Tooltip'; +import translate from 'redux-polyglot/translate'; +import { polyglot as polyglotPropTypes } from '../../../propTypes'; -const ZoomableFormat = ({ children }) => { +const ZoomableFormat = ({ children, p }) => { const [open, setOpen] = useState(false); const handleClickOpen = () => { @@ -21,16 +23,18 @@ const ZoomableFormat = ({ children }) => { <>
{children} - + + + + +
@@ -65,6 +69,7 @@ const ZoomableFormat = ({ children }) => { ZoomableFormat.propTypes = { children: PropTypes.node.isRequired, + p: polyglotPropTypes.isRequired, }; -export default ZoomableFormat; +export default translate(ZoomableFormat); From c9352d033f68b38303a2a73d34b625aa9858539f Mon Sep 17 00:00:00 2001 From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:39:19 +0100 Subject: [PATCH 3/5] feat: add more format compatible with fullscreen mode --- .../formats/chart/bubbleChart/BubbleView.js | 35 ++--- .../js/formats/chart/network/NetworkView.js | 63 +++++---- .../formats/chart/streamgraph/Streamgraph.js | 129 ++++++++---------- .../utils/components/ZoomableFormat.js | 4 +- 4 files changed, 113 insertions(+), 118 deletions(-) diff --git a/src/app/js/formats/chart/bubbleChart/BubbleView.js b/src/app/js/formats/chart/bubbleChart/BubbleView.js index 5209add4c..d4f5f268c 100644 --- a/src/app/js/formats/chart/bubbleChart/BubbleView.js +++ b/src/app/js/formats/chart/bubbleChart/BubbleView.js @@ -8,6 +8,7 @@ import memoize from 'lodash/memoize'; import injectData from '../../injectData'; import Bubble from './Bubble'; import { getColor } from '../../utils/colorUtils'; +import ZoomableFormat from '../../utils/components/ZoomableFormat'; const styles = { container: memoize(({ diameter }) => ({ @@ -19,19 +20,21 @@ const styles = { }; export const BubbleView = ({ data, diameter, colorSet }) => ( -
- {data.map(({ data: { _id: key }, r, x, y, value }, index) => ( - - ))} -
+ +
+ {data.map(({ data: { _id: key }, r, x, y, value }, index) => ( + + ))} +
+
); BubbleView.propTypes = { @@ -52,12 +55,10 @@ const mapStateToProps = (_, { formatData, diameter: stringDiameter }) => { }; } - const packingFunction = pack() - .size([diameter, diameter]) - .padding(5); + const packingFunction = pack().size([diameter, diameter]).padding(5); const root = hierarchy({ name: 'root', children: formatData }) - .sum(d => d.value) + .sum((d) => d.value) .sort((a, b) => b.value - a.value); const data = packingFunction(root).leaves(); diff --git a/src/app/js/formats/chart/network/NetworkView.js b/src/app/js/formats/chart/network/NetworkView.js index 0fccd512e..b6a89ae17 100644 --- a/src/app/js/formats/chart/network/NetworkView.js +++ b/src/app/js/formats/chart/network/NetworkView.js @@ -15,6 +15,7 @@ import { scaleLinear } from 'd3-scale'; import injectData from '../../injectData'; import MouseIcon from '../../utils/components/MouseIcon'; +import ZoomableFormat from '../../utils/components/ZoomableFormat'; const simulationOptions = { animate: true, @@ -39,7 +40,7 @@ const zoomOptions = { minScale: 0.25, maxScale: 16 }; class Network extends Component { mouseIcon = ''; - createSimulation = options => { + createSimulation = (options) => { // extends react-vis-force createSimulation to get a reference on the simulation this.simulation = createSimulation(options); @@ -63,34 +64,36 @@ class Network extends Component { const { nodes, links, colorSet } = this.props; return ( -
- - {nodes.map(node => ( - - ))} - {links.map((link, index) => ( - - ))} - - -
{this.mouseIcon}
-
+ +
+ + {nodes.map((node) => ( + + ))} + {links.map((link, index) => ( + + ))} + + +
{this.mouseIcon}
+
+
); } } @@ -161,7 +164,7 @@ const mapStateToProps = (state, { formatData }) => { .range([1, 20]); return { - nodes: nodes.map(node => ({ + nodes: nodes.map((node) => ({ ...node, radius: nodeScale(node.radius), })), diff --git a/src/app/js/formats/chart/streamgraph/Streamgraph.js b/src/app/js/formats/chart/streamgraph/Streamgraph.js index 87cec9e5e..ca0d2596b 100644 --- a/src/app/js/formats/chart/streamgraph/Streamgraph.js +++ b/src/app/js/formats/chart/streamgraph/Streamgraph.js @@ -22,6 +22,7 @@ import MouseIcon from '../../utils/components/MouseIcon'; import CenterIcon from '../../utils/components/CenterIcon'; import stylesToClassname from '../../../lib/stylesToClassName'; +import ZoomableFormat from '../../utils/components/ZoomableFormat'; const styles = StyleSheet.create({ divContainer: { @@ -265,13 +266,13 @@ class Streamgraph extends PureComponent { } const area = d3 .area() - .x(d => { + .x((d) => { return this.xAxisScale(d.data.date); }) - .y0(d => { + .y0((d) => { return this.yAxisScale(d[0]); }) - .y1(d => { + .y1((d) => { return this.yAxisScale(d[1]); }); @@ -280,7 +281,7 @@ class Streamgraph extends PureComponent { .data(stackedData) .enter() .append('path') - .attr('d', d => (d ? area(d) : [])) + .attr('d', (d) => (d ? area(d) : [])) .attr('name', (d, i) => { const currentName = nameList[i]; const currentColor = z[i]; @@ -453,11 +454,12 @@ class Streamgraph extends PureComponent { this.hoveredKey = d3.select(nodes[i]).attr('name'); this.hoveredValue = d.find( - elem => elem.data.date.getFullYear() === parseInt(date), + (elem) => + elem.data.date.getFullYear() === parseInt(date), ).data[this.hoveredKey]; this.hoveredColor = colorNameList.find( - elem => elem.name === this.hoveredKey, + (elem) => elem.name === this.hoveredKey, ).color; d3.select(nodes[i]).classed('hover', true); @@ -501,7 +503,7 @@ class Streamgraph extends PureComponent { setMouseOutStreams(tooltip, colorNameList) { const componentContext = this; if (this.streams) { - this.streams.on('mouseout', function() { + this.streams.on('mouseout', function () { componentContext.mouseIsOverStream = false; componentContext.streams .transition() @@ -539,13 +541,8 @@ class Streamgraph extends PureComponent { } setGraph() { - const { - valuesObjectsArray, - valuesArray, - dateMin, - dateMax, - namesList, - } = transformDataIntoMapArray(this.props.formatData); + const { valuesObjectsArray, valuesArray, dateMin, dateMax, namesList } = + transformDataIntoMapArray(this.props.formatData); const svgWidth = this.divContainer.current.clientWidth; const { margin, height: svgHeight } = this.state; @@ -614,21 +611,13 @@ class Streamgraph extends PureComponent { .selectAll('#d3DivContainer') .remove(); - d3.select(this.divContainer.current) - .selectAll('#vertical') - .remove(); + d3.select(this.divContainer.current).selectAll('#vertical').remove(); - d3.select(this.divContainer.current) - .selectAll('#tooltip') - .remove(); + d3.select(this.divContainer.current).selectAll('#tooltip').remove(); - d3.select(this.anchor.current) - .selectAll('g') - .remove(); + d3.select(this.anchor.current).selectAll('g').remove(); - d3.select(this.anchor.current) - .selectAll('defs') - .remove(); + d3.select(this.anchor.current).selectAll('defs').remove(); } componentDidMount() { @@ -665,52 +654,54 @@ class Streamgraph extends PureComponent { loading = ''; } return ( -
+
- {loading} +
+ {loading} +
+ +
+ {this.mouseIcon} +
+ +
+ {this.centerIcon} +
+ + + +
- -
- {this.mouseIcon} -
- -
- {this.centerIcon} -
- - - - -
+ ); } } diff --git a/src/app/js/formats/utils/components/ZoomableFormat.js b/src/app/js/formats/utils/components/ZoomableFormat.js index 92d31669d..adb8ba17f 100644 --- a/src/app/js/formats/utils/components/ZoomableFormat.js +++ b/src/app/js/formats/utils/components/ZoomableFormat.js @@ -28,8 +28,8 @@ const ZoomableFormat = ({ children, p }) => { onClick={handleClickOpen} sx={{ position: 'absolute', - right: 32, - bottom: 32, + right: 8, + bottom: 8, }} > From 2e4562c0678b530e42a2ea004fcfa44c7f57328b Mon Sep 17 00:00:00 2001 From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:12:28 +0100 Subject: [PATCH 4/5] feat: make network chart using the fullscreen feature --- src/app/js/formats/chart/network/NetworkView.js | 15 ++++++++++++--- .../js/formats/utils/components/ZoomableFormat.js | 8 ++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/app/js/formats/chart/network/NetworkView.js b/src/app/js/formats/chart/network/NetworkView.js index b6a89ae17..4c67605fe 100644 --- a/src/app/js/formats/chart/network/NetworkView.js +++ b/src/app/js/formats/chart/network/NetworkView.js @@ -12,6 +12,7 @@ import compose from 'recompose/compose'; import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; import { scaleLinear } from 'd3-scale'; +import { StyleSheet, css } from 'aphrodite/no-important'; import injectData from '../../injectData'; import MouseIcon from '../../utils/components/MouseIcon'; @@ -19,6 +20,8 @@ import ZoomableFormat from '../../utils/components/ZoomableFormat'; const simulationOptions = { animate: true, + width: '100%', + height: '100%', strength: { charge: ({ radius }) => -radius * 100, }, @@ -29,12 +32,17 @@ const labelOffset = { y: ({ radius }) => radius * Math.sin(Math.PI / 4) + 6, }; -const styles = { +const styles = StyleSheet.create({ container: { overflow: 'hidden', userSelect: 'none', + height: '100%', + minHeight: '600px', }, -}; + network: { + minHeight: '600px', + }, +}); const zoomOptions = { minScale: 0.25, maxScale: 16 }; @@ -65,7 +73,7 @@ class Network extends Component { return ( -
+
{nodes.map((node) => ( { return ( <>
- {children} + {!open ? children : null} + { style={{ borderRadius: '5px', margin: '24px', + height: '100%', + maxHeight: 'calc(100% - 48px)', + overflow: 'scroll', }} > - {children} + {open ? children : null}
From 2854701e9d180da92856134d98f40edbf3edc834 Mon Sep 17 00:00:00 2001 From: AlasDiablo <25723276+AlasDiablo@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:15:40 +0100 Subject: [PATCH 5/5] fix: add a z-index to vega tooltip to be usable in dialog --- .../js/formats/utils/components/vega-component/VegaComponent.js | 1 + .../utils/components/vega-lite-component/VegaLiteComponent.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/js/formats/utils/components/vega-component/VegaComponent.js b/src/app/js/formats/utils/components/vega-component/VegaComponent.js index 9afc5b3a4..69cad8c99 100644 --- a/src/app/js/formats/utils/components/vega-component/VegaComponent.js +++ b/src/app/js/formats/utils/components/vega-component/VegaComponent.js @@ -75,6 +75,7 @@ function CustomActionVega(props) { return ( + +