@cdc/chart 4.26.7 → 4.26.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIG.md +502 -368
- package/dist/cdcchart-CYc-Kayd.es.js +22488 -0
- package/dist/cdcchart-DCKn8NmI.es.js +20695 -0
- package/dist/cdcchart-Dyui9nk_.es.js +45828 -0
- package/dist/cdcchart.js +4 -97406
- package/examples/__data__/vega-remote-data.json +6 -0
- package/examples/dendrogram.json +18 -0
- package/examples/feature/bar/bar-chart-race-annual-change.json +68 -0
- package/examples/feature/heatmap/average-age-categorical.json +2 -2
- package/examples/feature/heatmap/calendar-heatmap.json +2 -2
- package/examples/feature/heatmap/cell-values-bottom-axis.json +2 -2
- package/examples/feature/heatmap/sparse-aggregation.json +2 -2
- package/examples/feature/line/line-chart-race.json +50 -0
- package/examples/feature/sankey/sankey-example-data.json +40 -1441
- package/examples/feature/sankey/sankey_chart_data.csv +9 -20
- package/examples/network.json +31 -0
- package/examples/private/box-plot.json +573 -0
- package/examples/private/custom-color-issue.json +625 -0
- package/examples/private/custom-colors.json +522 -0
- package/examples/private/cutoff.json +10750 -0
- package/examples/private/overlay-legend.json +485 -0
- package/examples/private/pie-test.json +469 -0
- package/examples/vega-remote.json +22 -0
- package/package.json +11 -5
- package/src/CdcChartComponent.tsx +405 -208
- package/src/_stories/Chart.BarRace.stories.tsx +443 -0
- package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
- package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
- package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
- package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
- package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
- package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
- package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
- package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
- package/src/_stories/Chart.LineRace.stories.tsx +149 -0
- package/src/_stories/Chart.Network.stories.tsx +432 -0
- package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
- package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
- package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
- package/src/_stories/Chart.PieLabels.stories.tsx +4 -2
- package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
- package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
- package/src/_stories/Chart.Sankey.stories.tsx +417 -0
- package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
- package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
- package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
- package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
- package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
- package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
- package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
- package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
- package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
- package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
- package/src/_stories/_mock/editor-test-configs.json +20 -10
- package/src/_stories/_mock/editor-test-datasets.json +38 -42
- package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
- package/src/_stories/_mock/heatmap.json +2 -2
- package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
- package/src/_stories/_mock/sankey_mock.json +23 -27
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
- package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
- package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
- package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
- package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
- package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
- package/src/components/Axis/BottomAxis.tsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +3 -3
- package/src/components/Axis/RightAxis.tsx +21 -13
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -6
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -3
- package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +29 -25
- package/src/components/BarChart/components/BarChart.Vertical.tsx +37 -26
- package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
- package/src/components/BarChart/components/context.tsx +6 -0
- package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
- package/src/components/BarChart/helpers/index.ts +8 -0
- package/src/components/BarChart/helpers/portionPattern.ts +243 -0
- package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
- package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
- package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +104 -0
- package/src/components/BarChart/helpers/useBarChart.ts +60 -59
- package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
- package/src/components/BarChartRace/BarChartRace.tsx +230 -0
- package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
- package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
- package/src/components/BarChartRace/bar-chart-race.css +160 -0
- package/src/components/BarChartRace/helpers.test.ts +114 -0
- package/src/components/BarChartRace/helpers.ts +185 -0
- package/src/components/BarChartRace/index.ts +3 -0
- package/src/components/BoxPlot/helpers/index.ts +11 -82
- package/src/components/Brush/BrushSelector.tsx +9 -23
- package/src/components/Brush/MiniChartPreview.tsx +33 -1
- package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
- package/src/components/Dendrogram/dendrogram.css +113 -0
- package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
- package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
- package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
- package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
- package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
- package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
- package/src/components/Dendrogram/index.tsx +1 -0
- package/src/components/Dendrogram/types/index.ts +66 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1005 -760
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
- package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
- package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +88 -54
- package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
- package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +233 -33
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
- package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
- package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
- package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +2 -1
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +62 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +70 -24
- package/src/components/Forecasting/Forecasting.tsx +15 -6
- package/src/components/HeatMap/components/HeatMap.tsx +511 -178
- package/src/components/HeatMap/heatmap.constants.ts +7 -3
- package/src/components/HeatMap/heatmap.css +101 -2
- package/src/components/HeatMap/helpers/layout.ts +80 -0
- package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
- package/src/components/HeatMap/tests/helpers.test.ts +79 -0
- package/src/components/Legend/Legend.Component.tsx +110 -90
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -34
- package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
- package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
- package/src/components/LineChart/helpers.test.ts +126 -38
- package/src/components/LineChart/helpers.ts +9 -4
- package/src/components/LineChart/index.tsx +69 -24
- package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
- package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
- package/src/components/LineChartRace/LineChartRace.tsx +134 -0
- package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
- package/src/components/LineChartRace/helpers.test.ts +88 -0
- package/src/components/LineChartRace/helpers.ts +157 -0
- package/src/components/LineChartRace/index.ts +3 -0
- package/src/components/LineChartRace/line-chart-race.css +16 -0
- package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
- package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
- package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
- package/src/components/LinearChart.tsx +81 -51
- package/src/components/Network/components/Network.tsx +372 -0
- package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
- package/src/components/Network/helpers/layoutNetwork.ts +87 -0
- package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
- package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
- package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
- package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
- package/src/components/Network/index.tsx +3 -0
- package/src/components/Network/network.css +107 -0
- package/src/components/Network/types/index.ts +56 -0
- package/src/components/PairedBarChart.jsx +5 -10
- package/src/components/PairedBarChart.test.tsx +71 -0
- package/src/components/PieChart/PieChart.tsx +9 -8
- package/src/components/RacingChartRenderer.tsx +44 -0
- package/src/components/Regions/components/Regions.test.tsx +179 -0
- package/src/components/Regions/components/Regions.tsx +104 -69
- package/src/components/Regions/index.tsx +2 -3
- package/src/components/Sankey/components/Sankey.tsx +264 -437
- package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
- package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
- package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
- package/src/components/Sankey/sankey.scss +57 -116
- package/src/components/Sankey/types/index.ts +41 -4
- package/src/components/ScatterPlot/ScatterPlot.jsx +28 -4
- package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
- package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
- package/src/components/raceTiming.ts +18 -0
- package/src/data/initial-state.js +77 -31
- package/src/helpers/applyChartTypeContract.ts +71 -0
- package/src/helpers/axisLabelFontSize.ts +2 -2
- package/src/helpers/boxPlotStats.ts +60 -0
- package/src/helpers/bumpChartTooltip.ts +2 -1
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
- package/src/helpers/calculateRightYAxisWidth.ts +60 -0
- package/src/helpers/colorAssignmentHelpers.ts +2 -2
- package/src/helpers/createNewChartConfig.ts +56 -0
- package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
- package/src/helpers/forecastLegend.ts +88 -0
- package/src/helpers/getBoxPlotConfig.ts +18 -37
- package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
- package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
- package/src/helpers/getColorScale.ts +31 -22
- package/src/helpers/getDynamicYAxisCategories.ts +69 -0
- package/src/helpers/getMinMax.ts +9 -7
- package/src/helpers/getNewRuntime.ts +5 -2
- package/src/helpers/getStackedSeriesMax.ts +51 -0
- package/src/helpers/getV21ChartDistributionColors.ts +57 -0
- package/src/helpers/handleRankByValue.ts +15 -15
- package/src/helpers/seriesColumnSettings.ts +10 -0
- package/src/helpers/sizeHelpers.ts +4 -3
- package/src/helpers/smallMultiplesHelpers.ts +10 -13
- package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
- package/src/helpers/tests/boxPlotStats.test.ts +31 -0
- package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
- package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
- package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
- package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
- package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
- package/src/helpers/tests/getColorScale.test.ts +226 -1
- package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
- package/src/helpers/tests/getMinMax.test.ts +41 -0
- package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
- package/src/helpers/tests/handleRankByValue.test.ts +55 -37
- package/src/helpers/tests/seriesColumnSettings.test.ts +23 -0
- package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
- package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
- package/src/helpers/tooltipHelpers.ts +16 -0
- package/src/hooks/tests/useTooltip.test.tsx +353 -1
- package/src/hooks/usePrefersReducedMotion.ts +22 -0
- package/src/hooks/useReduceData.ts +4 -36
- package/src/hooks/useScales.ts +15 -0
- package/src/hooks/useTooltip.tsx +95 -64
- package/src/scss/main.scss +74 -2
- package/src/store/chart.reducer.ts +4 -3
- package/src/test/CdcChart.dataTable.test.tsx +490 -16
- package/src/test/CdcChart.dendrogram.test.tsx +277 -0
- package/src/test/CdcChart.finalization.test.tsx +143 -0
- package/src/test/CdcChart.network.test.tsx +271 -0
- package/src/types/ChartConfig.ts +91 -54
- package/src/types/ChartContext.ts +5 -0
- package/src/types/Horizon.ts +1 -1
- package/LICENSE +0 -201
- package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
- package/src/components/Sankey/components/ColumnList.tsx +0 -19
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
- package/src/components/Sankey/useSankeyAlert.tsx +0 -60
- package/src/data/legacy-defaults.ts +0 -18
- package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
- package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
|
@@ -1,489 +1,316 @@
|
|
|
1
|
-
import { useContext,
|
|
1
|
+
import { useContext, useMemo, useState } from 'react'
|
|
2
2
|
|
|
3
3
|
// External Libraries
|
|
4
4
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
5
|
-
import {
|
|
5
|
+
import { sankey, sankeyJustify, sankeyLinkHorizontal } from 'd3-sankey'
|
|
6
6
|
import { Group } from '@visx/group'
|
|
7
7
|
import { Text } from '@visx/text'
|
|
8
|
+
import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
|
|
8
9
|
|
|
9
10
|
// Cdc
|
|
10
11
|
import './../sankey.scss'
|
|
11
12
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
12
13
|
import ConfigContext from '../../../ConfigContext'
|
|
13
14
|
import type { ChartContext } from '../../../types/ChartContext'
|
|
14
|
-
import type { SankeyNode, SankeyProps } from '../types'
|
|
15
|
-
import useSankeyAlert from '../useSankeyAlert'
|
|
16
15
|
import { getSankeyTooltip } from '../helpers/getSankeyTooltip'
|
|
16
|
+
import { prepareSankeyData } from '../helpers/prepareSankeyData'
|
|
17
|
+
import type { SankeyLayoutLink, SankeyLayoutNode, SankeyProps } from '../types'
|
|
18
|
+
|
|
19
|
+
const formatNodeValue = (value: unknown, locale?: string) =>
|
|
20
|
+
typeof value === 'number' ? value.toLocaleString(locale) : String(value ?? '')
|
|
17
21
|
|
|
18
22
|
const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
19
|
-
const { config, handleChartAriaLabels } = useContext<ChartContext>(ConfigContext)
|
|
23
|
+
const { colorPalettes, config, handleChartAriaLabels } = useContext<ChartContext>(ConfigContext)
|
|
20
24
|
const { sankey: sankeyConfig } = config
|
|
25
|
+
const [activeNode, setActiveNode] = useState<string>('')
|
|
21
26
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
const preparedData = useMemo(
|
|
28
|
+
() => prepareSankeyData(config?.data, sankeyConfig?.columns),
|
|
29
|
+
[config?.data, sankeyConfig?.columns]
|
|
30
|
+
)
|
|
31
|
+
const paletteColors = useMemo(() => {
|
|
32
|
+
const orderedCustomColors = config.general?.palette?.customColorsOrdered
|
|
33
|
+
const colors =
|
|
34
|
+
Array.isArray(orderedCustomColors) && orderedCustomColors.length > 0
|
|
35
|
+
? orderedCustomColors
|
|
36
|
+
: getPaletteColors(config, colorPalettes)
|
|
37
|
+
const validColors = colors.filter(color => typeof color === 'string' && color.trim() !== '')
|
|
38
|
+
|
|
39
|
+
return validColors.length ? validColors : [sankeyConfig?.nodeColor?.default || '#005eaa']
|
|
40
|
+
}, [colorPalettes, config, sankeyConfig?.nodeColor?.default])
|
|
41
|
+
|
|
42
|
+
const paletteColorPair = useMemo(
|
|
43
|
+
() => ({
|
|
44
|
+
node: paletteColors[0] || sankeyConfig?.nodeColor?.default || '#005eaa',
|
|
45
|
+
link: paletteColors[paletteColors.length - 1] || sankeyConfig?.linkColor?.default || paletteColors[0] || '#7bafd4'
|
|
46
|
+
}),
|
|
47
|
+
[paletteColors, sankeyConfig?.linkColor?.default, sankeyConfig?.nodeColor?.default]
|
|
48
|
+
)
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const layout = useMemo(() => {
|
|
51
|
+
const containerWidth = Number(width) || 640
|
|
52
|
+
const isCompact = containerWidth < 520
|
|
53
|
+
const configuredHorizontalScrollWidth = sankeyConfig?.horizontalScrollWidth
|
|
54
|
+
const hasHorizontalScrollWidth =
|
|
55
|
+
(typeof configuredHorizontalScrollWidth === 'number' &&
|
|
56
|
+
Number.isFinite(configuredHorizontalScrollWidth) &&
|
|
57
|
+
configuredHorizontalScrollWidth > 0) ||
|
|
58
|
+
(typeof configuredHorizontalScrollWidth === 'string' &&
|
|
59
|
+
configuredHorizontalScrollWidth.trim() !== '' &&
|
|
60
|
+
Number.isFinite(Number(configuredHorizontalScrollWidth)) &&
|
|
61
|
+
Number(configuredHorizontalScrollWidth) > 0)
|
|
62
|
+
const horizontalScrollWidth = hasHorizontalScrollWidth ? Number(configuredHorizontalScrollWidth) : containerWidth
|
|
63
|
+
const isScrollable = hasHorizontalScrollWidth && containerWidth < horizontalScrollWidth
|
|
64
|
+
const labelWidth = isCompact
|
|
65
|
+
? Math.max(64, Math.min(96, Math.floor(containerWidth * 0.28)))
|
|
66
|
+
: Math.max(128, Math.min(180, Math.floor(containerWidth * 0.22)))
|
|
67
|
+
const horizontalMargin = Math.max(Number(sankeyConfig?.margin?.margin_x) || 0, labelWidth + 16)
|
|
68
|
+
const topMargin = Math.max(Number(sankeyConfig?.margin?.margin_y) || 0, 18)
|
|
69
|
+
const bottomMargin = 28
|
|
70
|
+
const layoutWidth = Math.max(isScrollable ? horizontalScrollWidth : containerWidth, horizontalMargin * 2 + 120)
|
|
71
|
+
const configuredHeight = Number(config?.heights?.vertical) || Number(height) || 500
|
|
72
|
+
const minimumHeight = Math.max(
|
|
73
|
+
isCompact ? 360 : 420,
|
|
74
|
+
preparedData.nodes.length * (isCompact ? 28 : 34),
|
|
75
|
+
preparedData.links.length * 20
|
|
76
|
+
)
|
|
77
|
+
const layoutHeight = Math.max(configuredHeight, minimumHeight)
|
|
78
|
+
const nodeWidth = Math.max(10, Math.min(Number(sankeyConfig?.nodeSize?.nodeWidth) || 18, isCompact ? 14 : 24))
|
|
79
|
+
const nodePadding = Math.max(8, Math.min(Number(sankeyConfig?.nodePadding) || 24, isCompact ? 16 : 32))
|
|
80
|
+
const iterations = Math.max(Number(sankeyConfig?.iterations) || 1, 6)
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const graph = {
|
|
84
|
+
nodes: preparedData.nodes.map(node => ({ ...node })),
|
|
85
|
+
links: preparedData.links.map(link => ({ ...link }))
|
|
51
86
|
}
|
|
52
|
-
})
|
|
53
|
-
setLargestGroupWidth(largest)
|
|
54
|
-
}, [groupRefs, sankeyConfig, window.innerWidth])
|
|
55
|
-
|
|
56
|
-
if (config.visualizationType !== 'Sankey') return
|
|
57
87
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let textPositionVertical = 0
|
|
93
|
-
let classStyle = 'node-value--storynode'
|
|
94
|
-
let storyNodes = true
|
|
95
|
-
|
|
96
|
-
if (data?.storyNodeText?.every(node => node.StoryNode !== id)) {
|
|
97
|
-
storyNodes = false
|
|
98
|
-
textPositionVertical = 10
|
|
99
|
-
textPositionHorizontal = 8
|
|
100
|
-
classStyle = 'node-value'
|
|
88
|
+
const sankeyGenerator = sankey<SankeyLayoutNode, any>()
|
|
89
|
+
.nodeId(node => node.id)
|
|
90
|
+
.nodeWidth(nodeWidth)
|
|
91
|
+
.nodePadding(nodePadding)
|
|
92
|
+
.iterations(iterations)
|
|
93
|
+
.nodeAlign(sankeyJustify)
|
|
94
|
+
.extent([
|
|
95
|
+
[horizontalMargin, topMargin],
|
|
96
|
+
[layoutWidth - horizontalMargin, layoutHeight - bottomMargin]
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
const sankeyGraph = sankeyGenerator(graph as any)
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
error: '',
|
|
103
|
+
isCompact,
|
|
104
|
+
isScrollable,
|
|
105
|
+
labelWidth,
|
|
106
|
+
links: sankeyGraph.links as SankeyLayoutLink[],
|
|
107
|
+
nodes: sankeyGraph.nodes as SankeyLayoutNode[],
|
|
108
|
+
width: layoutWidth,
|
|
109
|
+
height: layoutHeight
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return {
|
|
113
|
+
error: error instanceof Error ? error.message : 'Unable to render Sankey data.',
|
|
114
|
+
isCompact,
|
|
115
|
+
isScrollable,
|
|
116
|
+
labelWidth,
|
|
117
|
+
links: [] as SankeyLayoutLink[],
|
|
118
|
+
nodes: [] as SankeyLayoutNode[],
|
|
119
|
+
width: layoutWidth,
|
|
120
|
+
height: layoutHeight
|
|
121
|
+
}
|
|
101
122
|
}
|
|
123
|
+
}, [height, preparedData, sankeyConfig, width, config?.heights?.vertical])
|
|
102
124
|
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
const activeFlow = useMemo(() => {
|
|
126
|
+
if (!activeNode) return null
|
|
105
127
|
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
const nodeIds = new Set<string>([activeNode])
|
|
129
|
+
const linkIds = new Set<string>()
|
|
130
|
+
const nodesToVisit = [activeNode]
|
|
108
131
|
|
|
109
|
-
|
|
132
|
+
while (nodesToVisit.length) {
|
|
133
|
+
const currentNodeId = nodesToVisit.shift()
|
|
134
|
+
const downstreamLinks = layout.links.filter(link => link.source.id === currentNodeId)
|
|
110
135
|
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
downstreamLinks.forEach(link => {
|
|
137
|
+
linkIds.add(link.id)
|
|
113
138
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const sourceObj: any = link.source
|
|
118
|
-
if (targetObj.id === id) {
|
|
119
|
-
sourceNodes.push(sourceObj.id)
|
|
139
|
+
if (!nodeIds.has(link.target.id)) {
|
|
140
|
+
nodeIds.add(link.target.id)
|
|
141
|
+
nodesToVisit.push(link.target.id)
|
|
120
142
|
}
|
|
121
143
|
})
|
|
122
|
-
|
|
123
|
-
sourceNodes.forEach(id => {
|
|
124
|
-
links.forEach(link => {
|
|
125
|
-
const targetObj: any = link.target
|
|
126
|
-
const sourceObj: any = link.source
|
|
127
|
-
if (targetObj.id === tooltipID && sourceObj.id === id) {
|
|
128
|
-
activeLinks.push(link)
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
144
|
}
|
|
133
145
|
|
|
134
|
-
return {
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const sankeyToolTip = getSankeyTooltip(data, tooltipID)
|
|
138
|
-
|
|
139
|
-
// Draw the nodes
|
|
140
|
-
const allNodes = sankeyData.nodes.map((node, i) => {
|
|
141
|
-
let { textPositionHorizontal, textPositionVertical, classStyle, storyNodes } = nodeStyle(node.id)
|
|
142
|
-
let { sourceNodes } = activeConnection(tooltipID)
|
|
143
|
-
|
|
144
|
-
let opacityValue = sankeyConfig.opacity.nodeOpacityDefault
|
|
145
|
-
let nodeColor = sankeyConfig.nodeColor.default
|
|
146
|
-
|
|
147
|
-
if (tooltipID !== node.id && tooltipID !== '' && !sourceNodes.includes(node.id)) {
|
|
148
|
-
nodeColor = sankeyConfig.nodeColor.inactive
|
|
149
|
-
opacityValue = sankeyConfig.opacity.nodeOpacityInactive
|
|
150
|
-
}
|
|
146
|
+
return { nodeIds, linkIds }
|
|
147
|
+
}, [activeNode, layout.links])
|
|
151
148
|
|
|
152
|
-
|
|
149
|
+
if (config.visualizationType !== 'Sankey') return null
|
|
153
150
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
* @returns {number} The length of the link.
|
|
160
|
-
*/
|
|
161
|
-
const linkLength = () =>
|
|
162
|
-
Math.sqrt(
|
|
163
|
-
Math.pow(links[0].target.x0! - links[0].source.x1!, 2) + Math.pow(links[0].target.y0! - links[0].source.y1!, 2)
|
|
164
|
-
) - largestGroupWidth
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<Group className='' key={i}>
|
|
168
|
-
<rect
|
|
169
|
-
height={node.y1! - node.y0! + 2} // increasing node size to account for smaller nodes
|
|
170
|
-
width={maxNodeWidth}
|
|
171
|
-
x={node.x0}
|
|
172
|
-
y={node.y0! - 1} //adjusting here the node starts so it looks more center with the link
|
|
173
|
-
fill={nodeColor}
|
|
174
|
-
fillOpacity={opacityValue}
|
|
175
|
-
rx={sankeyConfig.rxValue}
|
|
176
|
-
onMouseEnter={() => {}}
|
|
177
|
-
// todo: move enable tooltips to sankey
|
|
178
|
-
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
179
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
180
|
-
onClick={() => handleNodeClick(node.id)}
|
|
181
|
-
style={{ pointerEvents: 'visible', cursor: 'pointer' }}
|
|
182
|
-
/>
|
|
183
|
-
{storyNodes ? (
|
|
184
|
-
<>
|
|
185
|
-
<Text
|
|
186
|
-
width={linkLength()}
|
|
187
|
-
/* Text Position Horizontal
|
|
188
|
-
x0 is the left edge of the node
|
|
189
|
-
# - positions text # units to the right of the left edge of the node */
|
|
190
|
-
x={node.x0! + textPositionHorizontal}
|
|
191
|
-
textAnchor={sankeyData.nodes.length - 1 === i ? 'end' : 'start'}
|
|
192
|
-
verticalAnchor='end'
|
|
193
|
-
/*Text Position Vertical
|
|
194
|
-
y1 and y0 are the top and bottom edges of the node
|
|
195
|
-
y1+y0 = total height
|
|
196
|
-
dividing by 2 gives you the midpoint of the node
|
|
197
|
-
minus 30 raises the vertical position to be higher
|
|
198
|
-
*/
|
|
199
|
-
y={(node.y1! + node.y0!) / 2 - 30}
|
|
200
|
-
/* Using x and y in combination with dominant baseline allows for a more
|
|
201
|
-
precise positioning of the text within the svg
|
|
202
|
-
dominant baseline allows for different vertical alignments
|
|
203
|
-
text-before-edge aligns the text's bottom edge with the bottom edge of the container
|
|
204
|
-
*/
|
|
205
|
-
fill={sankeyConfig.nodeFontColor}
|
|
206
|
-
fontWeight='bold' // font weight
|
|
207
|
-
className='node-text'
|
|
208
|
-
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
209
|
-
onClick={() => handleNodeClick(node.id)}
|
|
210
|
-
onMouseEnter={() => {}}
|
|
211
|
-
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
212
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
213
|
-
>
|
|
214
|
-
{(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
|
|
215
|
-
</Text>
|
|
216
|
-
<Text
|
|
217
|
-
width={linkLength()}
|
|
218
|
-
verticalAnchor='middle'
|
|
219
|
-
className={classStyle}
|
|
220
|
-
x={node.x0! + textPositionHorizontal}
|
|
221
|
-
y={(node.y1! + node.y0! + 25) / 2}
|
|
222
|
-
fill={sankeyConfig.storyNodeFontColor || sankeyConfig.nodeFontColor}
|
|
223
|
-
fontWeight='bold'
|
|
224
|
-
textAnchor='start'
|
|
225
|
-
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
226
|
-
onClick={() => handleNodeClick(node.id)}
|
|
227
|
-
onMouseEnter={() => {}}
|
|
228
|
-
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
229
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
230
|
-
>
|
|
231
|
-
{typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value}
|
|
232
|
-
</Text>
|
|
233
|
-
<Text
|
|
234
|
-
width={linkLength()}
|
|
235
|
-
x={node.x0! + textPositionHorizontal}
|
|
236
|
-
// plus 50 will move the vertical position down
|
|
237
|
-
y={(node.y1! + node.y0!) / 2 + 50}
|
|
238
|
-
fill={sankeyConfig.nodeFontColor}
|
|
239
|
-
fontWeight='bold'
|
|
240
|
-
textAnchor={sankeyData.nodes.length === i ? 'end' : 'start'}
|
|
241
|
-
className='node-text'
|
|
242
|
-
verticalAnchor='start'
|
|
243
|
-
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
244
|
-
onClick={() => handleNodeClick(node.id)}
|
|
245
|
-
onMouseEnter={() => {}}
|
|
246
|
-
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
247
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
248
|
-
>
|
|
249
|
-
{(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
|
|
250
|
-
</Text>
|
|
251
|
-
</>
|
|
252
|
-
) : (
|
|
253
|
-
<>
|
|
254
|
-
<Text
|
|
255
|
-
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
256
|
-
onClick={() => handleNodeClick(node.id)}
|
|
257
|
-
onMouseEnter={() => {}}
|
|
258
|
-
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
259
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
260
|
-
x={node.x0! + textPositionHorizontal}
|
|
261
|
-
y={(node.y1! + node.y0!) / 2 + textPositionVertical}
|
|
262
|
-
dominantBaseline='text-before-edge'
|
|
263
|
-
fill={sankeyConfig.nodeFontColor}
|
|
264
|
-
fontWeight='bold'
|
|
265
|
-
textAnchor='start'
|
|
266
|
-
>
|
|
267
|
-
{node.id}
|
|
268
|
-
</Text>
|
|
269
|
-
<text
|
|
270
|
-
x={node.x0! + textPositionHorizontal}
|
|
271
|
-
/* adding 30 allows the node value to be on the next line underneath the node id */
|
|
272
|
-
y={(node.y1! + node.y0!) / 2 + 30}
|
|
273
|
-
dominantBaseline='text-before-edge'
|
|
274
|
-
fill={sankeyConfig.nodeFontColor}
|
|
275
|
-
//fontSize={16}
|
|
276
|
-
fontWeight='bold'
|
|
277
|
-
textAnchor='start'
|
|
278
|
-
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
279
|
-
onClick={() => handleNodeClick(node.id)}
|
|
280
|
-
onMouseEnter={() => {}}
|
|
281
|
-
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
282
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
283
|
-
>
|
|
284
|
-
<tspan className={classStyle}>
|
|
285
|
-
{sankeyConfig.nodeValueStyle.textBefore +
|
|
286
|
-
(typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value) +
|
|
287
|
-
sankeyConfig.nodeValueStyle.textAfter}
|
|
288
|
-
</tspan>
|
|
289
|
-
</text>
|
|
290
|
-
</>
|
|
291
|
-
)}
|
|
292
|
-
</Group>
|
|
293
|
-
)
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
// Draw the links
|
|
297
|
-
const allLinks = links.map((link, i) => {
|
|
298
|
-
const linkGenerator = sankeyLinkHorizontal()
|
|
299
|
-
const path = linkGenerator(link)
|
|
300
|
-
let opacityValue = sankeyConfig.opacity.LinkOpacityDefault
|
|
301
|
-
let strokeColor = sankeyConfig.linkColor.default
|
|
302
|
-
|
|
303
|
-
let { activeLinks } = activeConnection(tooltipID)
|
|
304
|
-
|
|
305
|
-
if (!activeLinks.includes(link) && tooltipID !== '') {
|
|
306
|
-
strokeColor = sankeyConfig.linkColor.inactive
|
|
307
|
-
opacityValue = sankeyConfig.opacity.LinkOpacityInactive
|
|
308
|
-
}
|
|
151
|
+
const tooltipId = `cdc-open-viz-tooltip-${runtime?.uniqueId || 'chart'}-sankey`
|
|
152
|
+
const hasRenderableData = layout.links.length > 0 && layout.nodes.length > 0
|
|
153
|
+
const valuePrefix = sankeyConfig?.nodeValueStyle?.textBefore ?? '('
|
|
154
|
+
const valueSuffix = sankeyConfig?.nodeValueStyle?.textAfter ?? ')'
|
|
155
|
+
const a11y = handleChartAriaLabels(config)
|
|
309
156
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
d={path!}
|
|
314
|
-
stroke={strokeColor}
|
|
315
|
-
fill='none'
|
|
316
|
-
strokeOpacity={opacityValue}
|
|
317
|
-
strokeWidth={link.width! + 2}
|
|
318
|
-
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
319
|
-
onClick={() => handleNodeClick(link.target.id || null)}
|
|
320
|
-
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
321
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
322
|
-
/>
|
|
323
|
-
)
|
|
324
|
-
})
|
|
157
|
+
const toggleActiveNode = (nodeId: string) => {
|
|
158
|
+
setActiveNode(currentActiveNode => (currentActiveNode === nodeId ? '' : nodeId))
|
|
159
|
+
}
|
|
325
160
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
161
|
+
const handleKeyboardToggle = (event: React.KeyboardEvent<SVGElement>, nodeId: string) => {
|
|
162
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
163
|
+
event.preventDefault()
|
|
164
|
+
toggleActiveNode(nodeId)
|
|
165
|
+
}
|
|
330
166
|
|
|
331
|
-
|
|
332
|
-
|
|
167
|
+
const getNodeOpacity = (nodeId: string) => {
|
|
168
|
+
if (!activeFlow) return Number(sankeyConfig?.opacity?.nodeOpacityDefault) || 1
|
|
169
|
+
return activeFlow.nodeIds.has(nodeId)
|
|
170
|
+
? Number(sankeyConfig?.opacity?.nodeOpacityDefault) || 1
|
|
171
|
+
: Number(sankeyConfig?.opacity?.nodeOpacityInactive) || 0.1
|
|
172
|
+
}
|
|
333
173
|
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
174
|
+
const getLinkOpacity = (link: SankeyLayoutLink) => {
|
|
175
|
+
if (!activeFlow) return Number(sankeyConfig?.opacity?.LinkOpacityDefault) || 0.75
|
|
176
|
+
return activeFlow.linkIds.has(link.id)
|
|
177
|
+
? Number(sankeyConfig?.opacity?.LinkOpacityDefault) || 0.75
|
|
178
|
+
: Number(sankeyConfig?.opacity?.LinkOpacityInactive) || 0.1
|
|
179
|
+
}
|
|
337
180
|
|
|
338
|
-
|
|
339
|
-
|
|
181
|
+
const getNodeColor = (nodeId: string) =>
|
|
182
|
+
!activeFlow || activeFlow.nodeIds.has(nodeId)
|
|
183
|
+
? paletteColorPair.node
|
|
184
|
+
: sankeyConfig?.nodeColor?.inactive || '#808080'
|
|
340
185
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
186
|
+
const getLinkColor = (link: SankeyLayoutLink) =>
|
|
187
|
+
!activeFlow || activeFlow.linkIds.has(link.id)
|
|
188
|
+
? paletteColorPair.link
|
|
189
|
+
: sankeyConfig?.linkColor?.inactive || '#D3D3D3'
|
|
345
190
|
|
|
191
|
+
if (!hasRenderableData) {
|
|
346
192
|
return (
|
|
347
|
-
<
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
width={sankeyGenerator.nodeWidth()}
|
|
351
|
-
x={node.x0}
|
|
352
|
-
y={node.y0! - 1} //adjusting here the node starts so it looks more center with the link
|
|
353
|
-
fill={nodeColor}
|
|
354
|
-
fillOpacity={opacityValue}
|
|
355
|
-
rx={sankeyConfig.rxValue}
|
|
356
|
-
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
357
|
-
data-tooltip-id={`tooltip`}
|
|
358
|
-
onClick={() => handleNodeClick(node.id)}
|
|
359
|
-
style={{ pointerEvents: 'visible', cursor: 'pointer' }}
|
|
360
|
-
/>
|
|
361
|
-
{storyNodes ? (
|
|
362
|
-
<>
|
|
363
|
-
<Text
|
|
364
|
-
/* Text Position Horizontal
|
|
365
|
-
x0 is the left edge of the node
|
|
366
|
-
# - positions text # units to the right of the left edge of the node */
|
|
367
|
-
x={node.x0! + textPositionHorizontal}
|
|
368
|
-
textAnchor={sankeyData.nodes.length - 1 === i ? 'end' : 'start'}
|
|
369
|
-
verticalAnchor='end'
|
|
370
|
-
/*Text Position Vertical
|
|
371
|
-
y1 and y0 are the top and bottom edges of the node
|
|
372
|
-
y1+y0 = total height
|
|
373
|
-
dividing by 2 gives you the midpoint of the node
|
|
374
|
-
minus 30 raises the vertical position to be higher
|
|
375
|
-
*/
|
|
376
|
-
y={(node.y1! + node.y0!) / 2 - 30}
|
|
377
|
-
/* Using x and y in combination with dominant baseline allows for a more
|
|
378
|
-
precise positioning of the text within the svg
|
|
379
|
-
dominant baseline allows for different vertical alignments
|
|
380
|
-
text-before-edge aligns the text's bottom edge with the bottom edge of the container
|
|
381
|
-
*/
|
|
382
|
-
fill={sankeyConfig.nodeFontColor}
|
|
383
|
-
fontWeight='bold' // font weight
|
|
384
|
-
style={{ pointerEvents: 'none' }}
|
|
385
|
-
className='node-text'
|
|
386
|
-
>
|
|
387
|
-
{(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
|
|
388
|
-
</Text>
|
|
389
|
-
<Text
|
|
390
|
-
verticalAnchor='end'
|
|
391
|
-
className={classStyle}
|
|
392
|
-
x={node.x0! + textPositionHorizontal}
|
|
393
|
-
y={(node.y1! + node.y0! + 25) / 2}
|
|
394
|
-
fill={sankeyConfig.storyNodeFontColor || sankeyConfig.nodeFontColor}
|
|
395
|
-
fontWeight='bold'
|
|
396
|
-
textAnchor='start'
|
|
397
|
-
style={{ pointerEvents: 'none' }}
|
|
398
|
-
>
|
|
399
|
-
{typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value}
|
|
400
|
-
</Text>
|
|
401
|
-
<Text
|
|
402
|
-
x={node.x0! + textPositionHorizontal}
|
|
403
|
-
// plus 50 will move the vertical position down
|
|
404
|
-
y={(node.y1! + node.y0!) / 2 + 50}
|
|
405
|
-
fill={sankeyConfig.nodeFontColor}
|
|
406
|
-
fontWeight='bold'
|
|
407
|
-
textAnchor={sankeyData.nodes.length === i ? 'end' : 'start'}
|
|
408
|
-
style={{ pointerEvents: 'none' }}
|
|
409
|
-
className='node-text'
|
|
410
|
-
verticalAnchor='end'
|
|
411
|
-
>
|
|
412
|
-
{(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
|
|
413
|
-
</Text>
|
|
414
|
-
</>
|
|
415
|
-
) : (
|
|
416
|
-
<>
|
|
417
|
-
<text
|
|
418
|
-
x={node.x0! + textPositionHorizontal}
|
|
419
|
-
y={(node.y1! + node.y0!) / 2 + textPositionVertical}
|
|
420
|
-
dominantBaseline='text-before-edge'
|
|
421
|
-
fill={sankeyConfig.nodeFontColor}
|
|
422
|
-
fontWeight='bold'
|
|
423
|
-
textAnchor='start'
|
|
424
|
-
style={{ pointerEvents: 'none' }}
|
|
425
|
-
>
|
|
426
|
-
<tspan id={node.id} className='node-id'>
|
|
427
|
-
{node.id}
|
|
428
|
-
</tspan>
|
|
429
|
-
</text>
|
|
430
|
-
<text
|
|
431
|
-
x={node.x0! + textPositionHorizontal}
|
|
432
|
-
/* adding 30 allows the node value to be on the next line underneath the node id */
|
|
433
|
-
y={(node.y1! + node.y0!) / 2 + 30}
|
|
434
|
-
dominantBaseline='text-before-edge'
|
|
435
|
-
fill={sankeyConfig.nodeFontColor}
|
|
436
|
-
fontWeight='bold'
|
|
437
|
-
textAnchor='start'
|
|
438
|
-
style={{ pointerEvents: 'none' }}
|
|
439
|
-
>
|
|
440
|
-
<tspan onClick={() => handleNodeClick(node.id)} className={classStyle}>
|
|
441
|
-
{sankeyConfig.nodeValueStyle.textBefore +
|
|
442
|
-
(typeof node.value === 'number' ? node.value.toLocaleString() : node.value) +
|
|
443
|
-
sankeyConfig.nodeValueStyle.textAfter}
|
|
444
|
-
</tspan>
|
|
445
|
-
</text>
|
|
446
|
-
</>
|
|
447
|
-
)}
|
|
448
|
-
</Group>
|
|
193
|
+
<div className='sankey-chart sankey-chart--empty' role='status'>
|
|
194
|
+
{layout.error || config?.chartMessage?.noData || 'No Data Available'}
|
|
195
|
+
</div>
|
|
449
196
|
)
|
|
450
|
-
}
|
|
197
|
+
}
|
|
451
198
|
|
|
452
|
-
return
|
|
453
|
-
|
|
454
|
-
|
|
199
|
+
return (
|
|
200
|
+
<div
|
|
201
|
+
className={`sankey-chart${layout.isCompact ? ' sankey-chart--compact' : ''}${
|
|
202
|
+
layout.isScrollable ? ' sankey-chart--scrollable' : ''
|
|
203
|
+
}`}
|
|
204
|
+
>
|
|
205
|
+
<div
|
|
206
|
+
className='sankey-chart__scroll-area'
|
|
207
|
+
role={layout.isScrollable ? 'region' : undefined}
|
|
208
|
+
tabIndex={layout.isScrollable ? 0 : undefined}
|
|
209
|
+
aria-label={layout.isScrollable ? 'Scrollable Sankey diagram' : undefined}
|
|
210
|
+
>
|
|
455
211
|
<svg
|
|
456
212
|
className='sankey-chart__diagram'
|
|
457
|
-
|
|
458
|
-
height={Number(config.heights.vertical)}
|
|
459
|
-
style={{ overflow: 'visible' }}
|
|
213
|
+
height={layout.height}
|
|
460
214
|
role='img'
|
|
215
|
+
viewBox={`0 0 ${layout.width} ${layout.height}`}
|
|
216
|
+
width={layout.isScrollable ? layout.width : '100%'}
|
|
461
217
|
aria-label={a11y}
|
|
218
|
+
data-rejected-row-count={preparedData.rejectedRowCount}
|
|
462
219
|
>
|
|
463
|
-
<Group className='
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
220
|
+
<Group className='sankey-chart__links'>
|
|
221
|
+
{layout.links.map(link => {
|
|
222
|
+
const path = sankeyLinkHorizontal()(link as any)
|
|
223
|
+
const tooltipLabel = `${link.source.id} → ${link.target.id}`
|
|
224
|
+
const tooltipValue = formatNodeValue(link.value, config.locale)
|
|
225
|
+
const linkOpacity = getLinkOpacity(link)
|
|
226
|
+
const linkWidth = Math.max(1, link.width || 1)
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<path
|
|
230
|
+
key={link.id}
|
|
231
|
+
className='sankey-chart__link'
|
|
232
|
+
d={path || ''}
|
|
233
|
+
fill='none'
|
|
234
|
+
stroke={getLinkColor(link)}
|
|
235
|
+
strokeOpacity={linkOpacity}
|
|
236
|
+
strokeWidth={linkWidth}
|
|
237
|
+
onClick={() => toggleActiveNode(link.source.id)}
|
|
238
|
+
data-tooltip-html={config.enableTooltips ? getSankeyTooltip(tooltipLabel, tooltipValue) : undefined}
|
|
239
|
+
data-tooltip-id={tooltipId}
|
|
240
|
+
data-link-source-id={link.source.id}
|
|
241
|
+
data-link-target-id={link.target.id}
|
|
242
|
+
/>
|
|
243
|
+
)
|
|
244
|
+
})}
|
|
467
245
|
</Group>
|
|
468
|
-
</svg>
|
|
469
246
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
247
|
+
<Group className='sankey-chart__nodes'>
|
|
248
|
+
{layout.nodes.map(node => {
|
|
249
|
+
const nodeHeight = Math.max(1, (node.y1 || 0) - (node.y0 || 0))
|
|
250
|
+
const nodeWidth = Math.max(1, (node.x1 || 0) - (node.x0 || 0))
|
|
251
|
+
const labelOnLeft = !node.targetLinks?.length
|
|
252
|
+
const labelX = labelOnLeft ? (node.x0 || 0) - 8 : (node.x1 || 0) + 8
|
|
253
|
+
const labelY = ((node.y0 || 0) + (node.y1 || 0)) / 2
|
|
254
|
+
const value = formatNodeValue(node.value, config.locale)
|
|
255
|
+
const label = `${node.id} ${valuePrefix}${value}${valueSuffix}`
|
|
256
|
+
const tooltipValue = `${node.id}: ${value}`
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<Group key={node.id} className='sankey-chart__node-group'>
|
|
260
|
+
<rect
|
|
261
|
+
className='sankey-chart__node'
|
|
262
|
+
height={nodeHeight}
|
|
263
|
+
width={nodeWidth}
|
|
264
|
+
x={node.x0}
|
|
265
|
+
y={node.y0}
|
|
266
|
+
fill={getNodeColor(node.id)}
|
|
267
|
+
fillOpacity={getNodeOpacity(node.id)}
|
|
268
|
+
rx={Number(sankeyConfig?.rxValue) || 0}
|
|
269
|
+
role='button'
|
|
270
|
+
tabIndex={0}
|
|
271
|
+
aria-label={`${node.id}, ${value}`}
|
|
272
|
+
onClick={() => toggleActiveNode(node.id)}
|
|
273
|
+
onKeyDown={event => handleKeyboardToggle(event, node.id)}
|
|
274
|
+
data-tooltip-html={config.enableTooltips ? getSankeyTooltip(node.id, tooltipValue) : undefined}
|
|
275
|
+
data-tooltip-id={tooltipId}
|
|
276
|
+
data-node-id={node.id}
|
|
277
|
+
/>
|
|
278
|
+
<Text
|
|
279
|
+
className='sankey-chart__label'
|
|
280
|
+
data-label-side={labelOnLeft ? 'left' : 'right'}
|
|
281
|
+
data-node-id={node.id}
|
|
282
|
+
dominantBaseline='middle'
|
|
283
|
+
fill={sankeyConfig?.nodeFontColor}
|
|
284
|
+
fontWeight={600}
|
|
285
|
+
pointerEvents='none'
|
|
286
|
+
textAnchor={labelOnLeft ? 'end' : 'start'}
|
|
287
|
+
verticalAnchor='middle'
|
|
288
|
+
width={layout.labelWidth}
|
|
289
|
+
x={labelX}
|
|
290
|
+
y={labelY}
|
|
291
|
+
>
|
|
292
|
+
{label}
|
|
293
|
+
</Text>
|
|
294
|
+
</Group>
|
|
295
|
+
)
|
|
296
|
+
})}
|
|
297
|
+
</Group>
|
|
298
|
+
</svg>
|
|
483
299
|
</div>
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
300
|
+
|
|
301
|
+
<ReactTooltip
|
|
302
|
+
id={tooltipId}
|
|
303
|
+
afterHide={() => setActiveNode('')}
|
|
304
|
+
events={['click']}
|
|
305
|
+
place='bottom'
|
|
306
|
+
style={{
|
|
307
|
+
backgroundColor: 'rgba(238, 238, 238, 1)',
|
|
308
|
+
color: 'black',
|
|
309
|
+
boxShadow: '0 3px 10px rgb(0 0 0 / 0.2)'
|
|
310
|
+
}}
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
487
313
|
)
|
|
488
314
|
}
|
|
315
|
+
|
|
489
316
|
export default Sankey
|