@cdc/chart 4.26.6 → 4.26.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CONFIG.md +9 -7
  2. package/dist/cdcchart.js +32200 -31292
  3. package/examples/private/flueview.json +22051 -0
  4. package/package.json +3 -11
  5. package/src/CdcChart.tsx +2 -2
  6. package/src/CdcChartComponent.tsx +40 -10
  7. package/src/_stories/Chart.PieLabels.stories.tsx +47 -0
  8. package/src/_stories/Chart.smoke.stories.tsx +14 -1
  9. package/src/_stories/ChartPrefixSuffix.smoke.stories.tsx +28 -1
  10. package/src/components/BarChart/components/BarChart.Horizontal.tsx +1 -2
  11. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
  12. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -1
  13. package/src/components/BarChart/components/BarChart.Vertical.tsx +1 -2
  14. package/src/components/BarChart/helpers/getBarData.ts +28 -28
  15. package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
  16. package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +98 -0
  17. package/src/components/BarChart/helpers/useBarChart.ts +4 -5
  18. package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +7 -6
  19. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
  20. package/src/components/BoxPlot/helpers/index.ts +4 -3
  21. package/src/components/Brush/BrushSelector.tsx +1 -1
  22. package/src/components/EditorPanel/EditorPanel.tsx +90 -29
  23. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +16 -7
  24. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +3 -3
  25. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +19 -4
  26. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +3 -3
  27. package/src/components/EditorPanel/useEditorPermissions.test.tsx +32 -0
  28. package/src/components/EditorPanel/useEditorPermissions.ts +5 -1
  29. package/src/components/Forecasting/Forecasting.tsx +1 -1
  30. package/src/components/Legend/Legend.Component.tsx +54 -59
  31. package/src/components/Legend/Legend.Suppression.tsx +3 -3
  32. package/src/components/Legend/LegendGroup/LegendGroup.tsx +0 -1
  33. package/src/components/LineChart/helpers.ts +0 -2
  34. package/src/components/LineChart/index.tsx +2 -3
  35. package/src/components/LinearChart.tsx +4 -3
  36. package/src/components/PieChart/PieChart.tsx +117 -10
  37. package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
  38. package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
  39. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -10
  40. package/src/data/initial-state.js +2 -0
  41. package/src/helpers/categoryOrder.ts +60 -0
  42. package/src/helpers/getBoxPlotConfig.ts +19 -12
  43. package/src/helpers/getComboChartConfig.ts +0 -1
  44. package/src/helpers/getExcludedData.ts +0 -1
  45. package/src/helpers/getHasBoundarySuppression.ts +4 -3
  46. package/src/helpers/getMinMax.ts +2 -1
  47. package/src/helpers/getNewRuntime.ts +0 -2
  48. package/src/helpers/getPiePercent.ts +3 -3
  49. package/src/helpers/isConvertLineToBarGraph.ts +2 -2
  50. package/src/helpers/seriesColumnSettings.ts +21 -3
  51. package/src/helpers/sizeHelpers.ts +0 -2
  52. package/src/helpers/tests/categoryOrder.test.ts +57 -0
  53. package/src/helpers/tests/seriesColumnSettings.test.ts +60 -0
  54. package/src/hooks/tests/useRightAxis.test.tsx +143 -2
  55. package/src/hooks/tests/useScales.test.tsx +3 -6
  56. package/src/hooks/tests/useTooltip.test.tsx +132 -0
  57. package/src/hooks/useRightAxis.ts +19 -26
  58. package/src/hooks/useTooltip.tsx +14 -10
  59. package/src/store/chart.reducer.ts +0 -1
  60. package/src/test/CdcChart.dataTable.test.tsx +94 -0
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@cdc/chart",
3
- "version": "4.26.6",
3
+ "version": "4.26.7",
4
4
  "description": "React component for visualizing tabular data in various types of charts",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Matthew Pallansch <mpallansch@adittech.com>",
7
7
  "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
