@cdc/chart 4.26.2 → 4.26.3

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 (70) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +35674 -32430
  3. package/examples/data/data-with-metadata.json +10 -0
  4. package/examples/feature/pie/planet-pie-example-config.json +2 -1
  5. package/examples/metadata-variables.json +58 -0
  6. package/package.json +3 -3
  7. package/src/CdcChart.tsx +8 -4
  8. package/src/CdcChartComponent.tsx +321 -288
  9. package/src/_stories/Chart.CustomColors.stories.tsx +74 -0
  10. package/src/_stories/Chart.Defaults.stories.tsx +95 -0
  11. package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
  12. package/src/_stories/Chart.stories.tsx +36 -2
  13. package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
  14. package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
  15. package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
  16. package/src/_stories/_mock/paired-bar-abbr.json +421 -0
  17. package/src/_stories/_mock/pie_custom_colors.json +268 -0
  18. package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
  19. package/src/components/Annotations/components/AnnotationDraggable.styles.css +10 -10
  20. package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -1
  21. package/src/components/Annotations/components/AnnotationList.styles.css +11 -11
  22. package/src/components/Axis/BottomAxis.tsx +10 -3
  23. package/src/components/Axis/PairedBarAxis.tsx +10 -4
  24. package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
  25. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
  26. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
  27. package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
  28. package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
  29. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
  30. package/src/components/BarChart/helpers/useBarChart.ts +3 -0
  31. package/src/components/Brush/BrushSelector.tsx +2 -1
  32. package/src/components/Brush/MiniChartPreview.tsx +21 -26
  33. package/src/components/EditorPanel/EditorPanel.tsx +56 -43
  34. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +9 -9
  35. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
  36. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +39 -1
  37. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +24 -42
  38. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -2
  39. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +45 -42
  40. package/src/components/EditorPanel/editor-panel.scss +1 -1
  41. package/src/components/ForestPlot/ForestPlot.tsx +26 -22
  42. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
  43. package/src/components/Legend/helpers/createFormatLabels.tsx +3 -2
  44. package/src/components/LinearChart/tests/LinearChart.test.tsx +77 -0
  45. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -0
  46. package/src/components/LinearChart.tsx +26 -6
  47. package/src/components/PieChart/PieChart.tsx +19 -4
  48. package/src/components/RadarChart/RadarChart.tsx +1 -1
  49. package/src/components/Regions/components/Regions.tsx +6 -6
  50. package/src/components/Sankey/components/Sankey.tsx +3 -3
  51. package/src/components/Sankey/sankey.scss +1 -1
  52. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  53. package/src/components/Sparkline/index.scss +4 -2
  54. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
  55. package/src/data/initial-state.js +23 -14
  56. package/src/data/legacy-defaults.ts +18 -0
  57. package/src/helpers/abbreviateNumber.ts +24 -17
  58. package/src/helpers/getChartPatternId.ts +17 -0
  59. package/src/helpers/getMinMax.ts +16 -2
  60. package/src/helpers/seriesColumnSettings.ts +114 -0
  61. package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
  62. package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
  63. package/src/hooks/useRightAxis.ts +14 -0
  64. package/src/hooks/useScales.ts +92 -56
  65. package/src/hooks/useTooltip.tsx +20 -3
  66. package/src/scss/main.scss +152 -79
  67. package/src/test/CdcChart.test.jsx +2 -2
  68. package/src/types/ChartConfig.ts +4 -0
  69. package/tests/fixtures/chart-config-with-metadata.json +29 -0
  70. package/tests/fixtures/data-with-metadata.json +10 -0
