@cdc/chart 4.24.11 → 4.24.12-2

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 (65) hide show
  1. package/dist/cdcchart.js +32134 -32039
  2. package/examples/feature/sankey/sankey-example-data.json +126 -13
  3. package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
  4. package/examples/private/DEV-8850-2.json +493 -0
  5. package/examples/private/DEV-9822.json +574 -0
  6. package/examples/private/DEV-9840.json +553 -0
  7. package/examples/private/DEV-9850-3.json +461 -0
  8. package/examples/private/chart.json +1084 -0
  9. package/examples/private/ci_formatted.json +202 -0
  10. package/examples/private/ci_issue.json +3016 -0
  11. package/examples/private/completed.json +634 -0
  12. package/examples/private/dem-data-long.csv +20 -0
  13. package/examples/private/dem-data-long.json +36 -0
  14. package/examples/private/demographic_data.csv +157 -0
  15. package/examples/private/demographic_data.json +2654 -0
  16. package/examples/private/demographic_dynamic.json +443 -0
  17. package/examples/private/demographic_standard.json +560 -0
  18. package/examples/private/ehdi.json +29939 -0
  19. package/examples/private/test.json +448 -20047
  20. package/index.html +9 -6
  21. package/package.json +2 -2
  22. package/src/CdcChart.tsx +62 -82
  23. package/src/_stories/Chart.Anchors.stories.tsx +31 -0
  24. package/src/_stories/Chart.DynamicSeries.stories.tsx +8 -1
  25. package/src/_stories/Chart.stories.tsx +32 -0
  26. package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
  27. package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
  28. package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
  29. package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
  30. package/src/_stories/_mock/short_dates.json +288 -0
  31. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
  32. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  33. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
  34. package/src/components/BarChart/components/BarChart.Vertical.tsx +28 -40
  35. package/src/components/BarChart/helpers/getBarData.ts +28 -0
  36. package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
  37. package/src/components/BoxPlot/BoxPlot.tsx +12 -70
  38. package/src/components/BoxPlot/helpers/index.ts +54 -0
  39. package/src/components/BrushChart.tsx +23 -26
  40. package/src/components/EditorPanel/EditorPanel.tsx +55 -79
  41. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +1 -0
  42. package/src/components/EditorPanel/useEditorPermissions.ts +5 -1
  43. package/src/components/Legend/Legend.Component.tsx +2 -2
  44. package/src/{hooks/useLegendClasses.ts → components/Legend/helpers/getLegendClasses.ts} +5 -5
  45. package/src/components/Legend/helpers/index.ts +2 -1
  46. package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
  47. package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
  48. package/src/components/LineChart/helpers.ts +1 -0
  49. package/src/components/LineChart/index.tsx +47 -1
  50. package/src/components/LinearChart.tsx +180 -172
  51. package/src/components/PieChart/PieChart.tsx +7 -1
  52. package/src/components/Sankey/components/ColumnList.tsx +19 -0
  53. package/src/components/Sankey/components/Sankey.tsx +479 -0
  54. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
  55. package/src/components/Sankey/index.tsx +1 -510
  56. package/src/components/Sankey/sankey.scss +16 -16
  57. package/src/components/Sankey/types/index.ts +1 -1
  58. package/src/data/initial-state.js +4 -3
  59. package/src/helpers/countNumOfTicks.ts +57 -0
  60. package/src/helpers/getQuartiles.ts +15 -18
  61. package/src/hooks/useMinMax.ts +18 -4
  62. package/src/hooks/useScales.ts +38 -4
  63. package/src/hooks/useTooltip.tsx +5 -1
  64. package/src/scss/DataTable.scss +5 -0
  65. package/src/scss/main.scss +6 -2
