@cdc/core 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 (186) hide show
  1. package/LICENSE +201 -0
  2. package/_stories/Gallery.Charts.stories.tsx +1 -1
  3. package/_stories/Gallery.DataBite.stories.tsx +1 -1
  4. package/_stories/Gallery.Maps.stories.tsx +1 -1
  5. package/_stories/PageART.stories.tsx +1 -1
  6. package/_stories/PageBRFSS.stories.tsx +1 -1
  7. package/_stories/PageCancerRegistries.stories.tsx +1 -1
  8. package/_stories/PageEasternEquineEncephalitis.stories.tsx +3 -3
  9. package/_stories/PageExcessiveAlcoholUse.stories.tsx +1 -1
  10. package/_stories/PageMaternalMortality.stories.tsx +1 -1
  11. package/_stories/PageOralHealth.stories.tsx +1 -1
  12. package/_stories/PageRespiratory.stories.tsx +4 -4
  13. package/_stories/PageSmokingTobacco.stories.tsx +1 -1
  14. package/_stories/PageStateDiabetesProfiles.stories.tsx +1 -1
  15. package/_stories/PageWastewater.stories.tsx +4 -4
  16. package/_stories/VegaImport.stories.tsx +3 -3
  17. package/assets/callout-flag.svg +7 -0
  18. package/components/AdvancedEditor/EmbedEditor.tsx +1 -1
  19. package/components/Alert/components/Alert.styles.css +2 -2
  20. package/components/ComboBox/combobox.styles.css +48 -48
  21. package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
  22. package/components/DataTable/DataTable.tsx +46 -18
  23. package/components/DataTable/DataTableStandAlone.tsx +1 -0
  24. package/components/DataTable/components/ChartHeader.tsx +21 -12
  25. package/components/DataTable/components/MapHeader.tsx +34 -28
  26. package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
  27. package/components/DataTable/data-table.css +50 -52
  28. package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
  29. package/components/DataTable/helpers/getChartCellValue.ts +10 -7
  30. package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
  31. package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
  32. package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
  33. package/components/DownloadButton.tsx +14 -6
  34. package/components/EditorPanel/ColumnsEditor.tsx +38 -31
  35. package/components/EditorPanel/CustomSortOrder.tsx +94 -0
  36. package/components/EditorPanel/DataTableEditor.tsx +139 -23
  37. package/components/EditorPanel/EditorPanel.styles.css +71 -71
  38. package/components/EditorPanel/EditorPanel.tsx +3 -8
  39. package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
  40. package/components/EditorPanel/FootnotesEditor.tsx +2 -2
  41. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +7 -6
  42. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -10
  43. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
  44. package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
  45. package/{styles/v2/components → components/EditorPanel}/editor.scss +67 -13
  46. package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
  47. package/components/EditorPanel/sections/VisualSection.tsx +11 -0
  48. package/components/EditorWrapper/editor-wrapper.style.css +1 -1
  49. package/components/Filters/Filters.tsx +3 -5
  50. package/components/Filters/components/Tabs.tsx +19 -7
  51. package/{styles → components/Filters}/filters.scss +3 -3
  52. package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
  53. package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
  54. package/components/Layout/components/Responsive.tsx +14 -6
  55. package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
  56. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +12 -18
  57. package/components/Layout/components/Visualization/index.tsx +39 -38
  58. package/components/Layout/components/Visualization/visualizations.scss +232 -15
  59. package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
  60. package/components/Layout/components/VisualizationContainer.tsx +37 -0
  61. package/components/Layout/components/VisualizationContent.test.tsx +182 -0
  62. package/components/Layout/components/VisualizationContent.tsx +75 -0
  63. package/components/Layout/index.tsx +5 -5
  64. package/components/Layout/styles/editor-utils.scss +3 -3
  65. package/components/Layout/styles/editor.scss +4 -4
  66. package/components/Legend/Legend.Gradient.tsx +7 -1
  67. package/components/Loader/loader.styles.css +2 -2
  68. package/components/Loading.jsx +1 -1
  69. package/components/MediaControls.tsx +10 -2
  70. package/components/MultiSelect/multiselect.styles.css +19 -19
  71. package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
  72. package/components/PaletteSelector/PaletteSelector.css +15 -15
  73. package/components/RichTooltip/richTooltip.css +6 -6
  74. package/components/Table/table.styles.css +2 -2
  75. package/components/Waiting.tsx +1 -1
  76. package/components/_stories/Filters.stories.tsx +1 -1
  77. package/components/_stories/styles.scss +0 -1
  78. package/components/elements/Button.jsx +1 -1
  79. package/components/elements/Card.jsx +1 -1
  80. package/{styles/v2/components → components/elements}/button.scss +9 -8
  81. package/components/inputs/InputCheckbox.jsx +1 -1
  82. package/components/inputs/InputSelect.tsx +1 -1
  83. package/components/inputs/InputText.jsx +1 -1
  84. package/components/inputs/InputToggle.tsx +1 -1
  85. package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
  86. package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
  87. package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
  88. package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
  89. package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
  90. package/{styles → components}/loading.scss +1 -1
  91. package/components/managers/DataDesigner.tsx +1 -1
  92. package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
  93. package/components/ui/Accordion.jsx +1 -1
  94. package/components/ui/Icon.tsx +1 -1
  95. package/components/ui/LoadSpin.jsx +1 -1
  96. package/components/ui/Modal.jsx +1 -1
  97. package/components/ui/Overlay.jsx +1 -1
  98. package/components/ui/Title/index.test.tsx +34 -0
  99. package/components/ui/Title/index.tsx +24 -7
  100. package/components/ui/Title/title.styles.css +119 -25
  101. package/components/ui/Tooltip.tsx +1 -1
  102. package/components/ui/_stories/Title.stories.tsx +1 -1
  103. package/{styles/v2/components → components/ui}/accordion.scss +3 -3
  104. package/components/ui/accordion.styles.css +11 -11
  105. package/{styles/v2/components → components/ui}/modal.scss +2 -2
  106. package/{styles/v2/components → components/ui}/overlay.scss +6 -6
  107. package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
  108. package/{styles → components}/waiting.scss +9 -3
  109. package/devTemplate/dev.js +50 -0
  110. package/dist/cove-main.css +528 -231
  111. package/dist/cove-main.css.map +1 -1
  112. package/generateViteConfig.js +2 -2
  113. package/helpers/backfillDefaults.ts +35 -0
  114. package/helpers/constants.ts +12 -0
  115. package/helpers/cove/date.ts +32 -3
  116. package/helpers/cove/number.ts +29 -15
  117. package/helpers/coveUpdateWorker.ts +12 -8
  118. package/helpers/displayDataAsText.ts +1 -1
  119. package/helpers/embed/embedHelper.js +13 -2
  120. package/helpers/embed/index.ts +0 -4
  121. package/helpers/extractDataAndMetadata.ts +20 -0
  122. package/helpers/fetchRemoteData.ts +14 -8
  123. package/helpers/labelHash.ts +9 -0
  124. package/helpers/markupProcessor.ts +56 -38
  125. package/helpers/prepareScreenshot.ts +6 -3
  126. package/helpers/testing.ts +1 -1
  127. package/helpers/tests/abbreviateNumber.test.ts +59 -0
  128. package/helpers/tests/backfillDefaults.test.ts +253 -0
  129. package/helpers/tests/date.test.ts +46 -0
  130. package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
  131. package/helpers/tests/markupProcessor.test.ts +315 -124
  132. package/helpers/tests/number.test.ts +42 -0
  133. package/helpers/tests/prepareScreenshot.test.ts +28 -28
  134. package/helpers/tests/testStandaloneBuild.ts +36 -26
  135. package/helpers/tests/useDataVizClasses.test.ts +66 -0
  136. package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
  137. package/helpers/useDataVizClasses.ts +13 -7
  138. package/helpers/ver/4.24.4.ts +24 -0
  139. package/helpers/ver/4.26.3.ts +44 -0
  140. package/helpers/ver/4.26.4.ts +31 -0
  141. package/helpers/ver/tests/4.26.3.test.ts +168 -0
  142. package/helpers/ver/tests/4.26.4.test.ts +88 -0
  143. package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
  144. package/package.json +2 -2
  145. package/styles/_global.scss +7 -7
  146. package/styles/_reset.scss +2 -2
  147. package/styles/{v2/base → base}/_file-selector.scss +4 -4
  148. package/styles/{v2/base → base}/_general.scss +2 -4
  149. package/styles/{v2/base → base}/index.scss +1 -1
  150. package/styles/base.scss +107 -165
  151. package/styles/cove-main.scss +3 -6
  152. package/styles/layout/_component.scss +110 -0
  153. package/styles/{v2/layout → layout}/_data-table.scss +7 -7
  154. package/styles/layout/_wrapper-padding.scss +27 -0
  155. package/styles/{v2/main.scss → main.scss} +3 -1
  156. package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
  157. package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
  158. package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
  159. package/styles/{v2/utils → utils}/index.scss +2 -1
  160. package/types/Axis.ts +2 -0
  161. package/types/ComponentStyles.ts +1 -0
  162. package/types/ConfigureData.ts +1 -0
  163. package/types/MarkupInclude.ts +1 -0
  164. package/types/MarkupVariable.ts +2 -1
  165. package/types/Palette.ts +1 -0
  166. package/types/Table.ts +9 -0
  167. package/types/Visualization.ts +1 -0
  168. package/styles/_common-components.css +0 -73
  169. package/styles/_variables.scss +0 -63
  170. package/styles/v2/layout/_component.scss +0 -21
  171. package/styles/v2/utils/_variables.scss +0 -9
  172. package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
  173. /package/{styles/v2/components → components/ui}/icon.scss +0 -0
  174. /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
  175. /package/styles/{v2/base → base}/_heading.scss +0 -0
  176. /package/styles/{v2/base → base}/_reset.scss +0 -0
  177. /package/styles/{v2/layout → layout}/_alert.scss +0 -0
  178. /package/styles/{v2/layout → layout}/_progression.scss +0 -0
  179. /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
  180. /package/styles/{v2/layout → layout}/index.scss +0 -0
  181. /package/styles/{v2/themes → themes}/index.scss +0 -0
  182. /package/styles/{v2/utils → utils}/_align.scss +0 -0
  183. /package/styles/{v2/utils → utils}/_animations.scss +0 -0
  184. /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
  185. /package/styles/{v2/utils → utils}/_grid.scss +0 -0
  186. /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
