@cdc/chart 4.25.3 → 4.25.6
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/dist/cdcchart.js +46641 -42561
- package/index.html +130 -129
- package/package.json +22 -27
- package/src/CdcChartComponent.tsx +75 -35
- package/src/_stories/Chart.CI.stories.tsx +10 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
- package/src/_stories/Chart.stories.tsx +99 -86
- package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
- package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
- package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
- package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
- package/{examples/private/not-loading.json → src/_stories/_mock/pie_calculated_area.json} +152 -95
- package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
- package/src/components/AreaChart/components/AreaChart.jsx +33 -5
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -37
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +8 -8
- package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -36
- package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
- package/src/components/BarChart/components/context.tsx +20 -2
- package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
- package/src/components/BarChart/helpers/index.ts +5 -2
- package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
- package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
- package/src/components/BoxPlot/BoxPlot.tsx +2 -1
- package/src/components/Brush/BrushChart.tsx +73 -0
- package/src/components/Brush/BrushController..tsx +39 -0
- package/src/components/DeviationBar.jsx +2 -2
- package/src/components/EditorPanel/EditorPanel.tsx +232 -147
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +36 -36
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +52 -25
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +12 -4
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +5 -5
- package/src/components/ForestPlot/ForestPlot.tsx +2 -2
- package/src/components/HoverLine/HoverLine.tsx +74 -0
- package/src/components/Legend/Legend.Component.tsx +1 -1
- package/src/components/Legend/Legend.Suppression.tsx +59 -25
- package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
- package/src/components/Legend/helpers/index.ts +1 -1
- package/src/components/LineChart/LineChartProps.ts +3 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
- package/src/components/LineChart/helpers.ts +133 -56
- package/src/components/LineChart/index.tsx +106 -55
- package/src/components/LinearChart.tsx +178 -198
- package/src/components/PairedBarChart.jsx +3 -2
- package/src/components/PieChart/PieChart.tsx +127 -102
- package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
- package/src/components/Sparkline/components/SparkLine.tsx +80 -18
- package/src/data/initial-state.js +11 -6
- package/src/helpers/countNumOfTicks.ts +1 -1
- package/src/helpers/dataHelpers.ts +23 -2
- package/src/helpers/getNewRuntime.ts +35 -0
- package/src/helpers/getPiePercent.ts +22 -0
- package/src/helpers/getTransformedData.ts +22 -0
- package/src/helpers/sizeHelpers.ts +1 -1
- package/src/helpers/tests/getNewRuntime.test.ts +82 -0
- package/src/helpers/tests/getPiePercent.test.ts +38 -0
- package/src/hooks/useMinMax.ts +21 -28
- package/src/hooks/useRightAxis.ts +5 -3
- package/src/hooks/useScales.ts +15 -6
- package/src/hooks/useTooltip.tsx +218 -203
- package/src/index.jsx +2 -2
- package/src/scss/main.scss +13 -6
- package/src/store/chart.actions.ts +2 -6
- package/src/store/chart.reducer.ts +23 -23
- package/src/types/ChartConfig.ts +11 -3
- package/src/types/ChartContext.ts +0 -2
- package/examples/private/DEV-8850-2.json +0 -493
- package/examples/private/DEV-9822.json +0 -574
- package/examples/private/DEV-9840.json +0 -553
- package/examples/private/DEV-9850-3.json +0 -461
- package/examples/private/chart.json +0 -1084
- package/examples/private/ci_formatted.json +0 -202
- package/examples/private/ci_issue.json +0 -3016
- package/examples/private/completed.json +0 -634
- package/examples/private/dem-data-long.csv +0 -20
- package/examples/private/dem-data-long.json +0 -36
- package/examples/private/demographic_data.csv +0 -157
- package/examples/private/demographic_data.json +0 -2654
- package/examples/private/demographic_dynamic.json +0 -443
- package/examples/private/demographic_standard.json +0 -560
- package/examples/private/ehdi.json +0 -29939
- package/examples/private/test.json +0 -493
- package/src/components/ZoomBrush.tsx +0 -251
package/src/hooks/useTooltip.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
2
|
// Local imports
|
|
3
|
+
import parse from 'html-react-parser'
|
|
3
4
|
import ConfigContext from '../ConfigContext'
|
|
4
5
|
import { type ChartContext } from '../types/ChartContext'
|
|
5
6
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
@@ -8,6 +9,7 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
|
8
9
|
import { localPoint } from '@visx/event'
|
|
9
10
|
import { bisector } from 'd3-array'
|
|
10
11
|
import _ from 'lodash'
|
|
12
|
+
import { getHorizontalBarHeights } from '../components/BarChart/helpers/getBarHeights'
|
|
11
13
|
|
|
12
14
|
export const useTooltip = props => {
|
|
13
15
|
const {
|
|
@@ -21,31 +23,35 @@ export const useTooltip = props => {
|
|
|
21
23
|
setSharedFilter,
|
|
22
24
|
isDraggingAnnotation
|
|
23
25
|
} = useContext<ChartContext>(ConfigContext)
|
|
24
|
-
const { xScale, yScale, showTooltip, hideTooltip } = props
|
|
26
|
+
const { xScale, yScale, seriesScale, showTooltip, hideTooltip } = props
|
|
25
27
|
const { xAxis, visualizationType, orientation, yAxis, runtime } = config
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
* Provides the tooltip information based on the tooltip data array and svg cursor coordinates
|
|
29
|
-
* @function getTooltipInformation
|
|
30
|
-
* @param {Array} tooltipDataArray - The array containing the tooltip data.
|
|
31
|
-
* @param {Object} eventSvgCoords - The object containing the SVG coordinates of the event.
|
|
32
|
-
* @return {Object} - The tooltip information with tooltip data.
|
|
33
|
-
*/
|
|
29
|
+
const Y_AXIS_SIZE = Number(config.yAxis.size || 0)
|
|
34
30
|
|
|
35
|
-
// function handles only Single series
|
|
31
|
+
// function handles only Single series hovered data tooltips
|
|
36
32
|
const findDataKeyByThreshold = (mouseY, datapoint) => {
|
|
37
|
-
let sum = 0
|
|
38
|
-
let threshold = Number(yScale.invert(mouseY))
|
|
39
33
|
let hoveredKey = null
|
|
40
34
|
let hoveredValue = null
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
const dynamicSeries = config.series.find(s => s.dynamicCategory)
|
|
36
|
+
if (dynamicSeries) {
|
|
37
|
+
hoveredKey = datapoint[dynamicSeries.dynamicCategory]
|
|
38
|
+
hoveredValue = datapoint[dynamicSeries.dataKey]
|
|
39
|
+
} else {
|
|
40
|
+
let sum = 0
|
|
41
|
+
let threshold
|
|
42
|
+
try {
|
|
43
|
+
threshold = Number(yScale.invert(mouseY))
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
for (let key of config.runtime?.seriesKeys) {
|
|
48
|
+
if (datapoint.hasOwnProperty(key)) {
|
|
49
|
+
sum += Number(datapoint[key])
|
|
50
|
+
if (sum >= threshold) {
|
|
51
|
+
hoveredValue = datapoint[key]
|
|
52
|
+
hoveredKey = key
|
|
53
|
+
break
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
}
|
|
@@ -62,25 +68,6 @@ export const useTooltip = props => {
|
|
|
62
68
|
return showMissingDataValue ? 'N/A' : formattedValue
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
const getTooltipInformation = (tooltipDataArray, eventSvgCoords) => {
|
|
66
|
-
const { x, y } = eventSvgCoords
|
|
67
|
-
let initialTooltipData = tooltipDataArray || {}
|
|
68
|
-
|
|
69
|
-
const tooltipData = {
|
|
70
|
-
data: initialTooltipData,
|
|
71
|
-
dataXPosition: x + 10,
|
|
72
|
-
dataYPosition: y
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const tooltipInformation = {
|
|
76
|
-
tooltipLeft: tooltipData.dataXPosition,
|
|
77
|
-
tooltipTop: tooltipData.dataYPosition,
|
|
78
|
-
tooltipData: tooltipData
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return tooltipInformation
|
|
82
|
-
}
|
|
83
|
-
|
|
84
71
|
/**
|
|
85
72
|
* Handles the mouse over event for the tooltip.
|
|
86
73
|
* @function handleTooltipMouseOver
|
|
@@ -89,81 +76,84 @@ export const useTooltip = props => {
|
|
|
89
76
|
*/
|
|
90
77
|
const handleTooltipMouseOver = (e, additionalChartData) => {
|
|
91
78
|
if (visualizationType === 'Bump Chart') return
|
|
92
|
-
e.stopPropagation()
|
|
79
|
+
//e.stopPropagation()
|
|
93
80
|
if (isDraggingAnnotation) return
|
|
94
81
|
|
|
95
82
|
const eventSvgCoords = localPoint(e)
|
|
96
83
|
const { x, y } = eventSvgCoords
|
|
97
84
|
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const position = seriesObj?.axis ? String(seriesObj.axis).toLowerCase() : 'left'
|
|
110
|
-
return position
|
|
111
|
-
}
|
|
85
|
+
const resolvedScaleValues = getResolvedScaleValues([x, y])
|
|
86
|
+
const singleSeriesValue = getYValueFromCoordinate(y, resolvedScaleValues)
|
|
87
|
+
const columnsWithTooltips = []
|
|
88
|
+
const tooltipItems = [] as any[][]
|
|
89
|
+
for (const [colKey, column] of Object.entries(config.columns)) {
|
|
90
|
+
const formattingParams = {
|
|
91
|
+
addColPrefix: column.prefix,
|
|
92
|
+
addColSuffix: column.suffix,
|
|
93
|
+
addColRoundTo: column.roundToPlace || '',
|
|
94
|
+
addColCommas: column.commas
|
|
95
|
+
}
|
|
112
96
|
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
addColSuffix: config.columns[colKeys].suffix,
|
|
122
|
-
addColRoundTo: config.columns[colKeys].roundToPlace ? config.columns[colKeys].roundToPlace : '',
|
|
123
|
-
addColCommas: config.columns[colKeys].commas
|
|
124
|
-
}
|
|
125
|
-
let closestValue = null
|
|
97
|
+
const pieColumnData = additionalChartData?.arc?.data[column.name]
|
|
98
|
+
const columnData =
|
|
99
|
+
config.tooltips.singleSeries && visualizationType === 'Line'
|
|
100
|
+
? resolvedScaleValues.filter(
|
|
101
|
+
value => value[config.runtime.series[0].dynamicCategory] === singleSeriesValue
|
|
102
|
+
)[0][colKey]
|
|
103
|
+
: resolvedScaleValues[0]?.[colKey]
|
|
104
|
+
const closestValue = config.visualizationType === 'Pie' ? pieColumnData : columnData
|
|
126
105
|
|
|
127
|
-
|
|
128
|
-
closestValue = arc?.data[colVals.name]
|
|
129
|
-
} else {
|
|
130
|
-
closestValue = resolvedScaleValues[0]?.[colVals.name]
|
|
131
|
-
}
|
|
106
|
+
const formattedValue = formatColNumber(closestValue, 'left', true, config, formattingParams)
|
|
132
107
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (colVals.tooltips) {
|
|
136
|
-
columnsWithTooltips.push([colVals.label, formattedValue])
|
|
137
|
-
}
|
|
108
|
+
if (column.tooltips) {
|
|
109
|
+
columnsWithTooltips.push([column.label, formattedValue])
|
|
138
110
|
}
|
|
139
|
-
|
|
111
|
+
}
|
|
112
|
+
const additionalTooltipItems = [] as [string, string | number][]
|
|
140
113
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
114
|
+
columnsWithTooltips.forEach(columnData => {
|
|
115
|
+
additionalTooltipItems.push([columnData[0], columnData[1]])
|
|
116
|
+
})
|
|
144
117
|
|
|
145
|
-
|
|
146
|
-
|
|
118
|
+
if (visualizationType === 'Pie') {
|
|
119
|
+
const roundTo = Number(config.dataFormat.roundTo) || 0
|
|
147
120
|
|
|
148
|
-
|
|
121
|
+
const pieData = additionalChartData?.data ?? {}
|
|
122
|
+
const startAngle = additionalChartData?.startAngle ?? 0
|
|
123
|
+
const endAngle = additionalChartData?.endAngle ?? 0
|
|
124
|
+
const actualPieValue = Number(additionalChartData.data[config?.yAxis?.dataKey])
|
|
149
125
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
126
|
+
const degrees = ((endAngle - startAngle) * 180) / Math.PI
|
|
127
|
+
const pctOf360 = (degrees / 360) * 100
|
|
128
|
+
const pctString = value => value.toFixed(roundTo) + '%'
|
|
129
|
+
const showPiePercent = config.dataFormat.showPiePercent || false
|
|
153
130
|
|
|
131
|
+
if (showPiePercent && pieData[config.xAxis.dataKey] === 'Calculated Area') {
|
|
132
|
+
tooltipItems.push(['', 'Calculated Area'])
|
|
133
|
+
} else {
|
|
154
134
|
tooltipItems.push(
|
|
155
|
-
|
|
156
|
-
[
|
|
157
|
-
|
|
158
|
-
|
|
135
|
+
[config.xAxis.dataKey, pieData[config.xAxis.dataKey]],
|
|
136
|
+
[
|
|
137
|
+
config.runtime.yAxis.dataKey,
|
|
138
|
+
showPiePercent ? pctString(actualPieValue) : formatNumber(pieData[config.runtime.yAxis.dataKey])
|
|
139
|
+
],
|
|
140
|
+
showPiePercent ? [] : ['Percent', pctString(pctOf360)]
|
|
159
141
|
)
|
|
160
142
|
}
|
|
143
|
+
}
|
|
161
144
|
|
|
162
|
-
|
|
163
|
-
|
|
145
|
+
if (visualizationType === 'Forest Plot') {
|
|
146
|
+
tooltipItems.push([config.xAxis.dataKey, getClosestYValue(y)])
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const isLinearChart = !['Pie', 'Forest Plot'].includes(visualizationType)
|
|
150
|
+
if (isLinearChart) {
|
|
151
|
+
const getAxisPosition = seriesKey => {
|
|
152
|
+
const seriesObj = config.runtime.series.filter(s => s.dataKey === seriesKey)[0]
|
|
153
|
+
const position = seriesObj?.axis ? String(seriesObj.axis).toLowerCase() : 'left'
|
|
154
|
+
return position
|
|
164
155
|
}
|
|
165
|
-
|
|
166
|
-
if (visualizationType !== 'Pie' && visualizationType !== 'Forest Plot' && !config.tooltips.singleSeries) {
|
|
156
|
+
if (!config.tooltips.singleSeries || visualizationType === 'Line') {
|
|
167
157
|
tooltipItems.push(
|
|
168
158
|
...getIncludedTooltipSeries()
|
|
169
159
|
?.filter(seriesKey => {
|
|
@@ -191,37 +181,55 @@ export const useTooltip = props => {
|
|
|
191
181
|
})
|
|
192
182
|
)
|
|
193
183
|
|
|
194
|
-
|
|
184
|
+
const runtimeSeries =
|
|
185
|
+
config.tooltips.singleSeries && visualizationType === 'Line'
|
|
186
|
+
? [_.find(config.runtime.series, d => d.dataKey === singleSeriesValue)]
|
|
187
|
+
: config.runtime.series
|
|
188
|
+
|
|
189
|
+
runtimeSeries?.forEach(series => {
|
|
195
190
|
if (series?.dynamicCategory) {
|
|
196
191
|
const seriesKey = series.dataKey
|
|
197
192
|
const resolvedScaleValue = resolvedScaleValues.find(v => v[series.dynamicCategory] === seriesKey)
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
193
|
+
if (resolvedScaleValue) {
|
|
194
|
+
const value = resolvedScaleValue[series.originalDataKey]
|
|
195
|
+
const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
|
|
196
|
+
tooltipItems.push([seriesKey, formattedValue, getAxisPosition(seriesKey)])
|
|
197
|
+
}
|
|
202
198
|
}
|
|
203
199
|
})
|
|
204
|
-
}
|
|
200
|
+
} else {
|
|
201
|
+
const dynamicSeries = config.series.find(s => s.dynamicCategory)
|
|
205
202
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const [seriesKey, value] = findDataKeyByThreshold(y,
|
|
203
|
+
// Show Only the Hovered Series in Tooltip
|
|
204
|
+
const dataColumn = resolvedScaleValues[0]
|
|
205
|
+
const [seriesKey, value] = findDataKeyByThreshold(y, dataColumn)
|
|
209
206
|
if (seriesKey && value) {
|
|
210
|
-
|
|
207
|
+
const xVal = dataColumn[config.xAxis.dataKey]
|
|
208
|
+
const closestXScaleValue = getXValueFromCoordinate(x - Y_AXIS_SIZE)
|
|
209
|
+
|
|
210
|
+
tooltipItems.push([config.xAxis.dataKey, closestXScaleValue || xVal])
|
|
211
211
|
const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
|
|
212
212
|
tooltipItems.push([seriesKey, formattedValue])
|
|
213
|
+
} else if (dynamicSeries) {
|
|
214
|
+
Object.keys(dataColumn).forEach(key => {
|
|
215
|
+
tooltipItems.push([key, dataColumn[key]])
|
|
216
|
+
})
|
|
213
217
|
}
|
|
214
218
|
}
|
|
215
|
-
|
|
216
|
-
return [...tooltipItems, ...additionalTooltipItems]
|
|
217
219
|
}
|
|
218
220
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const tooltipDataArray = getTooltipDataArray()
|
|
221
|
+
const dataXPosition = eventSvgCoords.x + 10
|
|
222
|
+
const dataYPosition = eventSvgCoords.y
|
|
222
223
|
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
const tooltipInformation = {
|
|
225
|
+
tooltipLeft: dataXPosition,
|
|
226
|
+
tooltipTop: dataYPosition,
|
|
227
|
+
tooltipData: {
|
|
228
|
+
data: [...tooltipItems, ...additionalTooltipItems],
|
|
229
|
+
dataXPosition,
|
|
230
|
+
dataYPosition
|
|
231
|
+
}
|
|
232
|
+
}
|
|
225
233
|
showTooltip(tooltipInformation)
|
|
226
234
|
}
|
|
227
235
|
|
|
@@ -291,10 +299,7 @@ export const useTooltip = props => {
|
|
|
291
299
|
return closestX
|
|
292
300
|
}
|
|
293
301
|
|
|
294
|
-
if (
|
|
295
|
-
config.xAxis.type === 'categorical' ||
|
|
296
|
-
(visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')
|
|
297
|
-
) {
|
|
302
|
+
if (config.xAxis.type === 'categorical' || visualizationType === 'Combo') {
|
|
298
303
|
let range = xScale.range()[1] - xScale.range()[0]
|
|
299
304
|
let eachBand = range / (xScale.domain().length + 1)
|
|
300
305
|
|
|
@@ -302,17 +307,62 @@ export const useTooltip = props => {
|
|
|
302
307
|
const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
|
|
303
308
|
return xScale.domain()[index] // fixes off by 1 error
|
|
304
309
|
}
|
|
310
|
+
}
|
|
305
311
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
+
const findClosest = (dataArray: [any, number][], mouseXorY) => {
|
|
313
|
+
let dataColumn: Object
|
|
314
|
+
dataArray.find(([d, xOrY]) => {
|
|
315
|
+
if (xOrY > mouseXorY) {
|
|
316
|
+
return true
|
|
317
|
+
}
|
|
318
|
+
dataColumn = d
|
|
319
|
+
})
|
|
320
|
+
return dataColumn
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const getYValueFromCoordinate = (y, xData) => {
|
|
324
|
+
let closestYSeriesValue = null
|
|
325
|
+
let minDistance = Number.MAX_VALUE
|
|
326
|
+
let offset = y
|
|
327
|
+
|
|
328
|
+
xData.forEach(d => {
|
|
329
|
+
const yPosition = yScale(d[config.runtime.series[0].originalDataKey])
|
|
330
|
+
const distance = Math.abs(Number(yPosition - offset))
|
|
331
|
+
|
|
332
|
+
if (distance <= minDistance) {
|
|
333
|
+
minDistance = distance
|
|
334
|
+
closestYSeriesValue = d[config.runtime.series[0].dynamicCategory]
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
return closestYSeriesValue
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const getClosestYValueHorizontalChart = mouseY => {
|
|
341
|
+
const barGroups = yScale.domain().map((group, index) => ({ group, index }))
|
|
342
|
+
const barsWithHeights = getHorizontalBarHeights<{ group }>(config, barGroups)
|
|
343
|
+
|
|
344
|
+
const barGroup = findClosest(
|
|
345
|
+
barsWithHeights.map(d => [d, _.round(d.y)]),
|
|
346
|
+
mouseY
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
const subGroupMouseY = mouseY - barGroup.y
|
|
350
|
+
const columns = data.filter(d => d[config.xAxis.dataKey] === barGroup.group)
|
|
351
|
+
|
|
352
|
+
if (config.series.length > 1 && !config.series.find(s => s.dynamicCategory)) {
|
|
353
|
+
const seriesWithY = config.series.map((c, i) => [c, config.barHeight * i]) as [Object, number][]
|
|
354
|
+
const hoveredSeries = findClosest(seriesWithY, subGroupMouseY)
|
|
355
|
+
const exludeColumns = config.series.filter(s => s.dataKey !== hoveredSeries.dataKey).map(s => s.dataKey)
|
|
356
|
+
const dataColumn = _.omit(columns[0], exludeColumns)
|
|
357
|
+
return dataColumn
|
|
358
|
+
} else {
|
|
359
|
+
const columnsWithY = columns.map((c, i) => [c, config.barHeight * i]) as [Object, number][]
|
|
360
|
+
const dataColumn = findClosest(columnsWithY, subGroupMouseY)
|
|
361
|
+
return dataColumn
|
|
312
362
|
}
|
|
313
363
|
}
|
|
314
364
|
|
|
315
|
-
const getClosestYValue = (yPosition, key) => {
|
|
365
|
+
const getClosestYValue = (yPosition, key = '') => {
|
|
316
366
|
if (visualizationType === 'Pie') return
|
|
317
367
|
let minDistance = Number.MAX_VALUE
|
|
318
368
|
let closestYValue = null
|
|
@@ -369,11 +419,15 @@ export const useTooltip = props => {
|
|
|
369
419
|
}
|
|
370
420
|
|
|
371
421
|
/**
|
|
372
|
-
* Provides an array of objects with the closest
|
|
373
|
-
* @param {String} closestXScaleValue
|
|
374
|
-
* @returns an array of objects with the closest y series data items
|
|
422
|
+
* Provides an array of objects with the closest series data items
|
|
375
423
|
*/
|
|
376
|
-
const
|
|
424
|
+
const getResolvedScaleValues = ([x, y]) => {
|
|
425
|
+
if (orientation !== 'vertical') {
|
|
426
|
+
if (config.visualizationType === 'Bar' && config.tooltips.singleSeries) {
|
|
427
|
+
return [getClosestYValueHorizontalChart(y)]
|
|
428
|
+
}
|
|
429
|
+
return data.filter(d => d[xAxis.dataKey] === getClosestYValue(y))
|
|
430
|
+
}
|
|
377
431
|
const runtimeSeries = config.runtime.series.filter(
|
|
378
432
|
series => visualizationType === 'Pie' || (series.tooltip === true && !series.dynamicCategory)
|
|
379
433
|
)
|
|
@@ -403,21 +457,22 @@ export const useTooltip = props => {
|
|
|
403
457
|
const colNames = Object.values(config.columns).map(column => column.name)
|
|
404
458
|
// @ Murad why are we adding them twice?
|
|
405
459
|
includedSeries.push(...colNames, ...colNames)
|
|
460
|
+
const closestXScaleValue = getXValueFromCoordinate(x - Y_AXIS_SIZE)
|
|
406
461
|
|
|
407
|
-
|
|
408
|
-
const dataToSearch = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
|
|
409
|
-
// Return an empty array if no matching data is found.
|
|
410
|
-
if (!dataToSearch || dataToSearch.length === 0) {
|
|
411
|
-
return []
|
|
412
|
-
}
|
|
462
|
+
let dataToSearch = (data || []).filter(d => d[xAxis.dataKey] === closestXScaleValue)
|
|
413
463
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
464
|
+
if (config.tooltips.singleSeries && config.visualizationType !== 'Line') {
|
|
465
|
+
const dynamicSeries = config.series.find(s => s.dynamicCategory)
|
|
466
|
+
if (dynamicSeries) {
|
|
467
|
+
const dataWithXScale = dataToSearch.map(
|
|
468
|
+
d => [d, seriesScale(d[dynamicSeries.dynamicCategory])] as [Object, number]
|
|
469
|
+
)
|
|
470
|
+
const xOffset = x - Y_AXIS_SIZE - xScale(closestXScaleValue)
|
|
471
|
+
dataToSearch = [findClosest(dataWithXScale, xOffset)]
|
|
472
|
+
}
|
|
420
473
|
}
|
|
474
|
+
|
|
475
|
+
return dataToSearch.map(d => _.pick(d, includedSeries))
|
|
421
476
|
}
|
|
422
477
|
|
|
423
478
|
/**
|
|
@@ -427,67 +482,26 @@ export const useTooltip = props => {
|
|
|
427
482
|
* @returns {Array} Array of items to be included in the tooltip.
|
|
428
483
|
*/
|
|
429
484
|
const getIncludedTooltipSeries = () => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (!config.dashboard) {
|
|
452
|
-
switch (visualizationType) {
|
|
453
|
-
case 'Combo':
|
|
454
|
-
standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys, ...ciItems]
|
|
455
|
-
break
|
|
456
|
-
case 'Forecasting':
|
|
457
|
-
standardLoopItems = [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
|
|
458
|
-
break
|
|
459
|
-
case 'Line':
|
|
460
|
-
standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
|
|
461
|
-
break
|
|
462
|
-
case 'Area Chart':
|
|
463
|
-
standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
|
|
464
|
-
break
|
|
465
|
-
case 'Bar':
|
|
466
|
-
standardLoopItems =
|
|
467
|
-
orientation === 'vertical'
|
|
468
|
-
? [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
|
|
469
|
-
: [runtime.yAxis.dataKey, ...runtime?.seriesKeys]
|
|
470
|
-
break
|
|
471
|
-
case 'Pie':
|
|
472
|
-
standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
|
|
473
|
-
default:
|
|
474
|
-
throw new Error('No visualization type found in handleTooltipMouseOver')
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (config.dashboard) {
|
|
479
|
-
standardLoopItems = [
|
|
480
|
-
runtime.xAxis.dataKey,
|
|
481
|
-
...runtime?.barSeriesKeys,
|
|
482
|
-
...runtime?.lineSeriesKeys,
|
|
483
|
-
...stageColumns,
|
|
484
|
-
...ciItems
|
|
485
|
-
]
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return standardLoopItems
|
|
489
|
-
} catch (error) {
|
|
490
|
-
console.error('COVE', error)
|
|
485
|
+
const forcastingSeries = config.runtime.series.filter(series => series.type === 'Forecasting')
|
|
486
|
+
const stageColumns = forcastingSeries.map(series => series.stageColumn)
|
|
487
|
+
const ciItems = forcastingSeries.flatMap(series =>
|
|
488
|
+
series.confidenceIntervals?.filter(ci => ci.showInTooltip).map(ci => [ci.low, ci.high])
|
|
489
|
+
)
|
|
490
|
+
const common = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
|
|
491
|
+
switch (visualizationType) {
|
|
492
|
+
case 'Line':
|
|
493
|
+
case 'Area Chart':
|
|
494
|
+
case 'Pie':
|
|
495
|
+
return common
|
|
496
|
+
case 'Combo':
|
|
497
|
+
return [...common, ...ciItems]
|
|
498
|
+
case 'Forecasting':
|
|
499
|
+
return [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
|
|
500
|
+
|
|
501
|
+
case 'Bar':
|
|
502
|
+
return orientation === 'vertical' ? common : [runtime.yAxis.dataKey, ...runtime?.seriesKeys]
|
|
503
|
+
default:
|
|
504
|
+
throw new Error('No visualization type found in handleTooltipMouseOver')
|
|
491
505
|
}
|
|
492
506
|
}
|
|
493
507
|
|
|
@@ -544,7 +558,9 @@ export const useTooltip = props => {
|
|
|
544
558
|
config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ` : ''
|
|
545
559
|
)} ${config.xAxis.type === 'date' ? formattedDate : value}`}</li>
|
|
546
560
|
)
|
|
547
|
-
|
|
561
|
+
if (visualizationType === 'Pie' && config.dataFormat.showPiePercent && value === 'Calculated Area') {
|
|
562
|
+
return <li className='tooltip-heading'>{`${capitalize('Calculated Area')} `}</li>
|
|
563
|
+
}
|
|
548
564
|
if (key === config.xAxis.dataKey)
|
|
549
565
|
return (
|
|
550
566
|
<li className='tooltip-heading'>{`${capitalize(
|
|
@@ -569,14 +585,14 @@ export const useTooltip = props => {
|
|
|
569
585
|
let newValue = label || value
|
|
570
586
|
const style = displayGray ? { color: '#8b8b8a' } : {}
|
|
571
587
|
|
|
572
|
-
if (index == 1 && config.
|
|
588
|
+
if (index == 1 && config.yAxis?.inlineLabel) {
|
|
573
589
|
newValue = `${config.dataFormat.prefix}${newValue}${config.dataFormat.suffix}`
|
|
574
590
|
}
|
|
575
591
|
const activeLabel = getSeriesNameFromLabel(key)
|
|
576
592
|
const displayText = activeLabel ? `${activeLabel}: ${newValue}` : newValue
|
|
577
593
|
|
|
578
594
|
return (
|
|
579
|
-
<li style={style} className='tooltip-body'>
|
|
595
|
+
<li style={style} className='tooltip-body mb-1'>
|
|
580
596
|
{displayText}
|
|
581
597
|
</li>
|
|
582
598
|
)
|
|
@@ -586,7 +602,6 @@ export const useTooltip = props => {
|
|
|
586
602
|
getIncludedTooltipSeries,
|
|
587
603
|
getXValueFromCoordinate,
|
|
588
604
|
getXValueFromCoordinateDate,
|
|
589
|
-
getYScaleValues,
|
|
590
605
|
handleTooltipClick,
|
|
591
606
|
handleTooltipMouseOff,
|
|
592
607
|
handleTooltipMouseOver,
|
package/src/index.jsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom/client'
|
|
3
3
|
|
|
4
|
-
import CdcChart from './CdcChart'
|
|
5
4
|
import './coreStyles_chart.scss'
|
|
6
|
-
|
|
7
5
|
import '@cdc/core/styles/cove-main.scss'
|
|
8
6
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
9
7
|
|
|
8
|
+
import CdcChart from './CdcChart'
|
|
9
|
+
|
|
10
10
|
let isEditor = window.location.href.includes('editor=true')
|
|
11
11
|
let isDebug = window.location.href.includes('debug=true')
|
|
12
12
|
|
package/src/scss/main.scss
CHANGED
|
@@ -97,11 +97,6 @@
|
|
|
97
97
|
margin-bottom: 24px;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
.visually-hidden {
|
|
101
|
-
position: fixed;
|
|
102
|
-
left: -10000px;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
100
|
.loader {
|
|
106
101
|
width: 100%;
|
|
107
102
|
text-align: center;
|
|
@@ -144,6 +139,17 @@
|
|
|
144
139
|
}
|
|
145
140
|
|
|
146
141
|
.legend-container {
|
|
142
|
+
#supression-tooltip {
|
|
143
|
+
font-family: 'Nunito', sans-serif;
|
|
144
|
+
font-size: 0.8337rem;
|
|
145
|
+
font-weight: 400;
|
|
146
|
+
max-width: 16.7rem;
|
|
147
|
+
padding: 0.5rem 1rem;
|
|
148
|
+
border-radius: 4px;
|
|
149
|
+
box-shadow: 0px 2px 2px rgba(28, 29, 31, 0.45);
|
|
150
|
+
white-space: normal;
|
|
151
|
+
line-height: 1.4;
|
|
152
|
+
}
|
|
147
153
|
background: #fff;
|
|
148
154
|
width: 100%;
|
|
149
155
|
vertical-align: top;
|
|
@@ -232,12 +238,13 @@
|
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
&__outer {
|
|
235
|
-
&.
|
|
241
|
+
&.link-container {
|
|
236
242
|
display: flex;
|
|
237
243
|
flex-direction: row;
|
|
238
244
|
position: absolute;
|
|
239
245
|
left: 0%;
|
|
240
246
|
top: 108%;
|
|
247
|
+
|
|
241
248
|
& > * {
|
|
242
249
|
margin: 0;
|
|
243
250
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
2
2
|
import { ChartConfig } from '../types/ChartConfig'
|
|
3
|
-
|
|
4
|
-
type Action<T, P = undefined, R = undefined> = {
|
|
5
|
-
type: T
|
|
6
|
-
payload?: P
|
|
7
|
-
}
|
|
3
|
+
import { Action } from '@cdc/core/types/Action'
|
|
8
4
|
|
|
9
5
|
// Action Types
|
|
10
6
|
type SET_CONFIG = Action<'SET_CONFIG', ChartConfig>
|
|
@@ -34,7 +30,7 @@ type ChartActions =
|
|
|
34
30
|
| SET_CONTAINER
|
|
35
31
|
| SET_LOADED_EVENT
|
|
36
32
|
| SET_DRAG_ANNOTATIONS
|
|
37
|
-
| SET_BRUSH_CONFIG
|
|
38
33
|
| SET_LOADING
|
|
34
|
+
| SET_BRUSH_CONFIG
|
|
39
35
|
|
|
40
36
|
export default ChartActions
|