@cdc/chart 4.26.2 → 4.26.4

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 (165) hide show
  1. package/CONFIG.md +169 -0
  2. package/README.md +42 -17
  3. package/dist/cdcchart.js +39419 -35180
  4. package/examples/__data__/data-with-metadata.json +10 -0
  5. package/examples/feature/bar/additional-column-tooltip.json +3 -3
  6. package/examples/feature/deviation/planet-deviation-config.json +2 -2
  7. package/examples/feature/paired-bar/paired-bar-example.json +2 -2
  8. package/examples/feature/pie/planet-pie-example-config.json +2 -1
  9. package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
  10. package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
  11. package/examples/feature/tests-covid/covid-example-config.json +2 -2
  12. package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
  13. package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
  14. package/examples/metadata-variables.json +58 -0
  15. package/examples/minimal-example.json +24 -0
  16. package/examples/private/test2.json +35012 -0
  17. package/package.json +4 -4
  18. package/src/CdcChart.tsx +8 -4
  19. package/src/CdcChartComponent.tsx +326 -290
  20. package/src/_stories/Chart.CustomColors.smoke.stories.tsx +97 -0
  21. package/src/_stories/Chart.Defaults.smoke.stories.tsx +95 -0
  22. package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
  23. package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
  24. package/src/_stories/{Chart.SmallMultiples.stories.tsx → Chart.SmallMultiples.smoke.stories.tsx} +17 -1
  25. package/src/_stories/Chart.SmallestLeftAxisMax.smoke.stories.tsx +64 -0
  26. package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +48 -2
  27. package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
  28. package/src/_stories/ChartBar.Editor.stories.tsx +177 -115
  29. package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
  30. package/src/_stories/ChartEditor.Editor.stories.tsx +24 -1
  31. package/src/_stories/{Chart.CustomColors.stories.tsx → ChartLine.DashedDates.smoke.stories.tsx} +4 -4
  32. package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
  33. package/src/_stories/_mock/paired-bar-abbr.json +421 -0
  34. package/src/_stories/_mock/paired-bar.json +1 -1
  35. package/src/_stories/_mock/pie_custom_colors.json +268 -0
  36. package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
  37. package/src/components/Annotations/components/AnnotationDraggable.styles.css +10 -10
  38. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -1
  39. package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -1
  40. package/src/components/Annotations/components/AnnotationList.styles.css +11 -11
  41. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
  42. package/src/components/Axis/BottomAxis.tsx +10 -3
  43. package/src/components/Axis/LeftAxis.tsx +2 -4
  44. package/src/components/Axis/LeftAxisGridlines.tsx +1 -3
  45. package/src/components/Axis/PairedBarAxis.tsx +10 -4
  46. package/src/components/BarChart/components/BarChart.Horizontal.tsx +21 -33
  47. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +21 -35
  48. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +21 -36
  49. package/src/components/BarChart/components/BarChart.Vertical.tsx +21 -34
  50. package/src/components/BarChart/components/BarChart.tsx +2 -6
  51. package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
  52. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
  53. package/src/components/BarChart/helpers/useBarChart.ts +4 -1
  54. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
  55. package/src/components/Brush/BrushSelector.tsx +2 -1
  56. package/src/components/Brush/MiniChartPreview.tsx +21 -26
  57. package/src/components/EditorPanel/EditorPanel.tsx +692 -954
  58. package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
  59. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +218 -167
  60. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
  61. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +39 -1
  62. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +169 -135
  63. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +154 -109
  64. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  65. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +95 -28
  66. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +45 -42
  67. package/src/components/EditorPanel/editor-panel.scss +1 -1
  68. package/src/components/EditorPanel/useEditorPermissions.ts +1 -10
  69. package/src/components/Forecasting/Forecasting.tsx +2 -2
  70. package/src/components/ForestPlot/ForestPlot.tsx +26 -22
  71. package/src/components/HorizonChart/HorizonChart.tsx +10 -2
  72. package/src/components/HoverLine/HoverLine.tsx +6 -8
  73. package/src/components/Legend/Legend.Component.tsx +4 -4
  74. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +9 -4
  75. package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
  76. package/src/components/Legend/helpers/createFormatLabels.tsx +3 -2
  77. package/src/components/Legend/helpers/index.ts +0 -2
  78. package/src/components/LineChart/LineChartProps.ts +1 -0
  79. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +3 -2
  80. package/src/components/LineChart/index.tsx +11 -4
  81. package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
  82. package/src/components/LinearChart/tests/LinearChart.test.tsx +224 -0
  83. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -0
  84. package/src/components/LinearChart.tsx +124 -47
  85. package/src/components/PairedBarChart.jsx +2 -2
  86. package/src/components/PieChart/PieChart.tsx +30 -13
  87. package/src/components/RadarChart/RadarChart.tsx +1 -1
  88. package/src/components/Regions/components/Regions.tsx +36 -26
  89. package/src/components/Sankey/components/Sankey.tsx +3 -3
  90. package/src/components/Sankey/sankey.scss +1 -1
  91. package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
  92. package/src/components/SmallMultiples/SmallMultipleTile.tsx +1 -0
  93. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  94. package/src/components/Sparkline/components/SparkLine.tsx +1 -1
  95. package/src/components/Sparkline/index.scss +4 -2
  96. package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
  97. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
  98. package/src/data/initial-state.js +24 -14
  99. package/src/data/legacy-defaults.ts +18 -0
  100. package/src/helpers/abbreviateNumber.ts +24 -17
  101. package/src/helpers/calculateLeftYAxisWidth.ts +193 -0
  102. package/src/helpers/getChartPatternId.ts +17 -0
  103. package/src/helpers/getMinMax.ts +16 -2
  104. package/src/helpers/seriesColumnSettings.ts +114 -0
  105. package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
  106. package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
  107. package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
  108. package/src/helpers/tooltipHelpers.ts +176 -0
  109. package/src/hooks/useHighlightedBars.ts +9 -16
  110. package/src/hooks/useProgrammaticTooltip.ts +16 -2
  111. package/src/hooks/useRightAxis.ts +14 -0
  112. package/src/hooks/useScales.ts +92 -56
  113. package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
  114. package/src/hooks/useTooltip.tsx +186 -40
  115. package/src/scss/main.scss +152 -79
  116. package/src/test/CdcChart.test.jsx +26 -2
  117. package/src/types/ChartConfig.ts +5 -0
  118. package/tests/fixtures/chart-config-with-metadata.json +29 -0
  119. package/tests/fixtures/data-with-metadata.json +10 -0
  120. package/examples/chart-regression-2.json +0 -2360
  121. package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
  122. package/examples/feature/__data__/city-temperature.json +0 -2198
  123. package/examples/feature/bar/Barchart_with_negative.json +0 -34
  124. package/examples/feature/boxplot/box-plot-data.json +0 -71
  125. package/examples/feature/boxplot/boxplot-data.json +0 -156
  126. package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
  127. package/examples/feature/forest-plot/log.json +0 -26
  128. package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
  129. package/examples/region-issue.json +0 -2065
  130. package/examples/xaxis.json +0 -493
  131. /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
  132. /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
  133. /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
  134. /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
  135. /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
  136. /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
  137. /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
  138. /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
  139. /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
  140. /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
  141. /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
  142. /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
  143. /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
  144. /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
  145. /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
  146. /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
  147. /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
  148. /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
  149. /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
  150. /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
  151. /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
  152. /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
  153. /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
  154. /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
  155. /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
  156. /package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +0 -0
  157. /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
  158. /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
  159. /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
  160. /package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +0 -0
  161. /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
  162. /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
  163. /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
  164. /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
  165. /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