@@ -0,0 +1,74 @@
1
+ import { getBarData } from '../getBarData'
2
+
3
+ import { TransformedData } from '../../../../types/ChartContext'
4
+ import { ChartConfig } from '../../../../types/ChartConfig'
5
+
6
+ describe('getBarData', () => {
7
+ it('should return the original data when there is no dynamicSeries', () => {
8
+ const config = {
9
+ series: [{ dataKey: 'value' }],
10
+ runtime: { originalXAxis: { dataKey: 'category' } }
11
+ } as ChartConfig
12
+ const data: TransformedData[] = [
13
+ { category: 'A', value: 10 },
14
+ { category: 'B', value: 20 }
15
+ ]
16
+ const hasConfidenceInterval = false
17
+
18
+ const result = getBarData(config, data, hasConfidenceInterval)
19
+
20
+ expect(result).toEqual(data)
21
+ })
22
+
23
+ it('should return transformed data when there is a dynamicSeries but no confidence interval', () => {
24
+ const config = {
25
+ series: [{ dataKey: 'value', dynamicCategory: 'subCategory' }],
26
+ runtime: { originalXAxis: { dataKey: 'category' } }
27
+ } as ChartConfig
28
+ const data: TransformedData[] = [
29
+ { category: 'A', subCategory: 'X', value: 10 },
30
+ { category: 'A', subCategory: 'Y', value: 20 },
31
+ { category: 'B', subCategory: 'X', value: 30 }
32
+ ]
33
+ const hasConfidenceInterval = false
34
+
35
+ const result = getBarData(config, data, hasConfidenceInterval)
36
+
37
+ expect(result).toEqual([
38
+ { category: 'A', X: 10, Y: 20, dynamicData: true },
39
+ { category: 'B', X: 30, dynamicData: true }
40
+ ])
41
+ })
42
+
43
+ it('should return transformed data with confidence intervals when there is a dynamicSeries and confidence interval', () => {
44
+ const config = {
45
+ series: [{ dataKey: 'value', dynamicCategory: 'subCategory' }],
46
+ runtime: { originalXAxis: { dataKey: 'category' } },
47
+ confidenceKeys: { lower: 'lowerCI', upper: 'upperCI' }
48
+ } as ChartConfig
49
+ const data: TransformedData[] = [
50
+ { category: 'A', subCategory: 'X', value: 10, lowerCI: 5, upperCI: 15 },
51
+ { category: 'A', subCategory: 'Y', value: 20, lowerCI: 15, upperCI: 25 },
52
+ { category: 'B', subCategory: 'X', value: 30, lowerCI: 25, upperCI: 35 }
53
+ ]
54
+ const hasConfidenceInterval = true
55
+
56
+ const result = getBarData(config, data, hasConfidenceInterval)
57
+
58
+ expect(result).toEqual([
59
+ {
60
+ category: 'A',
61
+ X: 10,
62
+ Y: 20,
63
+ CI: { X: { lower: 5, upper: 15 }, Y: { lower: 15, upper: 25 } },
64
+ dynamicData: true
65
+ },
66
+ {
67
+ category: 'B',
68
+ X: 30,
69
+ CI: { X: { lower: 25, upper: 35 } },
70
+ dynamicData: true
71
+ }
72
+ ])
73
+ })
74
+ })
@@ -4,81 +4,22 @@ import { Group } from '@visx/group'
4
4
  import ConfigContext from '../../ConfigContext'
5
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
6
  import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
7
- import { scaleBand } from '@visx/scale'
7
+ import { handleTooltip, calculateBoxPlotStats, createPlots } from './helpers/index'
8
8
  import _ from 'lodash'
