@cdc/chart 4.24.12-2 → 4.25.1
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 +79411 -78816
- package/examples/feature/boxplot/boxplot.json +2 -157
- package/examples/feature/boxplot/testing.csv +23 -38
- package/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json +394 -30
- package/examples/private/not-loading.json +360 -0
- package/index.html +7 -14
- package/package.json +2 -2
- package/src/CdcChart.tsx +92 -1512
- package/src/CdcChartComponent.tsx +1105 -0
- package/src/_stories/Chart.Anchors.stories.tsx +1 -1
- package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
- package/src/_stories/Chart.DynamicSeries.stories.tsx +1 -1
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
- package/src/_stories/Chart.ScatterPlot.stories.tsx +19 -0
- package/src/_stories/Chart.tooltip.stories.tsx +1 -2
- package/src/_stories/ChartAnnotation.stories.tsx +1 -1
- package/src/_stories/ChartAxisLabels.stories.tsx +1 -1
- package/src/_stories/ChartAxisTitles.stories.tsx +1 -1
- package/src/_stories/ChartEditor.stories.tsx +1 -1
- package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
- package/src/_stories/ChartLine.Symbols.stories.tsx +18 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +1 -1
- package/src/_stories/_mock/line_chart_symbols.json +437 -0
- package/src/_stories/_mock/scatterplot-image-download.json +1244 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +3 -11
- package/src/components/Annotations/components/AnnotationDropdown.tsx +3 -3
- package/src/components/Axis/Categorical.Axis.tsx +3 -4
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +14 -5
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +10 -4
- package/src/components/BoxPlot/BoxPlot.tsx +34 -32
- package/src/components/BoxPlot/helpers/index.ts +108 -18
- package/src/components/DeviationBar.jsx +2 -6
- package/src/components/EditorPanel/EditorPanel.tsx +62 -6
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +4 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +44 -7
- package/src/components/ForestPlot/ForestPlot.tsx +176 -26
- package/src/components/Legend/Legend.Component.tsx +29 -38
- package/src/components/Legend/Legend.Suppression.tsx +3 -5
- package/src/components/Legend/Legend.tsx +2 -2
- package/src/components/Legend/LegendLine.Shape.tsx +51 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +29 -26
- package/src/components/Legend/helpers/getLegendClasses.ts +20 -38
- package/src/components/Legend/helpers/index.ts +14 -7
- package/src/components/Legend/tests/getLegendClasses.test.ts +3 -20
- package/src/components/LineChart/components/LineChart.Circle.tsx +90 -88
- package/src/components/LineChart/index.tsx +4 -0
- package/src/components/LinearChart.tsx +54 -29
- package/src/components/PairedBarChart.jsx +2 -9
- package/src/components/ZoomBrush.tsx +5 -7
- package/src/data/initial-state.js +6 -3
- package/src/helpers/getBoxPlotConfig.ts +68 -0
- package/src/helpers/getColorScale.ts +28 -0
- package/src/helpers/getComboChartConfig.ts +42 -0
- package/src/helpers/getExcludedData.ts +37 -0
- package/src/helpers/getTopAxis.ts +7 -0
- package/src/hooks/useBarChart.ts +28 -9
- package/src/hooks/{useHighlightedBars.js → useHighlightedBars.ts} +2 -1
- package/src/hooks/useIntersectionObserver.ts +37 -0
- package/src/hooks/useMinMax.ts +4 -0
- package/src/hooks/useReduceData.ts +1 -1
- package/src/hooks/useTooltip.tsx +9 -1
- package/src/index.jsx +1 -0
- package/src/scss/DataTable.scss +0 -5
- package/src/scss/main.scss +30 -115
- package/src/types/ChartConfig.ts +6 -3
- package/src/types/ChartContext.ts +1 -3
- package/src/helpers/getQuartiles.ts +0 -27
- package/src/hooks/useColorScale.ts +0 -50
- package/src/hooks/useIntersectionObserver.jsx +0 -29
- package/src/hooks/useTopAxis.js +0 -6
|
@@ -27,7 +27,7 @@ import CategoricalYAxis from './Axis/Categorical.Axis'
|
|
|
27
27
|
|
|
28
28
|
// Helpers
|
|
29
29
|
import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
|
|
30
|
-
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
30
|
+
import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
|
|
31
31
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
32
32
|
import { calcInitialHeight } from '../helpers/sizeHelpers'
|
|
33
33
|
|
|
@@ -36,13 +36,13 @@ import useMinMax from '../hooks/useMinMax'
|
|
|
36
36
|
import useReduceData from '../hooks/useReduceData'
|
|
37
37
|
import useRightAxis from '../hooks/useRightAxis'
|
|
38
38
|
import useScales, { getTickValues, filterAndShiftLinearDateTicks } from '../hooks/useScales'
|
|
39
|
-
import
|
|
39
|
+
import getTopAxis from '../helpers/getTopAxis'
|
|
40
40
|
import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
41
41
|
import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
42
42
|
import Annotation from './Annotations'
|
|
43
43
|
import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
|
|
44
|
-
import { fontSizes } from '@cdc/core/helpers/cove/fontSettings'
|
|
45
44
|
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
45
|
+
import _ from 'lodash'
|
|
46
46
|
|
|
47
47
|
type LinearChartProps = {
|
|
48
48
|
parentWidth: number
|
|
@@ -50,9 +50,14 @@ type LinearChartProps = {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const BOTTOM_LABEL_PADDING = 9
|
|
53
|
-
const X_TICK_LABEL_PADDING =
|
|
53
|
+
const X_TICK_LABEL_PADDING = 4.5
|
|
54
54
|
const DEFAULT_TICK_LENGTH = 8
|
|
55
55
|
const MONTH_AS_MS = 1000 * 60 * 60 * 24 * 30
|
|
56
|
+
const TICK_LABEL_FONT_SIZE = 16
|
|
57
|
+
const TICK_LABEL_FONT_SIZE_SMALL = 13
|
|
58
|
+
const AXIS_LABEL_FONT_SIZE = 18
|
|
59
|
+
const AXIS_LABEL_FONT_SIZE_SMALL = 14
|
|
60
|
+
const TICK_LABEL_MARGIN_RIGHT = 4.5
|
|
56
61
|
|
|
57
62
|
const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
|
|
58
63
|
// prettier-ignore
|
|
@@ -99,7 +104,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
99
104
|
// HOOKS % STATES
|
|
100
105
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
|
|
101
106
|
const { visSupportsReactTooltip } = useEditorPermissions()
|
|
102
|
-
const { hasTopAxis } =
|
|
107
|
+
const { hasTopAxis } = getTopAxis(config)
|
|
103
108
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
104
109
|
const [point, setPoint] = useState({ x: 0, y: 0 })
|
|
105
110
|
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
@@ -129,6 +134,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
129
134
|
const labelsOverflow = onlyShowTopPrefixSuffix && !suffixHasNoSpace
|
|
130
135
|
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
131
136
|
const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
|
|
137
|
+
const tickLabelFontSize = isMobileHeightViewport(currentViewport) ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
|
|
138
|
+
const axisLabelFontSize = isMobileHeightViewport(currentViewport) ? AXIS_LABEL_FONT_SIZE_SMALL : AXIS_LABEL_FONT_SIZE
|
|
139
|
+
const GET_TEXT_WIDTH_FONT = `normal ${tickLabelFontSize}px Nunito, sans-serif`
|
|
132
140
|
|
|
133
141
|
// zero if not forest plot
|
|
134
142
|
const forestRowsHeight = isForestPlot ? config.data.length * config.forestPlot.rowHeight : 0
|
|
@@ -414,12 +422,20 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
414
422
|
if (!yAxisAutoPadding) return
|
|
415
423
|
setYAxisAutoPadding(0)
|
|
416
424
|
}, [maxValue])
|
|
425
|
+
|
|
417
426
|
useEffect(() => {
|
|
418
427
|
if (orientation === 'horizontal') return
|
|
428
|
+
if (!labelsOverflow) return
|
|
429
|
+
|
|
430
|
+
// minimum percentage of the max value that the distance should be from the top grid line
|
|
431
|
+
const MINIMUM_DISTANCE_PERCENTAGE = 0.025
|
|
432
|
+
|
|
433
|
+
const topGridLine = Math.max(...yScale.ticks(handleNumTicks))
|
|
434
|
+
const needsPaddingThreshold = topGridLine - maxValue * MINIMUM_DISTANCE_PERCENTAGE
|
|
435
|
+
const maxValueIsGreaterThanThreshold = maxValue > needsPaddingThreshold
|
|
419
436
|
|
|
420
|
-
|
|
437
|
+
if (!maxValueIsGreaterThanThreshold) return
|
|
421
438
|
|
|
422
|
-
if (!maxValueIsGreaterThanTopGridLine || !labelsOverflow) return
|
|
423
439
|
const ticks = yScale.ticks(handleNumTicks)
|
|
424
440
|
const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
|
|
425
441
|
const nextTick = Math.max(...yScale.ticks(handleNumTicks)) + tickGap
|
|
@@ -427,9 +443,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
427
443
|
const calculatedPadding = (nextTick - maxValue) / divideBy
|
|
428
444
|
|
|
429
445
|
// if auto padding is too close to next tick, add one more ticks worth of padding
|
|
430
|
-
const PADDING_THRESHOLD = 0.025
|
|
431
446
|
const newPadding =
|
|
432
|
-
calculatedPadding >
|
|
447
|
+
calculatedPadding > MINIMUM_DISTANCE_PERCENTAGE ? calculatedPadding : calculatedPadding + tickGap / divideBy
|
|
433
448
|
|
|
434
449
|
/* sometimes even though the padding is getting to the next tick exactly,
|
|
435
450
|
d3 still doesn't show the tick. we add 0.1 to ensure to tip it over the edge */
|
|
@@ -447,7 +462,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
447
462
|
const numberOfTicks = filteredTicks?.length
|
|
448
463
|
const xMaxHalf = xScale.range()[0] || xMax / 2
|
|
449
464
|
const tickWidthAll = filteredTicks.map(tick =>
|
|
450
|
-
getTextWidth(formatNumber(tick.value, 'left'),
|
|
465
|
+
getTextWidth(formatNumber(tick.value, 'left'), GET_TEXT_WIDTH_FONT)
|
|
451
466
|
)
|
|
452
467
|
const accumulator = 100
|
|
453
468
|
const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
|
|
@@ -484,10 +499,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
484
499
|
return (
|
|
485
500
|
<Group className='bottom-axis'>
|
|
486
501
|
{props.ticks.map((tick, i) => {
|
|
487
|
-
const textWidth = getTextWidth(
|
|
488
|
-
formatNumber(tick.value, 'left'),
|
|
489
|
-
`normal ${fontSizes[config.fontSize]}px sans-serif`
|
|
490
|
-
)
|
|
491
502
|
const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
|
|
492
503
|
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
493
504
|
const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
|
|
@@ -506,6 +517,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
506
517
|
angle={-angle}
|
|
507
518
|
verticalAnchor={angle ? 'middle' : 'start'}
|
|
508
519
|
textAnchor={textAnchor}
|
|
520
|
+
fontSize={tickLabelFontSize}
|
|
509
521
|
>
|
|
510
522
|
{formatNumber(tick.value, 'left')}
|
|
511
523
|
</Text>
|
|
@@ -536,10 +548,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
536
548
|
<>
|
|
537
549
|
<Group className='bottom-axis'>
|
|
538
550
|
{props.ticks.map((tick, i) => {
|
|
539
|
-
const textWidth = getTextWidth(
|
|
540
|
-
formatNumber(tick.value, 'left'),
|
|
541
|
-
`normal ${fontSizes[config.fontSize]}px sans-serif`
|
|
542
|
-
)
|
|
543
551
|
const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
|
|
544
552
|
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
545
553
|
const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
|
|
@@ -557,6 +565,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
557
565
|
angle={-angle}
|
|
558
566
|
verticalAnchor={angle ? 'middle' : 'start'}
|
|
559
567
|
textAnchor={textAnchor}
|
|
568
|
+
fontSize={tickLabelFontSize}
|
|
560
569
|
>
|
|
561
570
|
{formatNumber(tick.value, 'left')}
|
|
562
571
|
</Text>
|
|
@@ -574,6 +583,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
574
583
|
stroke='#333'
|
|
575
584
|
textAnchor={'middle'}
|
|
576
585
|
verticalAnchor='start'
|
|
586
|
+
fontSize={axisLabelFontSize}
|
|
577
587
|
>
|
|
578
588
|
{runtime.xAxis.label}
|
|
579
589
|
</Text>
|
|
@@ -650,6 +660,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
650
660
|
transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
|
|
651
661
|
fontWeight='bold'
|
|
652
662
|
fill={config.yAxis.labelColor}
|
|
663
|
+
fontSize={axisLabelFontSize}
|
|
653
664
|
>
|
|
654
665
|
{props.label}
|
|
655
666
|
</Text>
|
|
@@ -1043,7 +1054,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1043
1054
|
const lastTick = props.ticks.length - 1 === i
|
|
1044
1055
|
const hideTopTick = lastTick && onlyShowTopPrefixSuffix && suffix && !suffixHasNoSpace
|
|
1045
1056
|
const valueOnLinePadding = hideAxis ? -8 : -12
|
|
1046
|
-
const labelXPadding = labelsAboveGridlines ? valueOnLinePadding :
|
|
1057
|
+
const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT
|
|
1047
1058
|
const labelYPadding = labelsAboveGridlines ? 4 : 0
|
|
1048
1059
|
const labelX = tick.to.x - labelXPadding
|
|
1049
1060
|
const labelY = tick.to.y - labelYPadding
|
|
@@ -1060,6 +1071,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1060
1071
|
to={isLogarithmicAxis ? to : tick.to}
|
|
1061
1072
|
stroke={config.yAxis.tickColor}
|
|
1062
1073
|
display={orientation === 'horizontal' ? 'none' : 'block'}
|
|
1074
|
+
fontSize={tickLabelFontSize}
|
|
1063
1075
|
/>
|
|
1064
1076
|
)}
|
|
1065
1077
|
|
|
@@ -1077,6 +1089,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1077
1089
|
}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
|
|
1078
1090
|
verticalAnchor={'start'}
|
|
1079
1091
|
textAnchor={'end'}
|
|
1092
|
+
fontSize={tickLabelFontSize}
|
|
1080
1093
|
>
|
|
1081
1094
|
{tick.formattedValue}
|
|
1082
1095
|
</Text>
|
|
@@ -1092,6 +1105,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1092
1105
|
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1093
1106
|
verticalAnchor={'start'}
|
|
1094
1107
|
textAnchor={'end'}
|
|
1108
|
+
fontSize={tickLabelFontSize}
|
|
1095
1109
|
>
|
|
1096
1110
|
{tick.formattedValue}
|
|
1097
1111
|
</Text>
|
|
@@ -1106,6 +1120,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1106
1120
|
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1107
1121
|
textAnchor={'end'}
|
|
1108
1122
|
verticalAnchor='middle'
|
|
1123
|
+
fontSize={tickLabelFontSize}
|
|
1109
1124
|
>
|
|
1110
1125
|
{tick.formattedValue}
|
|
1111
1126
|
</Text>
|
|
@@ -1121,6 +1136,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1121
1136
|
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1122
1137
|
textAnchor={'end'}
|
|
1123
1138
|
verticalAnchor='middle'
|
|
1139
|
+
fontSize={tickLabelFontSize}
|
|
1124
1140
|
>
|
|
1125
1141
|
{tick.formattedValue}
|
|
1126
1142
|
</Text>
|
|
@@ -1139,6 +1155,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1139
1155
|
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
1140
1156
|
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
1141
1157
|
fill={config.yAxis.tickLabelColor}
|
|
1158
|
+
fontSize={tickLabelFontSize}
|
|
1142
1159
|
>
|
|
1143
1160
|
{config.runtime.seriesLabelsAll[tick.formattedValue - 1]}
|
|
1144
1161
|
</Text>
|
|
@@ -1181,6 +1198,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1181
1198
|
paintOrder={'stroke'} // keeps stroke under fill
|
|
1182
1199
|
strokeLinejoin='round'
|
|
1183
1200
|
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1201
|
+
fontSize={tickLabelFontSize}
|
|
1184
1202
|
>
|
|
1185
1203
|
{suffix}
|
|
1186
1204
|
</BlurStrokeText>
|
|
@@ -1202,6 +1220,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1202
1220
|
strokeLinejoin='round'
|
|
1203
1221
|
paintOrder={'stroke'} // keeps stroke under fill
|
|
1204
1222
|
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1223
|
+
fontSize={tickLabelFontSize}
|
|
1205
1224
|
>
|
|
1206
1225
|
{`${tick.formattedValue}${combineDomSuffixWithValue ? suffix : ''}`}
|
|
1207
1226
|
</BlurStrokeText>
|
|
@@ -1217,6 +1236,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1217
1236
|
transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
|
|
1218
1237
|
fontWeight='bold'
|
|
1219
1238
|
fill={config.yAxis.labelColor}
|
|
1239
|
+
fontSize={axisLabelFontSize}
|
|
1220
1240
|
>
|
|
1221
1241
|
{props.label}
|
|
1222
1242
|
</Text>
|
|
@@ -1279,6 +1299,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1279
1299
|
verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
|
|
1280
1300
|
textAnchor={'start'}
|
|
1281
1301
|
fill={config.yAxis.rightAxisTickLabelColor}
|
|
1302
|
+
fontSize={tickLabelFontSize}
|
|
1282
1303
|
>
|
|
1283
1304
|
{tick.formattedValue}
|
|
1284
1305
|
</Text>
|
|
@@ -1298,6 +1319,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1298
1319
|
}, ${axisCenter}) rotate(-90)`}
|
|
1299
1320
|
fontWeight='bold'
|
|
1300
1321
|
fill={config.yAxis.rightAxisLabelColor}
|
|
1322
|
+
fontSize={axisLabelFontSize}
|
|
1301
1323
|
>
|
|
1302
1324
|
{props.label}
|
|
1303
1325
|
</Text>
|
|
@@ -1337,17 +1359,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1337
1359
|
numTicks={useDateSpanMonths ? dateSpanMonths : xTickCount}
|
|
1338
1360
|
tickStroke='#333'
|
|
1339
1361
|
tickValues={
|
|
1340
|
-
config.xAxis.manual
|
|
1362
|
+
config.runtime.xAxis.manual
|
|
1341
1363
|
? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
|
|
1342
|
-
: config.xAxis.type === 'date'
|
|
1364
|
+
: config.runtime.xAxis.type === 'date'
|
|
1343
1365
|
? xAxisDataMapped
|
|
1344
1366
|
: undefined
|
|
1345
1367
|
}
|
|
1346
1368
|
>
|
|
1347
1369
|
{props => {
|
|
1370
|
+
const hasDynamicCategory = config.series.some(s => s.dynamicCategory)
|
|
1348
1371
|
// For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
|
|
1349
1372
|
// so the last tick is always labeled
|
|
1350
|
-
if (config.xAxis.type === 'date' && !config.xAxis.manual) {
|
|
1373
|
+
if (config.runtime.xAxis.type === 'date' && !config.runtime.xAxis.manual && !hasDynamicCategory) {
|
|
1351
1374
|
props.ticks = filterAndShiftLinearDateTicks(config, props, xAxisDataMapped, formatDate)
|
|
1352
1375
|
}
|
|
1353
1376
|
|
|
@@ -1355,7 +1378,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1355
1378
|
useDateSpanMonths &&
|
|
1356
1379
|
xScale
|
|
1357
1380
|
.ticks(xTickCount)
|
|
1358
|
-
.map(t =>
|
|
1381
|
+
.map(t =>
|
|
1382
|
+
props.ticks.findIndex(
|
|
1383
|
+
tick => (typeof tick.value === 'number' ? tick.value : tick.value.getTime()) === t.getTime()
|
|
1384
|
+
)
|
|
1385
|
+
)
|
|
1359
1386
|
.slice(0, 2)
|
|
1360
1387
|
.reduce((acc, curr) => curr - acc)
|
|
1361
1388
|
|
|
@@ -1379,16 +1406,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1379
1406
|
|
|
1380
1407
|
// Calculate sumOfTickWidth here, before map function
|
|
1381
1408
|
const tickWidthMax = Math.max(
|
|
1382
|
-
...filteredTicks.map(tick =>
|
|
1383
|
-
getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
1384
|
-
)
|
|
1409
|
+
...filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
|
|
1385
1410
|
)
|
|
1386
1411
|
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
1387
1412
|
const accumulator = ismultiLabel ? 180 : 100
|
|
1388
1413
|
|
|
1389
|
-
const textWidths = filteredTicks.map(tick =>
|
|
1390
|
-
getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
1391
|
-
)
|
|
1414
|
+
const textWidths = filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
|
|
1392
1415
|
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
1393
1416
|
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
|
|
1394
1417
|
|
|
@@ -1480,6 +1503,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1480
1503
|
: undefined
|
|
1481
1504
|
}
|
|
1482
1505
|
fill={config.xAxis.tickLabelColor}
|
|
1506
|
+
fontSize={tickLabelFontSize}
|
|
1483
1507
|
>
|
|
1484
1508
|
{tick.formattedValue}
|
|
1485
1509
|
</Text>
|
|
@@ -1497,6 +1521,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1497
1521
|
verticalAnchor='start'
|
|
1498
1522
|
fontWeight='bold'
|
|
1499
1523
|
fill={config.xAxis.labelColor}
|
|
1524
|
+
fontSize={axisLabelFontSize}
|
|
1500
1525
|
>
|
|
1501
1526
|
{props.label}
|
|
1502
1527
|
</Text>
|
|
@@ -15,7 +15,6 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
15
15
|
|
|
16
16
|
const borderWidth = config.barHasBorder === 'true' ? 1 : 0
|
|
17
17
|
const halfWidth = width / 2
|
|
18
|
-
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
19
18
|
const offset = 1.02 // Offset of the left bar from the Axis
|
|
20
19
|
|
|
21
20
|
const groupOne = {
|
|
@@ -109,10 +108,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
109
108
|
const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
|
|
110
109
|
config.heights.horizontal = totalheight
|
|
111
110
|
// check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
|
|
112
|
-
const textWidth = getTextWidth(
|
|
113
|
-
formatNumber(d[groupOne.dataKey], 'left'),
|
|
114
|
-
`normal ${fontSize[config.fontSize]}px sans-serif`
|
|
115
|
-
)
|
|
111
|
+
const textWidth = getTextWidth(formatNumber(d[groupOne.dataKey], 'left'))
|
|
116
112
|
const textFits = textWidth < barWidth - 5 // minus padding dx(5)
|
|
117
113
|
|
|
118
114
|
return (
|
|
@@ -170,10 +166,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
170
166
|
const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
|
|
171
167
|
config.heights.horizontal = totalheight
|
|
172
168
|
// check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
|
|
173
|
-
const textWidth = getTextWidth(
|
|
174
|
-
formatNumber(d[groupTwo.dataKey], 'left'),
|
|
175
|
-
`normal ${fontSize[config.fontSize]}px sans-serif`
|
|
176
|
-
)
|
|
169
|
+
const textWidth = getTextWidth(formatNumber(d[groupTwo.dataKey], 'left'))
|
|
177
170
|
const isTextFits = textWidth < barWidth - 5 // minus padding dx(5)
|
|
178
171
|
|
|
179
172
|
return (
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Brush } from '@visx/brush'
|
|
2
2
|
import { Group } from '@visx/group'
|
|
3
3
|
import { Text } from '@visx/text'
|
|
4
|
-
import { useBarChart } from '../hooks/useBarChart'
|
|
5
4
|
import { FC, useContext, useEffect, useRef, useState } from 'react'
|
|
6
5
|
import ConfigContext from '../ConfigContext'
|
|
7
6
|
import { ScaleLinear, ScaleBand } from 'd3-scale'
|
|
8
7
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
9
8
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
10
9
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
10
|
+
import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
xScaleBrush: ScaleLinear<number, number>
|
|
@@ -19,7 +19,6 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
19
19
|
const { tableData, config, parseDate, formatDate, setBrushConfig, dashboardConfig } = useContext(ConfigContext)
|
|
20
20
|
const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
|
|
21
21
|
const isDashboardFilters = sharedFilters?.length > 0
|
|
22
|
-
const { fontSize } = useBarChart()
|
|
23
22
|
const [showTooltip, setShowTooltip] = useState(false)
|
|
24
23
|
const [brushKey, setBrushKey] = useState(0)
|
|
25
24
|
const brushRef = useRef(null)
|
|
@@ -178,7 +177,6 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
178
177
|
showTooltip={showTooltip}
|
|
179
178
|
pixelDistance={textProps.endPosition - textProps.startPosition}
|
|
180
179
|
textProps={textProps}
|
|
181
|
-
fontSize={fontSize[config.fontSize]}
|
|
182
180
|
{...props}
|
|
183
181
|
isBrushing={brushRef.current?.state.isBrushing}
|
|
184
182
|
/>
|
|
@@ -202,7 +200,7 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
202
200
|
}
|
|
203
201
|
|
|
204
202
|
const BrushHandle = props => {
|
|
205
|
-
const { x, isBrushActive, isBrushing, className, textProps,
|
|
203
|
+
const { x, isBrushActive, isBrushing, className, textProps, showTooltip, left } = props
|
|
206
204
|
const pathWidth = 8
|
|
207
205
|
if (!isBrushActive) {
|
|
208
206
|
return null
|
|
@@ -212,7 +210,7 @@ const BrushHandle = props => {
|
|
|
212
210
|
const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
|
|
213
211
|
const textAnchor = isLeft ? 'end' : 'start'
|
|
214
212
|
const tooltipText = isLeft ? ` Drag edges to focus on a specific segment ` : ''
|
|
215
|
-
const textWidth = getTextWidth(tooltipText, `normal ${
|
|
213
|
+
const textWidth = getTextWidth(tooltipText, `normal ${appFontSize / 1.1}px sans-serif`)
|
|
216
214
|
|
|
217
215
|
return (
|
|
218
216
|
<>
|
|
@@ -221,7 +219,7 @@ const BrushHandle = props => {
|
|
|
221
219
|
x={(Number(textProps.xMax) - textWidth) / 2}
|
|
222
220
|
dy={-12}
|
|
223
221
|
pointerEvents='visiblePainted'
|
|
224
|
-
fontSize={
|
|
222
|
+
fontSize={appFontSize / 1.1}
|
|
225
223
|
>
|
|
226
224
|
{tooltipText}
|
|
227
225
|
</Text>
|
|
@@ -234,7 +232,7 @@ const BrushHandle = props => {
|
|
|
234
232
|
y={25}
|
|
235
233
|
verticalAnchor='start'
|
|
236
234
|
textAnchor={textAnchor}
|
|
237
|
-
fontSize={
|
|
235
|
+
fontSize={appFontSize / 1.4}
|
|
238
236
|
>
|
|
239
237
|
{isLeft ? textProps.startValue : textProps.endValue}
|
|
240
238
|
</Text>
|
|
@@ -11,10 +11,9 @@ export default {
|
|
|
11
11
|
showDownloadMediaButton: false,
|
|
12
12
|
theme: 'theme-blue',
|
|
13
13
|
animate: false,
|
|
14
|
-
fontSize: 'medium',
|
|
15
14
|
lineDatapointStyle: 'hover',
|
|
16
15
|
lineDatapointColor: 'Same as Line',
|
|
17
|
-
barHasBorder: '
|
|
16
|
+
barHasBorder: 'true',
|
|
18
17
|
isLollipopChart: false,
|
|
19
18
|
lollipopShape: 'circle',
|
|
20
19
|
lollipopColorStyle: 'two-tone',
|
|
@@ -167,6 +166,7 @@ export default {
|
|
|
167
166
|
seriesHighlight: [],
|
|
168
167
|
style: 'circles',
|
|
169
168
|
subStyle: 'linear blocks',
|
|
169
|
+
shape: 'circle',
|
|
170
170
|
tickRotation: '',
|
|
171
171
|
hideBorder: {
|
|
172
172
|
side: false,
|
|
@@ -198,13 +198,16 @@ export default {
|
|
|
198
198
|
bottomPrefix: '',
|
|
199
199
|
bottomAbbreviated: false
|
|
200
200
|
},
|
|
201
|
+
filters: [],
|
|
201
202
|
confidenceKeys: {},
|
|
202
203
|
visual: {
|
|
203
204
|
border: true,
|
|
204
205
|
accent: true,
|
|
205
206
|
background: true,
|
|
206
207
|
verticalHoverLine: false,
|
|
207
|
-
horizontalHoverLine: false
|
|
208
|
+
horizontalHoverLine: false,
|
|
209
|
+
lineDatapointSymbol: 'none',
|
|
210
|
+
maximumShapeAmount: 7
|
|
208
211
|
},
|
|
209
212
|
useLogScale: false,
|
|
210
213
|
filterBehavior: 'Filter Change',
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
3
|
+
import * as d3 from 'd3-array'
|
|
4
|
+
|
|
5
|
+
export const getBoxPlotConfig = (newConfig: ChartConfig, data: object[]) => {
|
|
6
|
+
const combinedData = data
|
|
7
|
+
const groups = _.uniq(_.map(combinedData, newConfig.xAxis.dataKey))
|
|
8
|
+
const seriesKeys = _.map(newConfig.series, 'dataKey')
|
|
9
|
+
const plots: any[] = []
|
|
10
|
+
|
|
11
|
+
groups.forEach(g => {
|
|
12
|
+
seriesKeys.forEach(seriesKey => {
|
|
13
|
+
try {
|
|
14
|
+
if (!g) throw new Error('No groups resolved in box plots')
|
|
15
|
+
|
|
16
|
+
// Start handle operations on combinedData
|
|
17
|
+
const { count, sortedData } = _.chain(combinedData)
|
|
18
|
+
// Filter by xAxis data key
|
|
19
|
+
.filter(item => item[newConfig.xAxis.dataKey] === g)
|
|
20
|
+
// perform multiple operations on the filtered data
|
|
21
|
+
.thru(filteredData => ({
|
|
22
|
+
count: filteredData.length,
|
|
23
|
+
sortedData: _.map(filteredData, item => Number(item[seriesKey])).sort()
|
|
24
|
+
}))
|
|
25
|
+
// get the results from the chain
|
|
26
|
+
.value()
|
|
27
|
+
|
|
28
|
+
if (!sortedData) throw new Error('boxplots dont have data yet')
|
|
29
|
+
if (!plots) throw new Error('boxplots dont have plots yet')
|
|
30
|
+
|
|
31
|
+
const q1 = d3.quantile(sortedData, 0.25)
|
|
32
|
+
const q3 = d3.quantile(sortedData, 0.75)
|
|
33
|
+
|
|
34
|
+
const iqr = q3 - q1
|
|
35
|
+
const lowerBounds = q1 - 1.5 * iqr
|
|
36
|
+
const upperBounds = q3 + 1.5 * iqr
|
|
37
|
+
const nonOutliers = sortedData.filter(value => value >= lowerBounds && value <= upperBounds)
|
|
38
|
+
plots.push({
|
|
39
|
+
columnCategory: g,
|
|
40
|
+
columnMax: d3.max(nonOutliers),
|
|
41
|
+
columnThirdQuartile: _.round(q3, newConfig.dataFormat.roundTo),
|
|
42
|
+
columnMedian: Number(d3.median(sortedData)).toFixed(newConfig.dataFormat.roundTo),
|
|
43
|
+
columnFirstQuartile: _.round(q1, newConfig.dataFormat.roundTo),
|
|
44
|
+
columnMin: _.min(nonOutliers),
|
|
45
|
+
columnCount: count,
|
|
46
|
+
columnSd: Number(d3.deviation(sortedData)).toFixed(newConfig.dataFormat.roundTo),
|
|
47
|
+
columnMean: Number(d3.mean(sortedData)).toFixed(newConfig.dataFormat.roundTo),
|
|
48
|
+
columnIqr: _.round(iqr, newConfig.dataFormat.roundTo),
|
|
49
|
+
values: sortedData,
|
|
50
|
+
columnLowerBounds: lowerBounds,
|
|
51
|
+
columnUpperBounds: upperBounds,
|
|
52
|
+
columnOutliers: _.filter(sortedData, value => value < lowerBounds || value > upperBounds),
|
|
53
|
+
columnNonOutliers: _.filter(sortedData, value => value >= lowerBounds && value <= upperBounds)
|
|
54
|
+
})
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.error('COVE: ', e.message) // eslint-disable-line
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Generate a flat list of categories based on seriesKeys and groups
|
|
62
|
+
const categories =
|
|
63
|
+
seriesKeys.length > 1
|
|
64
|
+
? _.flatMap(groups, value => _.map(seriesKeys, key => `${_.capitalize(key)} - ${_.capitalize(value)}`))
|
|
65
|
+
: groups
|
|
66
|
+
|
|
67
|
+
return [plots, categories]
|
|
68
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { colorPalettesChart as colorPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
|
|
2
|
+
import { scaleOrdinal } from '@visx/scale'
|
|
3
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
4
|
+
|
|
5
|
+
export const getColorScale = (config: ChartConfig): ((value: string) => string) => {
|
|
6
|
+
const configPalette = ['Paired Bar', 'Deviation Bar'].includes(config.visualizationType)
|
|
7
|
+
? config.twoColor.palette
|
|
8
|
+
: config.palette
|
|
9
|
+
const allPalettes: Record<string, string[]> = { ...colorPalettes, ...twoColorPalette }
|
|
10
|
+
let palette = config.customColors || allPalettes[configPalette]
|
|
11
|
+
let numberOfKeys = config.runtime.seriesKeys.length
|
|
12
|
+
let newColorScale
|
|
13
|
+
|
|
14
|
+
while (numberOfKeys > palette.length) {
|
|
15
|
+
palette = palette.concat(palette)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
palette = palette.slice(0, numberOfKeys)
|
|
19
|
+
|
|
20
|
+
newColorScale = () =>
|
|
21
|
+
scaleOrdinal({
|
|
22
|
+
domain: config.runtime.seriesLabelsAll,
|
|
23
|
+
range: palette,
|
|
24
|
+
unknown: null
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return newColorScale
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
3
|
+
import * as d3 from 'd3-array'
|
|
4
|
+
|
|
5
|
+
export const getComboChartConfig = (newConfig: ChartConfig) => {
|
|
6
|
+
if (newConfig.visualizationType !== 'Combo' || !newConfig.series) return
|
|
7
|
+
|
|
8
|
+
const runtimeKeys = {
|
|
9
|
+
barSeriesKeys: [],
|
|
10
|
+
lineSeriesKeys: [],
|
|
11
|
+
areaSeriesKeys: [],
|
|
12
|
+
forecastingSeriesKeys: []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Define a mapping of series types to runtime keys
|
|
16
|
+
const seriesTypeMap = new Map([
|
|
17
|
+
['Area Chart', 'areaSeriesKeys'],
|
|
18
|
+
['Forecasting', 'forecastingSeriesKeys'],
|
|
19
|
+
['Bar', 'barSeriesKeys'],
|
|
20
|
+
['Combo', 'barSeriesKeys'],
|
|
21
|
+
['Line', 'lineSeriesKeys'],
|
|
22
|
+
['dashed-sm', 'lineSeriesKeys'],
|
|
23
|
+
['dashed-md', 'lineSeriesKeys'],
|
|
24
|
+
['dashed-lg', 'lineSeriesKeys']
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
newConfig.series.forEach(series => {
|
|
28
|
+
const runtimeKey = seriesTypeMap.get(series.type)
|
|
29
|
+
if (runtimeKey) {
|
|
30
|
+
const valueToPush = runtimeKey === 'barSeriesKeys' || runtimeKey === 'lineSeriesKeys' ? series.dataKey : series
|
|
31
|
+
runtimeKeys[runtimeKey].push(valueToPush)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Change Combo series type to Bar
|
|
35
|
+
if (series.type === 'Combo') {
|
|
36
|
+
series.type = 'Bar'
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Assign the processed runtime keys to the configuration
|
|
41
|
+
return { ...newConfig.runtime, ...runtimeKeys }
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
4
|
+
export const getExcludedData = (newConfig: ChartConfig, data: object[]) => {
|
|
5
|
+
let newExcludedData = data
|
|
6
|
+
if (newConfig.exclusions && newConfig.exclusions.active) {
|
|
7
|
+
if (newConfig.xAxis.type === 'categorical' && newConfig.exclusions.keys?.length > 0) {
|
|
8
|
+
newExcludedData = data.filter(e => !newConfig.exclusions.keys.includes(e[newConfig.xAxis.dataKey]))
|
|
9
|
+
} else if (
|
|
10
|
+
isDateScale(newConfig.xAxis) &&
|
|
11
|
+
(newConfig.exclusions.dateStart || newConfig.exclusions.dateEnd) &&
|
|
12
|
+
newConfig.xAxis.dateParseFormat
|
|
13
|
+
) {
|
|
14
|
+
// Filter dates
|
|
15
|
+
const timestamp = e => new Date(e).getTime()
|
|
16
|
+
|
|
17
|
+
let startDate = timestamp(newConfig.exclusions.dateStart)
|
|
18
|
+
let endDate = timestamp(newConfig.exclusions.dateEnd) + 86399999 //Increase by 24h in ms (86400000ms - 1ms) to include selected end date for .getTime() comparative
|
|
19
|
+
|
|
20
|
+
let startDateValid = undefined !== typeof startDate && false === isNaN(startDate)
|
|
21
|
+
let endDateValid = undefined !== typeof endDate && false === isNaN(endDate)
|
|
22
|
+
|
|
23
|
+
if (startDateValid && endDateValid) {
|
|
24
|
+
newExcludedData = data.filter(
|
|
25
|
+
e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate && timestamp(e[newConfig.xAxis.dataKey]) <= endDate
|
|
26
|
+
)
|
|
27
|
+
} else if (startDateValid) {
|
|
28
|
+
newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate)
|
|
29
|
+
} else if (endDateValid) {
|
|
30
|
+
newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) <= endDate)
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
newExcludedData = data
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return newExcludedData
|
|
37
|
+
}
|