@cdc/chart 4.25.3 → 4.25.5-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.
Files changed (72) hide show
  1. package/dist/cdcchart.js +36051 -36995
  2. package/index.html +2 -1
  3. package/package.json +22 -27
  4. package/src/CdcChartComponent.tsx +10 -10
  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 +7 -0
  8. package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
  9. package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
  10. package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
  11. package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
  12. package/src/components/AreaChart/components/AreaChart.jsx +33 -5
  13. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  14. package/src/components/BarChart/components/BarChart.Horizontal.tsx +50 -40
  15. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
  16. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +19 -8
  17. package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -30
  18. package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
  19. package/src/components/BarChart/components/context.tsx +20 -2
  20. package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
  21. package/src/components/BarChart/helpers/index.ts +5 -2
  22. package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
  23. package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
  24. package/src/components/BoxPlot/BoxPlot.tsx +2 -1
  25. package/src/components/DeviationBar.jsx +2 -1
  26. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +34 -34
  27. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +51 -25
  28. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +10 -3
  29. package/src/components/EditorPanel/useEditorPermissions.ts +1 -4
  30. package/src/components/ForestPlot/ForestPlot.tsx +2 -2
  31. package/src/components/Legend/Legend.Component.tsx +1 -1
  32. package/src/components/Legend/Legend.Suppression.tsx +12 -22
  33. package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
  34. package/src/components/LineChart/LineChartProps.ts +3 -1
  35. package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
  36. package/src/components/LineChart/helpers.ts +133 -56
  37. package/src/components/LineChart/index.tsx +107 -53
  38. package/src/components/LinearChart.tsx +40 -89
  39. package/src/components/PairedBarChart.jsx +3 -2
  40. package/src/components/PieChart/PieChart.tsx +71 -91
  41. package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
  42. package/src/components/Sparkline/components/SparkLine.tsx +80 -18
  43. package/src/components/ZoomBrush.tsx +4 -4
  44. package/src/data/initial-state.js +3 -1
  45. package/src/helpers/countNumOfTicks.ts +1 -1
  46. package/src/helpers/dataHelpers.ts +23 -2
  47. package/src/helpers/sizeHelpers.ts +1 -1
  48. package/src/hooks/useMinMax.ts +21 -28
  49. package/src/hooks/useRightAxis.ts +4 -2
  50. package/src/hooks/useScales.ts +10 -6
  51. package/src/hooks/useTooltip.tsx +204 -203
  52. package/src/index.jsx +2 -2
  53. package/src/scss/main.scss +13 -6
  54. package/src/types/ChartConfig.ts +5 -0
  55. package/LICENSE +0 -201
  56. package/examples/private/DEV-8850-2.json +0 -493
  57. package/examples/private/DEV-9822.json +0 -574
  58. package/examples/private/DEV-9840.json +0 -553
  59. package/examples/private/DEV-9850-3.json +0 -461
  60. package/examples/private/chart.json +0 -1084
  61. package/examples/private/ci_formatted.json +0 -202
  62. package/examples/private/ci_issue.json +0 -3016
  63. package/examples/private/completed.json +0 -634
  64. package/examples/private/dem-data-long.csv +0 -20
  65. package/examples/private/dem-data-long.json +0 -36
  66. package/examples/private/demographic_data.csv +0 -157
  67. package/examples/private/demographic_data.json +0 -2654
  68. package/examples/private/demographic_dynamic.json +0 -443
  69. package/examples/private/demographic_standard.json +0 -560
  70. package/examples/private/ehdi.json +0 -29939
  71. package/examples/private/not-loading.json +0 -360
  72. package/examples/private/test.json +0 -493
@@ -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
@@ -117,92 +110,71 @@ const PieChart = props => {
117
110
  }
118
111
  }, [dataRef?.isIntersecting, config.animate]) // eslint-disable-line
119
112
 
