@cdc/chart 4.25.3 → 4.25.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/cdcchart.js +46641 -42561
  2. package/index.html +130 -129
  3. package/package.json +22 -27
  4. package/src/CdcChartComponent.tsx +75 -35
  5. package/src/_stories/Chart.CI.stories.tsx +10 -0
  6. package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
  7. package/src/_stories/Chart.stories.tsx +99 -86
  8. package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
  9. package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
  10. package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
  11. package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
  12. package/{examples/private/not-loading.json → src/_stories/_mock/pie_calculated_area.json} +152 -95
  13. package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
  14. package/src/components/AreaChart/components/AreaChart.jsx +33 -5
  15. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  16. package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -37
  17. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
  18. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +8 -8
  19. package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -36
  20. package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
  21. package/src/components/BarChart/components/context.tsx +20 -2
  22. package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
  23. package/src/components/BarChart/helpers/index.ts +5 -2
  24. package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
  25. package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
  26. package/src/components/BoxPlot/BoxPlot.tsx +2 -1
  27. package/src/components/Brush/BrushChart.tsx +73 -0
  28. package/src/components/Brush/BrushController..tsx +39 -0
  29. package/src/components/DeviationBar.jsx +2 -2
  30. package/src/components/EditorPanel/EditorPanel.tsx +232 -147
  31. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +36 -36
  32. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +52 -25
  33. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +12 -4
  34. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
  35. package/src/components/EditorPanel/useEditorPermissions.ts +5 -5
  36. package/src/components/ForestPlot/ForestPlot.tsx +2 -2
  37. package/src/components/HoverLine/HoverLine.tsx +74 -0
  38. package/src/components/Legend/Legend.Component.tsx +1 -1
  39. package/src/components/Legend/Legend.Suppression.tsx +59 -25
  40. package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
  41. package/src/components/Legend/helpers/index.ts +1 -1
  42. package/src/components/LineChart/LineChartProps.ts +3 -1
  43. package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
  44. package/src/components/LineChart/helpers.ts +133 -56
  45. package/src/components/LineChart/index.tsx +106 -55
  46. package/src/components/LinearChart.tsx +178 -198
  47. package/src/components/PairedBarChart.jsx +3 -2
  48. package/src/components/PieChart/PieChart.tsx +127 -102
  49. package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
  50. package/src/components/Sparkline/components/SparkLine.tsx +80 -18
  51. package/src/data/initial-state.js +11 -6
  52. package/src/helpers/countNumOfTicks.ts +1 -1
  53. package/src/helpers/dataHelpers.ts +23 -2
  54. package/src/helpers/getNewRuntime.ts +35 -0
  55. package/src/helpers/getPiePercent.ts +22 -0
  56. package/src/helpers/getTransformedData.ts +22 -0
  57. package/src/helpers/sizeHelpers.ts +1 -1
  58. package/src/helpers/tests/getNewRuntime.test.ts +82 -0
  59. package/src/helpers/tests/getPiePercent.test.ts +38 -0
  60. package/src/hooks/useMinMax.ts +21 -28
  61. package/src/hooks/useRightAxis.ts +5 -3
  62. package/src/hooks/useScales.ts +15 -6
  63. package/src/hooks/useTooltip.tsx +218 -203
  64. package/src/index.jsx +2 -2
  65. package/src/scss/main.scss +13 -6
  66. package/src/store/chart.actions.ts +2 -6
  67. package/src/store/chart.reducer.ts +23 -23
  68. package/src/types/ChartConfig.ts +11 -3
  69. package/src/types/ChartContext.ts +0 -2
  70. package/examples/private/DEV-8850-2.json +0 -493
  71. package/examples/private/DEV-9822.json +0 -574
  72. package/examples/private/DEV-9840.json +0 -553
  73. package/examples/private/DEV-9850-3.json +0 -461
  74. package/examples/private/chart.json +0 -1084
  75. package/examples/private/ci_formatted.json +0 -202
  76. package/examples/private/ci_issue.json +0 -3016
  77. package/examples/private/completed.json +0 -634
  78. package/examples/private/dem-data-long.csv +0 -20
  79. package/examples/private/dem-data-long.json +0 -36
  80. package/examples/private/demographic_data.csv +0 -157
  81. package/examples/private/demographic_data.json +0 -2654
  82. package/examples/private/demographic_dynamic.json +0 -443
  83. package/examples/private/demographic_standard.json +0 -560
  84. package/examples/private/ehdi.json +0 -29939
  85. package/examples/private/test.json +0 -493
  86. package/src/components/ZoomBrush.tsx +0 -251