9
- import { max, min, median, quantile } from 'd3-array'
10
- const CoveBoxPlot = ({ xScale, yScale }) => {
9
+
10
+ const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
11
11
  const { config, colorScale, seriesHighlight, transformedData: data } = useContext(ConfigContext)
12
12
  const { boxplot } = config
13
13
 
14
- // tooltips
15
14
  const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
16
-
17
- const handleTooltip = (d, key, q1, q3, median, iqr) => {
18
- return `
19
- <strong>${d.columnCategory}</strong></br>
20
- <strong>Key:${key}</strong></br>
21
- ${boxplot.labels.q1}: ${q1}<br/>
22
- ${boxplot.labels.q3}: ${q3}<br/>
23
- ${boxplot.labels.iqr}: ${iqr}<br/>
24
- ${boxplot.labels.median}: ${median}
25
- `
26
- }
27
-
28
15
  const boxWidth = xScale.bandwidth()
29
16
  const constrainedWidth = Math.min(40, boxWidth)
30
- const color_0 = colorPalettesChart[config?.palette][0] ? colorPalettesChart[config?.palette][0] : '#000'
31
- const seriesScale = scaleBand({
32
- range: [0, config.barThickness * 100 || 1],
33
- domain: config.series?.map(item => item?.dataKey)
34
- })
35
-
36
- const calculateBoxPlotStats = values => {
37
- if (!values || !values.length) return {}
38
- const sortedValues = values.sort((a, b) => a - b)
39
- return {
40
- min: min(values),
41
- max: max(values),
42
- median: median(values),
43
- firstQuartile: quantile(sortedValues, 0.25),
44
- thirdQuartile: quantile(sortedValues, 0.75)
45
- }
46
- }
47
-
48
- const getValuesBySeriesKey = (group: string) => {
49
- const allSeriesKeys = config.series.map(item => item?.dataKey)
50
- const result = {}
51
- const filteredData = data.filter(item => item[config.xAxis.dataKey] === group)
52
- allSeriesKeys.forEach(key => {
53
- result[key] = filteredData.map(item => item[key])
54
- })
55
-
56
- return result
57
- }
58
-
59
- interface Plot {
60
- columnCategory: string
61
- keyValues: { [key: string]: number[] }
62
- }
63
- const createPlots = data => {
64
- const dataKeys = data.map(d => d[config.xAxis.dataKey])
65
- const plots: Plot[] = []
66
- const groups: string[] = _.uniq(dataKeys)
67
- if (groups && groups.length > 0) {
68
- groups.forEach(group => {
69
- plots.push({
70
- columnCategory: group,
71
- keyValues: getValuesBySeriesKey(group)
72
- })
73
- })
74
- }
75
- return plots
76
- }
17
+ const color_0 = _.get(colorPalettesChart, [config.palette, 0], '#000')
77
18
 
78
19
  return (
79
20
  <ErrorBoundary component='BoxPlot'>
80
21
  <Group left={Number(config.yAxis.size)} className='boxplot' key={`boxplot-group`}>
81
- {createPlots(data).map((d, i) => {
22
+ {createPlots(data, config).map((d, i) => {
82
23
  const offset = boxWidth - constrainedWidth
83
24
  const radius = 4
84
25
 
@@ -87,12 +28,12 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
87
28
  key={`boxplotplot-${d.columnCategory}`}
88
29
  left={xScale(d.columnCategory) + (xScale.bandwidth() - seriesScale.bandwidth()) / 2}
89
30
  >
90
- {config.series.map(item => {
31
+ {config.series.map((item, index) => {
91
32
  const valuesByKey = d.keyValues[item.dataKey]
92
33
  const { min, max, median, firstQuartile, thirdQuartile } = calculateBoxPlotStats(valuesByKey)
93
34
  let iqr = Number(thirdQuartile - firstQuartile).toFixed(config.dataFormat.roundTo)
94
35
 
95
- const transparentPlot =
36
+ const isTransparent =
96
37
  config.legend.behavior === 'highlight' &&
97
38
  seriesHighlight.length > 0 &&
98
39
  seriesHighlight.indexOf(item.dataKey) === -1
@@ -100,10 +41,10 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
100
41
  config.legend.behavior === 'highlight' ||
101
42
  seriesHighlight.length === 0 ||
102
43
  seriesHighlight.indexOf(item.dataKey) !== -1
103
- const fillOpacity = transparentPlot ? 0.3 : 0.5
44
+ const fillOpacity = isTransparent ? 0.3 : 0.5
104
45
 
105
46
  return (
106
- <Group key={`boxplotplot-${item}`}>
47
+ <Group key={`boxplotplot-${item.dataKey}-${index}`}>
107
48
  {boxplot.plotNonOutlierValues &&
108
49
  valuesByKey.map((value, index) => {
109
50
  return (
@@ -123,8 +64,8 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
123
64
  display={displayPlot ? 'block' : 'none'}
124
65
  data-left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
125
66
  key={`box-plot-${i}-${item}`}
126
- min={min}
127
- max={max}
67
+ min={Number(min)}
68
+ max={Number(max)}
128
69
  left={seriesScale(item.dataKey)}
129
70
  firstQuartile={firstQuartile}
130
71
  thirdQuartile={thirdQuartile}
@@ -163,6 +104,7 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
163
104
  container
164
105
  containerProps={{
165
106
  'data-tooltip-html': handleTooltip(
107
+ boxplot,
166
108
  d,
167
109
  item.dataKey,
168
110
  firstQuartile,
@@ -0,0 +1,54 @@
1
+ import { max, min, median, quantile } from 'd3-array'
2
+ import _ from 'lodash'
3
+
4
+ interface Plot {
5
+ columnCategory: string
6
+ keyValues: { [key: string]: number[] }
7
+ }
8
+ export const handleTooltip = (boxplot, d, key, q1, q3, median, iqr) => {
9
+ return `
10
+ <strong>${d.columnCategory}</strong></br>
11
+ <strong>Key:${key}</strong></br>
12
+ ${boxplot.labels.q1}: ${q1}<br/>
13
+ ${boxplot.labels.q3}: ${q3}<br/>
14
+ ${boxplot.labels.iqr}: ${iqr}<br/>
15
+ ${boxplot.labels.median}: ${median}
16
+ `
17
+ }
18
+
19
+ export const calculateBoxPlotStats = values => {
20
+ if (!values || !values.length) return {}
21
+ const sortedValues = _.sortBy(values)
22
+ return {
23
+ min: min(values),
24
+ max: max(values),
25
+ median: median(values),
26
+ firstQuartile: quantile(sortedValues, 0.25),
27
+ thirdQuartile: quantile(sortedValues, 0.75)
28
+ }
29
+ }
30
+ const getValuesBySeriesKey = (group: string, config, data) => {
31
+ const allSeriesKeys = config.series.map(item => item?.dataKey)
32
+ const result = {}
33
+ const filteredData = data.filter(item => item[config.xAxis.dataKey] === group)
34
+ allSeriesKeys.forEach(key => {
35
+ result[key] = filteredData.map(item => item[key])
36
+ })
37
+
38
+ return result
39
+ }
40
+
41
+ export const createPlots = (data, config) => {
42
+ const dataKeys = data.map(d => d[config.xAxis.dataKey])
43
+ const plots: Plot[] = []
44
+ const groups: string[] = _.uniq(dataKeys)
45
+ if (groups && groups.length > 0) {
46
+ groups.forEach(group => {
47
+ plots.push({
48
+ columnCategory: group,
49
+ keyValues: getValuesBySeriesKey(group, config, data)
50
+ })
51
+ })
52
+ }
53
+ return plots
54
+ }
@@ -4,6 +4,7 @@ import ConfigContext from '../ConfigContext'
4
4
  import * as d3 from 'd3'
5
5
  import { Text } from '@visx/text'
6
6
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
7
+ import _ from 'lodash'
7
8
 
8
9
  interface BrushChartProps {
9
10
  xMax: number
@@ -11,7 +12,7 @@ interface BrushChartProps {
11
12
  }
12
13
 
13
14
  const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
14
- const { tableData, config, setBrushConfig, dashboardConfig, formatDate } = useContext(ConfigContext)
15
+ const { tableData, config, setBrushConfig, dashboardConfig, formatDate, parseDate } = useContext(ConfigContext)
15
16
  const [brushState, setBrushState] = useState({ isBrushing: false, selection: [] })
16
17
  const [brushKey, setBrushKey] = useState(0)
17
18
  const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
@@ -26,9 +27,15 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
26
27
 
27
28
  const tooltipText = 'Drag edges to focus on a specific segment '
28
29
  const textWidth = getTextWidth(tooltipText, `normal ${16 / 1.1}px sans-serif`)
30
+ const DASHBOARD_MARGIN = 50
31
+ const BRUSH_HEIGHT_MULTIPLIER = 1.5
29
32
 
30
33
  const calculateGroupTop = (): number => {
31
- return Number(yMax) + config.xAxis.axisBBox + brushheight * 1.5
34
+ if (dashboardConfig?.type === 'dashboard') {
35
+ return Number(yMax) + config.xAxis.axisBBox + brushheight * BRUSH_HEIGHT_MULTIPLIER + DASHBOARD_MARGIN
36
+ } else {
37
+ return Number(yMax) + config.xAxis.axisBBox + brushheight * BRUSH_HEIGHT_MULTIPLIER
38
+ }
32
39
  }
33
40
 
34
41
  const handleMouseOver = () => {
@@ -85,41 +92,31 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
85
92
 
86
93
  const [x0, x1] = selection.map(value => xScale.invert(value))
87
94
 
88
- // filter and update brush state directly
89
- const newFilteredData = tableData.filter(d => {
90
- const dateValue = d[config.runtime.originalXAxis.dataKey]
91
- // Check if the date value exists and is valid
92
- if (!dateValue) {
93
- return false
94
- }
95
+ const newFilteredData = _.filter(tableData, d => {
96
+ const parsedDate = new Date(d[config.xAxis.dataKey])
97
+ return parsedDate && !isNaN(parsedDate.getTime()) && parsedDate >= x0 && parsedDate <= x1
98
+ })
95
99
 
96
- const parsedDate = new Date(dateValue)
100
+ const sortByRecentDate = config.xAxis.sortByRecentDate
97
101
 
98
- // Check if parsedDate is a valid date
99
- if (isNaN(parsedDate.getTime())) {
100
- return false
101
- }
102
+ const sortedData = _.sortBy(newFilteredData, item => new Date(item[config.xAxis.dataKey]))
102
103
 
103
- // Check if the date falls within the selection range
104
- if (parsedDate >= x0 && parsedDate <= x1) {
105
- return true
106
- }
107
- })
104
+ // If ascending is false, reverse the sorted array
105
+ const finalData = !sortByRecentDate ? sortedData : sortedData.reverse()
108
106
 
109
- const firstDate = (newFilteredData.length && newFilteredData[0][config?.runtime?.originalXAxis?.dataKey]) ?? ''
110
- const lastDate =
111
- (newFilteredData.length &&
112
- newFilteredData[newFilteredData.length - 1][config?.runtime?.originalXAxis?.dataKey]) ??
113
- ''
107
+ // Retrieve the start and end dates based on the sorted data array
108
+ const startDate = _.get(_.first(finalData), config.xAxis.dataKey, '')
109
+ const endDate = _.get(_.last(finalData), config.xAxis.dataKey, '')
114
110
  // add custom blue colored handlers to each corners of brush
115
111
  svg.selectAll('.handle--custom').remove()
116
112
  // append handler
117
- svg.call(brushHandle, selection, firstDate, lastDate)
113
+ const [formattedStartDate, formattedEndDate] = [startDate, endDate].map(date => formatDate(parseDate(date)))
114
+ svg.call(brushHandle, selection, formattedStartDate, formattedEndDate)
118
115
 
119
116
  setBrushConfig({
120
117
  active: config.brush.active,
121
118
  isBrushing: isUserBrushing,
122
- data: newFilteredData
119
+ data: finalData
123
120
  })
124
121
  setBrushState({
125
122
  isBrushing: true,
@@ -647,7 +647,8 @@ const EditorPanel = () => {
647
647
  visSupportsValueAxisLine,
648
648
  visSupportsValueAxisMax,
649
649
  visSupportsValueAxisMin,
650
- visSupportsValueAxisTicks
650
+ visSupportsValueAxisTicks,
651
+ visSupportsYPadding
651
652
  } = useEditorPermissions()
652
653
 
653
654
  // when the visualization type changes we
@@ -1346,6 +1347,9 @@ const EditorPanel = () => {
1346
1347
  updateConfig(updatedConfig)
1347
1348
  }
1348
1349
 
1350
+ const hasDynamicCategory = ![undefined, '- Select - '].includes(config.series?.[0]?.dynamicCategory)
1351
+ const hasMultipleSeries = config.series?.length > 1
1352
+
1349
1353
  const editorContextValues = {
1350
1354
  addNewExclusion,
1351
1355
  data,
@@ -1496,7 +1500,8 @@ const EditorPanel = () => {
1496
1500
  </Panels.Series.Wrapper>
1497
1501
  )}
1498
1502
  </>
1499
- {config.series && config.series.length <= 1 && config.visualizationType === 'Bar' && (
1503
+ {((config.series && config.series.length && config.visualizationType === 'Bar') ||
1504
+ (config.series && config.series.length <= 1 && config.visualizationType === 'Line')) && (
1500
1505
  <>
1501
1506
  <span className='divider-heading'>Confidence Keys</span>
1502
1507
  <Select
@@ -1768,14 +1773,16 @@ const EditorPanel = () => {
1768
1773
  title={!config.yAxis.gridLines ? 'Show gridlines to enable' : ''}
1769
1774
  />
1770
1775
  )}
1771
- <CheckBox
1772
- value={config.yAxis.enablePadding}
1773
- section='yAxis'
1774
- fieldName='enablePadding'
1775
- label='Add Padding to Value Axis Scale'
1776
- updateField={updateField}
1777
- />
1778
- {config.yAxis.enablePadding && (
1776
+ {visSupportsYPadding() && (
1777
+ <CheckBox
1778
+ value={config.yAxis.enablePadding}
1779
+ section='yAxis'
1780
+ fieldName='enablePadding'
1781
+ label='Add Padding to Value Axis Scale'
1782
+ updateField={updateField}
1783
+ />
1784
+ )}
1785
+ {config.yAxis.enablePadding && visSupportsYPadding() && (
1779
1786
  <TextField
1780
1787
  type='number'
1781
1788
  section='yAxis'
@@ -1839,7 +1846,7 @@ const EditorPanel = () => {
1839
1846
  className='number-narrow'
1840
1847
  updateField={updateField}
1841
1848
  min={0}
1842
- />
1849
+ />{' '}
1843
1850
  <div className='two-col-inputs'>
1844
1851
  <TextField
1845
1852
  value={config.dataFormat.prefix}
@@ -1886,7 +1893,6 @@ const EditorPanel = () => {
1886
1893
  }
1887
1894
  />
1888
1895
  </div>
1889
-
1890
1896
  {config.orientation === 'horizontal' ? ( // horizontal - x is vertical y is horizontal
1891
1897
  <>
1892
1898
  {visSupportsValueAxisLine() && (
@@ -2015,20 +2021,23 @@ const EditorPanel = () => {
2015
2021
  updateField={updateField}
2016
2022
  />
2017
2023
  <span style={{ color: 'red', display: 'block' }}>{warningMsg.maxMsg}</span>
2018
- <TextField
2019
- value={config.yAxis.min}
2020
- section='yAxis'
2021
- fieldName='min'
2022
- type='number'
2023
- label='left axis min value'
2024
- placeholder='Auto'
2025
- updateField={updateField}
2026
- />
2027
- <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
2024
+ {config.visualizationType !== 'Area Chart' && config.visualizationSubType !== 'stacked' && (
2025
+ <>
2026
+ <TextField
2027
+ value={config.yAxis.min}
2028
+ section='yAxis'
2029
+ fieldName='min'
2030
+ type='number'
2031
+ label='left axis min value'
2032
+ placeholder='Auto'
2033
+ updateField={updateField}
2034
+ />
2035
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
2036
+ </>
2037
+ )}
2028
2038
  </>
2029
2039
  )
2030
2040
  )}
2031
-
2032
2041
  {/* start: anchors */}
2033
2042
  {visHasAnchors() && config.orientation !== 'horizontal' && (
2034
2043
  <div className='edit-block'>
@@ -2155,7 +2164,6 @@ const EditorPanel = () => {
2155
2164
  </button>
2156
2165
  </div>
2157
2166
  )}
2158
-
2159
2167
  {visHasAnchors() && config.orientation === 'horizontal' && (
2160
2168
  <div className='edit-block'>
2161
2169
  <span className='edit-label column-heading'>Anchors</span>
@@ -2764,13 +2772,12 @@ const EditorPanel = () => {
2764
2772
  />
2765
2773
  </>
2766
2774
  )}
2767
-
2768
2775
  <CheckBox
2769
2776
  value={config.exclusions.active}
2770
2777
  section='exclusions'
2771
2778
  fieldName='active'
2772
2779
  label={
2773
- config.xAxis.type === 'date'
2780
+ config.xAxis.type === 'date' || config.xAxis.type === 'date-time'
2774
2781
  ? 'Limit by start and/or end dates'
2775
2782
  : 'Exclude one or more values'
2776
2783
  }
@@ -2869,26 +2876,27 @@ const EditorPanel = () => {
2869
2876
  </>
2870
2877
  )}
2871
2878
 
2872
- {config.xAxis.type === 'date' && (
2873
- <>
2874
- <TextField
2875
- type='date'
2876
- section='exclusions'
2877
- fieldName='dateStart'
2878
- label='Start Date'
2879
- updateField={updateField}
2880
- value={config.exclusions.dateStart || ''}
2881
- />
2882
- <TextField
2883
- type='date'
2884
- section='exclusions'
2885
- fieldName='dateEnd'
2886
- label='End Date'
2887
- updateField={updateField}
2888
- value={config.exclusions.dateEnd || ''}
2889
- />
2890
- </>
2891
- )}
2879
+ {config.xAxis.type === 'date' ||
2880
+ (config.xAxis.type === 'date-time' && (
2881
+ <>
2882
+ <TextField
2883
+ type='date'
2884
+ section='exclusions'
2885
+ fieldName='dateStart'
2886
+ label='Start Date'
2887
+ updateField={updateField}
2888
+ value={config.exclusions.dateStart || ''}
2889
+ />
2890
+ <TextField
2891
+ type='date'
2892
+ section='exclusions'
2893
+ fieldName='dateEnd'
2894
+ label='End Date'
2895
+ updateField={updateField}
2896
+ value={config.exclusions.dateEnd || ''}
2897
+ />
2898
+ </>
2899
+ ))}
2892
2900
  </>
2893
2901
  )}
2894
2902
 
@@ -3671,38 +3679,6 @@ const EditorPanel = () => {
3671
3679
  updateField={updateField}
3672
3680
  />
3673
3681
 
3674
- {/* <fieldset className="checkbox-group">
3675
- <CheckBox value={config.legend.dynamicLegend} section="legend" fieldName="dynamicLegend" label="Dynamic Legend" updateField={updateField}/>
3676
- {config.legend.dynamicLegend && (
3677
- <>
3678
- <TextField value={config.legend.dynamicLegendDefaultText} section="legend" fieldName="dynamicLegendDefaultText" label="Dynamic Legend Default Text" updateField={updateField} />
3679
- <TextField value={config.legend.dynamicLegendItemLimit} type="number" min="0" section="legend" fieldName="dynamicLegendItemLimit" label={'Dynamic Legend Limit'} className="number-narrow" updateField={updateField}/>
3680
- <TextField value={config.legend.dynamicLegendItemLimitMessage} section="legend" fieldName="dynamicLegendItemLimitMessage" label="Dynamic Legend Item Limit Message" updateField={updateField} />
3681
- <TextField value={config.legend.dynamicLegendChartMessage} section="legend" fieldName="dynamicLegendChartMessage" label="Dynamic Legend Chart Message" updateField={updateField} />
3682
- </>
3683
- )}
3684
- </fieldset> */}
3685
-
3686
- <CheckBox
3687
- value={config.legend.hide ? true : false}
3688
- section='legend'
3689
- fieldName='hide'
3690
- label='Hide Legend'
3691
- updateField={updateField}
3692
- tooltip={
3693
- <Tooltip style={{ textTransform: 'none' }}>
3694
- <Tooltip.Target>
3695
- <Icon
3696
- display='question'
3697
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
3698
- />
3699
- </Tooltip.Target>
3700
- <Tooltip.Content>
3701
- <p>With a single-series chart, consider hiding the legend to reduce visual clutter.</p>
3702
- </Tooltip.Content>
3703
- </Tooltip>
3704
- }
3705
- />
3706
3682
  <CheckBox
3707
3683
  display={config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value)}
3708
3684
  value={config.legend.hideSuppressedLabels}
@@ -3755,7 +3731,7 @@ const EditorPanel = () => {
3755
3731
  </>
3756
3732
  } */}
3757
3733
  <Select
3758
- display={config.series?.length > 1}
3734
+ display={hasDynamicCategory || hasMultipleSeries}
3759
3735
  value={config.legend.behavior}
3760
3736
  section='legend'
3761
3737
  fieldName='behavior'
@@ -612,6 +612,7 @@ const SeriesItem = props => {
612
612
  const { series, getItemStyle, sortableItemStyles, chartsWithOptions, index: i } = props
613
613
  const showDynamicCategory =
614
614
  ['Bar', 'Line'].includes(config.visualizationType) &&
615
+ config.visualizationSubType !== 'Stacked' &&
615
616
  !config.series.find(s => s.dynamicCategory && s.dataKey !== series.dataKey)
616
617
  return (
617
618
  <Draggable key={series.dataKey} draggableId={`draggableFilter-${series.dataKey}`} index={i}>
@@ -153,7 +153,6 @@ export const useEditorPermissions = () => {
153
153
  }
154
154
  }
155
155
  const visHasBrushChart = () => {
156
- return false
157
156
  if (config.xAxis.type === 'categorical') return false
158
157
  return ['Line', 'Bar', 'Area Chart', 'Combo'].includes(visualizationType) && orientation === 'vertical'
159
158
  }
@@ -384,6 +383,10 @@ export const useEditorPermissions = () => {
384
383
  )
385
384
  }
386
385
 
386
+ const visSupportsYPadding = () => {
387
+ return !config.dataFormat.onlyShowTopPrefixSuffix || !config.dataFormat.suffix?.includes(' ')
388
+ }
389
+
387
390
  const visHasSingleSeriesTooltip = () => {
388
391
  if (visualizationType === 'Bar' || visualizationType === 'Line') {
389
392
  return true
@@ -457,6 +460,7 @@ export const useEditorPermissions = () => {
457
460
  visSupportsValueAxisMax,
458
461
  visSupportsValueAxisMin,
459
462
  visSupportsDynamicSeries,
463
+ visSupportsYPadding,
460
464
  visHasSingleSeriesTooltip,
461
465
  visHasCategoricalAxis
462
466
  }
@@ -2,7 +2,7 @@ import parse from 'html-react-parser'
2
2
  import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
3
3
  import LegendShape from '@cdc/core/components/LegendShape'
4
4
  import Button from '@cdc/core/components/elements/Button'
5
- import useLegendClasses from '../../hooks/useLegendClasses'
5
+ import { getLegendClasses } from './helpers/getLegendClasses'
6
6
  import { useHighlightedBars } from '../../hooks/useHighlightedBars'
7
7
  import { handleLineType } from '../../helpers/handleLineType'
8
8
 
@@ -48,7 +48,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
48
48
  },
49
49
  ref
50
50
  ) => {
51
- const { innerClasses, containerClasses } = useLegendClasses(config)
51
+ const { innerClasses, containerClasses } = getLegendClasses(config)
52
52
  const { runtime, legend } = config
53
53
 
54
54
  const [hasSuppression, setHasSuppression] = useState(false)
@@ -1,6 +1,6 @@
1
- import { ChartConfig } from '../types/ChartConfig'
1
+ import { ChartConfig } from './../../../types/ChartConfig'
2
2
 
3
- const useLegendClasses = (config: ChartConfig) => {
3
+ export const getLegendClasses = (config: ChartConfig) => {
4
4
  const { position, singleRow, reverseLabelOrder, verticalSorted, hideBorder } = config.legend
5
5
  const containerClasses = ['legend-container']
6
6
  const innerClasses = ['legend-container__inner']
@@ -40,11 +40,11 @@ const useLegendClasses = (config: ChartConfig) => {
40
40
 
41
41
  // Configure border classes
42
42
  if (hideBorder.side && (['right', 'left'].includes(position) || !position)) {
43
- containerClasses.push('no-border')
43
+ containerClasses.push('border-0')
44
44
  }
45
45
 
46
46
  if (hideBorder.topBottom && ['top', 'bottom'].includes(position)) {
47
- containerClasses.push('no-border')
47
+ containerClasses.push('border-0')
48
48
  }
49
49
 
50
50
  if (hideBorder.topBottom && ['top'].includes(position)) {
@@ -56,4 +56,4 @@ const useLegendClasses = (config: ChartConfig) => {
56
56
  innerClasses
57
57
  }
58
58
  }
59
- export default useLegendClasses
59
+ export default getLegendClasses