8
8
  "dependencies": {
9
- "@cdc/core": "^4.26.6",
9
+ "@cdc/core": "^4.26.7",
10
10
  "@hello-pangea/dnd": "^16.2.0",
11
11
  "@react-spring/web": "^9.7.5",
12
12
  "@rollup/plugin-dsv": "^3.0.2",
@@ -14,14 +14,11 @@
14
14
  "@visx/axis": "^3.12.0",
15
15
  "@visx/brush": "^3.12.0",
16
16
  "@visx/curve": "^3.12.0",
17
- "@visx/drag": "^3.12.0",
18
17
  "@visx/event": "^3.12.0",
19
18
  "@visx/glyph": "^3.12.0",
20
- "@visx/gradient": "^3.12.0",
21
19
  "@visx/heatmap": "^3.12.0",
22
20
  "@visx/legend": "^3.12.0",
23
21
  "@visx/marker": "^3.12.0",
24
- "@visx/mock-data": "^3.12.0",
25
22
  "@visx/pattern": "^3.12.0",
26
23
  "@visx/responsive": "^3.12.0",
27
24
  "@visx/scale": "^3.12.0",
@@ -32,20 +29,16 @@
32
29
  "@vitejs/plugin-react": "^5.1.2",
33
30
  "chroma-js": "^3.1.2",
34
31
  "d3-array": "^3.2.4",
35
- "d3-format": "^3.1.2",
36
32
  "d3-sankey": "^0.12.3",
37
33
  "d3-time-format": "^4.1.0",
38
34
  "dompurify": "^3.4.0",
39
35
  "html-react-parser": "^5.2.3",
40
- "js-base64": "^2.5.2",
41
36
  "lodash": "^4.17.23",
42
- "papaparse": "^5.5.2",
43
37
  "react-accessible-accordion": "^5.0.1",
44
38
  "react-icons": "^5.5.0",
45
39
  "react-tooltip": "5.8.2-beta.3",
46
40
  "resize-observer-polyfill": "^1.5.1",
47
41
  "sass": "^1.89.2",
48
- "use-debounce": "^10.1.0",
49
42
  "vite": "^7.3.1",
50
43
  "vite-plugin-css-injected-by-js": "^2.4.0",
51
44
  "vite-plugin-svgr": "^4.2.0",
@@ -53,11 +46,10 @@
53
46
  },
54
47
  "devDependencies": {
55
48
  "@types/d3-array": "^3.2.1",
56
- "@types/d3-format": "^3.0.4",
57
49
  "@types/d3-sankey": "^0.12.4",
58
50
  "@types/d3-time-format": "^4.0.3"
59
51
  },
60
- "gitHead": "f4a2a0d4a631a1b60fcf9534589697780e480b58",
52
+ "gitHead": "ca89fe0eb083a52a425fa5971bbe74716b5bb0f9",
61
53
  "main": "dist/cdcchart",
62
54
  "moduleName": "CdcChart",