@@ -0,0 +1,104 @@
1
+ {
2
+ "type": "chart",
3
+ "version": "4.26.3",
4
+ "visualizationType": "Line",
5
+ "visualizationSubType": "regular",
6
+ "title": "Smallest Left Axis Maximum Demo",
7
+ "titleStyle": "small",
8
+ "showTitle": true,
9
+ "theme": "theme-blue",
10
+ "animate": false,
11
+ "fontSize": "medium",
12
+ "lineDatapointStyle": "hover",
13
+ "lineDatapointColor": "Same as Line",
14
+ "isResponsiveTicks": false,
15
+ "general": {
16
+ "showDownloadButton": false,
17
+ "showMissingDataLabel": true,
18
+ "hideNullValue": true
19
+ },
20
+ "padding": { "left": 5, "right": 5 },
21
+ "heights": { "vertical": "300", "horizontal": 750 },
22
+ "barThickness": 0.35,
23
+ "orientation": "vertical",
24
+ "yAxis": {
25
+ "hideAxis": true,
26
+ "displayNumbersOnBar": false,
27
+ "hideLabel": false,
28
+ "hideTicks": true,
29
+ "size": "25",
30
+ "gridLines": true,
31
+ "enablePadding": false,
32
+ "min": "",
33
+ "max": "",
34
+ "numTicks": "5",
35
+ "scalePadding": "0",
36
+ "tickRotation": "",
37
+ "anchors": [],
38
+ "categories": [],
39
+ "label": "",
40
+ "inlineLabel": " cases",
41
+ "type": "linear",
42
+ "smallestLeftAxisMax": 5
43
+ },
44
+ "xAxis": {
45
+ "sortDates": false,
46
+ "anchors": [],
47
+ "type": "categorical",
48
+ "hideAxis": false,
49
+ "hideLabel": false,
50
+ "hideTicks": false,
51
+ "size": "0",
52
+ "tickRotation": "0",
53
+ "numTicks": "",
54
+ "labelOffset": 0,
55
+ "padding": "4",
56
+ "dataKey": "Month",
57
+ "label": "Month",
58
+ "tickWidthMax": 100
59
+ },
60
+ "table": {
61
+ "label": "Data Table",
62
+ "expanded": false,
63
+ "limitHeight": false,
64
+ "show": true,
65
+ "showDownloadUrl": false,
66
+ "showVertical": true
67
+ },
68
+ "series": [{ "dataKey": "Cases", "type": "Line", "axis": "Left", "tooltip": true }],
69
+ "tooltips": { "opacity": 90, "singleSeries": false },
70
+ "legend": {
71
+ "hide": true
72
+ },
73
+ "filters": [
74
+ {
75
+ "values": ["Region A", "Region B", "Region C"],
76
+ "active": "Region B",
77
+ "order": "asc",
78
+ "columnName": "Region",
79
+ "filterStyle": "dropdown"
80
+ }
81
+ ],
82
+ "data": [
83
+ { "Month": "Jan", "Cases": "12", "Region": "Region A" },
84
+ { "Month": "Feb", "Cases": "18", "Region": "Region A" },
85
+ { "Month": "Mar", "Cases": "15", "Region": "Region A" },
86
+ { "Month": "Apr", "Cases": "22", "Region": "Region A" },
87
+ { "Month": "May", "Cases": "28", "Region": "Region A" },
88
+ { "Month": "Jun", "Cases": "20", "Region": "Region A" },
89
+
90
+ { "Month": "Jan", "Cases": "1", "Region": "Region B" },
91
+ { "Month": "Feb", "Cases": "0", "Region": "Region B" },
92
+ { "Month": "Mar", "Cases": "1", "Region": "Region B" },
93
+ { "Month": "Apr", "Cases": "0", "Region": "Region B" },
94
+ { "Month": "May", "Cases": "1", "Region": "Region B" },
95
+ { "Month": "Jun", "Cases": "0", "Region": "Region B" },
96
+
97
+ { "Month": "Jan", "Cases": "45", "Region": "Region C" },
98
+ { "Month": "Feb", "Cases": "52", "Region": "Region C" },
99
+ { "Month": "Mar", "Cases": "48", "Region": "Region C" },
100
+ { "Month": "Apr", "Cases": "60", "Region": "Region C" },
101
+ { "Month": "May", "Cases": "55", "Region": "Region C" },
102
+ { "Month": "Jun", "Cases": "50", "Region": "Region C" }
103
+ ]
104
+ }
@@ -1,25 +1,25 @@
1
- .cdc-open-viz-module.type-chart .annotation__desktop-label {
2
- line-height: 1.1rem;
1
+ .cove-visualization.type-chart .annotation__desktop-label {
3
2
  font-size: 16px;
3
+ line-height: 1.1rem;
4
4
  }
5
5
 
6
- .cdc-open-viz-module.type-chart {
6
+ .cove-visualization.type-chart {
7
7
  .annotation__mobile-label-circle {
8
8
  stroke-width: 1;
9
9
  }
10
10
 
11
11
  .annotation__has-dropdown-number {
12
- display: flex;
13
- justify-content: center;
14
12
  align-items: center;
13
+ border: 1px solid #666;
15
14
  border-radius: 50%;
16
- width: 24px;
15
+ box-sizing: border-box;
16
+ display: flex;
17
+ font-size: 14px;
17
18
  height: 24px;
18
- min-width: 24px;
19
+ justify-content: center;
19
20
  min-height: 24px;
20
- border: 1px solid #666;
21
+ min-width: 24px;
21
22
  text-align: center;
22
- font-size: 14px;
23
- box-sizing: border-box;
23
+ width: 24px;
24
24
  }
25
25
  }
@@ -218,7 +218,7 @@ const Annotations = ({
218
218
  return (
219
219
  <HtmlLabel
220
220
  className='annotation__desktop-label'
221
- containerStyle={{ width: `${labelWidth}px` }}
221
+ containerStyle={{ width: 'fit-content', maxWidth: `${labelWidth}px` }}
222
222
  horizontalAnchor={horizontalAnchor}
223
223
  verticalAnchor={verticalAnchor}
224
224
  showAnchorLine={false}
@@ -1,4 +1,4 @@
1
- .cdc-open-viz-module.type-chart {
1
+ .cove-visualization.type-chart {
2
2
  .annotation__dropdown-list {
3
3
  list-style: none;
4
4
  }
@@ -1,23 +1,23 @@
1
- .cdc-open-viz-module.type-chart {
1
+ .cove-visualization.type-chart {
2
2
  .annotation__title-circle {
3
- display: flex;
4
- justify-content: center;
5
3
  align-items: center;
4
+ border: 1px solid #666;
6
5
  border-radius: 50%;
7
- padding: 8px;
8
- width: 24px;
6
+ display: flex;
7
+ font-size: 14px;
9
8
  height: 24px;
10
- margin-right: 5px;
9
+ justify-content: center;
11
10
  line-height: 1.5rem;
12
- border: 1px solid #666;
11
+ margin-right: 5px;
12
+ padding: 8px;
13
13
  text-align: center;
14
- font-size: 14px;
14
+ width: 24px;
15
15
  }
16
16
 
17
17
  .annotation__title-wrapper {
18
+ align-items: center;
18
19
  display: flex;
19
20
  flex-wrap: nowrap;
20
- align-items: center;
21
21
  }
22
22
 
23
23
  .annotation__title-wrapper .annotation__title-text {
@@ -32,8 +32,8 @@
32
32
  margin-top: 5px;
33
33
  }
34
34
 
35
- .cove-component__content {
36
- container-type: inline-size;
35
+ .cove-visualization__body {
37
36
  container-name: content;
37
+ container-type: inline-size;
38
38
  }
39
39
  }
@@ -11,7 +11,15 @@ import { Bar, AreaStack } from '@visx/shape'
11
11
  import { Group } from '@visx/group'
12
12
  import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
13
13
 
14
- const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff }) => {
14
+ const AreaChartStacked = ({
15
+ xScale,
16
+ yScale,
17
+ yMax,
18
+ xMax,
19
+ yAxisWidth,
20
+ handleTooltipMouseOver,
21
+ handleTooltipMouseOff
22
+ }) => {
15
23
  // import data from context
16
24
  let { transformedData: data, config, seriesHighlight, colorScale, rawData, parseDate } = useContext(ConfigContext)
17
25
  // Draw transparent bars over the chart to get tooltip data
@@ -39,7 +47,7 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
39
47
  <Group
40
48
  className='area-chart'
41
49
  key='area-wrapper'
42
- left={Number(config.yAxis.size) + strokeWidth / 2}
50
+ left={yAxisWidth + strokeWidth / 2}
43
51
  height={Number(yMax)}
44
52
  style={{ overflow: 'hidden' }}
45
53
  >
@@ -199,12 +199,13 @@ const BottomAxis: React.FC<BottomAxisProps> = ({
199
199
 
200
200
  return (
201
201
  <Group className='bottom-axis' width={parentWidth}>
202
- {filteredTicks.map((tick, i, propsTicks) => {
202
+ {filteredTicks.map((tick, i) => {
203
203
  // when using LogScale show major ticks values only
204
204
  const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
205
205
  const tickLength = showTick === 'block' ? MAJOR_TICK_LENGTH : DEFAULT_TICK_LENGTH
206
206
  const to = { x: tick.to.x, y: tickLength }
207
- const limitedWidth = 100 / propsTicks.length
207
+ const tickSlotWidth = filteredTicks.length > 0 ? xMax / filteredTicks.length : xMax
208
+ const limitedWidth = Math.max(Math.min(maxLengthOfTick, tickSlotWidth), 1)
208
209
 
209
210
  // Configure rotation using effective values (computed above without mutations)
210
211
  const tickRotation =
@@ -246,7 +247,13 @@ const BottomAxis: React.FC<BottomAxisProps> = ({
246
247
  </Group>
247
248
  )
248
249
  })}
249
- {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
250
+ {!config.xAxis.hideAxis && (
251
+ <Line
252
+ from={isForestPlot ? { ...props.axisFromPoint, x: 0 } : props.axisFromPoint}
253
+ to={props.axisToPoint}
254
+ stroke='#333'
255
+ />
256
+ )}
250
257
  <Text
251
258
  innerRef={xAxisTitleRef}
252
259
  className='x-axis-title-label'
@@ -36,7 +36,6 @@ interface LeftAxisProps {
36
36
  suffixWidth?: number
37
37
  horizontalYAxisLabelSpace?: number
38
38
  categoryLabelSpace?: number
39
- yLabelOffset?: number
40
39
  }
41
40
 
42
41
  const LeftAxis: React.FC<LeftAxisProps> = ({
@@ -53,8 +52,7 @@ const LeftAxis: React.FC<LeftAxisProps> = ({
53
52
  suffixRef,
54
53
  suffixWidth = 0,
55
54
  horizontalYAxisLabelSpace = 0,
56
- categoryLabelSpace = 0,
57
- yLabelOffset = 0
55
+ categoryLabelSpace = 0
58
56
  }) => {
59
57
  const { config, colorScale, seriesHighlight } = useContext(ConfigContext)
60
58
  const { runtime, orientation, visualizationType, visualizationSubType, heights } = config
@@ -387,7 +385,7 @@ const LeftAxis: React.FC<LeftAxisProps> = ({
387
385
  className='y-label'
388
386
  textAnchor='middle'
389
387
  verticalAnchor='start'
390
- transform={`translate(${-1 * yAxisWidth + yLabelOffset}, ${axisCenter}) rotate(-90)`}
388
+ transform={`translate(${-1 * yAxisWidth}, ${axisCenter}) rotate(-90)`}
391
389
  fontWeight='bold'
392
390
  fill={config.yAxis.labelColor}
393
391
  fontSize={axisLabelFontSize}
@@ -10,7 +10,6 @@ interface LeftAxisGridlinesProps {
10
10
  xMax: number
11
11
  yAxisWidth: number
12
12
  numTicks: number
13
- yLabelOffset: number
14
13
  axisLabelFontSize: number
15
14
  }
16
15
 
@@ -19,7 +18,6 @@ const LeftAxisGridlines: React.FC<LeftAxisGridlinesProps> = ({
19
18
  xMax,
20
19
  yAxisWidth,
21
20
  numTicks,
22
- yLabelOffset,
23
21
  axisLabelFontSize
24
22
  }) => {
25
23
  const { config } = useContext(ConfigContext)
@@ -60,7 +58,7 @@ const LeftAxisGridlines: React.FC<LeftAxisGridlinesProps> = ({
60
58
  className='y-label'
61
59
  textAnchor='middle'
62
60
  verticalAnchor='start'
63
- transform={`translate(${-1 * yAxisWidth + yLabelOffset}, ${axisCenter}) rotate(-90)`}
61
+ transform={`translate(${-1 * yAxisWidth}, ${axisCenter}) rotate(-90)`}
64
62
  fontWeight='bold'
65
63
  fill={config.yAxis.labelColor}
66
64
  fontSize={axisLabelFontSize}
@@ -63,7 +63,9 @@ export const PairedBarAxis: React.FC<PairedBarAxisProps> = ({
63
63
  const numberOfTicks = filteredTicks?.length
64
64
  const xMaxHalf = xScale.range()[0] || xMax / 2
65
65
 
66
- const tickWidthAll = filteredTicks.map(tick => getTextWidth(formatNumber(tick.value, 'left'), tickLabelFont))
66
+ const tickWidthAll = filteredTicks.map(tick =>
67
+ getTextWidth(tick.formattedValue ?? formatNumber(tick.value, 'left', true), tickLabelFont)
68
+ )
67
69
  const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, BASE_TICK_WIDTH_ACCUMULATOR)
68
70
  const spaceBetweenEachTick = (xMaxHalf - sumOfTickWidth) / numberOfTicks
69
71
 
@@ -115,7 +117,7 @@ export const PairedBarAxis: React.FC<PairedBarAxisProps> = ({
115
117
  textAnchor={textAnchor}
116
118
  fontSize={tickLabelFontSize}
117
119
  >
118
- {formatNumber(tick.value, 'left')}
120
+ {tick.formattedValue ?? formatNumber(tick.value, 'left', true)}
119
121
  </Text>
120
122
  )}
121
123
  </Group>
@@ -129,7 +131,7 @@ export const PairedBarAxis: React.FC<PairedBarAxisProps> = ({
129
131
  top={yMax}
130
132
  left={yAxisWidth}
131
133
  label={runtime.xAxis.label}
132
- tickFormat={isDateScale(runtime.xAxis) ? formatDate : formatNumber}
134
+ tickFormat={isDateScale(runtime.xAxis) ? formatDate : tick => formatNumber(tick, 'left', true)}
133
135
  scale={g1xScale}
134
136
  stroke='#333'
135
137
  tickStroke='#333'
@@ -150,7 +152,11 @@ export const PairedBarAxis: React.FC<PairedBarAxisProps> = ({
150
152
  left={yAxisWidth}
151
153
  label={runtime.xAxis.label}
152
154
  tickFormat={
153
- isDateScale(runtime.xAxis) ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick
155
+ isDateScale(runtime.xAxis)
156
+ ? formatDate
157
+ : runtime.xAxis.dataKey !== 'Year'
158
+ ? tick => formatNumber(tick, 'left', true)
159
+ : tick => tick
154
160
  }
155
161
  scale={g2xScale}
156
162
  stroke='#333'
@@ -24,6 +24,9 @@ import { ChartContext } from '../../../types/ChartContext'
24
24
  import _ from 'lodash'
25
25
  import { getBarData } from '../helpers/getBarData'
26
26
  import { getHorizontalBarHeights } from '../helpers/getBarHeights'
27
+ import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
28
+ import { getChartPatternId } from '../../../helpers/getChartPatternId'
29
+ import { buildSeriesTooltipListHtml } from '../../../helpers/tooltipHelpers'
27
30
 
28
31
  const BarChartHorizontal = () => {
29
32
  const { xScale, yScale, yMax, seriesScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
@@ -72,7 +75,7 @@ const BarChartHorizontal = () => {
72
75
  return (
73
76
  <defs>
74
77
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
75
- const patternId = `chart-pattern-${key}`
78
+ const patternId = getChartPatternId(key)
76
79
  const size = pattern.patternSize || 8
77
80
 
78
81
  switch (pattern.shape) {
@@ -241,11 +244,14 @@ const BarChartHorizontal = () => {
241
244
  : xAxisValue
242
245
  const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
243
246
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
244
- const tooltip = `<ul>
245
- <li class="tooltip-heading"">${yAxisTooltip}</li>
246
- <li class="tooltip-body ">${tooltipBody}</li>
247
- <li class="tooltip-body ">${additionalColTooltip}</li>
248
- </li></ul>`
247
+ const tooltip = buildSeriesTooltipListHtml({
248
+ config,
249
+ colorScale,
250
+ heading: yAxisTooltip,
251
+ seriesKey: bar.key,
252
+ seriesText: tooltipBody,
253
+ extraRows: additionalColTooltip ? [additionalColTooltip] : []
254
+ })
249
255
 
250
256
  // configure colors
251
257
  let labelColor = APP_FONT_COLOR
@@ -324,33 +330,15 @@ const BarChartHorizontal = () => {
324
330
  })
325
331
 
326
332
  // Check if this bar should use a pattern
327
- const getPatternUrl = (): string | null => {
328
- if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
329
- return null
330
- }
331
-
332
- // Find a pattern that matches this specific bar
333
- for (const [patternKey, pattern] of Object.entries(config.legend.patterns)) {
334
- if (pattern.dataKey && pattern.dataValue) {
335
- // For grouped bar charts, check if the pattern's dataKey matches the current bar's series key
336
- // and if the pattern's dataValue matches the current bar's value
337
- if (pattern.dataKey === bar.key && String(bar.value) === String(pattern.dataValue)) {
338
- return `url(#chart-pattern-${patternKey})`
339
- }
340
- // Fallback for non-grouped charts: check datum field value
341
- else if (!config.series || config.series.length <= 1) {
342
- const dataFieldValue = datum[pattern.dataKey]
343
- if (String(dataFieldValue) === String(pattern.dataValue)) {
344
- return `url(#chart-pattern-${patternKey})`
345
- }
346
- }
347
- }
348
- }
349
-
350
- return null
351
- }
352
-
353
- const patternUrl = getPatternUrl()
333
+ const patternUrl = getPatternUrlForBar({
334
+ patterns: config.legend?.patterns,
335
+ datum,
336
+ seriesKey: bar.key,
337
+ seriesValue: bar.value,
338
+ seriesLabels: config.runtime?.seriesLabels,
339
+ seriesKeys: config.series?.map(series => series.dataKey),
340
+ allowNonSeriesFieldMatch: !config.series || config.series.length <= 1
341
+ })
354
342
  const baseBackground = getBarBackgroundColor()
355
343
 
356
344
  return (
@@ -14,6 +14,9 @@ import { type ChartContext } from '../../../types/ChartContext'
14
14
 
15
15
  import createBarElement from '@cdc/core/components/createBarElement'
16
16
  import { getHorizontalBarHeights } from '../helpers/getBarHeights'
17
+ import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
18
+ import { getChartPatternId } from '../../../helpers/getChartPatternId'
19
+ import { buildSeriesTooltipListHtml } from '../../../helpers/tooltipHelpers'
17
20
 
18
21
  const BarChartStackedHorizontal = () => {
19
22
  const { yMax, yScale, xScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
@@ -55,7 +58,7 @@ const BarChartStackedHorizontal = () => {
55
58
  return (
56
59
  <defs>
57
60
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
58
- const patternId = `chart-pattern-${key}`
61
+ const patternId = getChartPatternId(key)
59
62
  const size = pattern.patternSize || 8
60
63
 
61
64
  switch (pattern.shape) {
@@ -159,42 +162,25 @@ const BarChartStackedHorizontal = () => {
159
162
  const textWidth = getTextWidth(xAxisValue, `normal ${labelFontSize}px sans-serif`)
160
163
  const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
161
164
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${xAxisValue}`
162
- const tooltip = `<ul>
163
- <li class="tooltip-heading"">${yAxisTooltip}</li>
164
- <li class="tooltip-body ">${tooltipBody}</li>
165
- <li class="tooltip-body ">${additionalColTooltip}</li>
166
- </li></ul>`
165
+ const tooltip = buildSeriesTooltipListHtml({
166
+ config,
167
+ colorScale,
168
+ heading: yAxisTooltip,
169
+ seriesKey: bar.key,
170
+ seriesText: tooltipBody,
171
+ extraRows: additionalColTooltip ? [additionalColTooltip] : []
172
+ })
167
173
 
168
174
  // Check if this bar should use a pattern
169
- const getPatternUrl = (): string | null => {
170
- if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
171
- return null
172
- }
173
-
174
- // Find a pattern that matches this specific bar
175
- for (const [patternKey, pattern] of Object.entries(config.legend.patterns)) {
176
- if (pattern.dataKey && pattern.dataValue) {
177
- // For stacked bar charts, check if the pattern's dataKey matches the current bar's series key
178
- // and if the pattern's dataValue matches the current bar's value
179
- const barValue = data[bar.index][bar.key]
180
- if (pattern.dataKey === bar.key && String(barValue) === String(pattern.dataValue)) {
181
- return `url(#chart-pattern-${patternKey})`
182
- }
183
- // Fallback for non-series pattern matching (like the original stacked pattern test)
184
- // Only check this if the pattern dataKey is NOT a series key
185
- else if (!config.runtime.seriesLabels || !config.runtime.seriesLabels[pattern.dataKey]) {
186
- const dataFieldValue = data[bar.index][pattern.dataKey]
187
- if (String(dataFieldValue) === String(pattern.dataValue)) {
188
- return `url(#chart-pattern-${patternKey})`
189
- }
190
- }
191
- }
192
- }
193
-
194
- return null
195
- }
196
-
197
- const patternUrl = getPatternUrl()
175
+ const patternUrl = getPatternUrlForBar({
176
+ patterns: config.legend?.patterns,
177
+ datum: data[bar.index],
178
+ seriesKey: bar.key,
179
+ seriesValue: data[bar.index][bar.key],
180
+ seriesLabels: config.runtime?.seriesLabels,
181
+ seriesKeys: config.series?.map(series => series.dataKey),
182
+ allowNonSeriesFieldMatch: true
183
+ })
198
184
 
199
185
  return (
200
186
  <React.Fragment key={`stack-${stackIndex}-bar-${index}-${barStack.index}`}>
@@ -8,6 +8,9 @@ import Regions from '../../Regions'
8
8
  import { addMinimumBarHeights } from '../helpers'
9
9
 
10
10
  import createBarElement from '@cdc/core/components/createBarElement'
11
+ import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
12
+ import { getChartPatternId } from '../../../helpers/getChartPatternId'
13
+ import { buildSeriesTooltipListHtml } from '../../../helpers/tooltipHelpers'
11
14
 
12
15
  const BarChartStackedVertical = () => {
13
16
  const [barWidth, setBarWidth] = useState(0)
@@ -40,7 +43,7 @@ const BarChartStackedVertical = () => {
40
43
  return (
41
44
  <defs>
42
45
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
43
- const patternId = `chart-pattern-${key}`
46
+ const patternId = getChartPatternId(key)
44
47
  const size = pattern.patternSize || 8
45
48
 
46
49
  switch (pattern.shape) {
@@ -141,45 +144,27 @@ const BarChartStackedVertical = () => {
141
144
  : xAxisValue
142
145
  const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
143
146
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
144
- const tooltip = `<ul>
145
- <li class="tooltip-heading"">${xAxisTooltip}</li>
146
- <li class="tooltip-body ">${tooltipBody}</li>
147
- <li class="tooltip-body ">${additionalColTooltip}</li>
148
- </li></ul>`
147
+ const tooltip = buildSeriesTooltipListHtml({
148
+ config,
149
+ colorScale,
150
+ heading: xAxisTooltip,
151
+ seriesKey: bar.key,
152
+ seriesText: tooltipBody,
153
+ extraRows: additionalColTooltip ? [additionalColTooltip] : []
154
+ })
149
155
 
150
156
  setBarWidth(barThickness)
151
157
 
152
158
  // Check if this bar should use a pattern
153
- const getPatternUrl = (): string | null => {
154
- if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
155
- return null
156
- }
157
-
158
- // Find a pattern that matches this specific bar
159
- for (const [patternKey, patternObj] of Object.entries(config.legend.patterns)) {
160
- const pattern = patternObj as any
161
- if (pattern?.dataKey && pattern?.dataValue) {
162
- // For stacked bar charts, check if the pattern's dataKey matches the current bar's series key
163
- // and if the pattern's dataValue matches the current bar's value
164
- const barValue = bar.bar.data[bar.key]
165
- if (pattern.dataKey === bar.key && String(barValue) === String(pattern.dataValue)) {
166
- return `url(#chart-pattern-${patternKey})`
167
- }
168
- // Fallback for non-series pattern matching (like the original stacked pattern test)
169
- // Only check this if the pattern dataKey is NOT a series key
170
- else if (!config.runtime.seriesLabels || !config.runtime.seriesLabels[pattern.dataKey]) {
171
- const dataFieldValue = bar.bar.data[pattern.dataKey]
172
- if (String(dataFieldValue) === String(pattern.dataValue)) {
173
- return `url(#chart-pattern-${patternKey})`
174
- }
175
- }
176
- }
177
- }
178
-
179
- return null
180
- }
181
-
182
- const patternUrl = getPatternUrl()
159
+ const patternUrl = getPatternUrlForBar({
160
+ patterns: config.legend?.patterns,
161
+ datum: bar.bar.data,
162
+ seriesKey: bar.key,
163
+ seriesValue: bar.bar.data[bar.key],
164
+ seriesLabels: config.runtime?.seriesLabels,
165
+ seriesKeys: config.series?.map(series => series.dataKey),
166
+ allowNonSeriesFieldMatch: true
167
+ })
183
168
 
184
169
  return (
185
170
  <Group key={`${barStack.index}--${bar.index}--${orientation}`}>