From bf5ca793322463b6e9729e8c1f9ace67cde5cf3a Mon Sep 17 00:00:00 2001 From: Robin de Mourat Date: Wed, 14 Aug 2024 13:02:07 +0200 Subject: [PATCH] improve croissance viz --- src/components/ScrollyPage/ScrollyPage.scss | 2 +- src/i18n/messages.yml | 12 +- src/styles/app.scss | 20 +- .../GuerreEtCroissance/GuerreEtCroissance.js | 494 +++++++++--------- .../GuerreEtCroissance.scss | 124 +++-- .../GuerreEtCroissance/Legend.js | 212 ++++++++ .../GuerreEtCroissance/LineSeries.js | 76 ++- 7 files changed, 566 insertions(+), 374 deletions(-) create mode 100644 src/visualizations/GuerreEtCroissance/Legend.js diff --git a/src/components/ScrollyPage/ScrollyPage.scss b/src/components/ScrollyPage/ScrollyPage.scss index d8e61b2f..3341729b 100644 --- a/src/components/ScrollyPage/ScrollyPage.scss +++ b/src/components/ScrollyPage/ScrollyPage.scss @@ -121,7 +121,7 @@ > h3 { margin: 0; position: relative; - padding-top: $medium-gutter; + // padding-top: $medium-gutter; // top: -1em; } display: flex; diff --git a/src/i18n/messages.yml b/src/i18n/messages.yml index 7ec60892..5137fd93 100644 --- a/src/i18n/messages.yml +++ b/src/i18n/messages.yml @@ -543,14 +543,14 @@ GuerreEtCroissance: fr: an en: year tooltip: - fr: En ${year}, valeur de ${value} lt. (perte de ${loss} lt.) - en: In ${year}, value of ${value} lt. (loss of ${loss} lt.) + fr: En ${year}, valeur de ${value} ${unit} (perte de ${loss} ${unit}) + en: In ${year}, value of ${value} ${unit} (loss of ${loss} ${unit}) filters-title: fr: Afficher ... en: Display ... filters-directions-title: - fr: "Afficher les directions :" - en: "Display directions:" + fr: "Directions des fermes" + en: "Directions des fermes" filters-series-title: - fr: "Afficher les séries :" - en: "Display series:" \ No newline at end of file + fr: "Séries" + en: "Series" \ No newline at end of file diff --git a/src/styles/app.scss b/src/styles/app.scss index 712bf336..7b9f8dee 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -95,10 +95,14 @@ input[type="radio"] { margin: 0; font: inherit; color: currentColor; - width: 1em; - height: 1em; - min-width: 1em; - min-height: 1em; + width: .5em; + height: .5em; + min-width: .5em; + min-height: .5em; + max-width: .5em; + max-height: .5em; + position: relative; + top: .25em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(0.25em); @@ -114,8 +118,8 @@ input[type="radio"] { &::before { content: ""; - width: 0.65em; - height: 0.65em; + width: 0.3em; + height: 0.3em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; @@ -124,10 +128,10 @@ input[type="radio"] { } &:checked{ - background: red; + background: $color-accent-background; &::before { transform: scale(1); - background: red; + background: $color-accent-background; } } diff --git a/src/visualizations/GuerreEtCroissance/GuerreEtCroissance.js b/src/visualizations/GuerreEtCroissance/GuerreEtCroissance.js index 6a375194..4aceef0a 100644 --- a/src/visualizations/GuerreEtCroissance/GuerreEtCroissance.js +++ b/src/visualizations/GuerreEtCroissance/GuerreEtCroissance.js @@ -8,6 +8,7 @@ import { formatNumber } from "../../utils/misc"; import ReactTooltip from "react-tooltip"; import translate from "../../utils/translate"; import LineSeries from "./LineSeries"; +import Legend from "./Legend"; const MIN_YEAR = 1720; const MAX_YEAR = 1790; @@ -57,9 +58,9 @@ export default function GuerreEtCroissance({ useEffect(() => setVisibleSeries(series ? series.split(',').map(s => s.trim()) : defaultSeries), [series]); useEffect(() => setVisibleDirections(directions ? directions.split(',').map(d => d.trim()) : defaultDirections), [directions]); useEffect(() => setNavigationMetric(navigation_metric || defaultNavigationMetric), [navigation_metric]); - useEffect(() => { - ReactTooltip.rebuild(); - }, [lang, visibleSeries, visibleDirections]) + + + // const data = useMemo(() => { const cleanData = (inputData.get('impact-guerre-sur-croissance.csv') || []).map(datum => ({ ...datum, @@ -77,12 +78,18 @@ export default function GuerreEtCroissance({ // return groups; // }, [inputData]); + + let legendWidth = 200; + legendWidth = width < 400 ? width / 3 : legendWidth; + const matrixWidth = width - legendWidth; + const gutter = 10; - const sideWidth = width / 5 < 100 ? 100 : width / 5; + const sideWidth = matrixWidth / 5 < 100 ? 100 : matrixWidth / 5; const topLabelsHeight = gutter * 3; const bottomAxisHeight = gutter * 3; const rowHeight = (height - topLabelsHeight - bottomAxisHeight) / visibleSeries.length; - const cellWidth = (width - sideWidth - gutter * 5) / (visibleDirections.length < 3 ? 3 : visibleDirections.length); + const cellWidth = (matrixWidth - gutter * 6) / (visibleDirections.length + 1); + // const cellWidth = (matrixWidth - sideWidth - gutter * 5) / (visibleDirections.length < 3 ? 3 : visibleDirections.length); const xScale = scaleLinear().domain([MIN_YEAR, MAX_YEAR]).range([0, cellWidth - gutter * 3]); const navigationSources = [ @@ -90,25 +97,29 @@ export default function GuerreEtCroissance({ id: 'carrière', source: 'Carriere', label: translate('GuerreEtCroissance', 'navigation-interface-carriere', lang), - tickFormat: d => formatNumber(d) + ' u.' + tickFormat: d => formatNumber(d) + ' u.', + unit: 'u.', }, { id: 'entrées', source: 'Navigo entrées', label: translate('GuerreEtCroissance', 'navigation-interface-arrivals', lang), - tickFormat: d => formatNumber(d) + ' u.' + tickFormat: d => formatNumber(d) + ' u.', + unit: 'u.', }, { id: 'tonnage', source: 'Navigo tonnage', label: translate('GuerreEtCroissance', 'navigation-interface-tonnage', lang), - tickFormat: d => formatNumber(d) + ' tx.' + tickFormat: d => formatNumber(d) + ' tx.', + unit: 'tx.', }, { id: 'mileage', source: 'Navigo mileage_total', label: translate('GuerreEtCroissance', 'navigation-interface-distance', lang), - tickFormat: d => formatNumber(d) + ' m.' + tickFormat: d => formatNumber(d) + ' m.', + unit: 'm.', }, ]; const activeNavigationSource = navigationSources.find(({ id }) => id === navigationMetric).source; @@ -117,7 +128,7 @@ export default function GuerreEtCroissance({ // const legendX = sideWidth + (visibleDirections.length >= 4 ? cellWidth * 2.5 : cellWidth * 1.5) + gutter * 2; const seriesLabels = useMemo(() => { return { - 'total': translate('GuerreEtCroissance', 'total', lang), + 'total': translate('GuerreEtCroissance', 'total', lang), 'imports': translate('GuerreEtCroissance', 'imports', lang), 'exports': translate('GuerreEtCroissance', 'exports', lang), 'total_no_colonial_product': translate('GuerreEtCroissance', 'total_no_colonial_product', lang), @@ -130,6 +141,10 @@ export default function GuerreEtCroissance({ } }, [translate, lang]); + useEffect(() => { + ReactTooltip.rebuild(); + }, [lang, visibleSeries, visibleDirections, data, activeNavigationSource]); + const wars = [ { start: 1744, @@ -147,6 +162,14 @@ export default function GuerreEtCroissance({ name: 'indépendance américaine' }, ]; + let circleRadius = width / 1000; // width / (visibleDirections.length * visibleSeries.length * 20); + const numberOfCells = visibleDirections.length * visibleSeries.length; + if (numberOfCells < 3) { + circleRadius = width / 350; + } + else if (numberOfCells < 9) { + circleRadius = width / 500; + } return ( <> - - { - visibleDirections.map((direction, index) => { - const x = sideWidth + index * cellWidth; - return ( - - { - xAxisValues - .map(value => { - const x = xScale(value); - const yEnd = direction === 'Marseille' ? height - bottomAxisHeight : height - bottomAxisHeight - rowHeight; - return ( - - - - - {value} - - - ) - }) - } - - ) - }) - } - - - { - visibleDirections.map((direction, index) => { - const x = sideWidth + index * cellWidth; - const y = 0 // topLabelsHeight / 2; - return ( - -
-

- {direction} -

-
-
- ) - }) - } -
- - - { - visibleSeries.map((seriesId, index) => { - const y = topLabelsHeight + index * rowHeight; - return ( - - { - seriesId === 'navigation' ? - - : null - } - - -
-

- {seriesLabels[seriesId]} -

-
-
-
- */} + + + { + visibleDirections.map((direction, index) => { + const x = sideWidth + index * cellWidth; + return ( + { - seriesId === 'navigation' ? - setActiveYear(y)} - xScale={xScale} - gutter={gutter} - seriesLabels={seriesLabels} - wars={wars} - lang={lang} - displayYTicks - /> - : - visibleDirections.map((direction, index) => { - const x = index * cellWidth; - if (!data[seriesId]) { - return null; - } + xAxisValues + .map(value => { + const x = xScale(value); + const yEnd = direction === 'Marseille' ? height - bottomAxisHeight : height - bottomAxisHeight - rowHeight; return ( - d.direction_ferme === direction)} - xScale={xScale} - yDomain={[0, 350000000]} - tickFormat={d => formatNumber(d) + ' lt.'} - activeYear={activeYear} - onSetActiveYear={y => setActiveYear(y)} - gutter={gutter} - seriesLabels={seriesLabels} - wars={wars} - lang={lang} - displayYTicks={index === visibleDirections.length - 1} + + + + {value} + ) }) } - - ) - }) - } + ) + }) + } + + + { + visibleDirections.map((direction, index) => { + const x = sideWidth + index * cellWidth; + const y = 0 // topLabelsHeight / 2; + return ( + +
+

+ {direction} +

+
+
+ ) + }) + } +
+ + { + visibleSeries.map((seriesId, index) => { + const y = topLabelsHeight + index * rowHeight; + return ( + + { + seriesId === 'navigation' ? + + : null + } + + +
+

+ {seriesLabels[seriesId]} +

+
+
+
+ + { + seriesId === 'navigation' ? + setActiveYear(y)} + xScale={xScale} + gutter={gutter} + seriesLabels={seriesLabels} + wars={wars} + lang={lang} + displayYTicks + circleRadius={circleRadius} + unit={navigationSources.find(({ id }) => id === navigationMetric).unit} + /> + : + visibleDirections.map((direction, index) => { + const x = index * cellWidth; + if (!data[seriesId]) { + return null; + } + return ( + + d.direction_ferme === direction)} + xScale={xScale} + yDomain={[0, 350000000]} + tickFormat={d => formatNumber(d) + ' lt.'} + activeYear={activeYear} + onSetActiveYear={y => setActiveYear(y)} + gutter={gutter} + seriesLabels={seriesLabels} + wars={wars} + lang={lang} + displayYTicks={index === visibleDirections.length - 1} + circleRadius={circleRadius} + unit={'lt.'} + /> + + ) + }) + } + +
+ ) + }) + } +
- - - -
-