120
- const AnimatedPie = ({ arcs, path, getKey }) => {
121
- const transitions = useTransition(arcs, getKey, {
122
- from: enterUpdateTransition,
123
- enter: enterUpdateTransition,
124
- update: enterUpdateTransition,
125
- leave: enterUpdateTransition
113
+ function AnimatedPie({ arcs, path, getKey, colorScale, onHover, onLeave }) {
114
+ const enterExit = ({ startAngle, endAngle }) => ({ startAngle, endAngle })
115
+ const transitions = useTransition(arcs, {
116
+ keys: getKey,
117
+ from: enterExit,
118
+ enter: enterExit,
119
+ update: enterExit,
120
+ leave: enterExit
126
121
  })
127
122
 
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)
138
- }
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
- }}
156
- >
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
123
+ return transitions((styles, arc) => {
124
+ const key = getKey(arc)
125
+ let textColor = '#FFF'
176
126
 
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
127
+ if (key && _colorScale(key)) {
128
+ textColor = getContrastColor(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey]))
129
+ }
130
+ const roundTo = Number(config.dataFormat.roundTo) || 0
131
+ // Calculate the percentage of the full circle (360 degrees)
132
+ const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
182
133
 
183
- // Calculate the percentage of the full circle (360 degrees)
184
- const percentageOfCircle = (degrees / 360) * 100
185
- const roundedPercentage = percentageOfCircle.toFixed(roundTo)
134
+ const percentageOfCircle = (degrees / 360) * 100
135
+ const roundedPercentage = percentageOfCircle.toFixed(roundTo) + '%'
136
+ return (
137
+ <Group key={key} className={`slice-${key}`}>
138
+ {/* ── the slice */}
139
+ <animated.path
140
+ d={to([styles.startAngle, styles.endAngle], (start, end) =>
141
+ path({ ...arc, startAngle: start, endAngle: end })
142
+ )}
143
+ fill={colorScale(key)}
144
+ onMouseEnter={e =>
145
+ onHover(e, {
146
+ data: arc.data,
147
+ dataXPosition: e.clientX,
148
+ dataYPosition: e.clientY,
149
+ startAngle: arc.startAngle,
150
+ endAngle: arc.endAngle
151
+ })
152
+ }
153
+ onMouseLeave={onLeave}
154
+ />
186
155
 
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
- )
156
+ {/* ── the percentage label */}
157
+ {arc.endAngle - arc.startAngle > 0.1 && (
158
+ <animated.text
159
+ transform={to([styles.startAngle, styles.endAngle], (start, end) => {
160
+ const [x, y] = path.centroid({
161
+ ...arc,
162
+ startAngle: start,
163
+ endAngle: end
164
+ })
165
+ return `translate(${x},${y})`
166
+ })}
167
+ textAnchor='middle'
168
+ pointerEvents='none'
169
+ fill={textColor}
170
+ >
171
+ {/** compute text inside the spring callback */}
172
+ {roundedPercentage}
173
+ </animated.text>
174
+ )}
175
+ </Group>
176
+ )
177
+ })
206
178
  }
207
179
 
208
180
  let chartWidth = props.parentWidth
@@ -257,12 +229,20 @@ const PieChart = props => {
257
229
  {/* prettier-ignore */}
258
230
  <Pie
259
231
  data={filteredData || _data}
260
- pieValue={d => d[pivotKey || config.runtime.yAxis.dataKey]}
232
+ pieValue={d => parseFloat(d[pivotKey || config.runtime.yAxis.dataKey])}
261
233
  pieSortValues={() => -1}
262
234
  innerRadius={radius - donutThickness}
263
235
  outerRadius={radius}
264
236
  >
265
- {pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]}/>}
237
+ {pie => (
238
+ <AnimatedPie
239
+ {...pie}
240
+ getKey={d => d.data[config.runtime.xAxis.dataKey]}
241
+ colorScale={_colorScale}
242
+ onHover={handleTooltipMouseOver}
243
+ onLeave={handleTooltipMouseOff}
244
+ />
245
+ )}
266
246
  </Pie>
267
247
  </Group>
268
248
  </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}
@@ -7,7 +7,7 @@ import { ScaleLinear, ScaleBand } from 'd3-scale'
7
7
  import { isDateScale } from '@cdc/core/helpers/cove/date'
8
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
9
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
10
- import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
10
+ import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
11
11
 
