@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.
- package/LICENSE +201 -0
- package/_stories/Gallery.Charts.stories.tsx +1 -1
- package/_stories/Gallery.DataBite.stories.tsx +1 -1
- package/_stories/Gallery.Maps.stories.tsx +1 -1
- package/_stories/PageART.stories.tsx +1 -1
- package/_stories/PageBRFSS.stories.tsx +1 -1
- package/_stories/PageCancerRegistries.stories.tsx +1 -1
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +3 -3
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +1 -1
- package/_stories/PageMaternalMortality.stories.tsx +1 -1
- package/_stories/PageOralHealth.stories.tsx +1 -1
- package/_stories/PageRespiratory.stories.tsx +4 -4
- package/_stories/PageSmokingTobacco.stories.tsx +1 -1
- package/_stories/PageStateDiabetesProfiles.stories.tsx +1 -1
- package/_stories/PageWastewater.stories.tsx +4 -4
- package/_stories/VegaImport.stories.tsx +3 -3
- package/assets/callout-flag.svg +7 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +1 -1
- package/components/Alert/components/Alert.styles.css +2 -2
- package/components/ComboBox/combobox.styles.css +48 -48
- package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
- package/components/DataTable/DataTable.tsx +46 -18
- package/components/DataTable/DataTableStandAlone.tsx +1 -0
- package/components/DataTable/components/ChartHeader.tsx +21 -12
- package/components/DataTable/components/MapHeader.tsx +34 -28
- package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
- package/components/DataTable/data-table.css +50 -52
- package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
- package/components/DataTable/helpers/getChartCellValue.ts +10 -7
- package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
- package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
- package/components/DownloadButton.tsx +14 -6
- package/components/EditorPanel/ColumnsEditor.tsx +38 -31
- package/components/EditorPanel/CustomSortOrder.tsx +94 -0
- package/components/EditorPanel/DataTableEditor.tsx +139 -23
- package/components/EditorPanel/EditorPanel.styles.css +71 -71
- package/components/EditorPanel/EditorPanel.tsx +3 -8
- package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
- package/components/EditorPanel/FootnotesEditor.tsx +2 -2
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +7 -6
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -10
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
- package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
- package/{styles/v2/components → components/EditorPanel}/editor.scss +67 -13
- package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
- package/components/EditorPanel/sections/VisualSection.tsx +11 -0
- package/components/EditorWrapper/editor-wrapper.style.css +1 -1
- package/components/Filters/Filters.tsx +3 -5
- package/components/Filters/components/Tabs.tsx +19 -7
- package/{styles → components/Filters}/filters.scss +3 -3
- package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
- package/components/Layout/components/Responsive.tsx +14 -6
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +12 -18
- package/components/Layout/components/Visualization/index.tsx +39 -38
- package/components/Layout/components/Visualization/visualizations.scss +232 -15
- package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
- package/components/Layout/components/VisualizationContainer.tsx +37 -0
- package/components/Layout/components/VisualizationContent.test.tsx +182 -0
- package/components/Layout/components/VisualizationContent.tsx +75 -0
- package/components/Layout/index.tsx +5 -5
- package/components/Layout/styles/editor-utils.scss +3 -3
- package/components/Layout/styles/editor.scss +4 -4
- package/components/Legend/Legend.Gradient.tsx +7 -1
- package/components/Loader/loader.styles.css +2 -2
- package/components/Loading.jsx +1 -1
- package/components/MediaControls.tsx +10 -2
- package/components/MultiSelect/multiselect.styles.css +19 -19
- package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
- package/components/PaletteSelector/PaletteSelector.css +15 -15
- package/components/RichTooltip/richTooltip.css +6 -6
- package/components/Table/table.styles.css +2 -2
- package/components/Waiting.tsx +1 -1
- package/components/_stories/Filters.stories.tsx +1 -1
- package/components/_stories/styles.scss +0 -1
- package/components/elements/Button.jsx +1 -1
- package/components/elements/Card.jsx +1 -1
- package/{styles/v2/components → components/elements}/button.scss +9 -8
- package/components/inputs/InputCheckbox.jsx +1 -1
- package/components/inputs/InputSelect.tsx +1 -1
- package/components/inputs/InputText.jsx +1 -1
- package/components/inputs/InputToggle.tsx +1 -1
- package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
- package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
- package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
- package/{styles → components}/loading.scss +1 -1
- package/components/managers/DataDesigner.tsx +1 -1
- package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Icon.tsx +1 -1
- package/components/ui/LoadSpin.jsx +1 -1
- package/components/ui/Modal.jsx +1 -1
- package/components/ui/Overlay.jsx +1 -1
- package/components/ui/Title/index.test.tsx +34 -0
- package/components/ui/Title/index.tsx +24 -7
- package/components/ui/Title/title.styles.css +119 -25
- package/components/ui/Tooltip.tsx +1 -1
- package/components/ui/_stories/Title.stories.tsx +1 -1
- package/{styles/v2/components → components/ui}/accordion.scss +3 -3
- package/components/ui/accordion.styles.css +11 -11
- package/{styles/v2/components → components/ui}/modal.scss +2 -2
- package/{styles/v2/components → components/ui}/overlay.scss +6 -6
- package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
- package/{styles → components}/waiting.scss +9 -3
- package/devTemplate/dev.js +50 -0
- package/dist/cove-main.css +528 -231
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +2 -2
- package/helpers/backfillDefaults.ts +35 -0
- package/helpers/constants.ts +12 -0
- package/helpers/cove/date.ts +32 -3
- package/helpers/cove/number.ts +29 -15
- package/helpers/coveUpdateWorker.ts +12 -8
- package/helpers/displayDataAsText.ts +1 -1
- package/helpers/embed/embedHelper.js +13 -2
- package/helpers/embed/index.ts +0 -4
- package/helpers/extractDataAndMetadata.ts +20 -0
- package/helpers/fetchRemoteData.ts +14 -8
- package/helpers/labelHash.ts +9 -0
- package/helpers/markupProcessor.ts +56 -38
- package/helpers/prepareScreenshot.ts +6 -3
- package/helpers/testing.ts +1 -1
- package/helpers/tests/abbreviateNumber.test.ts +59 -0
- package/helpers/tests/backfillDefaults.test.ts +253 -0
- package/helpers/tests/date.test.ts +46 -0
- package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
- package/helpers/tests/markupProcessor.test.ts +315 -124
- package/helpers/tests/number.test.ts +42 -0
- package/helpers/tests/prepareScreenshot.test.ts +28 -28
- package/helpers/tests/testStandaloneBuild.ts +36 -26
- package/helpers/tests/useDataVizClasses.test.ts +66 -0
- package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
- package/helpers/useDataVizClasses.ts +13 -7
- package/helpers/ver/4.24.4.ts +24 -0
- package/helpers/ver/4.26.3.ts +44 -0
- package/helpers/ver/4.26.4.ts +31 -0
- package/helpers/ver/tests/4.26.3.test.ts +168 -0
- package/helpers/ver/tests/4.26.4.test.ts +88 -0
- package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
- package/package.json +2 -2
- package/styles/_global.scss +7 -7
- package/styles/_reset.scss +2 -2
- package/styles/{v2/base → base}/_file-selector.scss +4 -4
- package/styles/{v2/base → base}/_general.scss +2 -4
- package/styles/{v2/base → base}/index.scss +1 -1
- package/styles/base.scss +107 -165
- package/styles/cove-main.scss +3 -6
- package/styles/layout/_component.scss +110 -0
- package/styles/{v2/layout → layout}/_data-table.scss +7 -7
- package/styles/layout/_wrapper-padding.scss +27 -0
- package/styles/{v2/main.scss → main.scss} +3 -1
- package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
- package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
- package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
- package/styles/{v2/utils → utils}/index.scss +2 -1
- package/types/Axis.ts +2 -0
- package/types/ComponentStyles.ts +1 -0
- package/types/ConfigureData.ts +1 -0
- package/types/MarkupInclude.ts +1 -0
- package/types/MarkupVariable.ts +2 -1
- package/types/Palette.ts +1 -0
- package/types/Table.ts +9 -0
- package/types/Visualization.ts +1 -0
- package/styles/_common-components.css +0 -73
- package/styles/_variables.scss +0 -63
- package/styles/v2/layout/_component.scss +0 -21
- package/styles/v2/utils/_variables.scss +0 -9
- package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
- /package/{styles/v2/components → components/ui}/icon.scss +0 -0
- /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
- /package/styles/{v2/base → base}/_heading.scss +0 -0
- /package/styles/{v2/base → base}/_reset.scss +0 -0
- /package/styles/{v2/layout → layout}/_alert.scss +0 -0
- /package/styles/{v2/layout → layout}/_progression.scss +0 -0
- /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
- /package/styles/{v2/layout → layout}/index.scss +0 -0
- /package/styles/{v2/themes → themes}/index.scss +0 -0
- /package/styles/{v2/utils → utils}/_align.scss +0 -0
- /package/styles/{v2/utils → utils}/_animations.scss +0 -0
- /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
- /package/styles/{v2/utils → utils}/_grid.scss +0 -0
- /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
|
|
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
|
-
|
|
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
|
-
|
|
49
|
+
appearance: none;
|
|
52
50
|
background: #fff;
|
|
53
|
-
position: relative;
|
|
54
51
|
border: none;
|
|
55
52
|
border-collapse: collapse;
|
|
56
|
-
|
|
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
|
-
|
|
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 = (
|
|
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 =
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
|
134
|
-
{
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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 = ({
|
|
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=
|
|
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
|
-
|
|
26
|
-
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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 => {
|