@@ -6,6 +6,7 @@ import { Text } from '@visx/text'
6
6
 
7
7
  import ConfigContext from '../ConfigContext'
8
8
  import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
9
+ import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
9
10
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
10
11
 
11
12
  const PairedBarChart = ({ width, height, originalWidth }) => {
@@ -47,8 +48,8 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
47
48
  })
48
49
 
49
50
  // Set label color
50
- groupOne.labelColor = groupOne.color ? getContrastColor('#000', groupOne.color) : '#000'
51
- groupTwo.labelColor = groupTwo.color ? getContrastColor('#000', groupTwo.color) : '#000'
51
+ groupOne.labelColor = groupOne.color ? getContrastColor(APP_FONT_COLOR, groupOne.color) : APP_FONT_COLOR
52
+ groupTwo.labelColor = groupTwo.color ? getContrastColor(APP_FONT_COLOR, groupTwo.color) : APP_FONT_COLOR
52
53
 
53
54
  const label = config.yAxis.label ? `${config.yAxis.label}: ` : ''
54
55
 
@@ -1,5 +1,5 @@
1
1
  import React, { useContext, useState, useEffect, useRef, useMemo } from 'react'
2
- import { animated, useTransition, interpolate } from 'react-spring'
2
+ import { animated, useTransition, to } from '@react-spring/web'
3
3
 
4
4
  // visx
5
5
  import { Pie } from '@visx/shape'
@@ -14,16 +14,9 @@ import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
14
14
  import useIntersectionObserver from '../../hooks/useIntersectionObserver'
15
15
  import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
16
16
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
17
- import LegendComponent from '../Legend/Legend.Component'
18
- import { createFormatLabels } from '../Legend/helpers/createFormatLabels'
19
17
  import { scaleOrdinal } from '@visx/scale'
20
18
  import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
21
19
 