63
55
  "peerDependencies": {
package/src/CdcChart.tsx CHANGED
@@ -7,7 +7,7 @@ import { parseCsvWithQuotes } from '@cdc/core/helpers/parseCsvWithQuotes'
7
7
  import { addCsvCacheBuster } from '@cdc/core/helpers/csvCacheBuster'
8
8
  import { extractDataAndMetadata } from '@cdc/core/helpers/extractDataAndMetadata'
9
9
  import Loading from '@cdc/core/components/Loading'
10
- import _ from 'lodash'
10
+ import isEqual from 'lodash/isEqual'
11
11
  import EditorContext from '@cdc/core/contexts/EditorContext'
12
12
  interface CdcChartProps {
13
13
  configUrl?: string
@@ -90,7 +90,7 @@ const CdcChartWrapper: React.FC<CdcChartProps> = ({
90
90
  }, [configUrl, loadConfig])
91
91
 
92
92
  useEffect(() => {
93
- if (!_.isEqual(prevFiltersRef.current, config.filters)) {
93
+ if (!isEqual(prevFiltersRef.current, config.filters)) {
94
94
  prevFiltersRef.current = config.filters
95
95
  reloadFilteredData()
96
96
  }
@@ -101,6 +101,7 @@ import { prepareSmallMultiplesDataTable } from './helpers/smallMultiplesHelpers'
101
101
  import { calcInitialHeight } from './helpers/sizeHelpers'
102
102
  import { ensureSpecialChartAxisTypes } from './helpers/ensureSpecialChartAxisTypes'
103
103
  import { findColumnConfigByName } from './helpers/seriesColumnSettings'
104
+ import { sortByCategoryOrder } from './helpers/categoryOrder'
104
105
 
105
106
  // styles
106
107
  import './scss/main.scss'
@@ -231,6 +232,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
231
232
  config.enableMarkupVariables,
232
233
  config.markupVariables,
233
234
  config.data,
235
+ config.dataMetadata,
234
236
  config.filters,
235
237
  title,
236
238
  config.superTitle,
@@ -263,14 +265,30 @@ const CdcChart: React.FC<CdcChartProps> = ({
263
265
  const legendId = useId()
264
266
 
265
267
  const convertLineToBarGraph = isConvertLineToBarGraph(config, filteredData)
268
+ const xAxisDataKey = config.xAxis?.dataKey
269
+ const xAxisType = config.xAxis?.type
270
+ const xAxisCategoryOrderType = config.xAxis?.categoryOrderType
271
+ const xAxisCategoryOrder = config.xAxis?.categoryOrder
272
+ const categoryOrderConfig = useMemo(
273
+ () =>
274
+ ({
275
+ xAxis: {
276
+ dataKey: xAxisDataKey,
277
+ type: xAxisType,
278
+ categoryOrderType: xAxisCategoryOrderType,
279
+ categoryOrder: xAxisCategoryOrder
280
+ }
281
+ } as Pick<ChartConfig, 'xAxis'>),
282
+ [xAxisDataKey, xAxisType, xAxisCategoryOrderType, xAxisCategoryOrder]
283
+ )
266
284
 
267
285
  // Declaratively calculate series keys for pie charts based on filtered data
268
286
  const pieSeriesKeys = useMemo(() => {
269
- if (config.visualizationType !== 'Pie' || !config.xAxis?.dataKey) return null
287
+ if (visualizationType !== 'Pie' || !xAxisDataKey) return null
270
288
  const data = filteredData?.length > 0 ? filteredData : excludedData
271
289
  if (!data) return null
272
- return uniq(data.map(d => d[config.xAxis.dataKey]))
273
- }, [config.visualizationType, config.xAxis?.dataKey, filteredData, excludedData])
290
+ return uniq(sortByCategoryOrder(data, categoryOrderConfig).map(d => d[xAxisDataKey]))
291
+ }, [visualizationType, xAxisDataKey, categoryOrderConfig, filteredData, excludedData])
274
292
 
275
293
  const prepareConfig = (loadedConfig: ChartConfig) => {
276
294
  // Create defaults without version to avoid overriding legacy configs
@@ -458,13 +476,15 @@ const CdcChart: React.FC<CdcChartProps> = ({
458
476
  if (newConfig.visualizationType === 'Pie') {
459
477
  // Use the same data that will be passed to PieChart (after exclusions and filters)
460
478
  const pieData = currentData.length > 0 ? currentData : newExcludedData
461
- newConfig.runtime.seriesKeys = uniq(pieData.map(d => d[newConfig.xAxis.dataKey]))
479
+ newConfig.runtime.seriesKeys = uniq(sortByCategoryOrder(pieData, newConfig).map(d => d[newConfig.xAxis.dataKey]))
462
480
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
463
481
  newConfig.runtime.isPieChart = true // Flag to know when to use derived keys
464
482
  } else if (newConfig.visualizationType === 'Radar') {
465
483
  // Radar chart: seriesKeys are the entity names from xAxis.dataKey
466
484
  const radarData = currentData.length > 0 ? currentData : newExcludedData
467
- newConfig.runtime.seriesKeys = uniq(radarData.map(d => d[newConfig.xAxis.dataKey]))
485
+ newConfig.runtime.seriesKeys = uniq(
486
+ sortByCategoryOrder(radarData, newConfig).map(d => d[newConfig.xAxis.dataKey])
487
+ )
468
488
  newConfig.runtime.seriesLabelsAll = newConfig.runtime.seriesKeys
469
489
  } else {
470
490
  const finalData = dataOverride || newConfig.formattedData || newConfig.data
@@ -777,6 +797,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
777
797
  }
778
798
 
779
799
  const configDataDependency = configObj?.data?.length ? configObj.data : null
800
+ const configDataMetadataDependency = configObj?.dataMetadata ? JSON.stringify(configObj.dataMetadata) : null
780
801
  const configExclusionsDependency = [
781
802
  configObj?.xAxis?.type,
782
803
  configObj?.xAxis?.dataKey,
@@ -807,7 +828,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
807
828
  }
808
829
 
809
830
  load()
810
- }, [configDataDependency, configExclusionsDependency])
831
+ }, [configDataDependency, configDataMetadataDependency, configExclusionsDependency])
811
832
 
812
833
  /**
813
834
  * When cove has a config and container ref publish the cove_loaded event.
@@ -1058,6 +1079,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1058
1079
  rightSuffix,
1059
1080
  bottomPrefix,
1060
1081
  bottomSuffix,
1082
+ bottomCommas,
1061
1083
  bottomAbbreviated,
1062
1084
  preserveOriginalDecimals
1063
1085
  }
@@ -1153,7 +1175,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1153
1175
 
1154
1176
  if (
1155
1177
  (axis === 'left' && commas && abbreviated && shouldAbbreviate) ||
1156
- (axis === 'bottom' && commas && abbreviated && shouldAbbreviate)
1178
+ (axis === 'bottom' && bottomCommas && bottomAbbreviated && shouldAbbreviate)
1157
1179
  ) {
1158
1180
  num = num // eslint-disable-line
1159
1181
  } else {
@@ -1243,9 +1265,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
1243
1265
  return config?.xAxis?.dataKey ? transform.cleanData(data, excludedKey, keysToClean) : data
1244
1266
  }
1245
1267
 
1268
+ const orderedTableData = useMemo(
1269
+ () => sortByCategoryOrder(filteredData || excludedData, categoryOrderConfig),
1270
+ [filteredData, excludedData, categoryOrderConfig]
1271
+ )
1272
+
1246
1273
  const getTableRuntimeData = () => {
1247
1274
  if (visualizationType === 'Sankey') return config?.data?.[0]?.tableData
1248
- const data = filteredData || excludedData
1275
+ const data = orderedTableData
1249
1276
  if (config.visualizationType === 'Pie' && !config.dataFormat?.showPiePercent) {
1250
1277
  return getPiePercent(data, config?.yAxis?.dataKey)
1251
1278
  }
@@ -1271,7 +1298,10 @@ const CdcChart: React.FC<CdcChartProps> = ({
1271
1298
  }
1272
1299
 
1273
1300
  // Transform and clean data for chart rendering
1274
- const transformedData = getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean })
1301
+ const transformedData = sortByCategoryOrder(
1302
+ getTransformedData({ brushData: state.brushData, filteredData, excludedData, clean }),
1303
+ config
1304
+ )
1275
1305
  const configYAxisDomainData = (config as ChartConfig).yAxisDomainData
1276
1306
  const yAxisDomainData = useMemo(() => {
1277
1307
  if (Array.isArray(configYAxisDomainData) && configYAxisDomainData.length > 0) {
@@ -1723,7 +1753,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
1723
1753
  setSharedFilter,
1724
1754
  setSharedFilterValue,
1725
1755
  svgRef,
1726
- tableData: filteredData || excludedData,
1756
+ tableData: orderedTableData,
1727
1757
  transformedData,
1728
1758
  twoColorPalette,
1729
1759
  unfilteredData: stateData,
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { expect } from 'storybook/test'
3
+ import Chart from '../CdcChart'
4
+ import pieConfig from './_mock/pie_with_data.json'
5
+ import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
6
+ import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
7
+
8
+ const meta: Meta<typeof Chart> = {
9
+ title: 'Components/Templates/Chart/Pie Labels',
10
+ component: Chart
11
+ }
12
+
13
+ type Story = StoryObj<typeof Chart>
14
+
15
+ export const CategoryPercentageLabels: Story = {
16
+ args: {
17
+ config: editConfigKeys(pieConfig, [
18
+ { path: ['labels'], value: true },
19
+ { path: ['legend', 'hide'], value: true }
20
+ ]),
21
+ isEditor: true
22
+ },
23
+ play: async ({ canvasElement }) => {
24
+ await assertVisualizationRendered(canvasElement)
25
+ await waitForPresence('.pie-label', canvasElement)
26
+ expect(canvasElement.querySelector('line')).toBeNull()
27
+ }
28
+ }
29
+
30
+ export const DonutCategoryPercentageLabels: Story = {
31
+ args: {
32
+ config: editConfigKeys(pieConfig, [
33
+ { path: ['labels'], value: true },
34
+ { path: ['legend', 'hide'], value: true },
35
+ { path: ['pieType'], value: 'Donut' }
36
+ ]),
37
+ isEditor: true
38
+ },
39
+ play: async ({ canvasElement }) => {
40
+ await assertVisualizationRendered(canvasElement)
41
+ await waitForPresence('.pie-label--outside', canvasElement)
42
+ expect(canvasElement.querySelector('.pie-label--inside')).toBeNull()
43
+ expect(canvasElement.querySelector('line')).toBeNull()
44
+ }
45
+ }
46
+
47
+ export default meta
@@ -26,6 +26,19 @@ const meta: Meta<typeof Chart> = {
26
26
 
27
27
  type Story = StoryObj<typeof Chart>
28
28
 
29
+ const multipleLinesStoryData = [
30
+ { week_end: '2024-01-06', Adults: 45.1, Children: 29.3, pathogen: 'COVID-19' },
31
+ { week_end: '2024-01-13', Adults: 46.7, Children: 30.2, pathogen: 'COVID-19' },
32
+ { week_end: '2024-01-20', Adults: 48.0, Children: 31.0, pathogen: 'COVID-19' }
33
+ ]
34
+
35
+ const multipleLinesConfig = editConfigKeys(multipleLines, [
36
+ { path: ['tooltips', 'dateDisplayFormat'], value: '%b. %d %Y' },
37
+ { path: ['formattedData'], value: multipleLinesStoryData },
38
+ { path: ['dataUrl'], value: undefined },
39
+ { path: ['runtimeDataUrl'], value: undefined }
40
+ ])
41
+
29
42
  export const line_Chart_Two_Points_Regression_Test: Story = {
30
43
  args: {
31
44
  config: lineChartTwoPointsRegressionTest,
@@ -47,7 +60,7 @@ export const line_Chart_Two_Points_New_Chart: Story = {
47
60
 
48
61
  export const multiple_lines: Story = {
49
62
  args: {
50
- config: editConfigKeys(multipleLines, [{ path: ['tooltips', 'dateDisplayFormat'], value: '%b. %d %Y' }])
63
+ config: multipleLinesConfig
51
64
  },
52
65
  play: async ({ canvasElement }) => {
53
66
  await assertVisualizationRendered(canvasElement)
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { expect } from 'storybook/test'
2
3
  import barConfig from './_mock/line_chart_two_points_new_chart.json'
3
4
  import annotationConfig from './_mock/annotation_category_mock.json'
4
5
  import areaPrefix from './_mock/annotation_category_mock.json'
@@ -7,7 +8,7 @@ import scatterPlotConfig from './_mock/scatterplot_mock.json'
7
8
 
8
9
  import Chart from '../CdcChartComponent'
9
10
  import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
10
- import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
11
+ import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
11
12
 
12
13
  const meta: Meta<typeof Chart> = {
13
14
  title: 'Components/Templates/Chart/Prefix Suffix',
@@ -16,6 +17,11 @@ const meta: Meta<typeof Chart> = {
16
17
 
17
18
  type Story = StoryObj<typeof Chart>
18
19
 
20
+ const getSvgTextValues = (root: ParentNode | null, selector: string = 'text') => {
21
+ if (!root) return []
22
+ return Array.from(root.querySelectorAll(selector)).map(node => node.textContent?.trim() || '')
23
+ }
24
+
19
25
  export const Inline_Label: Story = {
20
26
  args: {
21
27
  config: editConfigKeys(barConfig, [
@@ -198,4 +204,25 @@ export const ScatterPlot_Bottom_Commas: Story = {
198
204
  }
199
205
  }
200
206
 
207
+ export const ScatterPlot_Bottom_Commas_And_Abbreviated: Story = {
208
+ args: {
209
+ config: editConfigKeys(scatterPlotConfig, [
210
+ { path: ['dataFormat', 'bottomCommas'], value: true },
211
+ { path: ['dataFormat', 'bottomAbbreviated'], value: true },
212
+ { path: ['dataFormat', 'bottomPrefix'], value: '$' },
213
+ { path: ['dataFormat', 'commas'], value: false },
214
+ { path: ['dataFormat', 'abbreviated'], value: false }
215
+ ])
216
+ },
217
+ play: async ({ canvasElement }) => {
218
+ await assertVisualizationRendered(canvasElement)
219
+ await waitForPresence('svg g.bottom-axis', canvasElement)
220
+
221
+ const svgText = getSvgTextValues(canvasElement, 'svg g.bottom-axis text')
222
+ const hasBottomAxisCurrencyAbbreviation = svgText.some(text => /^\$\d+(\.\d+)?K$/.test(text))
223
+
224
+ expect(hasBottomAxisCurrencyAbbreviation).toBe(true)
225
+ }
226
+ }
227
+
201
228
  export default meta
@@ -21,7 +21,6 @@ import isNumber from '@cdc/core/helpers/isNumber'
21
21
  // Local context and types
22
22
  import BarChartContext, { BarChartContextValues } from './context'
23
23
  import { ChartContext } from '../../../types/ChartContext'
24
- import _ from 'lodash'
25
24
  import { getBarData } from '../helpers/getBarData'
26
25
  import { getHorizontalBarHeights } from '../helpers/getBarHeights'
27
26
  import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
@@ -76,7 +75,7 @@ const BarChartHorizontal = () => {
76
75
  <defs>
77
76
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
78
77
  const patternId = getChartPatternId(key)
79
- const size = pattern.patternSize || 8
78
+ const size = pattern.patternSize || 10
80
79
 
81
80
  switch (pattern.shape) {
82
81
  case 'circles':
@@ -59,7 +59,7 @@ const BarChartStackedHorizontal = () => {
59
59
  <defs>
60
60
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
61
61
  const patternId = getChartPatternId(key)
62
- const size = pattern.patternSize || 8
62
+ const size = pattern.patternSize || 10
63
63
 
64
64
  switch (pattern.shape) {
65
65
  case 'circles':
@@ -44,7 +44,7 @@ const BarChartStackedVertical = () => {
44
44
  <defs>
45
45
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
46
46
  const patternId = getChartPatternId(key)
47
- const size = pattern.patternSize || 8
47
+ const size = pattern.patternSize || 10
48
48
 
49
49
  switch (pattern.shape) {
50
50
  case 'circles':
@@ -19,7 +19,6 @@ import createBarElement from '@cdc/core/components/createBarElement'
19
19
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
20
20
  // Types
21
21
  import { type ChartContext } from '../../../types/ChartContext'
22
- import _ from 'lodash'
23
22
  import { getBarData } from '../helpers/getBarData'
24
23
  import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
25
24
  import { getChartPatternId } from '../../../helpers/getChartPatternId'
@@ -90,7 +89,7 @@ const BarChartVertical = () => {
90
89
  <defs>
91
90
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
92
91
  const patternId = getChartPatternId(key)
93
- const size = pattern.patternSize || 8
92
+ const size = pattern.patternSize || 10
94
93
 
95
94
  switch (pattern.shape) {
96
95
  case 'circles':
@@ -1,28 +1,28 @@
1
- import _ from 'lodash'
2
- import { TransformedData } from '../../../types/ChartContext'
3
- import { ChartConfig } from '../../../types/ChartConfig'
4
-
5
- export const getBarData = (config: ChartConfig, data: TransformedData[], hasConfidenceInterval: boolean) => {
6
- const dynamicSeries = config.series.find(s => s.dynamicCategory)
7
- if (!dynamicSeries) return data
8
- const { dynamicCategory, dataKey } = dynamicSeries
9
- const xAxisKey = config.runtime.originalXAxis.dataKey
10
- const xAxisGroupDataLookup = _.groupBy(data, xAxisKey)
11
- return Object.values(xAxisGroupDataLookup).map(group => {
12
- return group.reduce((acc, datum) => {
13
- const dataValue = datum[dataKey]
14
- const dataCategory = datum[dynamicCategory]
15
- if (hasConfidenceInterval) {
16
- const { lower, upper } = config.confidenceKeys
17
- if (!acc.CI) acc.CI = {}
18
- const lowerValue = datum[lower]
19
- const upperValue = datum[upper]
20
- acc.CI[dataCategory] = { lower: lowerValue, upper: upperValue }
21
- }
22
- acc[dataCategory] = dataValue
23
- acc[xAxisKey] = datum[xAxisKey]
24
- acc.dynamicData = true
25
- return acc
26
- }, {})
27
- })
28
- }
1
+ import groupBy from 'lodash/groupBy'
2
+ import { TransformedData } from '../../../types/ChartContext'
3
+ import { ChartConfig } from '../../../types/ChartConfig'
4
+
5
+ export const getBarData = (config: ChartConfig, data: TransformedData[], hasConfidenceInterval: boolean) => {
6
+ const dynamicSeries = config.series.find(s => s.dynamicCategory)
7
+ if (!dynamicSeries) return data
8
+ const { dynamicCategory, dataKey } = dynamicSeries
9
+ const xAxisKey = config.runtime.originalXAxis.dataKey
10
+ const xAxisGroupDataLookup = groupBy(data, xAxisKey)
11
+ return Object.values(xAxisGroupDataLookup).map(group => {
12
+ return group.reduce((acc, datum) => {
13
+ const dataValue = datum[dataKey]
14
+ const dataCategory = datum[dynamicCategory]
15
+ if (hasConfidenceInterval) {
16
+ const { lower, upper } = config.confidenceKeys
17
+ if (!acc.CI) acc.CI = {}
18
+ const lowerValue = datum[lower]
19
+ const upperValue = datum[upper]
20
+ acc.CI[dataCategory] = { lower: lowerValue, upper: upperValue }
21
+ }
22
+ acc[dataCategory] = dataValue
23
+ acc[xAxisKey] = datum[xAxisKey]
24
+ acc.dynamicData = true
25
+ return acc
26
+ }, {})
27
+ })
28
+ }
@@ -1,5 +1,5 @@
1
1
  import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
2
- import _ from 'lodash'
2
+ import round from 'lodash/round'
3
3
  import { ChartConfig } from '../../../types/ChartConfig'
4
4
 
5
5
  export const getHorizontalBarHeights = <T>(config: Partial<ChartConfig>, bars: (T & { index })[]) => {
@@ -41,7 +41,7 @@ export const getHorizontalBarHeights = <T>(config: Partial<ChartConfig>, bars: (
41
41
  // return new updated bars/groupes
42
42
  return bars.map((bar, i) => {
43
43
  // set bars Y dynamically to handle space between bars
44
- const y = bar.index === 0 ? 0 : _.round(totalBarHeight * i)
44
+ const y = bar.index === 0 ? 0 : round(totalBarHeight * i)
45
45
  return { ...bar, y, height: barHeight }
46
46
  })
47
47
  }
@@ -0,0 +1,98 @@
1
+ import { renderHook } from '@testing-library/react'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ import { createMockChartContext, createMockConfig } from '../../../LinearChart/tests/mockConfigContext'
4
+ import { useBarChart } from '../useBarChart'
5
+
6
+ const baseConfig = createMockConfig()
7
+
8
+ const makeConfigContext = (countColumnOverrides: Record<string, unknown> = {}) => {
9
+ const config = createMockConfig({
10
+ visualizationType: 'Bar',
11
+ orientation: 'horizontal',
12
+ barHeight: 25,
13
+ xAxis: {
14
+ ...baseConfig.xAxis,
15
+ type: 'categorical',
16
+ dataKey: 'Cause of death'
17
+ },
18
+ yAxis: {
19
+ ...baseConfig.yAxis,
20
+ labelPlacement: 'Below Bar'
21
+ },
22
+ series: [{ dataKey: 'Percentage', type: 'Bar', axis: 'Left', tooltip: true }] as any,
23
+ columns: {
24
+ Count: {
25
+ name: 'Count',
26
+ label: 'Count',
27
+ prefix: '',
28
+ suffix: '',
29
+ roundToPlace: 0,
30
+ commas: true,
31
+ dataTable: true,
32
+ tooltips: true,
33
+ ...countColumnOverrides
34
+ }
35
+ } as any,
36
+ dataFormat: {
37
+ abbreviated: false,
38
+ bottomAbbreviated: false,
39
+ bottomPrefix: '',
40
+ bottomRoundTo: 0,
41
+ bottomSuffix: '',
42
+ bottomCommas: false,
43
+ commas: false,
44
+ prefix: '',
45
+ preserveOriginalDecimals: false,
46
+ rightPrefix: '',
47
+ rightRoundTo: 0,
48
+ rightSuffix: '',
49
+ roundTo: 1,
50
+ suffix: '%'
51
+ } as any,
52
+ runtime: {
53
+ ...baseConfig.runtime,
54
+ xAxis: {
55
+ ...baseConfig.runtime.xAxis,
56
+ type: 'categorical',
57
+ dataKey: 'Cause of death'
58
+ },
59
+ yAxis: {
60
+ ...baseConfig.runtime.yAxis,
61
+ dataKey: 'Percentage'
62
+ },
63
+ series: [{ dataKey: 'Percentage', type: 'Bar', axis: 'Left', tooltip: true }],
64
+ seriesKeys: ['Percentage'],
65
+ seriesLabels: { Percentage: 'Percentage' },
66
+ seriesLabelsAll: ['Percentage']
67
+ } as any
68
+ })
69
+
70
+ return createMockChartContext(config, {
71
+ tableData: [
72
+ {
73
+ 'Cause of death': 'Cardiovascular conditions',
74
+ Percentage: 22,
75
+ Count: 146
76
+ }
77
+ ],
78
+ updateConfig: vi.fn()
79
+ })
80
+ }
81
+
82
+ describe('useBarChart', () => {
83
+ it('does not apply the left-axis suffix to an additional single-series tooltip column', () => {
84
+ const configContext = makeConfigContext()
85
+ const { result } = renderHook(() => useBarChart(vi.fn(), vi.fn(), configContext))
86
+
87
+ expect(result.current.getAdditionalColumn('Percentage', 'Cardiovascular conditions')).toBe('Count : 146 <br/>')
88
+ })
89
+
90
+ it('applies an additional column suffix when one is configured', () => {
91
+ const configContext = makeConfigContext({ suffix: ' deaths' })
92
+ const { result } = renderHook(() => useBarChart(vi.fn(), vi.fn(), configContext))
93
+
94
+ expect(result.current.getAdditionalColumn('Percentage', 'Cardiovascular conditions')).toBe(
95
+ 'Count : 146 deaths <br/>'
96
+ )
97
+ })
98
+ })
@@ -6,7 +6,7 @@ import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
6
6
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
7
7
  import { getVizSubType, getVizTitle } from '@cdc/core/helpers/metrics/utils'
8
8
  import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
9
- import { getSeriesOwnedColumnNames } from '../../../helpers/seriesColumnSettings'
9
+ import { getSeriesColumnFormattingParams, getSeriesOwnedColumnNames } from '../../../helpers/seriesColumnSettings'
10
10
 
11
11
  export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, configContext) => {
12
12
  const {
@@ -184,10 +184,9 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
184
184
  if (seriesOwnedColumnNames.includes(colConfig.name || colKeys)) return
185
185
  if (series && colConfig.series && colConfig.series !== series && !colConfig.tooltips) return
186
186
  const formattingParams = {
187
- addColPrefix: config.columns[colKeys].prefix,
188
- addColSuffix: config.columns[colKeys].suffix,
189
- addColRoundTo: config.columns[colKeys].roundToPlace ? config.columns[colKeys].roundToPlace : '',
190
- addColCommas: config.columns[colKeys].commas
187
+ addColPrefix: '',
188
+ addColSuffix: '',
189
+ ...getSeriesColumnFormattingParams(config.columns[colKeys])
191
190
  }
192
191
 
193
192
  const formattedValue = formatColNumber(
@@ -5,7 +5,8 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
5
5
  import ConfigContext from '../../ConfigContext'
6
6
  import { handleTooltip, createPlots } from './helpers/index'
7
7
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
8
- import _ from 'lodash'
8
+ import map from 'lodash/map'
9
+ import round from 'lodash/round'
9
10
 
10
11
  const BoxPlotHorizontal = ({ xScale, yScale, seriesScale }) => {
11
12
  const { config, transformedData: data, colorScale, seriesHighlight } = useContext(ConfigContext)
@@ -63,7 +64,7 @@ const BoxPlotHorizontal = ({ xScale, yScale, seriesScale }) => {
63
64
  maxProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
64
65
  medianProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
65
66
  outliers={
66
- config.boxplot.plotOutlierValues ? _.map(plot.columnOutliers[item.dataKey], item => item) : []
67
+ config.boxplot.plotOutlierValues ? map(plot.columnOutliers[item.dataKey], item => item) : []
67
68
  }
68
69
  outlierProps={{
69
70
  style: {
@@ -78,10 +79,10 @@ const BoxPlotHorizontal = ({ xScale, yScale, seriesScale }) => {
78
79
  config.boxplot,
79
80
  plot.columnCategory,
80
81
  item.dataKey,
81
- _.round(plot.q1[item.dataKey], config.dataFormat.roundTo),
82
- _.round(plot.q3[item.dataKey], config.dataFormat.roundTo),
83
- _.round(plot.median[item.dataKey], config.dataFormat.roundTo),
84
- _.round(plot.iqr[item.dataKey], config.dataFormat.roundTo),
82
+ round(plot.q1[item.dataKey], config.dataFormat.roundTo),
83
+ round(plot.q3[item.dataKey], config.dataFormat.roundTo),
84
+ round(plot.median[item.dataKey], config.dataFormat.roundTo),
85
+ round(plot.iqr[item.dataKey], config.dataFormat.roundTo),
85
86
  config.xAxis.label,
86
87
  defaultColor
87
88
  ),