@@ -0,0 +1,268 @@
1
+ {
2
+ "annotations": [],
3
+ "type": "chart",
4
+ "debugSvg": false,
5
+ "chartMessage": { "noData": "No Data Available" },
6
+ "title": "Pie Chart Custom Colors",
7
+ "showTitle": true,
8
+ "showDownloadMediaButton": false,
9
+ "theme": "theme-blue",
10
+ "animate": false,
11
+ "fontSize": "medium",
12
+ "lineDatapointStyle": "hover",
13
+ "lineDatapointColor": "Same as Line",
14
+ "barHasBorder": "false",
15
+ "isLollipopChart": false,
16
+ "lollipopShape": "circle",
17
+ "lollipopColorStyle": "two-tone",
18
+ "visualizationSubType": "regular",
19
+ "barStyle": "",
20
+ "roundingStyle": "standard",
21
+ "tipRounding": "top",
22
+ "isResponsiveTicks": false,
23
+ "general": {
24
+ "annotationDropdownText": "Annotations",
25
+ "showDownloadButton": false,
26
+ "showMissingDataLabel": true,
27
+ "showSuppressedSymbol": true,
28
+ "hideNullValue": true,
29
+ "palette": {
30
+ "name": "qualitative_standard",
31
+ "version": "2.0",
32
+ "isReversed": false,
33
+ "customColorsOrdered": ["#0B6E4F", "#C75000", "#7C4DFF", "#0077B6", "#D7263D"]
34
+ }
35
+ },
36
+ "padding": { "left": 5, "right": 5 },
37
+ "preliminaryData": [],
38
+ "yAxis": {
39
+ "hideAxis": false,
40
+ "displayNumbersOnBar": false,
41
+ "hideLabel": false,
42
+ "hideTicks": false,
43
+ "size": 50,
44
+ "gridLines": false,
45
+ "enablePadding": false,
46
+ "min": "",
47
+ "max": "",
48
+ "labelColor": "#333",
49
+ "tickLabelColor": "#333",
50
+ "tickColor": "#333",
51
+ "rightHideAxis": true,
52
+ "rightAxisSize": 0,
53
+ "rightLabel": "",
54
+ "rightLabelOffsetSize": 0,
55
+ "rightAxisLabelColor": "#333",
56
+ "rightAxisTickLabelColor": "#333",
57
+ "rightAxisTickColor": "#333",
58
+ "numTicks": "",
59
+ "axisPadding": 0,
60
+ "scalePadding": 10,
61
+ "tickRotation": 0,
62
+ "anchors": [],
63
+ "shoMissingDataLabel": true,
64
+ "showMissingDataLine": true,
65
+ "categories": [],
66
+ "dataKey": "Age-adjusted rate"
67
+ },
68
+ "boxplot": {
69
+ "plots": [],
70
+ "borders": "true",
71
+ "firstQuartilePercentage": 25,
72
+ "thirdQuartilePercentage": 75,
73
+ "boxWidthPercentage": 40,
74
+ "plotOutlierValues": false,
75
+ "plotNonOutlierValues": true,
76
+ "legend": { "showHowToReadText": false, "howToReadText": "" },
77
+ "labels": {
78
+ "q1": "Lower Quartile",
79
+ "q2": "q2",
80
+ "q3": "Upper Quartile",
81
+ "q4": "q4",
82
+ "minimum": "Minimum",
83
+ "maximum": "Maximum",
84
+ "mean": "Mean",
85
+ "median": "Median",
86
+ "sd": "Standard Deviation",
87
+ "iqr": "Interquartile Range",
88
+ "total": "Total",
89
+ "outliers": "Outliers",
90
+ "values": "Values",
91
+ "lowerBounds": "Lower Bounds",
92
+ "upperBounds": "Upper Bounds"
93
+ }
94
+ },
95
+ "topAxis": { "hasLine": false },
96
+ "isLegendValue": false,
97
+ "barThickness": "0.37",
98
+ "barHeight": 25,
99
+ "barSpace": 15,
100
+ "heights": { "vertical": 300, "horizontal": 750 },
101
+ "xAxis": {
102
+ "sortDates": false,
103
+ "anchors": [],
104
+ "type": "categorical",
105
+ "showTargetLabel": true,
106
+ "targetLabel": "Target",
107
+ "hideAxis": false,
108
+ "hideLabel": false,
109
+ "hideTicks": false,
110
+ "size": 75,
111
+ "tickRotation": 0,
112
+ "min": "",
113
+ "max": "",
114
+ "labelColor": "#333",
115
+ "tickLabelColor": "#333",
116
+ "tickColor": "#333",
117
+ "numTicks": "",
118
+ "labelOffset": 65,
119
+ "axisPadding": 200,
120
+ "target": 0,
121
+ "maxTickRotation": 45,
122
+ "padding": 0,
123
+ "dataKey": "Race"
124
+ },
125
+ "table": {
126
+ "label": "Data Table",
127
+ "expanded": true,
128
+ "limitHeight": false,
129
+ "height": "",
130
+ "caption": "",
131
+ "showDownloadUrl": false,
132
+ "showDataTableLink": true,
133
+ "showDownloadLinkBelow": true,
134
+ "indexLabel": "",
135
+ "download": true,
136
+ "showVertical": true,
137
+ "dateDisplayFormat": "",
138
+ "showMissingDataLabel": true,
139
+ "showSuppressedSymbol": true,
140
+ "show": true
141
+ },
142
+ "orientation": "vertical",
143
+ "color": "pinkpurple",
144
+ "columns": {},
145
+ "legend": {
146
+ "hide": false,
147
+ "behavior": "isolate",
148
+ "axisAlign": true,
149
+ "singleRow": true,
150
+ "colorCode": "",
151
+ "reverseLabelOrder": false,
152
+ "description": "Legend swatches should match pie slices.",
153
+ "dynamicLegend": false,
154
+ "dynamicLegendDefaultText": "Show All",
155
+ "dynamicLegendItemLimit": 5,
156
+ "dynamicLegendItemLimitMessage": "Dynamic Legend Item Limit Hit.",
157
+ "dynamicLegendChartMessage": "Select Options from the Legend",
158
+ "label": "Custom Segment Colors",
159
+ "lineMode": false,
160
+ "verticalSorted": false,
161
+ "highlightOnHover": false,
162
+ "hideSuppressedLabels": false,
163
+ "hideSuppressionLink": false,
164
+ "seriesHighlight": [],
165
+ "style": "circles",
166
+ "subStyle": "linear blocks",
167
+ "tickRotation": "",
168
+ "hideBorder": { "side": false, "topBottom": true },
169
+ "position": "right",
170
+ "order": "dataColumn",
171
+ "orderedValues": [],
172
+ "patterns": {},
173
+ "patternField": "",
174
+ "unified": true
175
+ },
176
+ "brush": { "height": 25, "active": false },
177
+ "exclusions": { "active": false, "keys": [] },
178
+ "palette": "qualitative-bold",
179
+ "isPaletteReversed": false,
180
+ "twoColor": { "palette": "monochrome-1", "isPaletteReversed": false },
181
+ "labels": false,
182
+ "dataFormat": {
183
+ "commas": false,
184
+ "prefix": "",
185
+ "suffix": "",
186
+ "abbreviated": false,
187
+ "bottomSuffix": "",
188
+ "bottomPrefix": "",
189
+ "bottomAbbreviated": false,
190
+ "showPiePercent": false
191
+ },
192
+ "confidenceKeys": {},
193
+ "visual": {
194
+ "border": true,
195
+ "accent": true,
196
+ "background": true,
197
+ "verticalHoverLine": false,
198
+ "horizontalHoverLine": false
199
+ },
200
+ "useLogScale": false,
201
+ "filterBehavior": "Filter Change",
202
+ "highlightedBarValues": [],
203
+ "series": [],
204
+ "tooltips": { "opacity": 90, "singleSeries": false, "dateDisplayFormat": "" },
205
+ "forestPlot": {
206
+ "startAt": 0,
207
+ "colors": { "line": "", "shape": "" },
208
+ "lineOfNoEffect": { "show": true },
209
+ "type": "",
210
+ "pooledResult": { "diamondHeight": 5, "column": "" },
211
+ "estimateField": "",
212
+ "estimateRadius": "",
213
+ "shape": "square",
214
+ "rowHeight": 20,
215
+ "description": { "show": true, "text": "description", "location": 0 },
216
+ "result": { "show": true, "text": "result", "location": 100 },
217
+ "radius": { "min": 2, "max": 10, "scalingColumn": "" },
218
+ "regression": { "lower": 0, "upper": 0, "estimateField": 0 },
219
+ "leftWidthOffset": 0,
220
+ "rightWidthOffset": 0,
221
+ "showZeroLine": false,
222
+ "leftLabel": "",
223
+ "rightLabel": ""
224
+ },
225
+ "area": { "isStacked": false },
226
+ "sankey": {
227
+ "title": { "defaultColor": "black" },
228
+ "iterations": 1,
229
+ "rxValue": 0.9,
230
+ "overallSize": { "width": 900, "height": 700 },
231
+ "margin": { "margin_y": 25, "margin_x": 0 },
232
+ "nodeSize": { "nodeWidth": 26, "nodeHeight": 40 },
233
+ "nodePadding": 55,
234
+ "nodeFontColor": "black",
235
+ "nodeColor": { "default": "#ff8500", "inactive": "#808080" },
236
+ "linkColor": { "default": "#ffc900", "inactive": "#D3D3D3" },
237
+ "opacity": {
238
+ "nodeOpacityDefault": 1,
239
+ "nodeOpacityInactive": 0.1,
240
+ "LinkOpacityDefault": 1,
241
+ "LinkOpacityInactive": 0.1
242
+ },
243
+ "storyNodeFontColor": "#006778",
244
+ "storyNodeText": [],
245
+ "nodeValueStyle": { "textBefore": "(", "textAfter": ")" },
246
+ "data": []
247
+ },
248
+ "datasets": {},
249
+ "visualizationType": "Pie",
250
+ "data": [
251
+ { "Race": "Hispanic or Latino", "Age-adjusted rate": "644.2" },
252
+ { "Race": "Non-Hispanic American Indian", "Age-adjusted rate": "636.1" },
253
+ { "Race": "Non-Hispanic Black", "Age-adjusted rate": "563.7" },
254
+ { "Race": "Non-Hispanic Asian or Pacific Islander", "Age-adjusted rate": "202.5" },
255
+ { "Race": "Non-Hispanic White", "Age-adjusted rate": "183.6" }
256
+ ],
257
+ "dataFileName": "valid-data-chart.csv",
258
+ "dataFileSourceType": "file",
259
+ "formattedData": [
260
+ { "Race": "Hispanic or Latino", "Age-adjusted rate": "644.2" },
261
+ { "Race": "Non-Hispanic American Indian", "Age-adjusted rate": "636.1" },
262
+ { "Race": "Non-Hispanic Black", "Age-adjusted rate": "563.7" },
263
+ { "Race": "Non-Hispanic Asian or Pacific Islander", "Age-adjusted rate": "202.5" },
264
+ { "Race": "Non-Hispanic White", "Age-adjusted rate": "183.6" }
265
+ ],
266
+ "dataDescription": { "horizontal": false, "series": false },
267
+ "version": "4.25.9"
268
+ }
@@ -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
  }