- {translate('GuerreEtCroissance', 'legend-title', lang)} -

-
    -
  • - - {translate('GuerreEtCroissance', 'legend-slope', lang)} -
  • -
  • - - +1%/{translate('GuerreEtCroissance', 'year', lang)} - - {translate('GuerreEtCroissance', 'legend-growth', lang)} -
  • -
  • - - - -1% - - - +2% - - - {translate('GuerreEtCroissance', 'legend-loss', lang)} -
  • -
-
- {/* - - - - Régression d'après la période de paix précédente - */} - {/* */} -
+ */} + + span:first-of-type { margin-right: 1rem; min-width: 1rem; @@ -86,30 +70,54 @@ } } } - } - .controls-container { - position: relative; - max-width: 100%; - box-sizing: border-box; - font-family: "IBM Plex Sans", sans-serif; - .collapsable-controls { - overflow-y: hidden; - max-height: 0; - background: white; - transition: .5s ease all; - ul{ - max-width: 100%; - box-sizing: border-box; - padding:0; - margin-top: 0; - padding-left: .5rem; + .legend-container { + margin-bottom: $medium-gutter; + } + + .ui-group-container { + font-family: $font-family-1; + margin-bottom: $medium-gutter; + + ul { + margin: 0 0 $small-gutter $medium-gutter; } - &.is-visible { - max-height: 100vh; - padding-top: 1rem; - box-shadow: 0 0 1rem lightgrey; + li { + margin-left: $medium-gutter; + margin-bottom: 0; + cursor: pointer; } } } + .metrics-container { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + pointer-events: none; + } + // .controls-container { + // position: relative; + // max-width: 100%; + // box-sizing: border-box; + // font-family: "IBM Plex Sans", sans-serif; + // .collapsable-controls { + // overflow-y: hidden; + // max-height: 0; + // background: white; + // transition: .5s ease all; + + // ul{ + // max-width: 100%; + // box-sizing: border-box; + // padding:0; + // margin-top: 0; + // padding-left: .5rem; + // } + // &.is-visible { + // max-height: 100vh; + // padding-top: 1rem; + // box-shadow: 0 0 1rem lightgrey; + // } + // } + // } } diff --git a/src/visualizations/GuerreEtCroissance/Legend.js b/src/visualizations/GuerreEtCroissance/Legend.js new file mode 100644 index 00000000..621e5875 --- /dev/null +++ b/src/visualizations/GuerreEtCroissance/Legend.js @@ -0,0 +1,212 @@ + +import translate from "../../utils/translate"; + +const Legend = ({ + x, + y, + width, + height, + lang, + atlasMode, + + defaultDirections, + visibleDirections, + setVisibleDirections, + defaultSeries, + visibleSeries, + setVisibleSeries, + + navigationMetric, + setNavigationMetric, + navigationSources, +}) => { + return ( + +
+
+

+ {translate('GuerreEtCroissance', 'legend-title', lang)} +

+
    +
  • + + {translate('GuerreEtCroissance', 'legend-slope', lang)} +
  • +
  • + + +1%/{translate('GuerreEtCroissance', 'year', lang)} + + {translate('GuerreEtCroissance', 'legend-growth', lang)} +
  • +
  • + + + -1% + + + +2% + + + {translate('GuerreEtCroissance', 'legend-loss', lang)} +
  • +
+
+
+ { + atlasMode ? +
+
+ {translate('GuerreEtCroissance', 'filters-directions-title', lang)} +
+
    + { + defaultDirections + .map(direction => { + const isVisible = visibleDirections.includes(direction) + const handleClick = () => { + if (isVisible && visibleDirections.length > 1) { + setVisibleDirections( + defaultDirections.filter(dir => visibleDirections.includes(dir) && dir !== direction) + // visibleDirections.filter(d => d !== direction) + ) + } else if (!isVisible) { + setVisibleDirections( + // [...visibleDirections, direction] + defaultDirections.filter(dir => visibleDirections.includes(dir) || dir === direction) + ) + } + } + return ( + +
  • + { }} + /> + +
  • + //
  • + // {direction} + //
  • + ) + }) + } +
+
+ {translate('GuerreEtCroissance', 'filters-series-title', lang)} +
+
    + { + defaultSeries + .map(series => { + const isVisible = visibleSeries.includes(series) + const handleClick = () => { + if (isVisible && visibleSeries.length > 1) { + setVisibleSeries( + defaultSeries.filter( + s => visibleSeries.includes(s) && s !== series + // d => d !== series + ) + ) + } else if (!isVisible) { + setVisibleSeries( + defaultSeries.filter(s => visibleSeries.includes(s) || s === series) + // [...visibleSeries, series] + ) + } + } + //
  • + + return ( +
  • + { }} + /> + +
  • + ) + }) + } +
+ +
+ : null + } + { + visibleSeries.includes('navigation') ? +
+
+ {translate('GuerreEtCroissance', 'navigation-interface-title', lang)} +
+
    + { + navigationSources.map(({ id, label }) => { + return ( +
  • setNavigationMetric(id)} + > + setNavigationMetric(id)} + /> + +
  • + ) + }) + } +
+
+ : null + } + +
+
+ +
+ ) +} + +export default Legend; \ No newline at end of file diff --git a/src/visualizations/GuerreEtCroissance/LineSeries.js b/src/visualizations/GuerreEtCroissance/LineSeries.js index 47055e00..55c45f22 100644 --- a/src/visualizations/GuerreEtCroissance/LineSeries.js +++ b/src/visualizations/GuerreEtCroissance/LineSeries.js @@ -23,7 +23,9 @@ const LineSeries = ({ tickFormat, lang, wars, + circleRadius, id, + unit = 'lt.' }) => { if (!inputData || !inputData[0]) { return null; @@ -45,14 +47,14 @@ const LineSeries = ({ const part2 = inputData[0].slope.split(' ').pop(); cleanSlope = +part2.split('%')[0] } - const yDomain = initialYDomain || [0, max(data.map(d => d.value))]; + const yDomain = initialYDomain || [0, max(data.map(d => max([d.value, d.peace_reg_memory])))]; let yTickSpan = 50000000; if (yDomain[1] <= 10000) { yTickSpan = 500; } else if (yDomain[1] <= 1000000) { yTickSpan = 50000; } else if (yDomain[1] <= 5000000) { - yTickSpan = 500000; + yTickSpan = 1000000; } const avgMem = data[0].avg_loss_mem.split(' ').pop().replace('memoire', ''); let slope = data[0].slope.split(' ').pop(); @@ -71,7 +73,6 @@ const LineSeries = ({ const slopeColorScale = scaleLinear().domain([-1.1, 3.5]).range(['lightgrey', '#336D7C']); const lossColorScale = scaleLinear().domain([-60, 60]).range(['#FEA43B', 'green']); - return ( @@ -165,7 +166,7 @@ const LineSeries = ({ return ( d.year === year).peace_reg_memory)) }) @@ -232,7 +234,7 @@ const LineSeries = ({ fill={isActive ? 'black' : "black"} cx={x} cy={y} - r={isActive ? 3 : 1.5} + r={isActive ? circleRadius * 2 : circleRadius} style={{ pointerEvents: 'none' }} /> @@ -242,58 +244,42 @@ const LineSeries = ({ { slope ?
+
{slope} +
+
+ {(+avgMem.split('%')[0] > 0 ? '+' : '') + avgMem} +
: null } - - {/* - {slope} - */} - - {(+avgMem.split('%')[0] > 0 ? '+' : '') + avgMem} - - {/* - {avgNoMem} - */}
) }