@@ -1,46 +1,44 @@
1
- .cove,
2
- .cdc-open-viz-module {
1
+ .cove-visualization {
3
2
  .table {
4
- width: unset;
5
3
  min-width: 100%;
4
+ width: unset;
6
5
  }
7
6
 
8
7
  .bs4 .table.table-width-unset {
9
8
  width: unset;
10
9
  }
11
10
 
12
- .collapsed+.table-container {
11
+ .collapsed + .table-container {
13
12
  border-bottom: none;
14
13
  }
15
14
 
16
15
  .table-container {
17
- overflow-x: auto;
18
- border-right: 1px solid var(--lightGray);
19
- border-left: 1px solid var(--lightGray);
20
16
  border-bottom: 1px solid var(--lightGray);
17
+ border-left: 1px solid var(--lightGray);
18
+ border-right: 1px solid var(--lightGray);
19
+ overflow-x: auto;
21
20
  }
22
21
 
23
22
  div.data-table-heading {
24
- position: relative;
25
23
  border: var(--cool-gray-10) 1px solid;
26
24
  border-radius: 6px;
25
+ position: relative;
27
26
 
28
27
  svg {
29
- position: absolute;
30
28
  height: 100%;
31
- width: 15px;
32
- top: 0;
29
+ position: absolute;
33
30
  right: 1em;
31
+ top: 0;
32
+ width: 15px;
34
33
  }
35
34
 
36
35
  &:focus {
37
- z-index: 2;
38
36
  position: relative;
37
+ z-index: 2;
39
38
  }
40
39
  }
41
40
 
42
41
  table.horizontal {
43
-
44
42
  th,
45
43
  td {
46
44
  min-width: 200px;
@@ -48,12 +46,12 @@
48
46
  }
49
47
 
50
48
  table.data-table {
51
- margin-bottom: 0;
49
+ appearance: none;
52
50
  background: #fff;
53
- position: relative;
54
51
  border: none;
55
52
  border-collapse: collapse;
56
- appearance: none;
53
+ margin-bottom: 0;
54
+ position: relative;
57
55
  table-layout: fixed;
58
56
 
59
57
  * {
@@ -61,15 +59,15 @@
61
59
  }
62
60
 
63
61
  thead {
64
- user-select: none;
65
62
  -moz-user-select: none;
66
63
  user-select: none;
64
+ user-select: none;
67
65
 
68
66
  button {
69
67
  background: none;
70
- font-size: initial;
71
- color: #fff !important;
72
68
  border: 0;
69
+ color: #fff !important;
70
+ font-size: initial;
73
71
  }
74
72
 
75
73
  tr {
@@ -81,13 +79,13 @@
81
79
  color: #fff !important;
82
80
 
83
81
  .resizer {
82
+ bottom: 0;
84
83
  cursor: e-resize;
85
- width: 10px;
86
84
  position: absolute;
87
- top: 0;
88
- bottom: 0;
89
85
  right: 0;
86
+ top: 0;
90
87
  touch-action: none;
88
+ width: 10px;
91
89
  }
92
90
 
93
91
  tr {
@@ -96,19 +94,20 @@
96
94
 
97
95
  th,
98
96
  td {
99
- padding: 0.5em 0.7em;
97
+ border-right: 1px solid var(--lightGray) !important;
100
98
  line-height: normal;
99
+ padding: 0.5em 0.7em;
101
100
  position: relative;
102
101
  text-align: left;
103
- border-right: 1px solid var(--lightGray) !important;
104
102
  }
105
103
 
106
104
  th {
107
105
  background-color: var(--primary);
108
- background-repeat: no-repeat;
109
106
  background-position: right 0.5em center;
107
+ background-repeat: no-repeat;
110
108
  background-size: 10px 5px;
111
- color: #fff !important
109
+ color: #fff !important;
110
+ cursor: pointer;
112
111
  }
113
112
 
114
113
  th:last-child,
@@ -134,8 +133,8 @@
134
133
 
135
134
  th,
136
135
  td {
137
- padding: 0.3em 0.7em;
138
136
  border-right: 1px solid rgba(0, 0, 0, 0.1);
137
+ padding: 0.3em 0.7em;
139
138
  white-space: nowrap;
140
139
 
141
140
  &:last-child {
@@ -154,36 +153,35 @@
154
153
  margin-left: 0 !important;
155
154
  }
156
155
  }
157
-
158
156
  }
159
157
 
160
158
  td a {
159
+ bottom: 0;
160
+ color: inherit;
161
+ display: block;
162
+ left: 0;
161
163
  padding: 0.3em 0.7em;
162
164
  position: absolute;
163
- top: 0;
164
- bottom: 0;
165
165
  right: 0;
166
- left: 0;
167
- display: block;
168
- color: inherit;
169
166
  text-decoration: none;
167
+ top: 0;
170
168
  }
171
169
 
172
170
  td div a {
173
- position: relative;
174
- padding: 0;
175
171
  display: inline;
172
+ padding: 0;
173
+ position: relative;
176
174
  }
177
175
 
178
176
  td span.table-link {
179
- text-decoration: underline;
180
- cursor: pointer;
181
177
  color: #075290;
178
+ cursor: pointer;
179
+ text-decoration: underline;
182
180
 
183
181
  svg {
182
+ margin-left: 5px;
184
183
  max-width: 13px;
185
184
  vertical-align: baseline;
186
- margin-left: 5px;
187
185
  }
188
186
  }
189
187
 
@@ -197,16 +195,16 @@
197
195
  position: relative;
198
196
 
199
197
  .no-data-message {
198
+ align-items: center;
200
199
  background: rgba(255, 255, 255, 0.5);
201
- top: 0;
202
- left: 0;
203
- right: 0;
204
200
  bottom: 0;
205
- position: absolute;
206
- text-align: center;
207
201
  display: flex;
208
- align-items: center;
209
202
  justify-content: center;
203
+ left: 0;
204
+ position: absolute;
205
+ right: 0;
206
+ text-align: center;
207
+ top: 0;
210
208
  z-index: 7;
211
209
 
212
210
  :is(h3) {
@@ -231,16 +229,16 @@
231
229
  }
232
230
 
233
231
  .data-table-pagination {
234
- margin: 1rem 0;
235
- display: flex;
236
232
  align-items: center;
233
+ display: flex;
234
+ margin: 1rem 0;
237
235
 
238
236
  ul {
237
+ display: flex;
239
238
  list-style: none;
240
239
  margin: 0 1rem 0 0;
241
- display: flex;
242
240
 
243
- li+li {
241
+ li + li {
244
242
  margin-left: 0.3rem;
245
243
  }
246
244
 
@@ -258,8 +256,8 @@
258
256
 
259
257
  button[disabled] {
260
258
  background: var(--mediumGray);
261
- opacity: 0.3;
262
259
  cursor: default;
260
+ opacity: 0.3;
263
261
 
264
262
  &:hover {
265
263
  background: var(--mediumGray);
@@ -271,9 +269,9 @@
271
269
  .btn-download {
272
270
  color: #fff;
273
271
  float: right;
272
+ margin: 1em 0;
274
273
  text-decoration: none;
275
274
  transition: 0.3s all;
276
- margin: 1em 0;
277
275
 
278
276
  &:hover {
279
277
  transition: 0.3s all;
@@ -281,7 +279,7 @@
281
279
  }
282
280
 
283
281
  .download-links a:not(:last-child) {
284
- margin-right: 10px;
285
282
  display: inline-block;
283
+ margin-right: 10px;
286
284
  }
287
- }
285
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Sorts two values based on a user-defined custom order array.
3
+ * Values not found in customOrder are pushed to the end.
4
+ */
5
+ export const applyCustomOrder = (valueA: string, valueB: string, customOrder: string[]): number => {
6
+ const indexA = customOrder.indexOf(String(valueA))
7
+ const indexB = customOrder.indexOf(String(valueB))
8
+
9
+ // Both found in custom order — sort by position
10
+ if (indexA !== -1 && indexB !== -1) return indexA - indexB
11
+ // Only A found — A comes first
12
+ if (indexA !== -1) return -1
13
+ // Only B found — B comes first
14
+ if (indexB !== -1) return 1
15
+ // Neither found — maintain relative order
16
+ return 0
17
+ }
@@ -31,8 +31,13 @@ const isAdditionalColumn = (column: string, config, rowData) => {
31
31
  return formattingParams
32
32
  }
33
33
 
34
- export const getChartCellValue = (row: string, column: string, config: TableConfig, runtimeData: Object[], rightAxisItemsMap) => {
35
-
34
+ export const getChartCellValue = (
35
+ row: string,
36
+ column: string,
37
+ config: TableConfig,
38
+ runtimeData: Object[],
39
+ rightAxisItemsMap
40
+ ) => {
36
41
  // Variables for xAxis config
37
42
  const { type, dateDisplayFormat, dateParseFormat, dataKey: xAxisDataKey } = config.xAxis || {}
38
43
  const { showMissingDataLabel } = config.general || {}
@@ -54,14 +59,13 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
54
59
  if (column === xAxisDataKey) {
55
60
  const dateFormat = config.table?.dateDisplayFormat || dateDisplayFormat
56
61
  if (type === 'date' || type === 'date-time') {
57
- cellValue = formatDate(dateFormat, parseDate(dateParseFormat, labelValue))
62
+ cellValue = formatDate(dateFormat, parseDate(dateParseFormat, labelValue), config.locale)
58
63
  } else if (type === 'continuous') {
59
64
  cellValue = formatNumber(runtimeData[row][column], 'bottom', false, config)
60
65
  } else {
61
66
  cellValue = labelValue
62
67
  }
63
68
  } else {
64
-
65
69
  let addColParams = isAdditionalColumn(column, config, rowObj)
66
70
 
67
71
  let piePercent = 0
@@ -69,9 +73,8 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
69
73
  piePercent = (_.toNumber(runtimeData[row][column]) / _.sumBy(runtimeData, d => _.toNumber(d[column]))) * 100 || 0
70
74
  }
71
75
 
72
- const valueToFormat = config.visualizationType === 'Pie' && !config.dataFormat.showPiePercent
73
- ? piePercent
74
- : runtimeData[row][column]
76
+ const valueToFormat =
77
+ config.visualizationType === 'Pie' && !config.dataFormat.showPiePercent ? piePercent : runtimeData[row][column]
75
78
 
76
79
  const hasAdditionalParams = Object.keys(addColParams).length > 0
77
80
 
@@ -0,0 +1,22 @@
1
+ import { Column } from '../../../types/Column'
2
+
3
+ const isVisibleDataTableColumn = ([, column]: [string, Column]) => {
4
+ return column?.dataTable === true && !!column?.name
5
+ }
6
+
7
+ export const getMapDataTableColumnKeys = (columns: Record<string, Column> = {}): string[] => {
8
+ return Object.entries(columns)
9
+ .filter(isVisibleDataTableColumn)
10
+ .map(([key, column], declarationIndex) => ({ key, order: column.order, declarationIndex }))
11
+ .sort((a, b) => {
12
+ const aOrder = a.order ?? Number.MAX_SAFE_INTEGER
13
+ const bOrder = b.order ?? Number.MAX_SAFE_INTEGER
14
+
15
+ if (aOrder !== bOrder) {
16
+ return aOrder - bOrder
17
+ }
18
+
19
+ return a.declarationIndex - b.declarationIndex
20
+ })
21
+ .map(({ key }) => key)
22
+ }
@@ -3,8 +3,11 @@ import CellAnchor from '../components/CellAnchor'
3
3
  import { DataTableProps } from '../DataTable'
4
4
  import { ReactNode } from 'react'
5
5
  import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
6
+ import parse from 'html-react-parser'
6
7
  import _ from 'lodash'
7
8
  import { hashObj } from '../../../helpers/hashObj'
9
+ import { sanitizeToSvgId } from '../../../helpers/cove/string'
10
+ import { getMapDataTableColumnKeys } from './getMapDataTableColumnKeys'
8
11
 
9
12
  type MapRowsProps = DataTableProps & {
10
13
  rows: string[]
@@ -22,9 +25,10 @@ const getGeoLabel = (config, row, formatLegendLocation, displayGeoName, runtimeD
22
25
  const { geoType, type } = config.general
23
26
 
24
27
  let labelValue
28
+ const displayOverride = runtimeData?.[row]?.[config.columns?.geo?.displayColumn]
25
29
  if (!['single-state', 'us-county'].includes(geoType) || type === 'us-geocode') {
26
30
  // Use the row (UID) for lookup - this allows "US-AL" to become "Alabama"
27
- labelValue = displayGeoName(row)
31
+ labelValue = displayGeoName(row, displayOverride)
28
32
 
29
33
  // If displayGeoName returned the same value (not found in lookups), use the raw imported data
30
34
  if (labelValue === row && runtimeData && config.columns?.geo?.name) {
@@ -67,11 +71,13 @@ export const getMapRowData = (
67
71
  displayGeoName: (row: string) => string,
68
72
  filterColumns: string[]
69
73
  ) => {
74
+ const orderedColumnKeys = getMapDataTableColumnKeys(columns as any)
75
+
70
76
  return rows.map((row: string) => {
71
77
  const dataRow = {}
72
78
  ;[
73
79
  ...filterColumns,
74
- ...Object.keys(columns).filter(column => columns[column].dataTable === true && columns[column].name)
80
+ ...orderedColumnKeys
75
81
  ].map(column => {
76
82
  const label = columns[column]?.label || columns[column]?.name || column
77
83
  if (column === 'geo') {
@@ -103,10 +109,10 @@ const mapCellArray = ({
103
109
  getPatternForRow
104
110
  }: MapRowsProps): ReactNode[][] => {
105
111
  const { allowMapZoom, geoType, type } = config.general
112
+ const orderedColumnKeys = getMapDataTableColumnKeys(columns as any)
113
+
106
114
  return rows.map(row =>
107
- Object.keys(columns)
108
- .filter(column => columns[column].dataTable === true && columns[column].name)
109
- .map(column => {
115
+ orderedColumnKeys.map(column => {
110
116
  if (column === 'geo') {
111
117
  const rowObj = runtimeData[row]
112
118
  if (!rowObj) {
@@ -128,26 +134,29 @@ const mapCellArray = ({
128
134
  // Check for pattern information
129
135
  const patternInfo = getPatternForRow(rowObj, config)
130
136
  const mapId = config.runtime?.uniqueId || 'map'
137
+ const sanitizedPatternDataKey = sanitizeToSvgId(patternInfo?.dataKey || '')
131
138
 
132
139
  return (
133
- <div className='col-12'>
134
- {validColor ? (
135
- patternInfo ? (
136
- <LegendShape
137
- fill={legendColor[0]}
138
- patternInfo={{
139
- pattern: patternInfo.pattern,
140
- patternId: `${mapId}--${patternInfo.dataKey}--${patternInfo.patternIndex}--table`,
141
- size: patternInfo.size,
142
- color: patternInfo.color
143
- }}
144
- />
140
+ <div style={{ display: 'flex', alignItems: 'flex-start', flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
141
+ <div style={{ flexShrink: 0 }}>
142
+ {validColor ? (
143
+ patternInfo ? (
144
+ <LegendShape
145
+ fill={legendColor[0]}
146
+ patternInfo={{
147
+ pattern: patternInfo.pattern,
148
+ patternId: `${mapId}--${sanitizedPatternDataKey}--${patternInfo.patternIndex}--table`,
149
+ size: patternInfo.size,
150
+ color: patternInfo.color
151
+ }}
152
+ />
153
+ ) : (
154
+ <LegendShape fill={legendColor[0]} />
155
+ )
145
156
  ) : (
146
- <LegendShape fill={legendColor[0]} />
147
- )
148
- ) : (
149
- <div className='d-inline-block me-2' style={{ width: '1rem', height: '1rem' }} />
150
- )}
157
+ <div className='me-2' style={{ width: '1rem', height: '1rem' }} />
158
+ )}
159
+ </div>
151
160
  <CellAnchor
152
161
  markup={labelValue}
153
162
  row={rowObj}
@@ -160,7 +169,8 @@ const mapCellArray = ({
160
169
  } else {
161
170
  const rowData = runtimeData[row]
162
171
  const dataValue = getDataValue(config, rowData, column)
163
- return displayDataAsText(dataValue, column, config)
172
+ const text = displayDataAsText(dataValue, column, config)
173
+ return typeof text === 'string' ? parse(text) : text
164
174
  }
165
175
  })
166
176
  )
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { getMapRowData } from '../mapCellMatrix'
3
+ import { getMapDataTableColumnKeys } from '../getMapDataTableColumnKeys'
3
4
 
4
5
  describe('getMapRowData', () => {
5
6
  const columns = {
@@ -77,4 +78,36 @@ describe('getMapRowData', () => {
77
78
  }
78
79
  ])
79
80
  })
81
+
82
+ it('orders visible columns using display order instead of config key order', () => {
83
+ const orderedConfig = {
84
+ ...config,
85
+ general: { ...config.general, geoType: 'us-state' }
86
+ }
87
+ const orderedColumns = {
88
+ geo: { dataTable: true, name: 'geo', label: 'Geo', order: 3 },
89
+ column1: { dataTable: true, name: 'column1', label: 'Column 1', order: 2 },
90
+ column2: { dataTable: true, name: 'column2', label: 'Column 2', order: 1 },
91
+ hidden: { dataTable: false, name: 'hidden', label: 'Hidden', order: 4 }
92
+ }
93
+
94
+ expect(getMapDataTableColumnKeys(orderedColumns)).toEqual(['column2', 'column1', 'geo'])
95
+
96
+ const orderedResult = getMapRowData(
97
+ rows,
98
+ orderedColumns,
99
+ orderedConfig,
100
+ formatLegendLocation,
101
+ runtimeData,
102
+ displayGeoName,
103
+ []
104
+ )
105
+
106
+ expect(Object.keys(orderedResult[0])).toEqual(['Column 2', 'Column 1', 'Geo'])
107
+ expect(orderedResult[0]).toEqual({
108
+ 'Column 2': 'data4',
109
+ 'Column 1': 'data3',
110
+ Geo: 'displayGeoName -> row2'
111
+ })
112
+ })
80
113
  })
@@ -6,14 +6,22 @@ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
6
6
  type DownloadButtonProps = {
7
7
  rawData: any[]
8
8
  fileName: string
9
- headerColor: string
10
- skipId: string | number
9
+ skipId?: string | number
11
10
  configUrl?: string
12
11
  interactionLabel?: string
13
12
  title?: string
13
+ config?: any
14
14
  }
15
15
 
16
- const DownloadButton = ({ rawData, fileName, headerColor, skipId, interactionLabel, configUrl, title, config }: DownloadButtonProps) => {
16
+ const DownloadButton = ({
17
+ rawData,
18
+ fileName,
19
+ skipId,
20
+ interactionLabel,
21
+ configUrl,
22
+ title,
23
+ config
24
+ }: DownloadButtonProps) => {
17
25
  const linkRef = useRef<HTMLAnchorElement>(null)
18
26
 
19
27
  const handleDownload = (event: React.MouseEvent<HTMLAnchorElement>) => {
@@ -59,13 +67,13 @@ const DownloadButton = ({ rawData, fileName, headerColor, skipId, interactionLab
59
67
  type='button'
60
68
  onClick={handleDownload}
61
69
  aria-label='Download this data in a CSV file format.'
62
- className={`${headerColor} no-border`}
63
- id={`${skipId}`}
70
+ className='no-border'
71
+ id={skipId != null ? `${skipId}` : undefined}
64
72
  data-html2canvas-ignore
65
73
  role='button'
66
74
  style={{ cursor: 'pointer' }}
67
75
  >
68
- Download Data (CSV)
76
+ {config?.table?.downloadDataLabel || 'Download Data (CSV)'}
69
77
  </a>
70
78
  )
71
79
  }
@@ -10,21 +10,23 @@ import FieldSetWrapper from './FieldSetWrapper'
10
10
  import { useDataColumns } from '../../hooks/useDataColumns'
11
11
  import Alert from '../Alert/components/Alert'
12
12
 
13
- interface ColumnsEditorProps {
14
- config: Partial<Visualization>
15
- updateField: UpdateFieldFunc<string | boolean | string[] | number | Column | Record<string, Partial<Column>>>
16
- deleteColumn: (colName: string) => void
17
- }
13
+ interface ColumnsEditorProps {
14
+ config: Partial<Visualization>
15
+ updateField: UpdateFieldFunc<string | boolean | string[] | number | Column | Record<string, Partial<Column>>>
16
+ deleteColumn: (colName: string) => void
17
+ hiddenColumnNames?: string[]
18
+ }
18
19
 
19
20
  type OpenControls = [Record<string, boolean>, Function] // useState type
20
21
 
21
- const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenControls }> = ({
22
- config,
23
- deleteColumn,
24
- updateField,
25
- colKey,
26
- controls
27
- }) => {
22
+ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenControls }> = ({
23
+ config,
24
+ deleteColumn,
25
+ updateField,
26
+ hiddenColumnNames = [],
27
+ colKey,
28
+ controls
29
+ }) => {
28
30
  const [openControls, setOpenControls] = controls
29
31
 
30
32
  const editColumn = (key, value) => {
@@ -58,17 +60,18 @@ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenCo
58
60
  const allColumns = useDataColumns(config.data)
59
61
 
60
62
  // Filter out groupBy and already configured columns
61
- const availableColumns = useMemo(() => {
62
- const configuredColumns = Object.values(config.columns).map(col => col.name)
63
- const cols = allColumns.filter(key => {
64
- if (config.table.groupBy === key) return false
65
- if (configuredColumns.includes(key)) return false
66
- return true
67
- })
68
- // Add current column name if it exists
69
- if (config.columns[colKey]?.name) cols.push(config.columns[colKey].name)
70
- return cols
71
- }, [allColumns, config.table.groupBy, config.columns, colKey])
63
+ const availableColumns = useMemo(() => {
64
+ const configuredColumns = Object.values(config.columns).map(col => col.name)
65
+ const cols = allColumns.filter(key => {
66
+ if (config.table.groupBy === key) return false
67
+ if (configuredColumns.includes(key)) return false
68
+ if (hiddenColumnNames.includes(key)) return false
69
+ return true
70
+ })
71
+ // Add current column name if it exists
72
+ if (config.columns[colKey]?.name) cols.push(config.columns[colKey].name)
73
+ return cols
74
+ }, [allColumns, config.table.groupBy, config.columns, colKey, hiddenColumnNames])
72
75
 
73
76
  const colName = config.columns[colKey]?.name
74
77
 
@@ -264,9 +267,12 @@ const FieldSet: React.FC<ColumnsEditorProps & { colKey: string; controls: OpenCo
264
267
  )
265
268
  }
266
269
 
267
- const ColumnsEditor: React.FC<ColumnsEditorProps> = ({ config, updateField, deleteColumn }) => {
268
- const openControls = useState({})
269
- const additionalColumns = Object.keys(config.columns)
270
+ const ColumnsEditor: React.FC<ColumnsEditorProps> = ({ config, updateField, deleteColumn, hiddenColumnNames = [] }) => {
271
+ const openControls = useState({})
272
+ const additionalColumns = Object.keys(config.columns).filter(colKey => {
273
+ const columnName = config.columns[colKey]?.name || colKey
274
+ return !hiddenColumnNames.includes(columnName)
275
+ })
270
276
 
271
277
  // just adds a new column but not set to any data yet
272
278
  const addColumnConfig = number => {
@@ -312,11 +318,12 @@ const ColumnsEditor: React.FC<ColumnsEditorProps> = ({ config, updateField, dele
312
318
  key={val + i}
313
319
  controls={openControls}
314
320
  config={config}
315
- deleteColumn={deleteColumn}
316
- updateField={updateField}
317
- colKey={val}
318
- />
319
- ))}
321
+ deleteColumn={deleteColumn}
322
+ updateField={updateField}
323
+ hiddenColumnNames={hiddenColumnNames}
324
+ colKey={val}
325
+ />
326
+ ))}
320
327
  <button
321
328
  className={'btn btn-primary'}
322
329
  onClick={event => {