22
- const enterUpdateTransition = ({ startAngle, endAngle }) => ({
23
- startAngle,
24
- endAngle
25
- })
26
-
27
20
  type TooltipData = {
28
21
  data: {
29
22
  [key: string]: string | number
@@ -53,28 +46,51 @@ const PieChart = props => {
53
46
  const pivotColumns = Object.values(config.columns).filter(column => column.showInViz)
54
47
  const dataNeedsPivot = pivotColumns.length > 0
55
48
  const pivotKey = dataNeedsPivot ? 'pivotColumn' : undefined
49
+ const showPercentage = config.dataFormat.showPiePercent
50
+ const labelForCalcArea = 'Calculated Area'
51
+
56
52
  const _data = useMemo(() => {
53
+ let baseData = []
54
+
57
55
  if (dataNeedsPivot) {
58
- let newData = []
59
56
  const primaryColumn = config.yAxis.dataKey
60
57
  const additionalColumns = pivotColumns.map(column => column.name)
61
58
  const allColumns = [primaryColumn, ...additionalColumns]
62
59
  const columnToUpdate = config.xAxis.dataKey
60
+
63
61
  data.forEach(d => {
64
62
  allColumns.forEach(col => {
65
- const data = d[col]
66
- if (data) {
67
- newData.push({
68
- [pivotKey]: data,
63
+ const val = d[col]
64
+ if (val) {
65
+ baseData.push({
66
+ [pivotKey]: val,
69
67
  [columnToUpdate]: `${d[columnToUpdate]} - ${col}`
70
68
  })
71
69
  }
72
70
  })
73
71
  })
74
- return newData
72
+ } else {
73
+ baseData = [...data]
75
74
  }
76
- return data
77
- }, [data, dataNeedsPivot])
75
+
76
+ // === ADD "OTHER" IF PERCENT MODE IS ENABLED ===
77
+ if (showPercentage) {
78
+ const total = baseData.reduce((sum, d) => {
79
+ const val = parseFloat(d[config.runtime.yAxis.dataKey])
80
+ return sum + (isNaN(val) ? 0 : val)
81
+ }, 0)
82
+
83
+ if (total < 100) {
84
+ const remaining = 100 - total
85
+ baseData.push({
86
+ [config.runtime.xAxis.dataKey]: labelForCalcArea,
87
+ [config.runtime.yAxis.dataKey]: remaining
88
+ })
89
+ }
90
+ }
91
+
92
+ return baseData
93
+ }, [data, dataNeedsPivot, showPercentage, config])
78
94
 
79
95
  const _colorScale = useMemo(() => {
80
96
  if (dataNeedsPivot) {
@@ -85,15 +101,31 @@ const PieChart = props => {
85
101
  const numberOfKeys = Object.entries(keys).length
86
102
  let palette = config.customColors || colorPalettes[config.palette]
87
103
  palette = palette.slice(0, numberOfKeys)
88
-
89
104
  return scaleOrdinal({
90
105
  domain: Object.keys(keys),
91
106
  range: palette,
92
107
  unknown: null
93
108
  })
94
109
  }
110
+
111
+ if (showPercentage) {
112
+ const keys = {}
113
+ _data.forEach(d => {
114
+ keys[d[config.xAxis.dataKey]] = true
115
+ })
116
+ // take out Calculated Area so it falls back to `unknown`
117
+ const domainKeys = Object.keys(keys).filter(k => k !== labelForCalcArea)
118
+
119
+ const basePalette = (config.customColors || colorPalettes[config.palette]).slice(0, domainKeys.length)
120
+ const COOL_GRAY_90 = getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-10').trim()
121
+ return scaleOrdinal({
122
+ domain: domainKeys,
123
+ range: basePalette,
124
+ unknown: COOL_GRAY_90
125
+ })
126
+ }
95
127
  return colorScale
96
- }, [colorScale, dataNeedsPivot])
128
+ }, [_data, dataNeedsPivot, colorScale])
97
129
 
98
130
  const triggerRef = useRef()
99
131
  const dataRef = useIntersectionObserver(triggerRef, {
@@ -117,92 +149,77 @@ const PieChart = props => {
117
149
  }
118
150
  }, [dataRef?.isIntersecting, config.animate]) // eslint-disable-line
119
151
 
120
- const AnimatedPie = ({ arcs, path, getKey }) => {
121
- const transitions = useTransition(arcs, getKey, {
122
- from: enterUpdateTransition,
123
- enter: enterUpdateTransition,
124
- update: enterUpdateTransition,
125
- leave: enterUpdateTransition
152
+ function AnimatedPie({ arcs, path, getKey, colorScale, onHover, onLeave }) {
153
+ const enterExit = ({ startAngle, endAngle }) => ({ startAngle, endAngle })
154
+ const transitions = useTransition(arcs, {
155
+ keys: getKey,
156
+ from: enterExit,
157
+ enter: enterExit,
158
+ update: enterExit,
159
+ leave: enterExit
126
160
  })
127
161
 
128
- // DEV-5053
129
- // onMouseLeave function doesn't work on animated.path for some reason.
130
- // As a workaround, we continue to fire the tooltipData while hovered,
131
- // and use this useEffect to hide the tooltip so it doesn't persist when users scroll.
132
- useEffect(() => {
133
- const timeout = setTimeout(() => {
134
- hideTooltip()
135
- }, 500)
136
- return () => {
137
- clearTimeout(timeout)
162
+ return transitions((styles, arc) => {
163
+ const key = getKey(arc)
164
+ let textColor = '#FFF'
165
+
166
+ if (key && _colorScale(key)) {
167
+ textColor = getContrastColor(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey]))
138
168
  }
139
- }, [tooltipData])
140
-
141
- return (
142
- <>
143
- {transitions.map(({ item: arc, props, key }, animatedPieIndex) => {
144
- return (
145
- <Group
146
- className={arc.data[config.xAxis.dataKey]}
147
- key={`${key}-${animatedPieIndex}`}
148
- style={{
149
- opacity:
150
- config.legend.behavior === 'highlight' &&
151
- seriesHighlight.length > 0 &&
152
- seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) === -1
153
- ? 0.5
154
- : 1
155
- }}
169
+ const roundTo = Number(config.dataFormat.roundTo) || 0
170
+ // Calculate the percentage of the full circle (360 degrees)
171
+ const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
172
+ const valueFromData = parseFloat(arc.data[config.runtime.yAxis.dataKey])
173
+ const percentageToDisplay = showPercentage ? valueFromData : (degrees / 360) * 100
174
+
175
+ let roundedPercentage = percentageToDisplay.toFixed(roundTo) + '%'
176
+ // add missing pie part
177
+ if (arc.data[config.xAxis.dataKey] === labelForCalcArea && config.dataFormat.showPiePercent) {
178
+ roundedPercentage = '**'
179
+ }
180
+
181
+ return (
182
+ <Group key={key} className={`slice-${key}`}>
183
+ {/* ── the slice */}
184
+ <animated.path
185
+ d={to([styles.startAngle, styles.endAngle], (start, end) =>
186
+ path({ ...arc, startAngle: start, endAngle: end })
187
+ )}
188
+ fill={colorScale(key)}
189
+ onMouseEnter={e =>
190
+ onHover(e, {
191
+ data: arc.data,
192
+ dataXPosition: e.clientX,
193
+ dataYPosition: e.clientY,
194
+ startAngle: arc.startAngle,
195
+ endAngle: arc.endAngle
196
+ })
197
+ }
198
+ onMouseLeave={onLeave}
199
+ />
200
+
201
+ {/* ── the percentage label */}
202
+ {arc.endAngle - arc.startAngle > 0.1 && (
203
+ <animated.text
204
+ transform={to([styles.startAngle, styles.endAngle], (start, end) => {
205
+ const [x, y] = path.centroid({
206
+ ...arc,
207
+ startAngle: start,
208
+ endAngle: end
209
+ })
210
+ return `translate(${x},${y})`
211
+ })}
212
+ textAnchor='middle'
213
+ pointerEvents='none'
214
+ fill={textColor}
156
215
  >
157
- <animated.path
158
- d={interpolate([props.startAngle, props.endAngle], (startAngle, endAngle) =>
159
- path({
160
- ...arc,
161
- startAngle,
162
- endAngle
163
- })
164
- )}
165
- fill={_colorScale(arc.data[config.runtime.xAxis.dataKey])}
166
- onMouseEnter={e => handleTooltipMouseOver(e, { data: arc.data[config.runtime.xAxis.dataKey], arc })}
167
- onMouseLeave={e => handleTooltipMouseOff()}
168
- />
169
- </Group>
170
- )
171
- })}
172
- {transitions.map(({ item: arc, key }, i) => {
173
- const roundTo = Number(config.dataFormat.roundTo) || 0
174
- const [centroidX, centroidY] = path.centroid(arc)
175
- const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
176
-
177
- let textColor = '#FFF'
178
- if (_colorScale(arc.data[config.runtime.xAxis.dataKey])) {
179
- textColor = getContrastColor(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey]))
180
- }
181
- const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
182
-
183
- // Calculate the percentage of the full circle (360 degrees)
184
- const percentageOfCircle = (degrees / 360) * 100
185
- const roundedPercentage = percentageOfCircle.toFixed(roundTo)
186
-
187
- return (
188
- <animated.g key={`${key}${i}`}>
189
- {hasSpaceForLabel && (
190
- <Text
191
- style={{ fill: textColor }}
192
- x={centroidX}
193
- y={centroidY}
194
- dy='.33em'
195
- textAnchor='middle'
196
- pointerEvents='none'
197
- >
198
- {roundedPercentage + '%'}
199
- </Text>
200
- )}
201
- </animated.g>
202
- )
203
- })}
204
- </>
205
- )
216
+ {/** compute text inside the spring callback */}
217
+ {roundedPercentage}
218
+ </animated.text>
219
+ )}
220
+ </Group>
221
+ )
222
+ })
206
223
  }
207
224
 
208
225
  let chartWidth = props.parentWidth
@@ -257,12 +274,20 @@ const PieChart = props => {
257
274
  {/* prettier-ignore */}
258
275
  <Pie
259
276
  data={filteredData || _data}
260
- pieValue={d => d[pivotKey || config.runtime.yAxis.dataKey]}
277
+ pieValue={d => parseFloat(d[pivotKey || config.runtime.yAxis.dataKey])}
261
278
  pieSortValues={() => -1}
262
279
  innerRadius={radius - donutThickness}
263
280
  outerRadius={radius}
264
281
  >
265
- {pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]}/>}
282
+ {pie => (
283
+ <AnimatedPie
284
+ {...pie}
285
+ getKey={d => d.data[config.runtime.xAxis.dataKey]}
286
+ colorScale={_colorScale}
287
+ onHover={handleTooltipMouseOver}
288
+ onLeave={handleTooltipMouseOff}
289
+ />
290
+ )}
266
291
  </Pie>
267
292
  </Group>
268
293
  </svg>
@@ -59,6 +59,11 @@ const ScatterPlot = ({ xScale, yScale }) => {
59
59
  opacity: 1,
60
60
  stroke: displayArea ? 'black' : ''
61
61
  }
62
+ if (item[s]==='') {
63
+ return <> </>
64
+ }
65
+
66
+
62
67
 
63
68
  return (
64
69
  <circle
@@ -18,17 +18,29 @@ type SparkLineProps = {
18
18
 
19
19
  const SparkLine: React.FC<SparkLineProps> = props => {
20
20
  const { width: parentWidth, height: parentHeight } = props
21
- const { transformedData: data, config, parseDate, formatDate, seriesHighlight, formatNumber, colorScale, handleChartAriaLabels } = useContext(ConfigContext)
21
+ const {
22
+ transformedData: data,
23
+ config,
24
+ parseDate,
25
+ formatDate,
26
+ seriesHighlight,
27
+ formatNumber,
28
+ colorScale,
29
+ handleChartAriaLabels
30
+ } = useContext(ConfigContext)
22
31
  let width = Number(parentWidth)
23
32
  const { minValue, maxValue } = useReduceData(config, data, ConfigContext)
24
33
 
25
- const margin = { top: 5, right: 10, bottom: 10, left: 10 }
34
+ const margin = { top: 5, right: 20, bottom: 10, left: 10 }
26
35
  const height = Number(parentHeight)
27
36
 
28
37
  const xMax = width - config.runtime.yAxis.size
29
38
  const yMax = height - margin.top - 20
30
39
 
31
- const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
40
+ const getXAxisData = d =>
41
+ config.runtime.xAxis.type === 'date'
42
+ ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
43
+ : d[config.runtime.originalXAxis.dataKey]
32
44
  const getYAxisData = (d, seriesKey) => d[seriesKey]
33
45
 
34
46
  let xScale
@@ -61,7 +73,10 @@ const SparkLine: React.FC<SparkLineProps> = props => {
61
73
  range: [0, xMax]
62
74
  })
63
75
 
64
- yScale = config.runtime.xAxis.type === 'date' ? scaleLinear({ domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)] }) : scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
76
+ yScale =
77
+ config.runtime.xAxis.type === 'date'
78
+ ? scaleLinear({ domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)] })
79
+ : scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
65
80
 
