@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,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext, useMemo } from 'react'
|
|
1
|
+
import React, { useContext, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import { AxisBottom, AxisLeft, AxisTop } from '@visx/axis'
|
|
3
3
|
import { Group } from '@visx/group'
|
|
4
4
|
import { HeatmapRect } from '@visx/heatmap'
|
|
@@ -10,8 +10,13 @@ import { formatNumber as formatColumnNumber } from '@cdc/core/helpers/cove/numbe
|
|
|
10
10
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
11
11
|
import { type ChartConfig, type HeatMapXAxisPosition } from '../../../types/ChartConfig'
|
|
12
12
|
import { buildTooltipListHtml } from '../../../helpers/tooltipHelpers'
|
|
13
|
-
import {
|
|
14
|
-
|
|
13
|
+
import {
|
|
14
|
+
findColumnConfigByName,
|
|
15
|
+
getAdditionalColumnFormattingParams,
|
|
16
|
+
getSeriesColumnFormattingParams
|
|
17
|
+
} from '../../../helpers/seriesColumnSettings'
|
|
18
|
+
import { HEATMAP_CONFIG_DEFAULTS, MAX_HEATMAP_COLUMN_WIDTH, MIN_HEATMAP_COLUMN_WIDTH } from '../heatmap.constants'
|
|
19
|
+
import { getHeatMapXAxisTickValues } from '../helpers/layout'
|
|
15
20
|
import './../heatmap.css'
|
|
16
21
|
|
|
17
22
|
/**
|
|
@@ -53,28 +58,46 @@ type HeatMapMargins = {
|
|
|
53
58
|
type HeatMapLayout = {
|
|
54
59
|
availableWidth: number
|
|
55
60
|
availableHeight: number
|
|
61
|
+
responsiveGridWidth: number
|
|
56
62
|
gridWidth: number
|
|
57
63
|
gridHeight: number
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
cellWidth: number
|
|
65
|
+
cellHeight: number
|
|
60
66
|
xOffset: number
|
|
61
67
|
yOffset: number
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
type HeatMapScrollState = {
|
|
71
|
+
scrollbarSpace: number
|
|
72
|
+
canScrollLeft: boolean
|
|
73
|
+
canScrollRight: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
const AXIS_TOP_LABEL_SPACE = 28
|
|
67
77
|
const AXIS_TICK_FONT_SIZE = 16
|
|
78
|
+
const AXIS_TICK_LENGTH = 8
|
|
68
79
|
const AXIS_TITLE_SPACE = 30
|
|
80
|
+
const AXIS_TITLE_FONT_SIZE = 18
|
|
69
81
|
const AXIS_TOP_TITLE_BASELINE = 18
|
|
70
82
|
const X_AXIS_TITLE_LABEL_SPACE = 32
|
|
71
83
|
const X_AXIS_TITLE_LABEL_PADDING = 9
|
|
72
84
|
const TICK_ROTATION_VERTICAL_ANCHOR_THRESHOLD = -50
|
|
73
85
|
const AXIS_MARGIN_PADDING = 12
|
|
86
|
+
const AXIS_OUTER_PADDING = 8
|
|
74
87
|
const MIN_LEFT_MARGIN = 64
|
|
75
88
|
const MAX_LEFT_MARGIN = 240
|
|
76
|
-
const MIN_X_AXIS_MARGIN =
|
|
89
|
+
const MIN_X_AXIS_MARGIN = 16
|
|
77
90
|
const MAX_X_AXIS_MARGIN = 220
|
|
91
|
+
const MIN_GRID_WIDTH = 24
|
|
92
|
+
const MIN_GRID_HEIGHT = 24
|
|
93
|
+
const MAX_HEATMAP_GRID_WIDTH = 1_000_000
|
|
94
|
+
const MAX_X_AXIS_SCROLL_INSET = MAX_LEFT_MARGIN
|
|
95
|
+
const SCROLL_EDGE_TOLERANCE = 1
|
|
96
|
+
const DEFAULT_SCROLL_STATE: HeatMapScrollState = {
|
|
97
|
+
scrollbarSpace: 0,
|
|
98
|
+
canScrollLeft: false,
|
|
99
|
+
canScrollRight: false
|
|
100
|
+
}
|
|
78
101
|
const AXIS_TICK_FONT = `normal ${AXIS_TICK_FONT_SIZE}px Nunito, sans-serif`
|
|
79
102
|
const DEFAULT_SERIES_LABEL = 'Series'
|
|
80
103
|
const DEFAULT_VALUE_LABEL = 'Value'
|
|
@@ -105,6 +128,30 @@ const getRotatedLabelHeight = (labels: string[], rotationDegrees: number) => {
|
|
|
105
128
|
return Math.ceil(Math.abs(Math.sin(radians)) * labelWidth + Math.abs(Math.cos(radians)) * AXIS_TICK_FONT_SIZE)
|
|
106
129
|
}
|
|
107
130
|
|
|
131
|
+
const getProjectedLabelWidth = (label: string, rotationDegrees: number) => {
|
|
132
|
+
const radians = (Math.abs(rotationDegrees) * Math.PI) / 180
|
|
133
|
+
|
|
134
|
+
return Math.abs(Math.cos(radians)) * getMeasuredTextWidth(label) + Math.abs(Math.sin(radians)) * AXIS_TICK_FONT_SIZE
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const getXAxisEndpointMargins = (labels: string[], rotationDegrees: number, xAxisPosition: HeatMapXAxisPosition) => {
|
|
138
|
+
if (!labels.length) return { left: 0, right: 0 }
|
|
139
|
+
|
|
140
|
+
const firstLabelWidth = getProjectedLabelWidth(labels[0], rotationDegrees)
|
|
141
|
+
const lastLabelWidth = getProjectedLabelWidth(labels[labels.length - 1], rotationDegrees)
|
|
142
|
+
|
|
143
|
+
if (!rotationDegrees) {
|
|
144
|
+
return {
|
|
145
|
+
left: firstLabelWidth / 2 + AXIS_OUTER_PADDING,
|
|
146
|
+
right: lastLabelWidth / 2 + AXIS_OUTER_PADDING
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return xAxisPosition === 'top'
|
|
151
|
+
? { left: 0, right: lastLabelWidth + AXIS_OUTER_PADDING }
|
|
152
|
+
: { left: firstLabelWidth + AXIS_OUTER_PADDING, right: 0 }
|
|
153
|
+
}
|
|
154
|
+
|
|
108
155
|
const getXAxisTitleDistance = (labels: string[], rotationDegrees: number, columnLabelGap: number) => {
|
|
109
156
|
const rotatedLabelHeight = getRotatedLabelHeight(labels, rotationDegrees)
|
|
110
157
|
return columnLabelGap + Math.max(rotatedLabelHeight + X_AXIS_TITLE_LABEL_PADDING, X_AXIS_TITLE_LABEL_SPACE)
|
|
@@ -131,7 +178,7 @@ const getXAxisTickLabelProps = (xAxisPosition: HeatMapXAxisPosition, xTickRotati
|
|
|
131
178
|
if (!xTickRotation) {
|
|
132
179
|
return {
|
|
133
180
|
fontSize: AXIS_TICK_FONT_SIZE,
|
|
134
|
-
textAnchor: 'middle',
|
|
181
|
+
textAnchor: 'middle' as const,
|
|
135
182
|
angle: 0,
|
|
136
183
|
dx: 0,
|
|
137
184
|
y: xAxisPosition === 'top' ? -columnLabelGap : columnLabelGap
|
|
@@ -140,9 +187,9 @@ const getXAxisTickLabelProps = (xAxisPosition: HeatMapXAxisPosition, xTickRotati
|
|
|
140
187
|
|
|
141
188
|
return {
|
|
142
189
|
fontSize: AXIS_TICK_FONT_SIZE,
|
|
143
|
-
textAnchor: xAxisPosition === 'top' ? 'start' : 'end',
|
|
190
|
+
textAnchor: xAxisPosition === 'top' ? ('start' as const) : ('end' as const),
|
|
144
191
|
angle: xTickRotation,
|
|
145
|
-
dx:
|
|
192
|
+
dx: 0,
|
|
146
193
|
y: xAxisPosition === 'top' ? -columnLabelGap : columnLabelGap,
|
|
147
194
|
...(isSteepRotation ? { verticalAnchor: 'middle' as const } : {})
|
|
148
195
|
}
|
|
@@ -240,39 +287,54 @@ const buildChartMargins = (
|
|
|
240
287
|
)
|
|
241
288
|
const xAxisPosition = getHeatMapXAxisPosition(config)
|
|
242
289
|
const xTickRotation = getNonNegativeConfigNumber(config.xAxis?.tickRotation ?? config.xAxis?.maxTickRotation, 0)
|
|
290
|
+
const endpointMargins = config.xAxis?.hideLabel
|
|
291
|
+
? { left: 0, right: 0 }
|
|
292
|
+
: getXAxisEndpointMargins(columnLabels, xTickRotation, xAxisPosition)
|
|
243
293
|
const xTickLabelSpace = config.xAxis?.hideLabel
|
|
244
294
|
? 0
|
|
245
295
|
: columnLabelGap + getRotatedLabelHeight(columnLabels, xTickRotation)
|
|
296
|
+
const xAxisTitleSpace =
|
|
297
|
+
!config.hideXAxisLabel && config.xAxis?.label
|
|
298
|
+
? getXAxisTitleDistance(config.xAxis?.hideLabel ? [] : columnLabels, xTickRotation, columnLabelGap) +
|
|
299
|
+
(xAxisPosition === 'top' ? AXIS_TITLE_FONT_SIZE : AXIS_TITLE_FONT_SIZE * 0.25)
|
|
300
|
+
: 0
|
|
246
301
|
const yAxisLabel = getHeatMapYAxisLabel(config)
|
|
247
302
|
const hasTopYAxisTitle = shouldRenderTopYAxisTitle(config)
|
|
248
303
|
const topYAxisTitleSpace = hasTopYAxisTitle ? AXIS_TOP_LABEL_SPACE : 0
|
|
249
304
|
const sideYAxisTitleSpace = !config.hideYAxisLabel && yAxisLabel && !hasTopYAxisTitle ? AXIS_TITLE_SPACE : 0
|
|
250
305
|
const left = clamp(
|
|
251
|
-
Math.max(
|
|
306
|
+
Math.max(
|
|
307
|
+
yAxisSize,
|
|
308
|
+
rowLabelWidth +
|
|
309
|
+
sideYAxisTitleSpace +
|
|
310
|
+
AXIS_MARGIN_PADDING +
|
|
311
|
+
(config.yAxis?.hideLabel
|
|
312
|
+
? 0
|
|
313
|
+
: getNonNegativeConfigNumber(config.heatmap?.rowLabelGap, HEATMAP_CONFIG_DEFAULTS.rowLabelGap)),
|
|
314
|
+
endpointMargins.left,
|
|
315
|
+
yAxisLabel ? 96 : 72
|
|
316
|
+
),
|
|
252
317
|
MIN_LEFT_MARGIN,
|
|
253
318
|
getAxisMarginMax(parentWidth, MAX_LEFT_MARGIN)
|
|
254
319
|
)
|
|
255
|
-
const
|
|
256
|
-
const topLabelSpace = !config.hideXAxisLabel && config.xAxis?.label ? AXIS_TOP_LABEL_SPACE : 0
|
|
320
|
+
const xTickSpace = config.xAxis?.hideTicks ? 0 : AXIS_TICK_LENGTH
|
|
257
321
|
const xAxisMargin = clamp(
|
|
258
|
-
Math.max(xAxisSize, xTickLabelSpace
|
|
322
|
+
Math.max(xAxisSize, Math.max(xTickLabelSpace, xAxisTitleSpace, xTickSpace) + AXIS_OUTER_PADDING),
|
|
259
323
|
MIN_X_AXIS_MARGIN,
|
|
260
324
|
MAX_X_AXIS_MARGIN
|
|
261
325
|
)
|
|
262
326
|
|
|
263
327
|
return {
|
|
264
328
|
top: (xAxisPosition === 'top' ? xAxisMargin : 24) + topYAxisTitleSpace,
|
|
265
|
-
right: 16,
|
|
266
|
-
bottom: xAxisPosition === 'bottom' ? xAxisMargin :
|
|
329
|
+
right: clamp(endpointMargins.right, 16, getAxisMarginMax(parentWidth, MAX_LEFT_MARGIN)),
|
|
330
|
+
bottom: xAxisPosition === 'bottom' ? xAxisMargin : AXIS_OUTER_PADDING,
|
|
267
331
|
left
|
|
268
332
|
}
|
|
269
333
|
}
|
|
270
334
|
|
|
271
335
|
/**
|
|
272
|
-
* Build a
|
|
273
|
-
*
|
|
274
|
-
* row-label-to-grid gap. Keeping that as an explicit HeatMap option makes visual
|
|
275
|
-
* tuning predictable when rows use long city, state, or category names.
|
|
336
|
+
* Build a rectangular-cell matrix that fills the area left after reserving axis margins.
|
|
337
|
+
* Row-label spacing belongs in the left margin, so the grid stays against the row axis.
|
|
276
338
|
*/
|
|
277
339
|
const buildGridLayout = (
|
|
278
340
|
parentWidth: number,
|
|
@@ -280,25 +342,26 @@ const buildGridLayout = (
|
|
|
280
342
|
margins: HeatMapMargins,
|
|
281
343
|
columnCount: number,
|
|
282
344
|
rowCount: number,
|
|
283
|
-
|
|
345
|
+
minimumColumnWidth = 0
|
|
284
346
|
): HeatMapLayout => {
|
|
285
347
|
const availableWidth = Math.max(parentWidth - margins.left - margins.right, 0)
|
|
286
348
|
const availableHeight = Math.max(parentHeight - margins.top - margins.bottom, 0)
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
const gridWidth =
|
|
291
|
-
const gridHeight =
|
|
292
|
-
const
|
|
293
|
-
const
|
|
349
|
+
const xOffset = 0
|
|
350
|
+
const responsiveGridWidth = Math.max(availableWidth - xOffset, 0)
|
|
351
|
+
const boundedMinimumGridWidth = Math.min(minimumColumnWidth * columnCount, MAX_HEATMAP_GRID_WIDTH)
|
|
352
|
+
const gridWidth = Math.max(responsiveGridWidth, boundedMinimumGridWidth)
|
|
353
|
+
const gridHeight = availableHeight
|
|
354
|
+
const cellWidth = gridWidth / Math.max(columnCount, 1)
|
|
355
|
+
const cellHeight = gridHeight / Math.max(rowCount, 1)
|
|
294
356
|
|
|
295
357
|
return {
|
|
296
358
|
availableWidth,
|
|
297
359
|
availableHeight,
|
|
360
|
+
responsiveGridWidth,
|
|
298
361
|
gridWidth,
|
|
299
362
|
gridHeight,
|
|
300
|
-
|
|
301
|
-
|
|
363
|
+
cellWidth,
|
|
364
|
+
cellHeight,
|
|
302
365
|
xOffset,
|
|
303
366
|
yOffset: 0
|
|
304
367
|
}
|
|
@@ -323,7 +386,7 @@ const getTooltipColumns = (
|
|
|
323
386
|
tooltipColumns.push({
|
|
324
387
|
label: value.label || columnName,
|
|
325
388
|
name: columnName,
|
|
326
|
-
options:
|
|
389
|
+
options: getAdditionalColumnFormattingParams(value)
|
|
327
390
|
})
|
|
328
391
|
|
|
329
392
|
return tooltipColumns
|
|
@@ -457,8 +520,11 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
457
520
|
formatDate,
|
|
458
521
|
formatTooltipsDate,
|
|
459
522
|
formatNumber,
|
|
460
|
-
handleChartAriaLabels
|
|
523
|
+
handleChartAriaLabels,
|
|
524
|
+
currentViewport
|
|
461
525
|
} = useContext(ConfigContext)
|
|
526
|
+
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
|
527
|
+
const [scrollState, setScrollState] = useState(DEFAULT_SCROLL_STATE)
|
|
462
528
|
|
|
463
529
|
const parseDateValue: ParseDateFn =
|
|
464
530
|
typeof parseDate === 'function'
|
|
@@ -499,9 +565,31 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
499
565
|
() => buildXLabelLookup(columns, config.xAxis?.type, formatDateValue, parseDateValue),
|
|
500
566
|
[columns, config.xAxis?.type, formatDateValue, parseDateValue]
|
|
501
567
|
)
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
568
|
+
const columnCount = Math.max(columns.length, 1)
|
|
569
|
+
const rowCount = Math.max(rowLabels.length, 1)
|
|
570
|
+
const xAxisPosition = getHeatMapXAxisPosition(config)
|
|
571
|
+
const xTickRotation = -getNonNegativeConfigNumber(config.xAxis?.tickRotation ?? config.xAxis?.maxTickRotation, 0)
|
|
572
|
+
const yTickRotation = -getNonNegativeConfigNumber(config.yAxis?.tickRotation, 0)
|
|
573
|
+
const xAxisDomain = useMemo(() => columns.map(column => column.key), [columns])
|
|
574
|
+
const viewportNumTicks = config.xAxis?.viewportNumTicks
|
|
575
|
+
const xAxisTickColor = config.xAxis?.tickColor || '#333'
|
|
576
|
+
const requestedXAxisTickCount =
|
|
577
|
+
(currentViewport && viewportNumTicks?.[String(currentViewport)]) || config.xAxis?.numTicks
|
|
578
|
+
const marginXAxisTickValues = useMemo(
|
|
579
|
+
() =>
|
|
580
|
+
getHeatMapXAxisTickValues({
|
|
581
|
+
domain: xAxisDomain,
|
|
582
|
+
formattedLabels: xLabelLookup,
|
|
583
|
+
rotationDegrees: xTickRotation,
|
|
584
|
+
requestedCount: requestedXAxisTickCount,
|
|
585
|
+
measureLabel: getMeasuredTextWidth,
|
|
586
|
+
fontSize: AXIS_TICK_FONT_SIZE
|
|
587
|
+
}),
|
|
588
|
+
[requestedXAxisTickCount, xAxisDomain, xLabelLookup, xTickRotation]
|
|
589
|
+
)
|
|
590
|
+
const marginXAxisLabels = useMemo(
|
|
591
|
+
() => marginXAxisTickValues.map(value => xLabelLookup[value] || value),
|
|
592
|
+
[marginXAxisTickValues, xLabelLookup]
|
|
505
593
|
)
|
|
506
594
|
|
|
507
595
|
const tooltipXLabelLookup = useMemo(
|
|
@@ -509,38 +597,159 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
509
597
|
[columns, config.xAxis?.type, formatTooltipDateValue, parseDateValue]
|
|
510
598
|
)
|
|
511
599
|
|
|
512
|
-
const
|
|
513
|
-
() => buildChartMargins(config, rowLabels,
|
|
514
|
-
[config,
|
|
600
|
+
const baseMargins = useMemo(
|
|
601
|
+
() => buildChartMargins(config, rowLabels, marginXAxisLabels, parentWidth),
|
|
602
|
+
[config, marginXAxisLabels, parentWidth, rowLabels]
|
|
515
603
|
)
|
|
516
604
|
const configuredRowLabelGap = getNonNegativeConfigNumber(
|
|
517
605
|
config.heatmap?.rowLabelGap,
|
|
518
606
|
HEATMAP_CONFIG_DEFAULTS.rowLabelGap
|
|
519
607
|
)
|
|
520
|
-
|
|
608
|
+
// Bound extreme gaps to the reserved label area so labels and the grid remain visible.
|
|
609
|
+
const rowLabelGap = config.yAxis?.hideLabel
|
|
610
|
+
? 0
|
|
611
|
+
: Math.min(
|
|
612
|
+
configuredRowLabelGap,
|
|
613
|
+
Math.max(
|
|
614
|
+
baseMargins.left -
|
|
615
|
+
getWidestLabelWidth(rowLabels) -
|
|
616
|
+
(!config.hideYAxisLabel && getHeatMapYAxisLabel(config) && !shouldRenderTopYAxisTitle(config)
|
|
617
|
+
? AXIS_TITLE_SPACE
|
|
618
|
+
: 0) -
|
|
619
|
+
AXIS_MARGIN_PADDING,
|
|
620
|
+
0
|
|
621
|
+
)
|
|
622
|
+
)
|
|
521
623
|
const columnLabelGap = getNonNegativeConfigNumber(
|
|
522
624
|
config.heatmap?.columnLabelGap,
|
|
523
625
|
HEATMAP_CONFIG_DEFAULTS.columnLabelGap
|
|
524
626
|
)
|
|
627
|
+
const horizontalScrollEnabled = Boolean(config.heatmap?.horizontalScroll ?? HEATMAP_CONFIG_DEFAULTS.horizontalScroll)
|
|
628
|
+
const minimumColumnWidth = horizontalScrollEnabled
|
|
629
|
+
? clamp(
|
|
630
|
+
getNonNegativeConfigNumber(config.heatmap?.minColumnWidth, HEATMAP_CONFIG_DEFAULTS.minColumnWidth),
|
|
631
|
+
MIN_HEATMAP_COLUMN_WIDTH,
|
|
632
|
+
MAX_HEATMAP_COLUMN_WIDTH
|
|
633
|
+
)
|
|
634
|
+
: 0
|
|
525
635
|
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
() => buildGridLayout(parentWidth, parentHeight, margins, columnCount, rowCount, rowLabelGap),
|
|
530
|
-
[parentWidth, parentHeight, margins, columnCount, rowCount, rowLabelGap]
|
|
636
|
+
const initialLayout = useMemo(
|
|
637
|
+
() => buildGridLayout(parentWidth, parentHeight, baseMargins, columnCount, rowCount, minimumColumnWidth),
|
|
638
|
+
[parentWidth, parentHeight, baseMargins, columnCount, rowCount, minimumColumnWidth]
|
|
531
639
|
)
|
|
640
|
+
const isHorizontallyScrollable =
|
|
641
|
+
horizontalScrollEnabled && initialLayout.gridWidth > initialLayout.responsiveGridWidth
|
|
642
|
+
useLayoutEffect(() => {
|
|
643
|
+
if (!isHorizontallyScrollable) {
|
|
644
|
+
setScrollState(currentState =>
|
|
645
|
+
currentState.scrollbarSpace === 0 && !currentState.canScrollLeft && !currentState.canScrollRight
|
|
646
|
+
? currentState
|
|
647
|
+
: DEFAULT_SCROLL_STATE
|
|
648
|
+
)
|
|
649
|
+
return
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const scrollArea = scrollAreaRef.current
|
|
653
|
+
if (!scrollArea) return
|
|
654
|
+
|
|
655
|
+
const updateScrollState = () => {
|
|
656
|
+
const measuredSpace = Math.max(scrollArea.offsetHeight - scrollArea.clientHeight, 0)
|
|
657
|
+
const maximumScrollLeft = Math.max(scrollArea.scrollWidth - scrollArea.clientWidth, 0)
|
|
658
|
+
const normalizedScrollLeft = Math.max(scrollArea.scrollLeft, 0)
|
|
659
|
+
const canScrollLeft = normalizedScrollLeft > SCROLL_EDGE_TOLERANCE
|
|
660
|
+
const canScrollRight = normalizedScrollLeft < maximumScrollLeft - SCROLL_EDGE_TOLERANCE
|
|
661
|
+
|
|
662
|
+
setScrollState(currentState =>
|
|
663
|
+
currentState.scrollbarSpace === measuredSpace &&
|
|
664
|
+
currentState.canScrollLeft === canScrollLeft &&
|
|
665
|
+
currentState.canScrollRight === canScrollRight
|
|
666
|
+
? currentState
|
|
667
|
+
: { scrollbarSpace: measuredSpace, canScrollLeft, canScrollRight }
|
|
668
|
+
)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
updateScrollState()
|
|
672
|
+
scrollArea.addEventListener('scroll', updateScrollState, { passive: true })
|
|
673
|
+
|
|
674
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
675
|
+
return () => scrollArea.removeEventListener('scroll', updateScrollState)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const resizeObserver = new ResizeObserver(updateScrollState)
|
|
679
|
+
resizeObserver.observe(scrollArea)
|
|
680
|
+
return () => {
|
|
681
|
+
scrollArea.removeEventListener('scroll', updateScrollState)
|
|
682
|
+
resizeObserver.disconnect()
|
|
683
|
+
}
|
|
684
|
+
}, [columnCount, isHorizontallyScrollable, marginXAxisLabels, minimumColumnWidth, parentHeight, parentWidth])
|
|
685
|
+
const chartHeight = Math.max(parentHeight - (isHorizontallyScrollable ? scrollState.scrollbarSpace : 0), 0)
|
|
686
|
+
const margins = useMemo(() => {
|
|
687
|
+
if (!yTickRotation || config.yAxis?.hideLabel) return baseMargins
|
|
688
|
+
|
|
689
|
+
const rotatedRowLabelHeight = getRotatedLabelHeight(rowLabels, Math.abs(yTickRotation))
|
|
690
|
+
// The last row label starts at its cell center. Its overflow and the x-axis
|
|
691
|
+
// occupy the same bottom margin; adding both reservations wastes plot height.
|
|
692
|
+
const halfRowFraction = 1 / (2 * Math.max(rowCount, 1))
|
|
693
|
+
const desiredBottomMargin = Math.max(
|
|
694
|
+
baseMargins.bottom,
|
|
695
|
+
Math.ceil(
|
|
696
|
+
(rotatedRowLabelHeight + AXIS_OUTER_PADDING - (chartHeight - baseMargins.top) * halfRowFraction) /
|
|
697
|
+
(1 - halfRowFraction)
|
|
698
|
+
)
|
|
699
|
+
)
|
|
700
|
+
const availableBottomMargin = Math.max(chartHeight - baseMargins.top - MIN_GRID_HEIGHT, 0)
|
|
701
|
+
const maximumBottomMargin = Math.max(baseMargins.bottom, availableBottomMargin)
|
|
702
|
+
|
|
703
|
+
return {
|
|
704
|
+
...baseMargins,
|
|
705
|
+
bottom: Math.min(desiredBottomMargin, maximumBottomMargin)
|
|
706
|
+
}
|
|
707
|
+
}, [baseMargins, chartHeight, config.yAxis?.hideLabel, rowLabels, rowCount, yTickRotation])
|
|
708
|
+
const { gridWidth, gridHeight, cellWidth, cellHeight, xOffset, yOffset } = useMemo(
|
|
709
|
+
() => buildGridLayout(parentWidth, chartHeight, margins, columnCount, rowCount, minimumColumnWidth),
|
|
710
|
+
[parentWidth, chartHeight, margins, columnCount, rowCount, minimumColumnWidth]
|
|
711
|
+
)
|
|
712
|
+
const xAxisEndpointMargins = useMemo(
|
|
713
|
+
() =>
|
|
714
|
+
config.xAxis?.hideLabel
|
|
715
|
+
? { left: 0, right: 0 }
|
|
716
|
+
: getXAxisEndpointMargins(marginXAxisLabels, xTickRotation, xAxisPosition),
|
|
717
|
+
[config.xAxis?.hideLabel, marginXAxisLabels, xAxisPosition, xTickRotation]
|
|
718
|
+
)
|
|
719
|
+
const scrollViewportWidth = Math.max(parentWidth - margins.left, 0)
|
|
720
|
+
const minimumVisibleCellWidth = Math.min(cellWidth, MIN_GRID_WIDTH)
|
|
721
|
+
const maximumViewportInset = Math.max(scrollViewportWidth - xOffset - minimumVisibleCellWidth, 0)
|
|
722
|
+
const scrollXAxisInset = isHorizontallyScrollable
|
|
723
|
+
? Math.min(Math.max(xAxisEndpointMargins.left - cellWidth / 2, 0), MAX_X_AXIS_SCROLL_INSET, maximumViewportInset)
|
|
724
|
+
: 0
|
|
725
|
+
const plotXOffset = xOffset + scrollXAxisInset
|
|
726
|
+
const scrollContentWidth = plotXOffset + gridWidth + margins.right
|
|
532
727
|
const cellGap = getNonNegativeConfigNumber(config.heatmap?.cellPadding, HEATMAP_CONFIG_DEFAULTS.cellPadding)
|
|
533
|
-
// During responsive measurement,
|
|
534
|
-
const effectiveCellGap = Math.min(cellGap, Math.max(
|
|
728
|
+
// During responsive measurement, cell dimensions can briefly be 0. Clamp the effective gap so SVG rects never go negative.
|
|
729
|
+
const effectiveCellGap = Math.min(cellGap, Math.max(Math.min(cellWidth, cellHeight) - 1, 0))
|
|
730
|
+
|
|
731
|
+
const xAxisTickValues = useMemo(
|
|
732
|
+
() =>
|
|
733
|
+
getHeatMapXAxisTickValues({
|
|
734
|
+
domain: xAxisDomain,
|
|
735
|
+
formattedLabels: xLabelLookup,
|
|
736
|
+
gridWidth,
|
|
737
|
+
rotationDegrees: xTickRotation,
|
|
738
|
+
requestedCount: requestedXAxisTickCount,
|
|
739
|
+
measureLabel: getMeasuredTextWidth,
|
|
740
|
+
fontSize: AXIS_TICK_FONT_SIZE
|
|
741
|
+
}),
|
|
742
|
+
[gridWidth, requestedXAxisTickCount, xAxisDomain, xLabelLookup, xTickRotation]
|
|
743
|
+
)
|
|
535
744
|
|
|
536
745
|
const xAxisScale = useMemo(
|
|
537
746
|
() =>
|
|
538
747
|
scaleBand<string>({
|
|
539
|
-
domain:
|
|
540
|
-
range: [
|
|
748
|
+
domain: xAxisDomain,
|
|
749
|
+
range: [plotXOffset, plotXOffset + gridWidth],
|
|
541
750
|
padding: 0
|
|
542
751
|
}),
|
|
543
|
-
[
|
|
752
|
+
[gridWidth, plotXOffset, xAxisDomain]
|
|
544
753
|
)
|
|
545
754
|
|
|
546
755
|
const yAxisScale = useMemo(
|
|
@@ -576,30 +785,28 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
576
785
|
}
|
|
577
786
|
|
|
578
787
|
const tooltipId = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
788
|
+
const scrollInstructionsId = `${tooltipId}-scroll-instructions`
|
|
579
789
|
const xAxisLabel = config.xAxis?.label
|
|
580
790
|
const yAxisLabel = getHeatMapYAxisLabel(config)
|
|
581
|
-
const
|
|
582
|
-
const
|
|
583
|
-
const
|
|
791
|
+
const chartAriaLabel = String(handleChartAriaLabels(config))
|
|
792
|
+
const scrollRegionLabel = `${chartAriaLabel}; ${xAxisLabel || xDataKey || 'HeatMap'}; scrollable columns`
|
|
793
|
+
const fixedYAxisAriaLabel = `${chartAriaLabel}; ${yAxisLabel || DEFAULT_SERIES_LABEL} row axis`
|
|
584
794
|
const showCellValues = Boolean(config.heatmap?.showCellValues)
|
|
585
795
|
const showTopYAxisTitle = shouldRenderTopYAxisTitle(config)
|
|
586
|
-
const visibleXAxisLabels = config.xAxis?.hideLabel ? [] :
|
|
796
|
+
const visibleXAxisLabels = config.xAxis?.hideLabel ? [] : xAxisTickValues.map(value => xLabelLookup[value] || value)
|
|
587
797
|
const xAxisTitleDistance = getXAxisTitleDistance(visibleXAxisLabels, Math.abs(xTickRotation), columnLabelGap)
|
|
588
|
-
const rowLabelTitleX = config.yAxis?.hideLabel ? 0 : -getWidestLabelWidth(rowLabels)
|
|
798
|
+
const rowLabelTitleX = config.yAxis?.hideLabel ? 0 : -getWidestLabelWidth(rowLabels) - rowLabelGap
|
|
589
799
|
const sideYAxisTitleX = -Math.max(Math.abs(rowLabelTitleX) + SIDE_Y_AXIS_TITLE_GAP, AXIS_TITLE_SPACE)
|
|
590
800
|
const hasXAxisTitle = !config.hideXAxisLabel && Boolean(xAxisLabel)
|
|
591
801
|
const topYAxisTitleY = xAxisPosition === 'top' && hasXAxisTitle ? -xAxisTitleDistance : -AXIS_TOP_TITLE_BASELINE
|
|
592
|
-
const renderXAxisTitle = (
|
|
802
|
+
const renderXAxisTitle = (
|
|
803
|
+
x = plotXOffset + gridWidth / 2,
|
|
804
|
+
y = xAxisPosition === 'top' ? -xAxisTitleDistance : gridHeight + xAxisTitleDistance
|
|
805
|
+
) => {
|
|
593
806
|
if (config.hideXAxisLabel || !xAxisLabel) return null
|
|
594
807
|
|
|
595
808
|
return (
|
|
596
|
-
<text
|
|
597
|
-
className='cdc-heatmap__axis-title'
|
|
598
|
-
x={xOffset + gridWidth / 2}
|
|
599
|
-
y={xAxisPosition === 'top' ? -xAxisTitleDistance : gridHeight + xAxisTitleDistance}
|
|
600
|
-
textAnchor='middle'
|
|
601
|
-
fontWeight={AXIS_TITLE_FONT_WEIGHT}
|
|
602
|
-
>
|
|
809
|
+
<text className='cdc-heatmap__axis-title' x={x} y={y} textAnchor='middle' fontWeight={AXIS_TITLE_FONT_WEIGHT}>
|
|
603
810
|
{xAxisLabel}
|
|
604
811
|
</text>
|
|
605
812
|
)
|
|
@@ -632,134 +839,260 @@ const HeatMap: React.FC<HeatMapProps> = ({ parentWidth, parentHeight }) => {
|
|
|
632
839
|
</text>
|
|
633
840
|
)
|
|
634
841
|
}
|
|
842
|
+
const cellTextWidths = new Map<string, number>()
|
|
843
|
+
const renderCells = () => (
|
|
844
|
+
<HeatmapRect
|
|
845
|
+
data={columns}
|
|
846
|
+
xScale={columnIndex => columnIndex * cellWidth + plotXOffset}
|
|
847
|
+
yScale={rowIndex => rowIndex * cellHeight + yOffset}
|
|
848
|
+
binWidth={cellWidth}
|
|
849
|
+
binHeight={cellHeight}
|
|
850
|
+
gap={effectiveCellGap}
|
|
851
|
+
bins={column => column.bins}
|
|
852
|
+
count={bin => (typeof bin.value === 'number' ? bin.value : 0)}
|
|
853
|
+
>
|
|
854
|
+
{heatmap =>
|
|
855
|
+
heatmap.map(columnBins =>
|
|
856
|
+
columnBins.map(bin => {
|
|
857
|
+
const hasValue = typeof bin.bin.value === 'number'
|
|
858
|
+
const xLabel = tooltipXLabelLookup[String(bin.bin.xValue)] || bin.bin.xLabel
|
|
859
|
+
const renderedCellWidth = Math.max(bin.width, 0)
|
|
860
|
+
const renderedCellHeight = Math.max(bin.height, 0)
|
|
861
|
+
const fillColor = hasValue ? heatMapColorScale(bin.bin.value as number) : undefined
|
|
862
|
+
const cellFontSize = Math.max(9, Math.min(16, renderedCellHeight * 0.5))
|
|
863
|
+
const cellValueText =
|
|
864
|
+
showCellValues && hasValue ? getFormattedValueText(bin.bin, formatHeatMapCellValue) : ''
|
|
865
|
+
const textWidthKey = `${cellFontSize}:${cellValueText}`
|
|
866
|
+
let cellTextWidth = cellTextWidths.get(textWidthKey)
|
|
867
|
+
if (
|
|
868
|
+
cellValueText &&
|
|
869
|
+
renderedCellWidth >= MIN_CELL_VALUE_WIDTH &&
|
|
870
|
+
renderedCellHeight >= MIN_CELL_VALUE_HEIGHT &&
|
|
871
|
+
cellTextWidth === undefined
|
|
872
|
+
) {
|
|
873
|
+
cellTextWidth =
|
|
874
|
+
getTextWidth(cellValueText, `bold ${cellFontSize}px Nunito, sans-serif`) ??
|
|
875
|
+
cellValueText.length * cellFontSize * 0.65
|
|
876
|
+
cellTextWidths.set(textWidthKey, cellTextWidth)
|
|
877
|
+
}
|
|
878
|
+
const shouldShowCellValue =
|
|
879
|
+
showCellValues &&
|
|
880
|
+
hasValue &&
|
|
881
|
+
renderedCellWidth >= MIN_CELL_VALUE_WIDTH &&
|
|
882
|
+
renderedCellHeight >= MIN_CELL_VALUE_HEIGHT &&
|
|
883
|
+
(cellTextWidth ?? 0) <= renderedCellWidth - 4
|
|
884
|
+
|
|
885
|
+
return (
|
|
886
|
+
<React.Fragment key={`heatmap-cell-${bin.column}-${bin.row}`}>
|
|
887
|
+
<rect
|
|
888
|
+
className={`visx-heatmap-rect cdc-heatmap__cell${hasValue ? '' : ' cdc-heatmap__cell--empty'}`}
|
|
889
|
+
role='img'
|
|
890
|
+
tabIndex={0}
|
|
891
|
+
aria-label={getHeatMapCellLabel({
|
|
892
|
+
cell: bin.bin,
|
|
893
|
+
config,
|
|
894
|
+
formatNumber: formatHeatMapCellValue,
|
|
895
|
+
xLabel,
|
|
896
|
+
xDataKey
|
|
897
|
+
})}
|
|
898
|
+
x={bin.x}
|
|
899
|
+
y={bin.y}
|
|
900
|
+
width={renderedCellWidth}
|
|
901
|
+
height={renderedCellHeight}
|
|
902
|
+
fill={fillColor}
|
|
903
|
+
data-tooltip-id={tooltipId}
|
|
904
|
+
data-tooltip-html={buildTooltipHtml({
|
|
905
|
+
cell: bin.bin,
|
|
906
|
+
additionalColumns,
|
|
907
|
+
config,
|
|
908
|
+
formatNumber: formatHeatMapCellValue,
|
|
909
|
+
xLabel,
|
|
910
|
+
xDataKey,
|
|
911
|
+
showSeriesRows: getHeatMapSeriesTooltipEnabled(config, bin.bin.rowKey)
|
|
912
|
+
})}
|
|
913
|
+
/>
|
|
914
|
+
{shouldShowCellValue && (
|
|
915
|
+
<text
|
|
916
|
+
className='cdc-heatmap__cell-value'
|
|
917
|
+
x={bin.x + renderedCellWidth / 2}
|
|
918
|
+
y={bin.y + renderedCellHeight / 2}
|
|
919
|
+
fill={getReadableCellTextColor(fillColor)}
|
|
920
|
+
fontSize={cellFontSize}
|
|
921
|
+
textAnchor='middle'
|
|
922
|
+
dominantBaseline='middle'
|
|
923
|
+
aria-hidden='true'
|
|
924
|
+
>
|
|
925
|
+
{cellValueText}
|
|
926
|
+
</text>
|
|
927
|
+
)}
|
|
928
|
+
</React.Fragment>
|
|
929
|
+
)
|
|
930
|
+
})
|
|
931
|
+
)
|
|
932
|
+
}
|
|
933
|
+
</HeatmapRect>
|
|
934
|
+
)
|
|
935
|
+
const renderXAxis = () => (
|
|
936
|
+
<>
|
|
937
|
+
{!config.xAxis?.hideAxis && (
|
|
938
|
+
<line
|
|
939
|
+
className='cdc-heatmap__x-axis-line'
|
|
940
|
+
x1={0}
|
|
941
|
+
x2={plotXOffset + gridWidth}
|
|
942
|
+
y1={xAxisPosition === 'top' ? 0 : gridHeight}
|
|
943
|
+
y2={xAxisPosition === 'top' ? 0 : gridHeight}
|
|
944
|
+
stroke={xAxisTickColor}
|
|
945
|
+
/>
|
|
946
|
+
)}
|
|
947
|
+
|
|
948
|
+
{xAxisPosition === 'top' ? (
|
|
949
|
+
<AxisTop
|
|
950
|
+
scale={xAxisScale}
|
|
951
|
+
tickValues={xAxisTickValues}
|
|
952
|
+
tickFormat={value => (config.xAxis?.hideLabel ? '' : xLabelLookup[String(value)] || String(value))}
|
|
953
|
+
hideAxisLine={true}
|
|
954
|
+
hideTicks={Boolean(config.xAxis?.hideTicks)}
|
|
955
|
+
hideZero={true}
|
|
956
|
+
tickStroke={xAxisTickColor}
|
|
957
|
+
tickLabelProps={() => getXAxisTickLabelProps(xAxisPosition, xTickRotation, columnLabelGap)}
|
|
958
|
+
/>
|
|
959
|
+
) : (
|
|
960
|
+
<AxisBottom
|
|
961
|
+
top={gridHeight}
|
|
962
|
+
scale={xAxisScale}
|
|
963
|
+
tickValues={xAxisTickValues}
|
|
964
|
+
tickFormat={value => (config.xAxis?.hideLabel ? '' : xLabelLookup[String(value)] || String(value))}
|
|
965
|
+
hideAxisLine={true}
|
|
966
|
+
hideTicks={Boolean(config.xAxis?.hideTicks)}
|
|
967
|
+
hideZero={true}
|
|
968
|
+
tickStroke={xAxisTickColor}
|
|
969
|
+
tickLabelProps={() => getXAxisTickLabelProps(xAxisPosition, xTickRotation, columnLabelGap)}
|
|
970
|
+
/>
|
|
971
|
+
)}
|
|
972
|
+
</>
|
|
973
|
+
)
|
|
974
|
+
const renderYAxis = () => (
|
|
975
|
+
<AxisLeft
|
|
976
|
+
scale={yAxisScale}
|
|
977
|
+
tickFormat={value => (config.yAxis?.hideLabel ? '' : value)}
|
|
978
|
+
hideAxisLine={Boolean(config.yAxis?.hideAxis)}
|
|
979
|
+
hideTicks={Boolean(config.yAxis?.hideTicks)}
|
|
980
|
+
tickLabelProps={() => ({
|
|
981
|
+
fontSize: AXIS_TICK_FONT_SIZE,
|
|
982
|
+
textAnchor: 'end',
|
|
983
|
+
angle: yTickRotation,
|
|
984
|
+
x: -AXIS_TICK_LENGTH - rowLabelGap,
|
|
985
|
+
dx: yTickRotation ? '-0.25em' : 0,
|
|
986
|
+
dy: '0.33em'
|
|
987
|
+
})}
|
|
988
|
+
/>
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
const tooltip = (
|
|
992
|
+
<ReactTooltip
|
|
993
|
+
id={tooltipId}
|
|
994
|
+
variant='light'
|
|
995
|
+
className='tooltip'
|
|
996
|
+
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
|
|
997
|
+
/>
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
if (isHorizontallyScrollable) {
|
|
1001
|
+
const fixedXAxisTitleY =
|
|
1002
|
+
margins.top + (xAxisPosition === 'top' ? -xAxisTitleDistance : gridHeight + xAxisTitleDistance)
|
|
1003
|
+
const fixedXAxisTitleX = scrollViewportWidth / 2
|
|
1004
|
+
|
|
1005
|
+
return (
|
|
1006
|
+
<div className='cdc-heatmap cdc-heatmap--scrollable'>
|
|
1007
|
+
<div
|
|
1008
|
+
className='cdc-heatmap__scroll-layout'
|
|
1009
|
+
style={{ gridTemplateColumns: `${margins.left}px minmax(0, 1fr)`, height: parentHeight }}
|
|
1010
|
+
>
|
|
1011
|
+
<svg
|
|
1012
|
+
width={margins.left}
|
|
1013
|
+
height={chartHeight}
|
|
1014
|
+
role='img'
|
|
1015
|
+
aria-label={fixedYAxisAriaLabel}
|
|
1016
|
+
className='cdc-heatmap__fixed-axis-svg'
|
|
1017
|
+
>
|
|
1018
|
+
<Group className='cdc-heatmap__fixed-y-axis' top={margins.top} left={margins.left}>
|
|
1019
|
+
{renderYAxis()}
|
|
1020
|
+
{renderYAxisTitle()}
|
|
1021
|
+
</Group>
|
|
1022
|
+
</svg>
|
|
1023
|
+
<div className='cdc-heatmap__scroll-plot'>
|
|
1024
|
+
<div
|
|
1025
|
+
ref={scrollAreaRef}
|
|
1026
|
+
className='cdc-heatmap__scroll-area'
|
|
1027
|
+
role='region'
|
|
1028
|
+
tabIndex={0}
|
|
1029
|
+
aria-label={scrollRegionLabel}
|
|
1030
|
+
aria-describedby={scrollInstructionsId}
|
|
1031
|
+
>
|
|
1032
|
+
<svg
|
|
1033
|
+
width={scrollContentWidth}
|
|
1034
|
+
height={chartHeight}
|
|
1035
|
+
aria-label={chartAriaLabel}
|
|
1036
|
+
className={`cdc-heatmap__svg cdc-heatmap__scroll-content${config.animate ? ' animated' : ''}`}
|
|
1037
|
+
>
|
|
1038
|
+
<Group className='cdc-heatmap__plot' top={margins.top} left={0}>
|
|
1039
|
+
{renderCells()}
|
|
1040
|
+
{renderXAxis()}
|
|
1041
|
+
</Group>
|
|
1042
|
+
</svg>
|
|
1043
|
+
</div>
|
|
1044
|
+
<span id={scrollInstructionsId} className='cdcdataviz-sr-only'>
|
|
1045
|
+
Scroll horizontally to view additional columns.
|
|
1046
|
+
</span>
|
|
1047
|
+
<div
|
|
1048
|
+
className={`cdc-heatmap__scroll-cue cdc-heatmap__scroll-cue--left${
|
|
1049
|
+
scrollState.canScrollLeft ? ' is-visible' : ''
|
|
1050
|
+
}`}
|
|
1051
|
+
style={{ bottom: scrollState.scrollbarSpace }}
|
|
1052
|
+
aria-hidden='true'
|
|
1053
|
+
/>
|
|
1054
|
+
<div
|
|
1055
|
+
className={`cdc-heatmap__scroll-cue cdc-heatmap__scroll-cue--right${
|
|
1056
|
+
scrollState.canScrollRight ? ' is-visible' : ''
|
|
1057
|
+
}`}
|
|
1058
|
+
style={{ bottom: scrollState.scrollbarSpace }}
|
|
1059
|
+
aria-hidden='true'
|
|
1060
|
+
/>
|
|
1061
|
+
{hasXAxisTitle && (
|
|
1062
|
+
<svg
|
|
1063
|
+
width={scrollViewportWidth}
|
|
1064
|
+
height={chartHeight}
|
|
1065
|
+
aria-hidden='true'
|
|
1066
|
+
className='cdc-heatmap__fixed-x-title'
|
|
1067
|
+
>
|
|
1068
|
+
{renderXAxisTitle(fixedXAxisTitleX, fixedXAxisTitleY)}
|
|
1069
|
+
</svg>
|
|
1070
|
+
)}
|
|
1071
|
+
</div>
|
|
1072
|
+
</div>
|
|
1073
|
+
{tooltip}
|
|
1074
|
+
</div>
|
|
1075
|
+
)
|
|
1076
|
+
}
|
|
635
1077
|
|
|
636
1078
|
return (
|
|
637
1079
|
<div className='cdc-heatmap'>
|
|
638
1080
|
<svg
|
|
639
1081
|
width={parentWidth}
|
|
640
1082
|
height={parentHeight}
|
|
641
|
-
aria-label={
|
|
1083
|
+
aria-label={chartAriaLabel}
|
|
642
1084
|
className={`cdc-heatmap__svg${config.animate ? ' animated' : ''}`}
|
|
643
1085
|
>
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
xScale={columnIndex => columnIndex * cellSize + xOffset}
|
|
649
|
-
yScale={rowIndex => rowIndex * cellSize + yOffset}
|
|
650
|
-
binWidth={cellSize}
|
|
651
|
-
binHeight={cellSize}
|
|
652
|
-
gap={effectiveCellGap}
|
|
653
|
-
bins={column => column.bins}
|
|
654
|
-
count={bin => (typeof bin.value === 'number' ? bin.value : 0)}
|
|
655
|
-
>
|
|
656
|
-
{heatmap =>
|
|
657
|
-
heatmap.map(columnBins =>
|
|
658
|
-
columnBins.map(bin => {
|
|
659
|
-
const hasValue = typeof bin.bin.value === 'number'
|
|
660
|
-
const xLabel = tooltipXLabelLookup[String(bin.bin.xValue)] || bin.bin.xLabel
|
|
661
|
-
const cellWidth = Math.max(bin.width, 0)
|
|
662
|
-
const cellHeight = Math.max(bin.height, 0)
|
|
663
|
-
const fillColor = hasValue ? heatMapColorScale(bin.bin.value as number) : undefined
|
|
664
|
-
const shouldShowCellValue =
|
|
665
|
-
showCellValues &&
|
|
666
|
-
hasValue &&
|
|
667
|
-
cellWidth >= MIN_CELL_VALUE_WIDTH &&
|
|
668
|
-
cellHeight >= MIN_CELL_VALUE_HEIGHT
|
|
669
|
-
return (
|
|
670
|
-
<React.Fragment key={`heatmap-cell-${bin.column}-${bin.row}`}>
|
|
671
|
-
<rect
|
|
672
|
-
className={`visx-heatmap-rect cdc-heatmap__cell${hasValue ? '' : ' cdc-heatmap__cell--empty'}`}
|
|
673
|
-
role='img'
|
|
674
|
-
tabIndex={0}
|
|
675
|
-
aria-label={getHeatMapCellLabel({
|
|
676
|
-
cell: bin.bin,
|
|
677
|
-
config,
|
|
678
|
-
formatNumber: formatHeatMapCellValue,
|
|
679
|
-
xLabel,
|
|
680
|
-
xDataKey
|
|
681
|
-
})}
|
|
682
|
-
x={bin.x}
|
|
683
|
-
y={bin.y}
|
|
684
|
-
width={cellWidth}
|
|
685
|
-
height={cellHeight}
|
|
686
|
-
fill={fillColor}
|
|
687
|
-
data-tooltip-id={tooltipId}
|
|
688
|
-
data-tooltip-html={buildTooltipHtml({
|
|
689
|
-
cell: bin.bin,
|
|
690
|
-
additionalColumns,
|
|
691
|
-
config,
|
|
692
|
-
formatNumber: formatHeatMapCellValue,
|
|
693
|
-
xLabel,
|
|
694
|
-
xDataKey,
|
|
695
|
-
showSeriesRows: getHeatMapSeriesTooltipEnabled(config, bin.bin.rowKey)
|
|
696
|
-
})}
|
|
697
|
-
/>
|
|
698
|
-
{shouldShowCellValue && (
|
|
699
|
-
<text
|
|
700
|
-
className='cdc-heatmap__cell-value'
|
|
701
|
-
x={bin.x + cellWidth / 2}
|
|
702
|
-
y={bin.y + cellHeight / 2}
|
|
703
|
-
fill={getReadableCellTextColor(fillColor)}
|
|
704
|
-
fontSize={Math.max(9, Math.min(12, cellHeight * 0.35))}
|
|
705
|
-
textAnchor='middle'
|
|
706
|
-
dominantBaseline='middle'
|
|
707
|
-
aria-hidden='true'
|
|
708
|
-
>
|
|
709
|
-
{getFormattedValueText(bin.bin, formatHeatMapCellValue)}
|
|
710
|
-
</text>
|
|
711
|
-
)}
|
|
712
|
-
</React.Fragment>
|
|
713
|
-
)
|
|
714
|
-
})
|
|
715
|
-
)
|
|
716
|
-
}
|
|
717
|
-
</HeatmapRect>
|
|
718
|
-
|
|
719
|
-
{xAxisPosition === 'top' ? (
|
|
720
|
-
<AxisTop
|
|
721
|
-
scale={xAxisScale}
|
|
722
|
-
tickFormat={value => (config.xAxis?.hideLabel ? '' : xLabelLookup[String(value)] || String(value))}
|
|
723
|
-
hideAxisLine={Boolean(config.xAxis?.hideAxis)}
|
|
724
|
-
hideTicks={Boolean(config.xAxis?.hideTicks)}
|
|
725
|
-
hideZero={true}
|
|
726
|
-
tickLabelProps={() => getXAxisTickLabelProps(xAxisPosition, xTickRotation, columnLabelGap)}
|
|
727
|
-
/>
|
|
728
|
-
) : (
|
|
729
|
-
<AxisBottom
|
|
730
|
-
top={gridHeight}
|
|
731
|
-
scale={xAxisScale}
|
|
732
|
-
tickFormat={value => (config.xAxis?.hideLabel ? '' : xLabelLookup[String(value)] || String(value))}
|
|
733
|
-
hideAxisLine={Boolean(config.xAxis?.hideAxis)}
|
|
734
|
-
hideTicks={Boolean(config.xAxis?.hideTicks)}
|
|
735
|
-
hideZero={true}
|
|
736
|
-
tickLabelProps={() => getXAxisTickLabelProps(xAxisPosition, xTickRotation, columnLabelGap)}
|
|
737
|
-
/>
|
|
738
|
-
)}
|
|
739
|
-
<AxisLeft
|
|
740
|
-
scale={yAxisScale}
|
|
741
|
-
tickFormat={value => (config.yAxis?.hideLabel ? '' : value)}
|
|
742
|
-
hideAxisLine={Boolean(config.yAxis?.hideAxis)}
|
|
743
|
-
hideTicks={Boolean(config.yAxis?.hideTicks)}
|
|
744
|
-
tickLabelProps={() => ({
|
|
745
|
-
fontSize: AXIS_TICK_FONT_SIZE,
|
|
746
|
-
textAnchor: 'end',
|
|
747
|
-
angle: yTickRotation,
|
|
748
|
-
dx: yTickRotation ? '-0.25em' : 0,
|
|
749
|
-
dy: '0.33em'
|
|
750
|
-
})}
|
|
751
|
-
/>
|
|
1086
|
+
<Group className='cdc-heatmap__plot' top={margins.top} left={margins.left}>
|
|
1087
|
+
{renderCells()}
|
|
1088
|
+
{renderXAxis()}
|
|
1089
|
+
{renderYAxis()}
|
|
752
1090
|
|
|
753
1091
|
{renderXAxisTitle()}
|
|
754
1092
|
{renderYAxisTitle()}
|
|
755
1093
|
</Group>
|
|
756
1094
|
</svg>
|
|
757
|
-
|
|
758
|
-
id={tooltipId}
|
|
759
|
-
variant='light'
|
|
760
|
-
className='tooltip'
|
|
761
|
-
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
|
|
762
|
-
/>
|
|
1095
|
+
{tooltip}
|
|
763
1096
|
</div>
|
|
764
1097
|
)
|
|
765
1098
|
}
|