@@ -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
  }
@@ -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), 0)
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'
@@ -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,8 @@ 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'
27
29
 
28
30
  const BarChartHorizontal = () => {
29
31
  const { xScale, yScale, yMax, seriesScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
@@ -72,7 +74,7 @@ const BarChartHorizontal = () => {
72
74
  return (
73
75
  <defs>
74
76
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
75
- const patternId = `chart-pattern-${key}`
77
+ const patternId = getChartPatternId(key)
76
78
  const size = pattern.patternSize || 8
77
79
 
78
80
  switch (pattern.shape) {
@@ -324,33 +326,15 @@ const BarChartHorizontal = () => {
324
326
  })
325
327
 
326
328
  // 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()
329
+ const patternUrl = getPatternUrlForBar({
330
+ patterns: config.legend?.patterns,
331
+ datum,
332
+ seriesKey: bar.key,
333
+ seriesValue: bar.value,
334
+ seriesLabels: config.runtime?.seriesLabels,
335
+ seriesKeys: config.series?.map(series => series.dataKey),
336
+ allowNonSeriesFieldMatch: !config.series || config.series.length <= 1
337
+ })
354
338
  const baseBackground = getBarBackgroundColor()
355
339
 
356
340
  return (
@@ -14,6 +14,8 @@ 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'
17
19
 
18
20
  const BarChartStackedHorizontal = () => {
19
21
  const { yMax, yScale, xScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
@@ -55,7 +57,7 @@ const BarChartStackedHorizontal = () => {
55
57
  return (
56
58
  <defs>
57
59
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
58
- const patternId = `chart-pattern-${key}`
60
+ const patternId = getChartPatternId(key)
59
61
  const size = pattern.patternSize || 8
60
62
 
61
63
  switch (pattern.shape) {
@@ -166,35 +168,15 @@ const BarChartStackedHorizontal = () => {
166
168
  </li></ul>`
167
169
 
168
170
  // 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()
171
+ const patternUrl = getPatternUrlForBar({
172
+ patterns: config.legend?.patterns,
173
+ datum: data[bar.index],
174
+ seriesKey: bar.key,
175
+ seriesValue: data[bar.index][bar.key],
176
+ seriesLabels: config.runtime?.seriesLabels,
177
+ seriesKeys: config.series?.map(series => series.dataKey),
178
+ allowNonSeriesFieldMatch: true
179
+ })
198
180
 
199
181
  return (
200
182
  <React.Fragment key={`stack-${stackIndex}-bar-${index}-${barStack.index}`}>