66
81
  seriesScale = scalePoint({
67
82
  domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
@@ -94,7 +109,14 @@ const SparkLine: React.FC<SparkLineProps> = props => {
94
109
 
95
110
  return (
96
111
  <ErrorBoundary component='SparkLine'>
97
- <svg role='img' aria-label={handleChartAriaLabels(config)} width={parentWidth} height={100} className={'sparkline'} tabIndex={0}>
112
+ <svg
113
+ role='img'
114
+ aria-label={handleChartAriaLabels(config)}
115
+ width={parentWidth}
116
+ height={100}
117
+ className={'sparkline'}
118
+ tabIndex={0}
119
+ >
98
120
  <title>{`Spark line graphic with the title ${config.title ? config.title : 'No Title Found'}`}</title>
99
121
  {config.runtime.lineSeriesKeys?.length > 0
100
122
  ? config.runtime.lineSeriesKeys
@@ -104,30 +126,70 @@ const SparkLine: React.FC<SparkLineProps> = props => {
104
126
  style={{ width }}
105
127
  className='sparkline-group'
106
128
  key={`series-${seriesKey}`}
107
- opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
108
- display={config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
129
+ opacity={
130
+ config.legend.behavior === 'highlight' &&
131
+ seriesHighlight.length > 0 &&
132
+ seriesHighlight.indexOf(seriesKey) === -1
133
+ ? 0.5
134
+ : 1
135
+ }
136
+ display={
137
+ config.legend.behavior === 'highlight' ||
138
+ seriesHighlight.length === 0 ||
139
+ seriesHighlight.indexOf(seriesKey) !== -1
140
+ ? 'block'
141
+ : 'none'
142
+ }
109
143
  >
110
- {config.labels && data.map((d, dataIndex) => {
111
- return (
112
- <Group key={`series-${seriesKey}-point-${dataIndex}`}>
113
- <Text x={xScale(getXAxisData(d))} y={yScale(getYAxisData(d, seriesKey))} fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'} textAnchor='middle'>
114
- {formatNumber(d[seriesKey])}
115
- </Text>
116
- </Group>
117
- )
118
- })}
144
+ {config.labels &&
145
+ data.map((d, dataIndex) => {
146
+ return (
147
+ <Group key={`series-${seriesKey}-point-${dataIndex}`}>
148
+ <Text
149
+ x={xScale(getXAxisData(d))}
150
+ y={yScale(getYAxisData(d, seriesKey))}
151
+ fill={
152
+ colorScale
153
+ ? colorScale(
154
+ config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey
155
+ )
156
+ : '#000'
157
+ }
158
+ textAnchor='middle'
159
+ >
160
+ {formatNumber(d[seriesKey])}
161
+ </Text>
162
+ </Group>
163
+ )
164
+ })}
119
165
  <LinePath
120
166
  curve={allCurves.curveLinear}
121
167
  data={data}
122
168
  x={d => xScale(getXAxisData(d))}
123
169
  y={d => yScale(getYAxisData(d, seriesKey))}
124
- stroke={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
170
+ stroke={
171
+ colorScale
172
+ ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey)
173
+ : '#000'
174
+ }
125
175
  strokeWidth={2}
126
176
  strokeOpacity={1}
127
177
  shapeRendering='geometricPrecision'
128
178
  markerEnd={`url(#${'arrow'}--${index})`}
129
179
  />
130
- <MarkerArrow id={`arrow--${index}`} refX={2} size={6} markerEnd={`url(#${'arrow'}--${index})`} strokeOpacity={1} fillOpacity={1} fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'} />
180
+ <MarkerArrow
181
+ id={`arrow--${index}`}
182
+ refX={2}
183
+ size={6}
184
+ markerEnd={`url(#${'arrow'}--${index})`}
185
+ strokeOpacity={1}
186
+ fillOpacity={1}
187
+ fill={
188
+ colorScale
189
+ ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey)
190
+ : '#000'
191
+ }
192
+ />
131
193
  </Group>
132
194
  <AxisBottom
133
195
  top={yMax + margin.top}
@@ -89,6 +89,11 @@ export default {
89
89
  topAxis: {
90
90
  hasLine: false
91
91
  },
92
+ brush: {
93
+ isActive: false,
94
+ isBrushing: false,
95
+ data: []
96
+ },
92
97
  isLegendValue: false,
93
98
  barThickness: 0.35,
94
99
  barHeight: 25,
@@ -120,7 +125,8 @@ export default {
120
125
  maxTickRotation: 0,
121
126
  padding: 5,
122
127
  showYearsOnce: false,
123
- sortByRecentDate: false
128
+ sortByRecentDate: false,
129
+ brushActive: false
124
130
  },
125
131
  table: {
126
132
  label: 'Data Table',
@@ -168,16 +174,15 @@ export default {
168
174
  groupBy: '',
169
175
  shape: 'circle',
170
176
  tickRotation: '',
177
+ order: 'dataColumn',
171
178
  hideBorder: {
172
179
  side: false,
173
180
  topBottom: true
174
181
  },
175
- position: 'right'
176
- },
177
- brush: {
178
- height: 45,
179
- active: false
182
+ position: 'right',
183
+ orderedValues: []
180
184
  },
185
+
181
186
  exclusions: {
182
187
  active: false,
183
188
  keys: []
@@ -25,7 +25,7 @@ export const countNumOfTicks = ({ axis, max, runtime, currentViewport, isHorizon
25
25
  }
26
26
  if (Number(tickCount) > Number(max) && !isHorizontal) {
27
27
  // cap it and round it so its an integer
28
- tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
28
+ tickCount = Math.max(2, Number(min) < 0 ? Math.round(max) * 2 : Math.round(max))
29
29
  }
30
30
  }
31
31
 
@@ -1,10 +1,31 @@
1
+ import { Series } from '@cdc/core/types/Series'
1
2
  import { ChartConfig } from '../types/ChartConfig'
2
3
 
3
4
  export const getSeriesWithData = (config: ChartConfig) => {
4
- const { filters, data, runtime } = config
5
+ const { filters, data, runtime, legend } = config
6
+ const { colorCode } = legend
5
7
  const { series } = runtime
6
8
 
7
9
  const filteredData = data.filter(d => filters.every(f => d[f.columnName] === f.active))
10
+ const colorCodeSeries = colorCode && Array.from(new Set(filteredData.map(d => d[colorCode])))
8
11
 
9
- return series.filter(s => filteredData.some(d => d[s.dynamicCategory || s.dataKey])).map(s => s.name || s.dataKey)
12
+ const result = series
13
+ .flatMap(s => {
14
+ if (!colorCode || s.type !== 'Bar') return s
15
+ return colorCodeSeries.map(c => ({ ...s, colorCodeSeries: c }))
16
+ })
17
+ .map(s => ({
18
+ ...s,
19
+ data: filteredData
20
+ .filter(d => !s.dynamicCategory || d[s.dynamicCategory] === s.dataKey)
21
+ .filter(d => !s.colorCodeSeries || d[colorCode] === s.colorCodeSeries)
22
+ .filter(d => {
23
+ const key = s.dynamicCategory ? s.originalDataKey : s.dataKey
24
+ return d[key] || d[key] === 0
25
+ })
26
+ }))
27
+ .filter(s => s.data.length)
28
+ .map(s => s.colorCodeSeries || s.name || s.dataKey)
29
+
30
+ return result
10
31
  }
@@ -0,0 +1,35 @@
1
+ import _ from 'lodash'
2
+
3
+ export const getNewRuntime = (visualizationConfig, newFilteredData) => {
4
+ const runtime = _.cloneDeep(visualizationConfig.runtime) || {}
5
+ runtime.series = []
6
+ runtime.seriesLabels = {}
7
+ runtime.seriesLabelsAll = []
8
+ const { filters, columns, dynamicSeriesType, dynamicSeriesLineType, xAxis } = visualizationConfig
9
+ if (newFilteredData?.length) {
10
+ const columnNames = Object.keys(newFilteredData[0])
11
+ columnNames.forEach(colName => {
12
+ const isNotXAxis = xAxis.dataKey !== colName
13
+ const isNotFiltered = !filters || !filters?.find(filter => filter.columnName === colName)
14
+ const noColConfiguration = !columns || Object.keys(columns).indexOf(colName) === -1
15
+ if (isNotXAxis && isNotFiltered && noColConfiguration) {
16
+ runtime.series.push({
17
+ dataKey: colName,
18
+ type: dynamicSeriesType,
19
+ lineType: dynamicSeriesLineType,
20
+ tooltip: true
21
+ })
22
+ }
23
+ })
24
+ }
25
+
26
+ runtime.seriesKeys = runtime.series
27
+ ? runtime.series.map(series => {
28
+ runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
29
+ runtime.seriesLabelsAll.push(series.name || series.dataKey)
30
+ return series.dataKey
31
+ })
32
+ : []
33
+
34
+ return runtime
35
+ }
@@ -0,0 +1,22 @@
1
+ import _ from 'lodash'
2
+
3
+ export const getPiePercent = (data: Record<string, any>[], seriesKey: string): Record<string, any>[] => {
4
+ // getonly the numeric values for each seriesKey
5
+ const numericValues = data.map(row => _.toNumber(row[seriesKey])).filter(v => !Number.isNaN(v))
6
+
7
+ const total = numericValues.reduce((sum, v) => sum + v, 0)
8
+
9
+ return data.map(row => {
10
+ const raw = _.toNumber(row[seriesKey])
11
+ if (Number.isNaN(raw)) {
12
+ // skip non-numeric / undefined
13
+ return row
14
+ }
15
+
16
+ const pct = total === 0 ? 0 : (raw / total) * 100
17
+ return {
18
+ ...row,
19
+ [seriesKey]: pct
20
+ }
21
+ })
22
+ }
@@ -0,0 +1,22 @@
1
+ type DataRow = Record<string, any>
2
+
3
+ export const getTransformedData = ({
4
+ brushData,
5
+ filteredData,
6
+ excludedData,
7
+ clean
8
+ }: {
9
+ brushData: DataRow[]
10
+ filteredData: DataRow[]
11
+ excludedData: DataRow[]
12
+ clean: (data: DataRow[]) => DataRow[]
13
+ }): DataRow[] => {
14
+ const data =
15
+ Array.isArray(brushData) && brushData.length > 0
16
+ ? brushData
17
+ : Array.isArray(filteredData) && filteredData.length > 0
18
+ ? filteredData
19
+ : excludedData
20
+
21
+ return clean(data)
22
+ }
@@ -2,7 +2,7 @@ import { clamp } from 'lodash'
2
2
 
3
3
  import { isMobileHeightViewport } from '@cdc/core/helpers/viewports'
4
4
  import { ChartConfig, ViewportSize } from '../types/ChartConfig'
5
- import { EDITOR_WIDTH } from '../CdcChartComponent'
5
+ import { EDITOR_WIDTH } from '@cdc/core/helpers/constants'
6
6
 
7
7
  export function getOrientation(
8
8
  { orientation, heights, visualizationType }: Pick<ChartConfig, 'orientation' | 'heights' | 'visualizationType'>,