@cdc/chart 4.24.4 → 4.24.7
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 +39611 -36038
- package/examples/feature/annotations/index.json +542 -0
- package/examples/xaxis.json +493 -0
- package/index.html +9 -8
- package/package.json +5 -4
- package/src/CdcChart.tsx +115 -71
- package/src/_stories/Chart.stories.tsx +26 -171
- package/src/_stories/ChartAnnotation.stories.tsx +32 -0
- package/src/_stories/_mock/annotation_category_mock.json +473 -0
- package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
- package/src/_stories/_mock/annotation_date-time_mock.json +530 -0
- package/src/_stories/_mock/bar-chart-suppressed.json +474 -0
- package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
- package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
- package/src/_stories/_mock/lollipop.json +171 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +154 -0
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
- package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
- package/src/components/Annotations/components/AnnotationList.tsx +42 -0
- package/src/components/Annotations/components/findNearestDatum.ts +138 -0
- package/src/components/Annotations/components/helpers/index.tsx +46 -0
- package/src/components/Annotations/index.tsx +13 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +78 -71
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -2
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -11
- package/src/components/BarChart/components/BarChart.Vertical.tsx +100 -87
- package/src/components/BarChart/helpers/index.ts +102 -0
- package/src/components/DeviationBar.jsx +4 -2
- package/src/components/EditorPanel/EditorPanel.tsx +435 -613
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +306 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +135 -7
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +2 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/components/panels.scss +4 -0
- package/src/components/EditorPanel/editor-panel.scss +19 -0
- package/src/components/EditorPanel/useEditorPermissions.js +23 -3
- package/src/components/Legend/Legend.Component.tsx +66 -15
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +5 -0
- package/src/components/LineChart/LineChartProps.ts +16 -6
- package/src/components/LineChart/components/LineChart.Circle.tsx +22 -11
- package/src/components/LineChart/helpers.ts +148 -10
- package/src/components/LineChart/index.tsx +71 -44
- package/src/components/LinearChart.jsx +184 -125
- package/src/components/PairedBarChart.jsx +9 -9
- package/src/components/PieChart/PieChart.tsx +4 -4
- package/src/components/Sankey/index.tsx +73 -20
- package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
- package/src/components/ZoomBrush.tsx +120 -55
- package/src/data/initial-state.js +14 -6
- package/src/helpers/handleChartTabbing.ts +8 -0
- package/src/helpers/isConvertLineToBarGraph.ts +4 -0
- package/src/hooks/{useBarChart.js → useBarChart.ts} +9 -22
- package/src/hooks/useColorScale.ts +1 -1
- package/src/hooks/useMinMax.ts +29 -5
- package/src/hooks/useScales.ts +48 -26
- package/src/hooks/useTooltip.tsx +62 -15
- package/src/scss/main.scss +69 -12
- package/src/types/ChartConfig.ts +53 -16
- package/src/types/ChartContext.ts +13 -0
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- package/src/_stories/ChartLine.preliminary.tsx +0 -19
- package/src/_stories/ChartSuppress.stories.tsx +0 -19
- package/src/_stories/_mock/suppress_mock.json +0 -911
- package/src/helpers/computeMarginBottom.ts +0 -56
- package/src/helpers/filterData.ts +0 -18
- package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
- /package/src/hooks/{useLegendClasses.js → useLegendClasses.ts} +0 -0
- /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
|
|
2
4
|
|
|
3
5
|
type UseMinMaxProps = {
|
|
4
6
|
/** config - standard chart config */
|
|
@@ -11,11 +13,13 @@ type UseMinMaxProps = {
|
|
|
11
13
|
existPositiveValue: boolean
|
|
12
14
|
/** data - standard data array */
|
|
13
15
|
data: Object[]
|
|
16
|
+
/** Table data -data array Filtered & Excluded */
|
|
17
|
+
tableData: Object[]
|
|
14
18
|
/** isAllLine: if all series are line type including dashed lines */
|
|
15
19
|
isAllLine: boolean
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAllLine }: UseMinMaxProps) => {
|
|
22
|
+
const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAllLine, tableData }: UseMinMaxProps) => {
|
|
19
23
|
let min = 0
|
|
20
24
|
let max = 0
|
|
21
25
|
|
|
@@ -27,6 +31,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
27
31
|
return { min, max }
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
const checkLineToBarGraph = () => {
|
|
35
|
+
return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
const { visualizationType, series } = config
|
|
31
39
|
const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
|
|
32
40
|
const minRequiredCIPadding = 1.15 // regardless of Editor if CI data, there must be 10% padding added
|
|
@@ -123,10 +131,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
// this should not apply to bar charts if there is negative CI data
|
|
126
|
-
if ((visualizationType === 'Bar' || (visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
134
|
+
if ((visualizationType === 'Bar' || checkLineToBarGraph() || (visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
127
135
|
min = 0
|
|
128
136
|
}
|
|
129
|
-
if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min < 0) {
|
|
137
|
+
if ((config.visualizationType === 'Bar' || checkLineToBarGraph() || (config.visualizationType === 'Combo' && !isAllLine)) && min < 0) {
|
|
130
138
|
min = min * 1.1
|
|
131
139
|
}
|
|
132
140
|
|
|
@@ -145,9 +153,25 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
145
153
|
min = enteredMinValue && isMinValid ? enteredMinValue : 0
|
|
146
154
|
}
|
|
147
155
|
|
|
148
|
-
if (config.visualizationType === 'Line') {
|
|
156
|
+
if (config.visualizationType === 'Line' && !checkLineToBarGraph()) {
|
|
149
157
|
const isMinValid = config.useLogScale ? enteredMinValue >= 0 && enteredMinValue < minValue : enteredMinValue < minValue
|
|
150
|
-
|
|
158
|
+
// update minValue for (0) Suppression points
|
|
159
|
+
const suppressedMinValue = tableData?.some((dataItem, index) => {
|
|
160
|
+
return config.preliminaryData?.some(pd => {
|
|
161
|
+
if (pd.type !== 'suppression' || !pd.style) return false
|
|
162
|
+
|
|
163
|
+
// Filter data item based on current series keys and check if pd.value is present
|
|
164
|
+
const relevantData = _.pick(dataItem, config.runtime?.seriesKeys)
|
|
165
|
+
const isValuePresent = _.values(relevantData).includes(pd.value)
|
|
166
|
+
|
|
167
|
+
// Check for value match condition
|
|
168
|
+
const valueMatch = pd.column ? dataItem[pd.column] === pd.value : isValuePresent
|
|
169
|
+
|
|
170
|
+
// Return true if the value matches and it's either the first or the last item
|
|
171
|
+
return valueMatch && (index === 0 || index === tableData.length - 1)
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
min = enteredMinValue && isMinValid ? enteredMinValue : suppressedMinValue ? 0 : minValue
|
|
151
175
|
}
|
|
152
176
|
//If data value max wasn't provided, calculate it
|
|
153
177
|
if (max === Number.MIN_VALUE) {
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -44,6 +44,10 @@ const useScales = (properties: useScaleProps) => {
|
|
|
44
44
|
let seriesScale = null
|
|
45
45
|
let xScaleNoPadding = null
|
|
46
46
|
let xScaleBrush = null
|
|
47
|
+
let xScaleAnnotation = scaleLinear({
|
|
48
|
+
domain: [0, 100],
|
|
49
|
+
range: [0, xMax]
|
|
50
|
+
})
|
|
47
51
|
|
|
48
52
|
// handle Horizontal bars
|
|
49
53
|
if (isHorizontal) {
|
|
@@ -62,7 +66,12 @@ const useScales = (properties: useScaleProps) => {
|
|
|
62
66
|
seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
// handle
|
|
69
|
+
// handle Linear scaled viz
|
|
70
|
+
if (config.xAxis.type === 'date' && !isHorizontal) {
|
|
71
|
+
const xAxisDataMappedSorted = xAxisDataMapped ? xAxisDataMapped.sort() : []
|
|
72
|
+
xScale = composeScaleBand(xAxisDataMappedSorted, [0, xMax], 1 - config.barThickness)
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
if (config.xAxis.type === 'date-time') {
|
|
67
76
|
let xAxisMin = Math.min(...xAxisDataMapped)
|
|
68
77
|
let xAxisMax = Math.max(...xAxisDataMapped)
|
|
@@ -72,9 +81,22 @@ const useScales = (properties: useScaleProps) => {
|
|
|
72
81
|
domain: [xAxisMin, xAxisMax],
|
|
73
82
|
range: [0, xMax]
|
|
74
83
|
})
|
|
75
|
-
|
|
84
|
+
|
|
76
85
|
xScale.type = scaleTypes.TIME
|
|
77
|
-
|
|
86
|
+
|
|
87
|
+
let minDistance = Number.MAX_VALUE
|
|
88
|
+
let xAxisDataMappedSorted = xAxisDataMapped ? xAxisDataMapped.sort() : []
|
|
89
|
+
for (let i = 0; i < xAxisDataMappedSorted.length - 1; i++) {
|
|
90
|
+
let distance = xScale(xAxisDataMappedSorted[i + 1]) - xScale(xAxisDataMappedSorted[i])
|
|
91
|
+
|
|
92
|
+
if (distance < minDistance) minDistance = distance
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (xAxisDataMapped.length === 1 || minDistance > xMax / 4) {
|
|
96
|
+
minDistance = xMax / 4
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
seriesScale = composeScaleBand(seriesDomain, [0, (config.barThickness || 1) * minDistance], 0)
|
|
78
100
|
}
|
|
79
101
|
|
|
80
102
|
// handle Deviation bar
|
|
@@ -233,40 +255,40 @@ const useScales = (properties: useScaleProps) => {
|
|
|
233
255
|
}
|
|
234
256
|
}
|
|
235
257
|
}
|
|
236
|
-
return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleBrush }
|
|
258
|
+
return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleBrush, xScaleAnnotation }
|
|
237
259
|
}
|
|
238
260
|
|
|
239
261
|
export default useScales
|
|
240
262
|
|
|
241
263
|
export const getTickValues = (xAxisDataMapped, xScale, num) => {
|
|
242
|
-
const xDomain = xScale.domain()
|
|
243
|
-
|
|
244
|
-
if(xScale.type === 'time'){
|
|
245
|
-
const xDomainMax = xAxisDataMapped[xAxisDataMapped.length - 1]
|
|
246
|
-
const xDomainMin = xAxisDataMapped[0]
|
|
247
|
-
const step = (xDomainMax - xDomainMin) / (num - 1)
|
|
248
|
-
const tickValues = []
|
|
249
|
-
for(let i = xDomainMax; i >= xDomainMin; i -= step){
|
|
250
|
-
tickValues.push(i)
|
|
264
|
+
const xDomain = xScale.domain()
|
|
265
|
+
|
|
266
|
+
if (xScale.type === 'time') {
|
|
267
|
+
const xDomainMax = xAxisDataMapped[xAxisDataMapped.length - 1]
|
|
268
|
+
const xDomainMin = xAxisDataMapped[0]
|
|
269
|
+
const step = (xDomainMax - xDomainMin) / (num - 1)
|
|
270
|
+
const tickValues = []
|
|
271
|
+
for (let i = xDomainMax; i >= xDomainMin; i -= step) {
|
|
272
|
+
tickValues.push(i)
|
|
251
273
|
}
|
|
252
|
-
if(tickValues[tickValues.length - 1] !== xDomainMin){
|
|
253
|
-
tickValues.push(xDomainMin)
|
|
274
|
+
if (tickValues[tickValues.length - 1] !== xDomainMin) {
|
|
275
|
+
tickValues.push(xDomainMin)
|
|
254
276
|
}
|
|
255
|
-
tickValues.reverse()
|
|
256
|
-
|
|
257
|
-
return tickValues
|
|
277
|
+
tickValues.reverse()
|
|
278
|
+
|
|
279
|
+
return tickValues
|
|
258
280
|
}
|
|
259
281
|
|
|
260
|
-
if(xDomain.length > 2){
|
|
261
|
-
const step = num || 1
|
|
262
|
-
const tickValues = []
|
|
263
|
-
for(let i = xDomain.length; i > 0; i -= step){
|
|
264
|
-
const adjustedIndex = Math.max(Math.round(i) - 1, 0)
|
|
265
|
-
tickValues.push(xDomain[adjustedIndex])
|
|
282
|
+
if (xDomain.length > 2) {
|
|
283
|
+
const step = num || 1
|
|
284
|
+
const tickValues = []
|
|
285
|
+
for (let i = xDomain.length; i > 0; i -= step) {
|
|
286
|
+
const adjustedIndex = Math.max(Math.round(i) - 1, 0)
|
|
287
|
+
tickValues.push(xDomain[adjustedIndex])
|
|
266
288
|
}
|
|
267
|
-
tickValues.reverse()
|
|
289
|
+
tickValues.reverse()
|
|
268
290
|
|
|
269
|
-
return tickValues
|
|
291
|
+
return tickValues
|
|
270
292
|
}
|
|
271
293
|
}
|
|
272
294
|
|
package/src/hooks/useTooltip.tsx
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
|
+
// Local imports
|
|
2
3
|
import ConfigContext from '../ConfigContext'
|
|
3
4
|
import { type ChartContext } from '../types/ChartContext'
|
|
4
|
-
|
|
5
|
-
// third party
|
|
6
|
-
import { localPoint } from '@visx/event'
|
|
7
|
-
import { bisector } from 'd3-array'
|
|
8
|
-
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
9
|
-
const transform = new DataTransform()
|
|
10
|
-
|
|
11
5
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
12
6
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
7
|
+
// Third-party library imports
|
|
8
|
+
import { localPoint } from '@visx/event'
|
|
9
|
+
import { bisector } from 'd3-array'
|
|
13
10
|
|
|
14
11
|
export const useTooltip = props => {
|
|
15
|
-
const { tableData, config, formatNumber, capitalize, formatDate, formatTooltipsDate, parseDate, setSharedFilter } = useContext<ChartContext>(ConfigContext)
|
|
12
|
+
const { tableData: data, config, formatNumber, capitalize, formatDate, formatTooltipsDate, parseDate, setSharedFilter, isDraggingAnnotation } = useContext<ChartContext>(ConfigContext)
|
|
16
13
|
const { xScale, yScale, showTooltip, hideTooltip } = props
|
|
17
14
|
const { xAxis, visualizationType, orientation, yAxis, runtime } = config
|
|
18
|
-
|
|
15
|
+
|
|
19
16
|
/**
|
|
20
17
|
* Provides the tooltip information based on the tooltip data array and svg cursor coordinates
|
|
21
18
|
* @function getTooltipInformation
|
|
@@ -23,6 +20,44 @@ export const useTooltip = props => {
|
|
|
23
20
|
* @param {Object} eventSvgCoords - The object containing the SVG coordinates of the event.
|
|
24
21
|
* @return {Object} - The tooltip information with tooltip data.
|
|
25
22
|
*/
|
|
23
|
+
|
|
24
|
+
// function handles only Single series hovred data tooltips
|
|
25
|
+
const findDataKeyByThreshold = (mouseY, datapoint) => {
|
|
26
|
+
let sum = 0
|
|
27
|
+
let threshold = Number(yScale.invert(mouseY))
|
|
28
|
+
let hoveredKey = null
|
|
29
|
+
let hoveredValue = null
|
|
30
|
+
|
|
31
|
+
for (let key of config.runtime?.seriesKeys) {
|
|
32
|
+
if (datapoint.hasOwnProperty(key)) {
|
|
33
|
+
sum += Number(datapoint[key])
|
|
34
|
+
if (sum >= threshold) {
|
|
35
|
+
hoveredValue = datapoint[key]
|
|
36
|
+
hoveredKey = key
|
|
37
|
+
break
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Return null if no matching data is found
|
|
43
|
+
return [hoveredKey, hoveredValue]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const getFormattedValue = (seriesKey, value, config, getAxisPosition) => {
|
|
47
|
+
// handle case where data is missing
|
|
48
|
+
const showMissingDataValue = config.general.showMissingDataLabel && (!value || value === 'null')
|
|
49
|
+
let formattedValue = seriesKey === config.xAxis.dataKey ? value : formatNumber(value, getAxisPosition(seriesKey))
|
|
50
|
+
formattedValue = showMissingDataValue ? 'N/A' : formattedValue
|
|
51
|
+
|
|
52
|
+
const suppressed = config.preliminaryData?.find(pd => pd.label && pd.type === 'suppression' && pd.displayTooltip && value === pd.value && (!pd.column || seriesKey === pd.column))
|
|
53
|
+
|
|
54
|
+
if (suppressed) {
|
|
55
|
+
formattedValue = suppressed.label
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return formattedValue
|
|
59
|
+
}
|
|
60
|
+
|
|
26
61
|
const getTooltipInformation = (tooltipDataArray, eventSvgCoords) => {
|
|
27
62
|
const { x, y } = eventSvgCoords
|
|
28
63
|
let initialTooltipData = tooltipDataArray || {}
|
|
@@ -50,6 +85,8 @@ export const useTooltip = props => {
|
|
|
50
85
|
*/
|
|
51
86
|
const handleTooltipMouseOver = (e, additionalChartData) => {
|
|
52
87
|
e.stopPropagation()
|
|
88
|
+
if (isDraggingAnnotation) return
|
|
89
|
+
|
|
53
90
|
const eventSvgCoords = localPoint(e)
|
|
54
91
|
const { x, y } = eventSvgCoords
|
|
55
92
|
|
|
@@ -137,18 +174,29 @@ export const useTooltip = props => {
|
|
|
137
174
|
if (visualizationType === 'Forest Plot') {
|
|
138
175
|
tooltipItems.push([config.xAxis.dataKey, getClosestYValue(y)])
|
|
139
176
|
}
|
|
140
|
-
|
|
141
|
-
if (visualizationType !== 'Pie' && visualizationType !== 'Forest Plot') {
|
|
177
|
+
// handle tooltip for all hovered series
|
|
178
|
+
if (visualizationType !== 'Pie' && visualizationType !== 'Forest Plot' && !config.tooltips.singleSeries) {
|
|
142
179
|
tooltipItems.push(
|
|
143
180
|
...getIncludedTooltipSeries()
|
|
144
|
-
?.filter(
|
|
181
|
+
?.filter(seriesKey => config.series?.find(item => item.dataKey === seriesKey && item?.tooltip) || config.xAxis?.dataKey == seriesKey || visualizationType === 'Forecasting')
|
|
145
182
|
?.flatMap(seriesKey => {
|
|
146
|
-
const
|
|
147
|
-
|
|
183
|
+
const value = resolvedScaleValues[0]?.[seriesKey]
|
|
184
|
+
const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
|
|
185
|
+
return [[seriesKey, formattedValue, getAxisPosition(seriesKey)]]
|
|
148
186
|
})
|
|
149
187
|
)
|
|
150
188
|
}
|
|
151
189
|
|
|
190
|
+
// handle tooltip for single hovered series
|
|
191
|
+
if (visualizationType !== 'Pie' && visualizationType !== 'Forest Plot' && config.tooltips.singleSeries) {
|
|
192
|
+
const [seriesKey, value] = findDataKeyByThreshold(y, resolvedScaleValues[0])
|
|
193
|
+
if (seriesKey && value) {
|
|
194
|
+
tooltipItems.push([config.xAxis.dataKey, closestXScaleValue])
|
|
195
|
+
const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
|
|
196
|
+
tooltipItems.push([seriesKey, formattedValue])
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
152
200
|
return [...tooltipItems, ...additionalTooltipItems]
|
|
153
201
|
}
|
|
154
202
|
|
|
@@ -323,7 +371,6 @@ export const useTooltip = props => {
|
|
|
323
371
|
const yScaleValues = dataToSearch.map(object => {
|
|
324
372
|
return Object.fromEntries(Object.entries(object).filter(([key, value]) => includedSeries.includes(key)))
|
|
325
373
|
})
|
|
326
|
-
|
|
327
374
|
return yScaleValues
|
|
328
375
|
} catch (error) {
|
|
329
376
|
console.error('COVE', error)
|
package/src/scss/main.scss
CHANGED
|
@@ -13,9 +13,21 @@
|
|
|
13
13
|
flex-direction: column-reverse;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
.cdc-open-viz-module.type-dashboard {
|
|
17
|
+
.cdc-open-viz-module.type-chart.isEditor {
|
|
18
|
+
.cdc-chart-inner-container {
|
|
19
|
+
background: white;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
.cdc-open-viz-module.type-chart {
|
|
17
25
|
@import 'DataTable';
|
|
18
26
|
|
|
27
|
+
&.isEditor .cdc-chart-inner-container {
|
|
28
|
+
background: white;
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
border-radius: 3px;
|
|
20
32
|
|
|
21
33
|
.checkbox-group {
|
|
@@ -75,11 +87,19 @@
|
|
|
75
87
|
margin-bottom: 20px;
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
.subtext,
|
|
91
|
+
.subtext--responsive-ticks,
|
|
78
92
|
.section-subtext {
|
|
79
|
-
padding:
|
|
93
|
+
padding: 0 1rem;
|
|
94
|
+
&--brush-active {
|
|
95
|
+
margin-top: 2.5em;
|
|
96
|
+
}
|
|
80
97
|
}
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
|
|
99
|
+
.type-pie {
|
|
100
|
+
svg.animated-pie {
|
|
101
|
+
width: auto !important;
|
|
102
|
+
}
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
.legend-container {
|
|
@@ -130,9 +150,6 @@
|
|
|
130
150
|
word-wrap: break-word;
|
|
131
151
|
white-space: pre-wrap;
|
|
132
152
|
word-break: break-word;
|
|
133
|
-
@include breakpoint(xs) {
|
|
134
|
-
font-size: $font-small;
|
|
135
|
-
}
|
|
136
153
|
}
|
|
137
154
|
}
|
|
138
155
|
|
|
@@ -153,10 +170,8 @@
|
|
|
153
170
|
h3,
|
|
154
171
|
p {
|
|
155
172
|
margin-bottom: 0.8em;
|
|
156
|
-
@include breakpoint(xs) {
|
|
157
|
-
font-size: $font-small + 0.2em;
|
|
158
|
-
}
|
|
159
173
|
}
|
|
174
|
+
|
|
160
175
|
& div.legend-item {
|
|
161
176
|
margin-bottom: 0.5em !important;
|
|
162
177
|
|
|
@@ -198,18 +213,47 @@
|
|
|
198
213
|
}
|
|
199
214
|
}
|
|
200
215
|
|
|
216
|
+
.div[class*='Asterisk'] {
|
|
217
|
+
transform: 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
201
220
|
.legend-preliminary {
|
|
202
221
|
display: flex;
|
|
203
222
|
flex-direction: row;
|
|
204
223
|
align-items: center;
|
|
205
|
-
|
|
206
|
-
|
|
224
|
+
font-size: 1em;
|
|
225
|
+
vertical-align: middle;
|
|
226
|
+
margin-bottom: 0.5em;
|
|
227
|
+
|
|
228
|
+
& > span {
|
|
229
|
+
display: flex;
|
|
230
|
+
justify-items: center;
|
|
231
|
+
align-items: center;
|
|
232
|
+
white-space: nowrap;
|
|
233
|
+
font-size: 1em;
|
|
234
|
+
margin-right: 8px;
|
|
235
|
+
max-height: 1px;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
& > span[class*='Asterisk'] {
|
|
239
|
+
transform: scale(1.8);
|
|
240
|
+
margin-top: 15px;
|
|
241
|
+
}
|
|
242
|
+
& > span[class*='Dagger'] {
|
|
243
|
+
margin-top: 2px;
|
|
244
|
+
}
|
|
245
|
+
& > span[class*='Sign'] {
|
|
246
|
+
margin-top: 1px;
|
|
207
247
|
}
|
|
208
248
|
|
|
209
249
|
& > svg {
|
|
210
250
|
width: 50px;
|
|
211
251
|
height: 23px;
|
|
212
252
|
}
|
|
253
|
+
|
|
254
|
+
& > p {
|
|
255
|
+
margin: 0;
|
|
256
|
+
}
|
|
213
257
|
}
|
|
214
258
|
|
|
215
259
|
.visx-tooltip {
|
|
@@ -236,6 +280,17 @@
|
|
|
236
280
|
&.legend-hidden > svg {
|
|
237
281
|
width: 100% !important;
|
|
238
282
|
}
|
|
283
|
+
&.dashboard-brush {
|
|
284
|
+
margin-bottom: 2.5em;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
svg.dragging-annotation * {
|
|
288
|
+
user-select: none;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
svg.dragging-annotation path {
|
|
292
|
+
pointer-events: none;
|
|
293
|
+
}
|
|
239
294
|
|
|
240
295
|
> svg {
|
|
241
296
|
overflow: visible;
|
|
@@ -651,8 +706,10 @@
|
|
|
651
706
|
padding: 1em;
|
|
652
707
|
}
|
|
653
708
|
|
|
654
|
-
.subtext
|
|
709
|
+
.subtext,
|
|
710
|
+
.subtext--responsive-ticks {
|
|
655
711
|
margin-top: 0px;
|
|
712
|
+
padding: 0 1rem;
|
|
656
713
|
}
|
|
657
714
|
|
|
658
715
|
.isEditor {
|
package/src/types/ChartConfig.ts
CHANGED
|
@@ -12,7 +12,10 @@ import { Legend } from '@cdc/core/types/Legend'
|
|
|
12
12
|
import { ConfidenceInterval } from '@cdc/core/types/ConfidenceInterval'
|
|
13
13
|
import { Region } from '@cdc/core/types/Region'
|
|
14
14
|
import { type PreliminaryDataItem } from '../components/LineChart/LineChartProps'
|
|
15
|
+
import { VizFilter } from '@cdc/core/types/VizFilter'
|
|
16
|
+
import { type Annotation } from '@cdc/core/types/Annotation'
|
|
15
17
|
|
|
18
|
+
export type ViewportSize = 'sm' | 'xs' | 'xxs' | 'lg'
|
|
16
19
|
export type ChartColumns = Record<string, Column>
|
|
17
20
|
|
|
18
21
|
type DataFormat = {
|
|
@@ -39,18 +42,6 @@ type Exclusions = {
|
|
|
39
42
|
dateEnd: string
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
type Filter = {
|
|
43
|
-
active: string
|
|
44
|
-
type: 'url'
|
|
45
|
-
columnName: string
|
|
46
|
-
showDropdown: boolean
|
|
47
|
-
filterStyle: string
|
|
48
|
-
label: string
|
|
49
|
-
order: 'asc' | 'desc' | 'cust'
|
|
50
|
-
values: string[]
|
|
51
|
-
queryParameter: string
|
|
52
|
-
}
|
|
53
|
-
|
|
54
45
|
export type Legend = {
|
|
55
46
|
seriesHighlight: string[]
|
|
56
47
|
additionalCategories: string[]
|
|
@@ -81,7 +72,8 @@ type Visual = {
|
|
|
81
72
|
horizontalHoverLine?: boolean
|
|
82
73
|
}
|
|
83
74
|
|
|
84
|
-
type AllChartsConfig = {
|
|
75
|
+
export type AllChartsConfig = {
|
|
76
|
+
annotations: Annotation[]
|
|
85
77
|
animate: boolean
|
|
86
78
|
general: General
|
|
87
79
|
barHasBorder: 'true' | 'false'
|
|
@@ -109,7 +101,7 @@ type AllChartsConfig = {
|
|
|
109
101
|
description: string
|
|
110
102
|
dynamicMarginTop: number
|
|
111
103
|
exclusions: Exclusions
|
|
112
|
-
filters:
|
|
104
|
+
filters: VizFilter[]
|
|
113
105
|
filterBehavior: FilterBehavior
|
|
114
106
|
fontSize: 'small' | 'medium' | 'large'
|
|
115
107
|
footnotes: string
|
|
@@ -161,6 +153,7 @@ type AllChartsConfig = {
|
|
|
161
153
|
twoColor: { palette: string }
|
|
162
154
|
type: 'chart' | 'dashboard'
|
|
163
155
|
useLogScale: boolean
|
|
156
|
+
uid: string | number
|
|
164
157
|
visual: Visual
|
|
165
158
|
visualizationType: 'Area Chart' | 'Bar' | 'Box Plot' | 'Deviation Bar' | 'Forest Plot' | 'Line' | 'Paired Bar' | 'Pie' | 'Scatter Plot' | 'Spark Line' | 'Combo' | 'Forecasting' | 'Sankey'
|
|
166
159
|
visualizationSubType: string
|
|
@@ -198,8 +191,52 @@ export type ForestPlotConfig = {
|
|
|
198
191
|
} & AllChartsConfig
|
|
199
192
|
|
|
200
193
|
export type LineChartConfig = {
|
|
201
|
-
|
|
194
|
+
allowLineToBarGraph: boolean
|
|
195
|
+
convertLineToBarGraph: boolean
|
|
202
196
|
lineDatapointStyle: 'hidden' | 'always show' | 'hover'
|
|
197
|
+
visualizationType: 'Line'
|
|
198
|
+
} & AllChartsConfig
|
|
199
|
+
|
|
200
|
+
export type SankeyLink = {
|
|
201
|
+
depth: number
|
|
202
|
+
height: number
|
|
203
|
+
id: string
|
|
204
|
+
index: number
|
|
205
|
+
layer: number
|
|
206
|
+
sourceLinks: SankeyLink[]
|
|
207
|
+
targetLinks: SankeyLink[]
|
|
208
|
+
value: number
|
|
209
|
+
x0: number
|
|
210
|
+
x1: number
|
|
211
|
+
y0: number
|
|
212
|
+
y1: number
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
type StoryNode = {
|
|
216
|
+
StoryNode: string
|
|
217
|
+
segmentTextAfter: string
|
|
218
|
+
segmentTextBefore: string
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export type SankeyChartConfig = {
|
|
222
|
+
enableTooltips: boolean
|
|
223
|
+
data: [
|
|
224
|
+
{
|
|
225
|
+
tooltips: Object[]
|
|
226
|
+
// data to display in the sankey chart tooltips
|
|
227
|
+
tooltipData: Object[]
|
|
228
|
+
// data to display in the data table, bypasses the default data table output
|
|
229
|
+
tableData: Object[]
|
|
230
|
+
links: {
|
|
231
|
+
source: SankeyLink
|
|
232
|
+
target: SankeyLink
|
|
233
|
+
value: number
|
|
234
|
+
}[],
|
|
235
|
+
storyNodeText: StoryNode[]
|
|
236
|
+
:
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
visualizationType: 'Sankey'
|
|
203
240
|
} & AllChartsConfig
|
|
204
241
|
|
|
205
|
-
export type ChartConfig = LineChartConfig | ForestPlotConfig | AllChartsConfig
|
|
242
|
+
export type ChartConfig = SankeyChartConfig | LineChartConfig | ForestPlotConfig | AllChartsConfig
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ChartConfig } from './ChartConfig'
|
|
2
2
|
import { PickD3Scale } from '@visx/scale'
|
|
3
3
|
import { type SharedFilter } from '@cdc/dashboard/src/types/SharedFilter'
|
|
4
|
+
import { type Annotation } from '@cdc/core/types/Annotation'
|
|
4
5
|
|
|
5
6
|
export type ColorScale = PickD3Scale<'ordinal', any, any>
|
|
6
7
|
|
|
@@ -21,10 +22,21 @@ type SharedChartContext = {
|
|
|
21
22
|
legendIsolateValues?: string[]
|
|
22
23
|
setLegendIsolateValues?: Function
|
|
23
24
|
getTextWidth?: () => string | number
|
|
25
|
+
brushConfig: { data: []; isBrushing: boolean; isActive: boolean }
|
|
26
|
+
setBrushConfig: Function
|
|
27
|
+
clean: Function
|
|
28
|
+
capitalize: (value: string) => string
|
|
29
|
+
// whether or not the legend is appearing below the chart
|
|
30
|
+
isLegendBottom?: boolean
|
|
31
|
+
// whether or not the chart is viewed within the editor screen
|
|
32
|
+
isEditor?: boolean
|
|
33
|
+
// whether or not the user is dragging an annotation
|
|
34
|
+
isDraggingAnnotation?: boolean
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
// Line Chart Specific Context
|
|
27
38
|
type LineChartContext = SharedChartContext & {
|
|
39
|
+
convertLineToBarGraph: boolean
|
|
28
40
|
dimensions: [screenWidth: number, screenHeight: number]
|
|
29
41
|
formatDate: Function
|
|
30
42
|
formatTooltipsDate: Function
|
|
@@ -46,6 +58,7 @@ type LineChartContext = SharedChartContext & {
|
|
|
46
58
|
export type ChartContext =
|
|
47
59
|
| LineChartContext
|
|
48
60
|
| (SharedChartContext & {
|
|
61
|
+
annotations: Annotation[]
|
|
49
62
|
dimensions: [screenWidth: number, screenHeight: number]
|
|
50
63
|
formatDate?: Function
|
|
51
64
|
formatTooltipsDate: Function
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { testZeroValue } from '../../src/components/BarChart/helpers'
|
|
2
|
+
|
|
3
|
+
describe('testZeroValue', () => {
|
|
4
|
+
test('returns true for "0" as string', () => {
|
|
5
|
+
expect(testZeroValue('0')).toBe(true)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('returns true for "0" as number', () => {
|
|
9
|
+
expect(testZeroValue(0)).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('returns true for 0.0 as number', () => {
|
|
13
|
+
expect(testZeroValue(0.0)).toBe(true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// check false case
|
|
17
|
+
test('returns false for "ABC" as string', () => {
|
|
18
|
+
expect(testZeroValue('ABC')).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
test('returns false for 999 as number', () => {
|
|
21
|
+
expect(testZeroValue(999)).toBe(false)
|
|
22
|
+
})
|
|
23
|
+
// test for null & undefined cases
|
|
24
|
+
test('returns undefined for a null', () => {
|
|
25
|
+
expect(testZeroValue(null)).toBe(undefined)
|
|
26
|
+
})
|
|
27
|
+
test('returns undefined for a undefined', () => {
|
|
28
|
+
expect(testZeroValue(undefined)).toBe(undefined)
|
|
29
|
+
})
|
|
30
|
+
})
|