12
12
  interface Props {
13
13
  xScaleBrush: ScaleLinear<number, number>
@@ -210,7 +210,7 @@ const BrushHandle = props => {
210
210
  const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
211
211
  const textAnchor = isLeft ? 'end' : 'start'
212
212
  const tooltipText = isLeft ? ` Drag edges to focus on a specific segment ` : ''
213
- const textWidth = getTextWidth(tooltipText, `${appFontSize / 1.1}px`)
213
+ const textWidth = getTextWidth(tooltipText, `${APP_FONT_SIZE / 1.1}px`)
214
214
 
215
215
  return (
216
216
  <>
@@ -219,7 +219,7 @@ const BrushHandle = props => {
219
219
  x={(Number(textProps.xMax) - textWidth) / 2}
220
220
  dy={-12}
221
221
  pointerEvents='visiblePainted'
222
- fontSize={appFontSize / 1.1}
222
+ fontSize={APP_FONT_SIZE / 1.1}
223
223
  >
224
224
  {tooltipText}
225
225
  </Text>
@@ -232,7 +232,7 @@ const BrushHandle = props => {
232
232
  y={25}
233
233
  verticalAnchor='start'
234
234
  textAnchor={textAnchor}
235
- fontSize={appFontSize / 1.4}
235
+ fontSize={APP_FONT_SIZE / 1.4}
236
236
  >
237
237
  {isLeft ? textProps.startValue : textProps.endValue}
238
238
  </Text>
@@ -168,11 +168,13 @@ export default {
168
168
  groupBy: '',
169
169
  shape: 'circle',
170
170
  tickRotation: '',
171
+ order: 'dataColumn',
171
172
  hideBorder: {
172
173
  side: false,
173
174
  topBottom: true
174
175
  },
175
- position: 'right'
176
+ position: 'right',
177
+ orderedValues: []
176
178
  },
177
179
  brush: {
178
180
  height: 45,
@@ -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
  }
@@ -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'>,
@@ -46,7 +46,6 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
46
46
 
47
47
  min = enteredMinValue && isMinValid ? Number(enteredMinValue) : minValue
48
48
  max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
49
-
50
49
  const { lower, upper } = config?.confidenceKeys || {}
51
50
 
52
51
  if (lower && upper && config.visualizationType === 'Bar') {
@@ -167,35 +166,28 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
167
166
  }
168
167
 
169
168
  if (config.visualizationType === 'Line' && !convertLineToBarGraph) {
170
- const isMinValid = isLogarithmicAxis
171
- ? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
172
- : Number(enteredMinValue) < minValue
173
- // update minValue for (0) Suppression points
174
- const suppressedMinValue = tableData?.some((dataItem, index) => {
175
- return config.preliminaryData?.some(pd => {
176
- if (pd.type !== 'suppression' || !pd.style) return false
177
-
178
- // Filter data item based on current series keys and check if pd.value is present
179
- const relevantData = _.pick(dataItem, config.runtime?.seriesKeys)
180
- const isValuePresent = _.values(relevantData).includes(pd.value)
181
-
182
- // Check for value match condition
183
- const valueMatch = pd.column ? dataItem[pd.column] === pd.value : isValuePresent
184
-
185
- // Return true if the value matches and it's either the first or the last item
186
- return valueMatch && (index === 0 || index === tableData.length - 1)
169
+ const numEnteredMin = Number(enteredMinValue)
170
+ const isMinValid = isLogarithmicAxis ? numEnteredMin >= 0 && numEnteredMin < minValue : numEnteredMin < minValue
171
+
172
+ const suppressedMinValue = tableData?.some((item, i, arr) =>
173
+ config.preliminaryData?.some(({ type, style, column, value }) => {
174
+ if (type !== 'suppression' || !style) return false
175
+
176
+ const values = _.values(_.pick(item, config.runtime?.seriesKeys))
177
+ const dynamicCategory = config.series[0].dynamicCategory
178
+
179
+ const match = column ? item[column] === value : values.includes(value)
180
+ const dynamic = dynamicCategory && (item[dynamicCategory] === column || !column)
181
+
182
+ return (match || dynamic) && (i === 0 || i === arr.length - 1)
187
183
  })
188
- })
189
- let isCategoricalAxis = config.yAxis.type === 'categorical'
190
- min =
191
- enteredMinValue !== '' && isMinValid
192
- ? Number(enteredMinValue)
193
- : suppressedMinValue
194
- ? 0
195
- : isCategoricalAxis
196
- ? 0
197
- : minValue
184
+ )
185
+
186
+ const isCategorical = config.yAxis.type === 'categorical'
187
+
188
+ min = enteredMinValue !== '' && isMinValid ? numEnteredMin : suppressedMinValue ? 0 : isCategorical ? 0 : minValue
198
189
  }
190
+
199
191
  //If data value max wasn't provided, calculate it
200
192
  if (max === Number.MIN_VALUE) {
201
193
  // if all values in data are negative set max = 0
@@ -241,6 +233,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
241
233
 
242
234
  if (config.visualizationType === 'Scatter Plot') {
243
235
  max = max * 1.1
236
+ min = min / 1.1
244
237
  }
245
238
 
246
239
  return { min, max, leftMax, rightMax }
@@ -1,9 +1,11 @@
1
1
  import { scaleLinear } from '@visx/scale'
2
2
  import useReduceData from './useReduceData'
3
+ import { TOP_PADDING } from './useScales'
3
4
 
4
5
  export default function useRightAxis({ config, yMax = 0, data = [], updateConfig }) {
5
6
  const hasRightAxis = config.visualizationType === 'Combo' && config.orientation === 'vertical'
6
- const rightSeriesKeys = config.series && config.series.filter(series => series.axis === 'Right').map(key => key.dataKey)
7
+ const rightSeriesKeys =
8
+ config.series && config.series.filter(series => series.axis === 'Right').map(key => key.dataKey)
7
9
  let { minValue } = useReduceData(config, data)
8
10
 
9
11
  const allRightAxisData = rightSeriesKeys => {
@@ -35,7 +37,7 @@ export default function useRightAxis({ config, yMax = 0, data = [], updateConfig
35
37
 
36
38
  const yScaleRight = scaleLinear({
37
39
  domain: [minValue, max],
38
- range: [yMax, 0]
40
+ range: [yMax, TOP_PADDING]
39
41
  })
40
42
 
41
43
  return { yScaleRight, hasRightAxis }
@@ -22,6 +22,8 @@ const scaleTypes = {
22
22
  BAND: 'band'
23
23
  }
24
24
 
25
+ export const TOP_PADDING = 10
26
+
25
27
  type useScaleProps = {
26
28
  config: ChartConfig // standard chart config
27
29
  data: Object[] // standard data array
@@ -80,11 +82,13 @@ const useScales = (properties: useScaleProps) => {
80
82
  if (xAxis.type === 'date-time' || xAxis.type === 'continuous') {
81
83
  let xAxisMin = Math.min(...xAxisDataMapped.map(Number))
82
84
  let xAxisMax = Math.max(...xAxisDataMapped.map(Number))
83
- xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
84
- xAxisMax +=
85
- visualizationType === 'Line'
86
- ? 0
87
- : (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
85
+ let paddingRatio = config.xAxis.padding ? config.xAxis.padding * 0.01 : 0
86
+ if (config.brush.active) {
87
+ paddingRatio = config.barThickness * 0.2
88
+ }
89
+
90
+ xAxisMin -= paddingRatio * (xAxisMax - xAxisMin)
91
+ xAxisMax += visualizationType === 'Line' ? 0 : paddingRatio * (xAxisMax - xAxisMin)
88
92
  const range = config.xAxis.sortByRecentDate ? [xMax, 0] : [0, xMax]
89
93
  xScale = scaleTime({
90
94
  domain: [xAxisMin, xAxisMax],
@@ -392,7 +396,7 @@ const composeYScale = ({ min, max, yMax, config, leftMax }) => {
392
396
 
393
397
  // If the visualization type is a bump chart then the domain and range need different values
394
398
  const domainSet = config.visualizationType === 'Bump Chart' ? [1, max] : [min, max]
395
- const yRange = config.visualizationType === 'Bump Chart' ? [30, yMax] : [yMax, 0]
399
+ const yRange = config.visualizationType === 'Bump Chart' ? [30, yMax] : [yMax, TOP_PADDING]
396
400
  // Return the configured scale function
397
401
  return scaleFunc({
398
402
  domain: domainSet,