@cdc/chart 4.25.6 → 4.25.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 +52667 -32246
- package/package.json +3 -2
- package/src/CdcChartComponent.tsx +13 -9
- package/src/_stories/Chart.BoxPlot.stories.tsx +35 -0
- package/src/_stories/Chart.stories.tsx +0 -7
- package/src/_stories/Chart.tooltip.stories.tsx +35 -275
- package/src/_stories/_mock/bar-chart-suppressed.json +2 -80
- package/src/_stories/_mock/boxplot_multiseries.json +252 -166
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +4 -8
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +34 -2
- package/src/components/BarChart/components/BarChart.Vertical.tsx +15 -0
- package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +131 -0
- package/src/components/BoxPlot/{BoxPlot.tsx → BoxPlot.Vertical.tsx} +4 -4
- package/src/components/BoxPlot/helpers/index.ts +32 -12
- package/src/components/BrushChart.tsx +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +19 -14
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
- package/src/components/Forecasting/{Forecasting.jsx → Forecasting.tsx} +32 -12
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +1 -0
- package/src/components/Legend/helpers/index.ts +2 -2
- package/src/components/LineChart/helpers.ts +7 -7
- package/src/components/LinearChart.tsx +127 -72
- package/src/data/initial-state.js +1 -5
- package/src/helpers/countNumOfTicks.ts +4 -19
- package/src/helpers/filterAndShiftLinearDateTicks.ts +58 -0
- package/src/helpers/getBridgedData.ts +13 -0
- package/src/helpers/tests/getBridgedData.test.ts +64 -0
- package/src/hooks/useScales.ts +42 -42
- package/src/hooks/useTooltip.tsx +3 -2
- package/src/scss/main.scss +2 -4
- package/src/store/chart.actions.ts +2 -2
- package/src/store/chart.reducer.ts +4 -12
- package/src/types/ChartConfig.ts +1 -6
- package/src/components/BoxPlot/index.tsx +0 -3
- /package/src/components/Brush/{BrushController..tsx → BrushController.tsx} +0 -0
|
@@ -59,7 +59,9 @@ export const BarChartHorizontal = () => {
|
|
|
59
59
|
|
|
60
60
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
61
61
|
|
|
62
|
-
const hasConfidenceInterval =
|
|
62
|
+
const hasConfidenceInterval = [config.confidenceKeys?.upper, config.confidenceKeys?.lower].every(
|
|
63
|
+
v => v != null && String(v).trim() !== ''
|
|
64
|
+
)
|
|
63
65
|
|
|
64
66
|
const _data = getBarData(config, data, hasConfidenceInterval)
|
|
65
67
|
|
|
@@ -262,6 +264,21 @@ export const BarChartHorizontal = () => {
|
|
|
262
264
|
}
|
|
263
265
|
})}
|
|
264
266
|
|
|
267
|
+
{(absentDataLabel || isSuppressed) && (
|
|
268
|
+
<rect
|
|
269
|
+
x={barX}
|
|
270
|
+
y={0}
|
|
271
|
+
width={yMax}
|
|
272
|
+
height={numbericBarHeight}
|
|
273
|
+
fill='transparent'
|
|
274
|
+
data-tooltip-place='top'
|
|
275
|
+
data-tooltip-offset='{"top":3}'
|
|
276
|
+
style={{ pointerEvents: 'all', cursor: 'pointer' }}
|
|
277
|
+
data-tooltip-html={tooltip}
|
|
278
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
|
|
265
282
|
{config.preliminaryData?.map((pd, index) => {
|
|
266
283
|
// check if user selected column
|
|
267
284
|
const selectedSuppressionColumn = !pd.column || pd.column === bar.key
|
|
@@ -301,7 +318,7 @@ export const BarChartHorizontal = () => {
|
|
|
301
318
|
)
|
|
302
319
|
})}
|
|
303
320
|
|
|
304
|
-
{!config.isLollipopChart && (
|
|
321
|
+
{!config.isLollipopChart && !hasConfidenceInterval && (
|
|
305
322
|
<Text // prettier-ignore
|
|
306
323
|
display={displayBar ? 'block' : 'none'}
|
|
307
324
|
x={bar.y}
|
|
@@ -315,6 +332,21 @@ export const BarChartHorizontal = () => {
|
|
|
315
332
|
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
316
333
|
</Text>
|
|
317
334
|
)}
|
|
335
|
+
|
|
336
|
+
{!config.isLollipopChart && hasConfidenceInterval && (
|
|
337
|
+
<Text // prettier-ignore
|
|
338
|
+
display={displayBar ? 'block' : 'none'}
|
|
339
|
+
x={bar.value < 0 ? bar.y + barWidth : bar.y - barWidth}
|
|
340
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
341
|
+
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
342
|
+
fill={labelColor}
|
|
343
|
+
dx={-textPadding}
|
|
344
|
+
verticalAnchor='middle'
|
|
345
|
+
textAnchor={bar.value < 0 ? 'end' : 'start'}
|
|
346
|
+
>
|
|
347
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
348
|
+
</Text>
|
|
349
|
+
)}
|
|
318
350
|
<Text // prettier-ignore
|
|
319
351
|
display={displayBar ? 'block' : 'none'}
|
|
320
352
|
x={bar.y}
|
|
@@ -288,6 +288,21 @@ export const BarChartVertical = () => {
|
|
|
288
288
|
}
|
|
289
289
|
})}
|
|
290
290
|
|
|
291
|
+
{(absentDataLabel || isSuppressed) && (
|
|
292
|
+
<rect
|
|
293
|
+
x={barX}
|
|
294
|
+
y={0}
|
|
295
|
+
width={barWidth}
|
|
296
|
+
height={yMax}
|
|
297
|
+
fill='transparent'
|
|
298
|
+
data-tooltip-place='top'
|
|
299
|
+
data-tooltip-offset='{"top":3}'
|
|
300
|
+
style={{ pointerEvents: 'all', cursor: 'pointer' }}
|
|
301
|
+
data-tooltip-html={tooltip}
|
|
302
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
|
|
291
306
|
{config.preliminaryData.map((pd, index) => {
|
|
292
307
|
// check if user selected column
|
|
293
308
|
const selectedSuppressionColumn = !pd.column || pd.column === bar.key
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import { Group } from '@visx/group'
|
|
3
|
+
import { BoxPlot } from '@visx/stats'
|
|
4
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
5
|
+
import ConfigContext from '../../ConfigContext'
|
|
6
|
+
import { handleTooltip, createPlots } from './helpers/index'
|
|
7
|
+
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
|
|
10
|
+
const BoxPlotHorizontal = ({ xScale, yScale, seriesScale }) => {
|
|
11
|
+
const { config, transformedData: data, colorScale, seriesHighlight } = useContext(ConfigContext)
|
|
12
|
+
const yOffset = Number(config.xAxis.size)
|
|
13
|
+
const defaultColor = APP_FONT_COLOR
|
|
14
|
+
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
15
|
+
// generate summary stats
|
|
16
|
+
const plots = createPlots(data, config)
|
|
17
|
+
const { plotNonOutlierValues, plotOutlierValues } = config.boxplot
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<ErrorBoundary component='BoxPlot Horizontal'>
|
|
21
|
+
<Group left={yOffset} top={0} className='boxplot'>
|
|
22
|
+
{plots.map(plot => {
|
|
23
|
+
const category = plot.columnCategory
|
|
24
|
+
|
|
25
|
+
return config.series.map(item => {
|
|
26
|
+
const y0 = yScale(category)
|
|
27
|
+
const offset = seriesScale(item.dataKey)
|
|
28
|
+
const isTransparent =
|
|
29
|
+
config.legend.behavior === 'highlight' &&
|
|
30
|
+
seriesHighlight.length > 0 &&
|
|
31
|
+
seriesHighlight.indexOf(item.dataKey) === -1
|
|
32
|
+
const displayPlot =
|
|
33
|
+
config.legend.behavior === 'highlight' ||
|
|
34
|
+
seriesHighlight.length === 0 ||
|
|
35
|
+
seriesHighlight.indexOf(item.dataKey) !== -1
|
|
36
|
+
const fillOpacity = isTransparent ? 0.3 : 0.7
|
|
37
|
+
// outlier & non-outlier arrays
|
|
38
|
+
const nonOut = plot.columnNonOutliers?.[item.dataKey] || []
|
|
39
|
+
const out = plot.columnOutliers?.[item.dataKey] || []
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Group key={`${category}-${item.dataKey}`} top={y0 + offset}>
|
|
43
|
+
{displayPlot && (
|
|
44
|
+
<BoxPlot
|
|
45
|
+
min={Number(plot.min[item.dataKey])}
|
|
46
|
+
max={Number(plot.max[item.dataKey])}
|
|
47
|
+
thirdQuartile={plot.q3[item.dataKey]}
|
|
48
|
+
firstQuartile={plot.q1[item.dataKey]}
|
|
49
|
+
median={plot.median[item.dataKey]}
|
|
50
|
+
horizontal={true}
|
|
51
|
+
valueScale={xScale}
|
|
52
|
+
boxWidth={seriesScale.bandwidth()}
|
|
53
|
+
fill={colorScale(item.dataKey)}
|
|
54
|
+
fillOpacity={1}
|
|
55
|
+
stroke={defaultColor}
|
|
56
|
+
boxProps={{
|
|
57
|
+
fill: colorScale(item.dataKey),
|
|
58
|
+
strokeWidth: config.boxplot.borders === 'true' ? 1.5 : 0,
|
|
59
|
+
stroke: defaultColor,
|
|
60
|
+
fillOpacity: fillOpacity
|
|
61
|
+
}}
|
|
62
|
+
minProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
|
|
63
|
+
maxProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
|
|
64
|
+
medianProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
|
|
65
|
+
outliers={
|
|
66
|
+
config.boxplot.plotOutlierValues ? _.map(plot.columnOutliers[item.dataKey], item => item) : []
|
|
67
|
+
}
|
|
68
|
+
outlierProps={{
|
|
69
|
+
style: {
|
|
70
|
+
fill: colorScale(item.dataKey),
|
|
71
|
+
opacity: fillOpacity,
|
|
72
|
+
stroke: defaultColor
|
|
73
|
+
}
|
|
74
|
+
}}
|
|
75
|
+
container
|
|
76
|
+
containerProps={{
|
|
77
|
+
'data-tooltip-html': handleTooltip(
|
|
78
|
+
config.boxplot,
|
|
79
|
+
plot.columnCategory,
|
|
80
|
+
item.dataKey,
|
|
81
|
+
_.round(plot.q1[item.dataKey], config.dataFormat.roundTo),
|
|
82
|
+
_.round(plot.q3[item.dataKey], config.dataFormat.roundTo),
|
|
83
|
+
_.round(plot.median[item.dataKey], config.dataFormat.roundTo),
|
|
84
|
+
_.round(plot.iqr[item.dataKey], config.dataFormat.roundTo),
|
|
85
|
+
config.xAxis.label,
|
|
86
|
+
defaultColor
|
|
87
|
+
),
|
|
88
|
+
'data-tooltip-id': tooltip_id,
|
|
89
|
+
tabIndex: -1
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{/* non-outlier points */}
|
|
95
|
+
{plotNonOutlierValues &&
|
|
96
|
+
nonOut.map((value, idx) => (
|
|
97
|
+
<circle
|
|
98
|
+
display={displayPlot ? 'block' : 'none'}
|
|
99
|
+
key={`non-${category}-${item.dataKey}-${idx}`}
|
|
100
|
+
cx={xScale(value)}
|
|
101
|
+
cy={seriesScale.bandwidth() / 2}
|
|
102
|
+
r={4}
|
|
103
|
+
opacity={fillOpacity}
|
|
104
|
+
fill={defaultColor}
|
|
105
|
+
style={{ stroke: defaultColor }}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
|
|
109
|
+
{/* outlier points */}
|
|
110
|
+
{plotOutlierValues &&
|
|
111
|
+
out.map((value, idx) => (
|
|
112
|
+
<circle
|
|
113
|
+
display={displayPlot ? 'block' : 'none'}
|
|
114
|
+
key={`out-${category}-${item.dataKey}-${idx}`}
|
|
115
|
+
cx={xScale(value)}
|
|
116
|
+
cy={seriesScale.bandwidth() / 2}
|
|
117
|
+
r={4}
|
|
118
|
+
opacity={fillOpacity}
|
|
119
|
+
fill={defaultColor}
|
|
120
|
+
style={{ stroke: defaultColor }}
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
123
|
+
</Group>
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
})}
|
|
127
|
+
</Group>
|
|
128
|
+
</ErrorBoundary>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
export default BoxPlotHorizontal
|
|
@@ -8,7 +8,7 @@ import { handleTooltip, createPlots } from './helpers/index'
|
|
|
8
8
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
9
9
|
import _ from 'lodash'
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const BoxPlotVertical = ({ xScale, yScale, seriesScale }) => {
|
|
12
12
|
const { config, colorScale, seriesHighlight, transformedData: data } = useContext(ConfigContext)
|
|
13
13
|
const { boxplot } = config
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
|
|
|
21
21
|
const color_0 = _.get(colorPalettesChart, [config.palette, 0], '#000')
|
|
22
22
|
const plots = createPlots(data, config)
|
|
23
23
|
return (
|
|
24
|
-
<ErrorBoundary component='BoxPlot'>
|
|
24
|
+
<ErrorBoundary component='BoxPlot Vertical'>
|
|
25
25
|
<Group left={Number(config.yAxis.size)} className='boxplot' key={`boxplot-group`}>
|
|
26
26
|
{plots.map((d, i) => {
|
|
27
27
|
const offset = boxWidth - constrainedWidth
|
|
@@ -41,7 +41,7 @@ const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
|
|
|
41
41
|
config.legend.behavior === 'highlight' ||
|
|
42
42
|
seriesHighlight.length === 0 ||
|
|
43
43
|
seriesHighlight.indexOf(item.dataKey) !== -1
|
|
44
|
-
const fillOpacity = isTransparent ? 0.3 : 0.
|
|
44
|
+
const fillOpacity = isTransparent ? 0.3 : 0.7
|
|
45
45
|
return (
|
|
46
46
|
<Group key={`boxplotplot-${item.dataKey}-${index}`}>
|
|
47
47
|
{boxplot.plotNonOutlierValues &&
|
|
@@ -131,4 +131,4 @@ const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
|
|
|
131
131
|
)
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
export default
|
|
134
|
+
export default BoxPlotVertical
|
|
@@ -33,7 +33,7 @@ export const calculateBoxPlotStats = (values: number[]) => {
|
|
|
33
33
|
if (!values || values.length === 0) return {}
|
|
34
34
|
|
|
35
35
|
// Sort the values
|
|
36
|
-
const sortedValues = _.sortBy(values)
|
|
36
|
+
const sortedValues = _.sortBy(values.map(v => Number(v)))
|
|
37
37
|
|
|
38
38
|
// Quartiles
|
|
39
39
|
const firstQuartile = d3.quantile(sortedValues, 0.25) ?? 0
|
|
@@ -45,18 +45,28 @@ export const calculateBoxPlotStats = (values: number[]) => {
|
|
|
45
45
|
// Outlier Bounds
|
|
46
46
|
const lowerBound = firstQuartile - 1.5 * iqr
|
|
47
47
|
const upperBound = thirdQuartile + 1.5 * iqr
|
|
48
|
+
// const lowerFence = q1 - 1.5 * iqr
|
|
49
|
+
// const upperFence = q3 + 1.5 * iqr
|
|
48
50
|
|
|
49
51
|
// Non-Outlier Values
|
|
50
52
|
const nonOutliers = sortedValues.filter(value => value >= lowerBound && value <= upperBound)
|
|
51
|
-
|
|
53
|
+
// **Outliers** =
|
|
54
|
+
const outliers = sortedValues.filter(v => v < lowerBound || v > upperBound)
|
|
55
|
+
const whiskerMax =
|
|
56
|
+
sortedValues
|
|
57
|
+
.slice()
|
|
58
|
+
.reverse()
|
|
59
|
+
.find(v => v <= upperBound) ?? thirdQuartile
|
|
60
|
+
const whiskerMin = nonOutliers.length > 0 ? nonOutliers[0] : firstQuartile
|
|
52
61
|
// Calculate Box Plot Stats
|
|
53
62
|
return {
|
|
54
|
-
min:
|
|
55
|
-
max:
|
|
56
|
-
median: d3.median(sortedValues),
|
|
63
|
+
min: whiskerMin,
|
|
64
|
+
max: whiskerMax,
|
|
65
|
+
median: d3.median(sortedValues),
|
|
57
66
|
firstQuartile,
|
|
58
67
|
thirdQuartile,
|
|
59
|
-
iqr
|
|
68
|
+
iqr,
|
|
69
|
+
outliers
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
|
|
@@ -109,16 +119,26 @@ export const createPlots = (data, config) => {
|
|
|
109
119
|
|
|
110
120
|
// Calculate outliers and non-outliers for each series key
|
|
111
121
|
Object.keys(keyValues).forEach(key => {
|
|
112
|
-
const
|
|
122
|
+
const raw = keyValues[key] ?? []
|
|
123
|
+
|
|
124
|
+
// 2) normalize → trim, drop empties/non-numbers, coerce to Number
|
|
125
|
+
const cleaned: number[] = raw
|
|
126
|
+
.map(v => (typeof v === 'string' ? v.trim() : v)) // trim strings
|
|
127
|
+
.filter(v => v != null && v !== '' && !isNaN(+v)) // drop null/''/non-nums
|
|
128
|
+
.map(v => +v)
|
|
129
|
+
|
|
130
|
+
if (cleaned.length === 0) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
113
133
|
|
|
114
134
|
// Calculate box plot statistics
|
|
115
|
-
const { firstQuartile, thirdQuartile, min, max, median, iqr } = calculateBoxPlotStats(
|
|
135
|
+
const { firstQuartile, thirdQuartile, min, max, median, iqr, outliers } = calculateBoxPlotStats(cleaned)
|
|
116
136
|
// Calculate outliers and non-outliers
|
|
117
|
-
columnOutliers[key] = calculateOutliers(
|
|
118
|
-
columnNonOutliers[key] = calculateNonOutliers(
|
|
137
|
+
columnOutliers[key] = calculateOutliers(cleaned, firstQuartile, thirdQuartile).map(Number)
|
|
138
|
+
columnNonOutliers[key] = calculateNonOutliers(cleaned, firstQuartile, thirdQuartile).map(Number)
|
|
119
139
|
columnMedian[key] = median
|
|
120
|
-
columnMin[key] = min
|
|
121
|
-
columnMax[key] = max
|
|
140
|
+
columnMin[key] = Number(min)
|
|
141
|
+
columnMax[key] = Number(max)
|
|
122
142
|
columnQ1[key] = firstQuartile
|
|
123
143
|
columnQ3[key] = thirdQuartile
|
|
124
144
|
columnIqr[key] = iqr
|
|
@@ -120,7 +120,7 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
|
|
|
120
120
|
svg.call(brushHandle, selection, formattedStartDate, formattedEndDate)
|
|
121
121
|
|
|
122
122
|
const payload = {
|
|
123
|
-
active: config.
|
|
123
|
+
active: config.xAxis.brushActive,
|
|
124
124
|
isBrushing: isUserBrushing,
|
|
125
125
|
data: finalData
|
|
126
126
|
}
|
|
@@ -20,6 +20,7 @@ import DataTableEditor from '@cdc/core/components/EditorPanel/DataTableEditor'
|
|
|
20
20
|
import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
|
|
21
21
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
22
22
|
import { Select, TextField, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
|
|
23
|
+
import MultiSelect from '@cdc/core/components/MultiSelect'
|
|
23
24
|
import { viewports } from '@cdc/core/helpers/getViewport'
|
|
24
25
|
import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
25
26
|
|
|
@@ -109,7 +110,7 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
|
|
|
109
110
|
let preliminaryData = config.preliminaryData ? [...config.preliminaryData] : []
|
|
110
111
|
const defaultValues = {
|
|
111
112
|
type: defaultType,
|
|
112
|
-
|
|
113
|
+
seriesKeys: [],
|
|
113
114
|
label: 'Suppressed',
|
|
114
115
|
column: '',
|
|
115
116
|
value: '',
|
|
@@ -159,7 +160,7 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
|
|
|
159
160
|
displayTable,
|
|
160
161
|
displayTooltip,
|
|
161
162
|
label,
|
|
162
|
-
|
|
163
|
+
seriesKeys,
|
|
163
164
|
style,
|
|
164
165
|
symbol,
|
|
165
166
|
type,
|
|
@@ -384,14 +385,18 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
|
|
|
384
385
|
</>
|
|
385
386
|
) : (
|
|
386
387
|
<>
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
388
|
+
<label>
|
|
389
|
+
<span className='edit-label'>ASSOCIATE TO THESE SERIES</span>
|
|
390
|
+
<MultiSelect
|
|
391
|
+
fieldName='seriesKeys'
|
|
392
|
+
updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
|
|
393
|
+
options={(config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys).map(c => ({
|
|
394
|
+
label: c,
|
|
395
|
+
value: c
|
|
396
|
+
}))}
|
|
397
|
+
selected={seriesKeys}
|
|
398
|
+
/>
|
|
399
|
+
</label>
|
|
395
400
|
<Select
|
|
396
401
|
value={column}
|
|
397
402
|
initial='Select'
|
|
@@ -1193,7 +1198,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
1193
1198
|
if (isDebug && config?.series?.length === 0) {
|
|
1194
1199
|
let setdatacol = setDataColumn()
|
|
1195
1200
|
if (setdatacol !== '') addNewSeries(setdatacol)
|
|
1196
|
-
if (isDebug) console.
|
|
1201
|
+
if (isDebug) console.log('### COVE DEBUG: Chart: Setting default datacol=', setdatacol) // eslint-disable-line
|
|
1197
1202
|
}
|
|
1198
1203
|
|
|
1199
1204
|
const chartsWithOptions = [
|
|
@@ -2926,9 +2931,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
2926
2931
|
/>
|
|
2927
2932
|
{visHasBrushChart() && (
|
|
2928
2933
|
<CheckBox
|
|
2929
|
-
value={config.
|
|
2930
|
-
section='
|
|
2931
|
-
fieldName='
|
|
2934
|
+
value={config.xAxis.brushActive}
|
|
2935
|
+
section='xAxis'
|
|
2936
|
+
fieldName='brushActive'
|
|
2932
2937
|
label='Brush Slider '
|
|
2933
2938
|
updateField={updateFieldDeprecated}
|
|
2934
2939
|
tooltip={
|
|
@@ -131,7 +131,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
131
131
|
options={Object.keys(approvedCurveTypes)}
|
|
132
132
|
/>
|
|
133
133
|
)}
|
|
134
|
-
{visualizationType === 'Bar' && (
|
|
134
|
+
{(visualizationType === 'Bar' || visualizationType === 'Box Plot') && (
|
|
135
135
|
<Select
|
|
136
136
|
value={config.orientation || 'vertical'}
|
|
137
137
|
fieldName='orientation'
|
|
@@ -178,7 +178,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
178
178
|
options={['standard', 'shallow', 'finger']}
|
|
179
179
|
/>
|
|
180
180
|
)}
|
|
181
|
-
{visualizationType === 'Bar' && config.orientation === 'horizontal' && (
|
|
181
|
+
{(visualizationType === 'Bar' || visualizationType === 'Box Plot') && config.orientation === 'horizontal' && (
|
|
182
182
|
<Select
|
|
183
183
|
value={config.yAxis.labelPlacement || 'Below Bar'}
|
|
184
184
|
section='yAxis'
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
|
-
|
|
2
|
+
import { replace } from 'lodash'
|
|
3
3
|
// cdc
|
|
4
4
|
import ConfigContext from '../../ConfigContext'
|
|
5
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
6
|
import { colorPalettesChart, sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
7
|
+
import { getBridgedData } from '../../helpers/getBridgedData'
|
|
7
8
|
|
|
8
9
|
// visx & d3
|
|
9
10
|
import { curveMonotoneX } from '@visx/curve'
|
|
@@ -11,7 +12,7 @@ import { Bar, Area, LinePath } from '@visx/shape'
|
|
|
11
12
|
import { Group } from '@visx/group'
|
|
12
13
|
|
|
13
14
|
const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, handleTooltipMouseOff }) => {
|
|
14
|
-
const { transformedData: data, rawData, config, seriesHighlight } = useContext(ConfigContext)
|
|
15
|
+
const { transformedData: data, rawData, config, seriesHighlight, parseDate } = useContext(ConfigContext)
|
|
15
16
|
const { xAxis, yAxis, legend, runtime } = config
|
|
16
17
|
const DEBUG = false
|
|
17
18
|
|
|
@@ -23,12 +24,17 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
23
24
|
if (!group || !group.stages) return false
|
|
24
25
|
return group.stages.map((stage, stageIndex) => {
|
|
25
26
|
const { behavior } = legend
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let displayArea =
|
|
27
|
+
let transparentArea =
|
|
28
|
+
behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(stage.key) === -1
|
|
29
|
+
let displayArea =
|
|
30
|
+
behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(stage.key) !== -1
|
|
31
|
+
const bridgedData = getBridgedData(stage.key, group.stageColumn, rawData)
|
|
29
32
|
|
|
30
33
|
return (
|
|
31
|
-
<Group
|
|
34
|
+
<Group
|
|
35
|
+
className={`forecasting-areas-combo-${index}`}
|
|
36
|
+
key={`forecasting-areas--stage-${replace(stage.key, / /g, '—')}-${index}`}
|
|
37
|
+
>
|
|
32
38
|
{group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
|
|
33
39
|
const palette = sequentialPalettes[stage.color] || colorPalettesChart[stage.color] || false
|
|
34
40
|
|
|
@@ -44,13 +50,19 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
44
50
|
|
|
45
51
|
if (ciGroup.high === '' || ciGroup.low === '') return
|
|
46
52
|
return (
|
|
47
|
-
<Group
|
|
53
|
+
<Group
|
|
54
|
+
key={`forecasting-areas--stage-${replace(
|
|
55
|
+
stage.key,
|
|
56
|
+
/ /g,
|
|
57
|
+
'—'
|
|
58
|
+
)}--group-${stageIndex}-${ciGroupIndex}`}
|
|
59
|
+
>
|
|
48
60
|
{/* prettier-ignore */}
|
|
49
61
|
<Area
|
|
50
62
|
curve={curveMonotoneX}
|
|
51
|
-
data={
|
|
63
|
+
data={bridgedData}
|
|
52
64
|
fill={getFill()}
|
|
53
|
-
opacity={transparentArea
|
|
65
|
+
opacity={transparentArea? 0.1 : 0.5 }
|
|
54
66
|
x={d => xScale(Date.parse(d[xAxis.dataKey]))}
|
|
55
67
|
y0={d => yScale(d[ciGroup.low])}
|
|
56
68
|
y1={d => yScale(d[ciGroup.high])}
|
|
@@ -59,10 +71,10 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
59
71
|
{ciGroupIndex === 0 && (
|
|
60
72
|
<>
|
|
61
73
|
{/* prettier-ignore */}
|
|
62
|
-
<LinePath data={
|
|
74
|
+
<LinePath data={bridgedData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.high]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
|
|
63
75
|
|
|
64
76
|
{/* prettier-ignore */}
|
|
65
|
-
<LinePath data={
|
|
77
|
+
<LinePath data={bridgedData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.low]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
|
|
66
78
|
</>
|
|
67
79
|
)}
|
|
68
80
|
</Group>
|
|
@@ -73,7 +85,15 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
73
85
|
})
|
|
74
86
|
})}
|
|
75
87
|
<Group key='tooltip-hover-section'>
|
|
76
|
-
<Bar
|
|
88
|
+
<Bar
|
|
89
|
+
key={'bars'}
|
|
90
|
+
width={Number(width)}
|
|
91
|
+
height={Number(height)}
|
|
92
|
+
fill={DEBUG ? 'red' : 'transparent'}
|
|
93
|
+
fillOpacity={0.05}
|
|
94
|
+
onMouseMove={e => handleTooltipMouseOver(e, data)}
|
|
95
|
+
onMouseOut={handleTooltipMouseOff}
|
|
96
|
+
/>
|
|
77
97
|
</Group>
|
|
78
98
|
</Group>
|
|
79
99
|
</ErrorBoundary>
|
|
@@ -19,9 +19,9 @@ export const getMarginTop = (isLegendBottom, config) => {
|
|
|
19
19
|
if (!isLegendBottom) {
|
|
20
20
|
return '0px'
|
|
21
21
|
}
|
|
22
|
-
if (isLegendBottom && config.
|
|
22
|
+
if (isLegendBottom && config.xAxis.brushActive && !config.legend.hide) {
|
|
23
23
|
const additiolMargin = 25
|
|
24
|
-
return `${DEFAULT_MARGIN_TOP + config.brush
|
|
24
|
+
return `${DEFAULT_MARGIN_TOP + config.brush?.height + additiolMargin}px`
|
|
25
25
|
} else {
|
|
26
26
|
return `${DEFAULT_MARGIN_TOP}px`
|
|
27
27
|
}
|
|
@@ -17,19 +17,19 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
17
17
|
|
|
18
18
|
const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
|
|
19
19
|
const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
|
|
20
|
-
pd => pd.
|
|
20
|
+
pd => pd.seriesKeys?.length && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
|
|
21
21
|
)
|
|
22
22
|
const isEffectLine = (pd, dataPoint) => {
|
|
23
23
|
if (dynamicCategory) {
|
|
24
24
|
return (
|
|
25
25
|
pd.type === 'effect' &&
|
|
26
26
|
pd.style !== 'Open Circles' &&
|
|
27
|
-
pd.seriesKey
|
|
27
|
+
pd.seriesKeys.includes(seriesKey) &&
|
|
28
28
|
String(dataPoint[dynamicSeriesKey]) === String(pd.value)
|
|
29
29
|
)
|
|
30
30
|
} else {
|
|
31
31
|
return (
|
|
32
|
-
pd.seriesKey
|
|
32
|
+
pd.seriesKeys.includes(seriesKey) &&
|
|
33
33
|
dataPoint[pd.column] === pd.value &&
|
|
34
34
|
pd.type === 'effect' &&
|
|
35
35
|
pd.style !== 'Open Circles'
|
|
@@ -71,11 +71,11 @@ export const filterCircles = (
|
|
|
71
71
|
): DataItem[] => {
|
|
72
72
|
// Filter and map preliminaryData to get circlesFiltered
|
|
73
73
|
const circlesFiltered = preliminaryData
|
|
74
|
-
?.filter(item => item.style.includes('Circles') && item.type === 'effect')
|
|
74
|
+
?.filter(item => item.style.includes('Circles') && item.type === 'effect' && item.seriesKeys?.length)
|
|
75
75
|
.map(item => ({
|
|
76
76
|
column: item.column,
|
|
77
77
|
value: item.value,
|
|
78
|
-
|
|
78
|
+
seriesKeys: item.seriesKeys,
|
|
79
79
|
circleSize: item.circleSize,
|
|
80
80
|
style: item.style
|
|
81
81
|
}))
|
|
@@ -85,7 +85,7 @@ export const filterCircles = (
|
|
|
85
85
|
circlesFiltered.forEach(fc => {
|
|
86
86
|
if (
|
|
87
87
|
item[fc.column] === fc.value &&
|
|
88
|
-
fc.seriesKey
|
|
88
|
+
fc.seriesKeys.includes(seriesKey) &&
|
|
89
89
|
item[seriesKey] &&
|
|
90
90
|
fc.style === 'Open Circles'
|
|
91
91
|
) {
|
|
@@ -98,7 +98,7 @@ export const filterCircles = (
|
|
|
98
98
|
}
|
|
99
99
|
if (
|
|
100
100
|
(!fc.value || item[fc.column] === fc.value) &&
|
|
101
|
-
fc.seriesKey
|
|
101
|
+
fc.seriesKeys.includes(seriesKey) &&
|
|
102
102
|
item[seriesKey] &&
|
|
103
103
|
fc.style === 'Filled Circles'
|
|
104
104
|
) {
|