@cdc/core 4.25.6 → 4.25.8
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/components/DataTable/DataTable.tsx +52 -19
- package/components/DataTable/DataTableStandAlone.tsx +4 -1
- package/components/DataTable/components/ChartHeader.tsx +44 -13
- package/components/DataTable/components/ExpandCollapse.tsx +14 -2
- package/components/DataTable/components/MapHeader.tsx +18 -1
- package/components/DataTable/components/SortIcon/sort-icon.css +21 -27
- package/components/DataTable/data-table.css +33 -4
- package/components/DataTable/helpers/customSort.ts +4 -2
- package/components/DownloadButton.tsx +4 -1
- package/components/EditorPanel/DataTableEditor.tsx +10 -1
- package/components/Filters/Filters.tsx +26 -9
- package/components/Footnotes/FootnotesStandAlone.tsx +2 -1
- package/components/{MediaControls.jsx → MediaControls.tsx} +32 -16
- package/components/Table/components/Row.tsx +15 -12
- package/dist/cove-main.css +0 -10
- package/dist/cove-main.css.map +1 -1
- package/helpers/cove/number.ts +6 -2
- package/helpers/coveUpdateWorker.ts +5 -1
- package/helpers/events.ts +32 -0
- package/helpers/isRightAlignedTableValue.js +1 -1
- package/helpers/metrics/helpers.ts +52 -0
- package/helpers/metrics/types.ts +43 -0
- package/helpers/vegaConfig.ts +647 -0
- package/helpers/ver/4.25.7.ts +26 -0
- package/helpers/ver/4.25.8.ts +61 -0
- package/helpers/ver/tests/4.25.8.test.ts +86 -0
- package/helpers/viewports.ts +2 -0
- package/package.json +6 -4
- package/styles/_accessibility.scss +8 -0
- package/styles/_button-section.scss +0 -2
- package/styles/_global.scss +0 -4
- package/styles/filters.scss +0 -4
- package/types/ForecastingSeriesKey.ts +15 -0
- package/types/Runtime.ts +1 -7
- package/types/Series.ts +4 -0
- package/types/Table.ts +1 -0
- package/helpers/events.js +0 -14
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
2
|
+
import { formatDate } from '@cdc/core/helpers/cove/date.js'
|
|
3
|
+
import { _ } from 'lodash'
|
|
4
|
+
import { compile as vegaLiteCompile } from 'vega-lite'
|
|
5
|
+
import { parse as vegaParse, View as vegaView } from 'vega'
|
|
6
|
+
|
|
7
|
+
const CURVE_LOOKUP = {
|
|
8
|
+
linear: 'Linear',
|
|
9
|
+
cardinal: 'Cardinal',
|
|
10
|
+
natural: 'Natural',
|
|
11
|
+
monotone: 'Monotone X',
|
|
12
|
+
step: 'Step',
|
|
13
|
+
basis: 'Curve Basis'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const SUPPORTED_MARKS = ['rect', 'line', 'area', 'shape', 'symbol']
|
|
17
|
+
const COMBO_MARKS = { rect: 'Bar', line: 'Line', area: 'Area Chart' }
|
|
18
|
+
|
|
19
|
+
export const isVegaConfig = config => {
|
|
20
|
+
return (
|
|
21
|
+
(config.scales ||
|
|
22
|
+
config.axes ||
|
|
23
|
+
config.marks ||
|
|
24
|
+
config.layer ||
|
|
25
|
+
config.params ||
|
|
26
|
+
config.transform ||
|
|
27
|
+
config.projection ||
|
|
28
|
+
config.encoding ||
|
|
29
|
+
config.spec) &&
|
|
30
|
+
!(config.xAxis || config.yAxis || config.general || config.columns)
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const getVegaErrors = (vegaOrVegaLiteConfig, vegaConfig) => {
|
|
35
|
+
const errors = []
|
|
36
|
+
|
|
37
|
+
const data = extractCoveData(vegaConfig)
|
|
38
|
+
|
|
39
|
+
if (vegaOrVegaLiteConfig.repeat || vegaOrVegaLiteConfig.spec) {
|
|
40
|
+
errors.push("COVE's Vega importer does not support vega-lite's repeat/spec operator.")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const configType = getVegaConfigType(vegaConfig)
|
|
44
|
+
if (!configType) {
|
|
45
|
+
errors.push(
|
|
46
|
+
`COVE's Vega importer could not find a COVE chart type that matches this Vega config. Supported marks are: ${SUPPORTED_MARKS.join(
|
|
47
|
+
', '
|
|
48
|
+
)}.`
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (data.length === 0 && errors.length === 0) {
|
|
53
|
+
errors.push(
|
|
54
|
+
"No data was found in the Vega config. COVE's Vega importer requires data to be embedded in the config; if the config was exported from 1CDP, make sure it was copied from the Preview tab."
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const stack = getStack(vegaConfig)
|
|
59
|
+
if (configType === 'Combo Chart' && stack && getMaxGroupSize(data, stack.groupby) > 1) {
|
|
60
|
+
const comboMarks = getComboMarks(vegaConfig)
|
|
61
|
+
errors.push(
|
|
62
|
+
`This config contains multiple types of marks (${comboMarks
|
|
63
|
+
.map(m => m.type)
|
|
64
|
+
.join(
|
|
65
|
+
', '
|
|
66
|
+
)}) and one of them appears to be stacked. COVE's combo charts do not support both stacked and unstacked data (e.g. a stacked bar chart with a line).`
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (configType === 'Map') {
|
|
71
|
+
const geoName = getGeoName(data)
|
|
72
|
+
if (!geoName) {
|
|
73
|
+
errors.push(
|
|
74
|
+
"COVE's Vega importer could not find a column containing geographies. To import a state-level choropleth map, one column in the dataset must contain state names."
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (errors.length) {
|
|
80
|
+
errors.push('Reach out to the COVE team if you think this Vega config should be supported.')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return errors
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const getVegaWarnings = (vegaOrVegaLiteConfig, vegaConfig) => {
|
|
87
|
+
const warnings = []
|
|
88
|
+
|
|
89
|
+
const configType = getVegaConfigType(vegaConfig)
|
|
90
|
+
const allMarks = getMarks(vegaConfig)
|
|
91
|
+
const comboMarks = getComboMarks(vegaConfig)
|
|
92
|
+
const allMarksTypeCount = [...new Set(allMarks.map(m => m.type))].length
|
|
93
|
+
if (
|
|
94
|
+
(configType !== 'Combo Chart' && allMarksTypeCount > 1) ||
|
|
95
|
+
(configType === 'Combo Chart' && allMarks.length > comboMarks.length)
|
|
96
|
+
) {
|
|
97
|
+
warnings.push(
|
|
98
|
+
`This Vega config contains multiple types of marks (${allMarks
|
|
99
|
+
.map(m => m.type)
|
|
100
|
+
.join(', ')}), but COVE's combo charts only support these types of marks: (${Object.keys(COMBO_MARKS).join(
|
|
101
|
+
', '
|
|
102
|
+
)}). Not all marks were imported.`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return warnings
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const parseVegaConfig = vegaConfig => {
|
|
110
|
+
try {
|
|
111
|
+
vegaConfig = vegaLiteCompile(vegaConfig).spec
|
|
112
|
+
} catch {}
|
|
113
|
+
return vegaConfig
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const getVegaConfigType = vegaConfig => {
|
|
117
|
+
if (vegaConfig.projections) {
|
|
118
|
+
return 'Map'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const comboMarks = getComboMarks(vegaConfig)
|
|
122
|
+
const typeCount = [...new Set(comboMarks.map(m => m.type))].length
|
|
123
|
+
if (comboMarks.length > 1 && typeCount > 1) {
|
|
124
|
+
return 'Combo Chart'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const mainMark = getMainMark(vegaConfig)
|
|
128
|
+
if (!mainMark) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (mainMark.type === 'line') {
|
|
133
|
+
return 'Line'
|
|
134
|
+
} else if (mainMark.type === 'rect') {
|
|
135
|
+
return 'Bar'
|
|
136
|
+
} else if (mainMark.type === 'area') {
|
|
137
|
+
return 'Area Chart'
|
|
138
|
+
} else if (mainMark.type === 'symbol') {
|
|
139
|
+
return 'Scatter Plot'
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const getMainMark = vegaConfig => {
|
|
144
|
+
let allMarks = getMarks(vegaConfig)
|
|
145
|
+
if (!allMarks.length) return
|
|
146
|
+
if (allMarks.length === 1) return allMarks[0]
|
|
147
|
+
const dataSizes = Object.fromEntries(
|
|
148
|
+
allMarks.map(mark => [mark.from?.data, getVegaData(vegaConfig, mark.from?.data).length])
|
|
149
|
+
)
|
|
150
|
+
return allMarks.sort((a, b) => (dataSizes[a.from?.data] < dataSizes[b.from?.data] ? 1 : -1))[0]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const getMarks = vegaConfig => {
|
|
154
|
+
const marks = vegaConfig.marks.map(m =>
|
|
155
|
+
m.type === 'group' && m.marks ? m.marks.find(mm => SUPPORTED_MARKS.includes(mm.type)) : m
|
|
156
|
+
)
|
|
157
|
+
return marks.filter(m => m && SUPPORTED_MARKS.includes(m.type))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const getComboMarks = vegaConfig => {
|
|
161
|
+
const allMarks = getMarks(vegaConfig)
|
|
162
|
+
return allMarks.filter(m => Object.keys(COMBO_MARKS).includes(m.type))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const getStack = vegaConfig => {
|
|
166
|
+
const stackData = vegaConfig.data.find(d => d.transform?.find(t => t.type === 'stack'))
|
|
167
|
+
return stackData ? stackData.transform.find(t => t.type === 'stack') : undefined
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const groupByMultiple = (array, keys) => {
|
|
171
|
+
return _.groupBy(array, item => keys.map(key => item[key]).join('-'))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const getGroupedData = (data, groupBy) => {
|
|
175
|
+
if (groupBy.length > 1) {
|
|
176
|
+
return groupByMultiple(data, groupBy)
|
|
177
|
+
}
|
|
178
|
+
return _.groupBy(data, groupBy[0])
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const getMaxGroupSize = (data, groupBy) => {
|
|
182
|
+
return Math.max(...Object.values(getGroupedData(data, groupBy)).map(d => d.length))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const mergeByKeys = (a1, a2, k1, k2) =>
|
|
186
|
+
a1.map(itm => ({
|
|
187
|
+
...a2.find(item => item[k2] === itm[k1] && item),
|
|
188
|
+
...itm
|
|
189
|
+
}))
|
|
190
|
+
|
|
191
|
+
const getVegaData = (vegaConfig, name) => {
|
|
192
|
+
if (!name) return []
|
|
193
|
+
const view = new vegaView(vegaParse(vegaConfig)).run()
|
|
194
|
+
try {
|
|
195
|
+
return view.data(name) || []
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(error)
|
|
198
|
+
return []
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const extractCoveData = vegaConfig => {
|
|
203
|
+
const marks = getMarks(vegaConfig)
|
|
204
|
+
const mainMark = getMainMark(vegaConfig)
|
|
205
|
+
const groupMark = getGroupMark(vegaConfig)
|
|
206
|
+
const facetName = groupMark?.from?.facet?.data
|
|
207
|
+
const name = facetName || mainMark?.from?.data
|
|
208
|
+
let data = getVegaData(vegaConfig, name)
|
|
209
|
+
|
|
210
|
+
if (data.length === 0) return data
|
|
211
|
+
|
|
212
|
+
if (!facetName) {
|
|
213
|
+
const otherNames = [...new Set(getMarks(vegaConfig).map(m => m.from?.data))].filter(n => n)
|
|
214
|
+
_.difference(otherNames, [name]).forEach(on => {
|
|
215
|
+
let mergedData
|
|
216
|
+
const otherData = getVegaData(vegaConfig, on)
|
|
217
|
+
const keys1 = Object.keys(data[0]).filter(k => new Set(data.map(d => d[k])).size === data.length)
|
|
218
|
+
const keys2 = Object.keys(otherData[0]).filter(k => new Set(otherData.map(d => d[k])).size === data.length)
|
|
219
|
+
keys1.forEach(k1 => {
|
|
220
|
+
keys2.forEach(k2 => {
|
|
221
|
+
mergedData = mergeByKeys(data, otherData, k1, k2)
|
|
222
|
+
if (mergedData.filter(d => d.hasOwnProperty(k1) && d.hasOwnProperty(k2)).length === data.length) {
|
|
223
|
+
data = mergedData
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const keysToRemove = getKeysToRemove(vegaConfig, data)
|
|
231
|
+
data.forEach(d => {
|
|
232
|
+
keysToRemove.forEach(k => delete d[k])
|
|
233
|
+
Object.keys(d).forEach(k => {
|
|
234
|
+
if (d[k] > 1000000000 && d[k] % 1000 === 0) {
|
|
235
|
+
d[k] = new Date(d[k])
|
|
236
|
+
}
|
|
237
|
+
if (d[k] instanceof Date) {
|
|
238
|
+
d[k] = formatDate('%Y-%m-%d', d[k] - 1000 * 60 * 60 * 10)
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
const sortDomain = vegaConfig.scales?.find(s => s.domain?.sort)?.domain
|
|
243
|
+
if (sortDomain) {
|
|
244
|
+
const keys = Object.keys(data[0])
|
|
245
|
+
const sortField =
|
|
246
|
+
(sortDomain.sort.field && keys.includes(sortDomain.sort.field) ? sortDomain.sort.field : null) || sortDomain.field
|
|
247
|
+
if (sortField) {
|
|
248
|
+
const sortDirection = sortDomain.sort.order === 'descending' ? -1 : 1
|
|
249
|
+
data.sort((a, b) => (a[sortField] > b[sortField] ? sortDirection : sortDirection * -1))
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return data
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const getGroupMark = vegaConfig => {
|
|
257
|
+
return vegaConfig.marks.find(m => m.type === 'group')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const isValidSeriesKey = (seriesKey, data) => {
|
|
261
|
+
const seriesVals = [...new Set(data.map(d => d[seriesKey]))]
|
|
262
|
+
const maxGroupSize = getMaxGroupSize(data, [seriesKey])
|
|
263
|
+
return seriesVals.length > 1 && seriesVals.length <= 10 && maxGroupSize > 1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const getSeriesKey = (vegaConfig, data, xField, yField) => {
|
|
267
|
+
const configType = getVegaConfigType(vegaConfig)
|
|
268
|
+
if (['Scatter Plot', 'Combo Chart'].includes(configType)) return
|
|
269
|
+
|
|
270
|
+
let seriesKey
|
|
271
|
+
|
|
272
|
+
const mainMark = getMainMark(vegaConfig)
|
|
273
|
+
const enterEncoder = mainMark.encode.enter
|
|
274
|
+
const updateEncoder = mainMark.encode.update
|
|
275
|
+
seriesKey =
|
|
276
|
+
updateEncoder?.fill?.field ||
|
|
277
|
+
enterEncoder?.fill?.field ||
|
|
278
|
+
updateEncoder?.stroke?.field ||
|
|
279
|
+
enterEncoder?.stroke?.field ||
|
|
280
|
+
updateEncoder?.shape?.field ||
|
|
281
|
+
enterEncoder?.shape?.field ||
|
|
282
|
+
updateEncoder?.size?.field ||
|
|
283
|
+
enterEncoder?.size?.field
|
|
284
|
+
if (isValidSeriesKey(seriesKey, data)) return seriesKey
|
|
285
|
+
|
|
286
|
+
const groupMark = getGroupMark(vegaConfig)
|
|
287
|
+
seriesKey = groupMark?.from?.facet?.groupby
|
|
288
|
+
if (isValidSeriesKey(seriesKey, data)) return seriesKey
|
|
289
|
+
|
|
290
|
+
const stack = getStack(vegaConfig)
|
|
291
|
+
if (stack) {
|
|
292
|
+
const groupBy = _.difference(stack.groupby, [xField, yField])
|
|
293
|
+
if (getMaxGroupSize(data, groupBy) > 1) {
|
|
294
|
+
let possibleKeys = _.difference(Object.keys(data[0]), [xField, yField])
|
|
295
|
+
const groupSizes = Object.fromEntries(possibleKeys.map(k => [k, getMaxGroupSize(data, [...groupBy, ...[k]])]))
|
|
296
|
+
possibleKeys = possibleKeys.filter(k => groupSizes[k] > 1 && isValidSeriesKey(k, data))
|
|
297
|
+
if (possibleKeys.length) {
|
|
298
|
+
return possibleKeys.sort((a, b) => (groupSizes[a] > groupSizes[b] ? 1 : -1))[0]
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const getKeysToRemove = (vegaConfig, data) => {
|
|
305
|
+
let keysToRemove = []
|
|
306
|
+
const stack = getStack(vegaConfig)
|
|
307
|
+
if (stack) {
|
|
308
|
+
keysToRemove = [...keysToRemove, ...(stack.as || ['y0', 'y1'])]
|
|
309
|
+
}
|
|
310
|
+
Object.keys(data[0]).forEach(k => {
|
|
311
|
+
if (typeof data[0][k] === 'object' && !(data[0][k] instanceof Date)) {
|
|
312
|
+
keysToRemove.push(k)
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
return keysToRemove
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const getXDateFormat = (xField, data) => {
|
|
319
|
+
const str = data[0][xField].toString()
|
|
320
|
+
if (str.match(/\d{4}\-\d{2}\-\d{2}/)) {
|
|
321
|
+
return '%Y-%m-%d'
|
|
322
|
+
} else if (str.match(/\d{2}\-\d{2}\-\d{4}/)) {
|
|
323
|
+
return '%m-%d-%Y'
|
|
324
|
+
} else if (str.match(/\d{2}\/\d{2}\/\d{4}/)) {
|
|
325
|
+
return '%m/%d/%Y'
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const getGeoName = (data: { [key: string]: any }[]) => {
|
|
330
|
+
const keys = Object.keys(data[0])
|
|
331
|
+
const lowerStates = states.map(s => s.toLowerCase())
|
|
332
|
+
for (let i = 0; i < data.length; i++) {
|
|
333
|
+
for (let j = 0; j < keys.length; j++) {
|
|
334
|
+
if (lowerStates.includes(`${data[i][keys[j]]}`.toLowerCase())) {
|
|
335
|
+
return keys[j]
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export const updateVegaData = (vegaConfig, newData) => {
|
|
342
|
+
const newConfig = JSON.parse(JSON.stringify(vegaConfig))
|
|
343
|
+
newConfig.data.forEach(d => {
|
|
344
|
+
if (newData[d.name]) {
|
|
345
|
+
d.values = newData[d.name]
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
return newConfig
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const stripVegaData = vegaConfig => {
|
|
352
|
+
const newConfig = JSON.parse(JSON.stringify(vegaConfig))
|
|
353
|
+
newConfig.data.forEach(d => {
|
|
354
|
+
if (d.values?.arcs) {
|
|
355
|
+
d.values.arcs = d.values.arcs.map(a => 0)
|
|
356
|
+
} else if (Array.isArray(d.values)) {
|
|
357
|
+
d.values = d.values.slice(0, 3)
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
return newConfig
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export const getSampleVegaJson = vegaConfig => {
|
|
364
|
+
return JSON.stringify(
|
|
365
|
+
Object.fromEntries(
|
|
366
|
+
vegaConfig.data.filter(d => d.values && !(d.format?.type === 'topojson')).map(d => [d.name, d.values])
|
|
367
|
+
),
|
|
368
|
+
null,
|
|
369
|
+
2
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export const convertVegaConfig = (configType: string, vegaConfig: any, config: any) => {
|
|
374
|
+
delete config.newViz
|
|
375
|
+
|
|
376
|
+
const data = extractCoveData(vegaConfig)
|
|
377
|
+
|
|
378
|
+
config.vegaType = configType
|
|
379
|
+
config.vegaConfig = stripVegaData(vegaConfig)
|
|
380
|
+
|
|
381
|
+
config.dataFileName = 'vega-data.json'
|
|
382
|
+
config.dataFileSourceType = 'file'
|
|
383
|
+
|
|
384
|
+
config.table = config.table || { expanded: false }
|
|
385
|
+
|
|
386
|
+
config.title = vegaConfig.title?.text || ''
|
|
387
|
+
config.showTitle = config.title ? true : false
|
|
388
|
+
|
|
389
|
+
const mainMark = getMainMark(vegaConfig)
|
|
390
|
+
const enterEncoder = mainMark.encode?.enter
|
|
391
|
+
const updateEncoder = mainMark.encode?.update
|
|
392
|
+
|
|
393
|
+
if (config.vegaType === 'Map') {
|
|
394
|
+
const geoName = getGeoName(data)
|
|
395
|
+
const colorData = updateEncoder?.fill || enterEncoder?.fill
|
|
396
|
+
const colorLabel = vegaConfig.legends?.length ? vegaConfig.legends[0].title : ''
|
|
397
|
+
const vegaColorScale = vegaConfig.scales.find(s => s.name === colorData.scale)
|
|
398
|
+
const legendItems = vegaColorScale.domain
|
|
399
|
+
const legendType = vegaColorScale.type === 'ordinal' ? 'category' : 'equalnumber'
|
|
400
|
+
|
|
401
|
+
if (!config.legend) config.legend = {}
|
|
402
|
+
|
|
403
|
+
config.columns ||= {}
|
|
404
|
+
config.columns.geo = {
|
|
405
|
+
name: geoName,
|
|
406
|
+
label: 'Location',
|
|
407
|
+
dataTable: true
|
|
408
|
+
}
|
|
409
|
+
config.columns.primary = {
|
|
410
|
+
name: colorData.field,
|
|
411
|
+
label: colorLabel,
|
|
412
|
+
dataTable: true,
|
|
413
|
+
tooltip: true
|
|
414
|
+
}
|
|
415
|
+
config.legend = {
|
|
416
|
+
type: legendType,
|
|
417
|
+
additionalCategories: [...legendItems],
|
|
418
|
+
categoryValuesOrder: [...legendItems],
|
|
419
|
+
numberOfItems: legendType === 'category' ? legendItems.length : 5,
|
|
420
|
+
position: 'top',
|
|
421
|
+
style: 'gradient',
|
|
422
|
+
subStyle: 'linear blocks',
|
|
423
|
+
hideBorder: true,
|
|
424
|
+
title: colorLabel
|
|
425
|
+
}
|
|
426
|
+
config.color = 'sequential-blue-2(MPX)'
|
|
427
|
+
} else {
|
|
428
|
+
const stack = getStack(vegaConfig)
|
|
429
|
+
const stackField = stack?.field
|
|
430
|
+
const groupMark = getGroupMark(vegaConfig)
|
|
431
|
+
|
|
432
|
+
let xField =
|
|
433
|
+
updateEncoder?.x?.field ||
|
|
434
|
+
enterEncoder?.x?.field ||
|
|
435
|
+
updateEncoder?.x2?.field ||
|
|
436
|
+
enterEncoder?.x2?.field ||
|
|
437
|
+
updateEncoder?.xc?.field ||
|
|
438
|
+
enterEncoder?.xc?.field
|
|
439
|
+
let yField =
|
|
440
|
+
updateEncoder?.y?.field ||
|
|
441
|
+
enterEncoder?.y?.field ||
|
|
442
|
+
updateEncoder?.y2?.field ||
|
|
443
|
+
enterEncoder?.y2?.field ||
|
|
444
|
+
updateEncoder?.yc?.field ||
|
|
445
|
+
enterEncoder?.yc?.field
|
|
446
|
+
|
|
447
|
+
const leftAxis = vegaConfig.axes.sort((a, b) => (a.grid ? 1 : -1)).find(a => a.orient === 'left')
|
|
448
|
+
const bottomAxis = vegaConfig.axes.sort((a, b) => (a.grid ? 1 : -1)).find(a => a.orient === 'bottom')
|
|
449
|
+
|
|
450
|
+
const yScale = vegaConfig.scales?.find(s => s.name === leftAxis?.scale)
|
|
451
|
+
const isHorizontalBar = config.vegaType === 'Bar' && yScale?.type === 'band'
|
|
452
|
+
|
|
453
|
+
if (isHorizontalBar) {
|
|
454
|
+
;[xField, yField] = [yField, xField]
|
|
455
|
+
xField = groupMark?.from?.facet?.groupby || xField
|
|
456
|
+
config.orientation = 'horizontal'
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
yField = stackField || yField
|
|
460
|
+
|
|
461
|
+
const seriesKey = getSeriesKey(vegaConfig, data, xField, yField)
|
|
462
|
+
|
|
463
|
+
const xDateFormat = getXDateFormat(xField, data)
|
|
464
|
+
config.xAxis = config.xAxis || {}
|
|
465
|
+
config.xAxis.dataKey = xField
|
|
466
|
+
config.xAxis.label = (isHorizontalBar ? leftAxis?.title : bottomAxis?.title) || ''
|
|
467
|
+
if (config.vegaType === 'Scatter Plot') {
|
|
468
|
+
config.xAxis.type = 'continuous'
|
|
469
|
+
}
|
|
470
|
+
if (xDateFormat) {
|
|
471
|
+
config.xAxis.type = config.vegaType === 'Bar' ? 'date' : 'date-time'
|
|
472
|
+
config.xAxis.dateParseFormat = xDateFormat
|
|
473
|
+
config.xAxis.dateDisplayFormat = '%b. %Y'
|
|
474
|
+
config.xAxis.showYearsOnce = true
|
|
475
|
+
config.xAxis.label = ''
|
|
476
|
+
|
|
477
|
+
config.tooltips = config.tooltips || {}
|
|
478
|
+
config.tooltips.dateDisplayFormat = '%B %-d, %Y'
|
|
479
|
+
|
|
480
|
+
config.table = config.table || {}
|
|
481
|
+
config.table.dateDisplayFormat = '%B %-d, %Y'
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
config.yAxis = config.yAxis || {}
|
|
485
|
+
config.yAxis.label = (isHorizontalBar ? bottomAxis?.title : leftAxis?.title) || ''
|
|
486
|
+
|
|
487
|
+
if (seriesKey) {
|
|
488
|
+
config.visualizationSubType = stack && getMaxGroupSize(data, stack.groupby) > 1 ? 'stacked' : ''
|
|
489
|
+
|
|
490
|
+
config.dataDescription = {
|
|
491
|
+
horizontal: false,
|
|
492
|
+
series: true,
|
|
493
|
+
singleRow: false,
|
|
494
|
+
seriesKey: seriesKey,
|
|
495
|
+
xKey: xField,
|
|
496
|
+
valueKeysTallSupport: [yField]
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
config.dataDescription = {
|
|
500
|
+
horizontal: false,
|
|
501
|
+
series: false,
|
|
502
|
+
singleRow: true
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (config.vegaType === 'Combo Chart') {
|
|
506
|
+
const comboMarks = getComboMarks(vegaConfig)
|
|
507
|
+
|
|
508
|
+
config.series = comboMarks.map(mark => {
|
|
509
|
+
const enterEncoder = mark.encode.enter
|
|
510
|
+
const updateEncoder = mark.encode.update
|
|
511
|
+
let yField =
|
|
512
|
+
updateEncoder?.y?.field || enterEncoder?.y?.field || updateEncoder?.y2?.field || enterEncoder?.y2?.field
|
|
513
|
+
return {
|
|
514
|
+
dataKey: yField.replace('_end', ''),
|
|
515
|
+
type: COMBO_MARKS[mark.type],
|
|
516
|
+
axis: 'Left',
|
|
517
|
+
tooltip: true
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
} else {
|
|
521
|
+
config.series = [
|
|
522
|
+
{
|
|
523
|
+
dataKey: yField,
|
|
524
|
+
type: config.vegaType,
|
|
525
|
+
axis: 'Left',
|
|
526
|
+
tooltip: true
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
config.legend = {
|
|
533
|
+
hide: !seriesKey && config.vegaType !== 'Combo Chart',
|
|
534
|
+
label: seriesKey,
|
|
535
|
+
position: 'top'
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const interpolateValue = updateEncoder?.interpolate?.value || enterEncoder?.interpolate?.value
|
|
539
|
+
if (config.vegaType == 'Area Chart' && interpolateValue) {
|
|
540
|
+
config.stackedAreaChartLineType = CURVE_LOOKUP[interpolateValue]
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
Object.assign(config.yAxis, {
|
|
544
|
+
size: 60,
|
|
545
|
+
gridLines: true,
|
|
546
|
+
hideAxis: !isHorizontalBar,
|
|
547
|
+
hideTicks: true
|
|
548
|
+
})
|
|
549
|
+
Object.assign(config.xAxis, {
|
|
550
|
+
size: isHorizontalBar ? 30 : null
|
|
551
|
+
})
|
|
552
|
+
Object.assign(config, {
|
|
553
|
+
isolatedDotsSameSize: true,
|
|
554
|
+
barThickness: 0.8
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (data) {
|
|
559
|
+
config.data = data
|
|
560
|
+
config = loadedVegaConfigData(config)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return config
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export const loadedVegaConfigData = (config: any) => {
|
|
567
|
+
const seriesKey = config.dataDescription?.seriesKey
|
|
568
|
+
if (seriesKey) {
|
|
569
|
+
if (!config.series) {
|
|
570
|
+
const seriesVals = [...new Set(config.data.map(d => d[seriesKey]))].sort((a, b) => (a > b ? 1 : -1))
|
|
571
|
+
config.series = seriesVals.map(val => {
|
|
572
|
+
return {
|
|
573
|
+
dataKey: val,
|
|
574
|
+
type: config.vegaType,
|
|
575
|
+
axis: 'Left',
|
|
576
|
+
tooltip: true
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
}
|
|
580
|
+
config.data.forEach(d => {
|
|
581
|
+
d[seriesKey] = `${d[seriesKey]}`
|
|
582
|
+
})
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (config.dataDescription) {
|
|
586
|
+
const transform = new DataTransform()
|
|
587
|
+
config.data = transform.autoStandardize(config.data)
|
|
588
|
+
config.data = transform.developerStandardize(config.data, config.dataDescription)
|
|
589
|
+
config.formattedData = config.data
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return config
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export const states = [
|
|
596
|
+
'Alabama',
|
|
597
|
+
'Alaska',
|
|
598
|
+
'Arizona',
|
|
599
|
+
'Arkansas',
|
|
600
|
+
'California',
|
|
601
|
+
'Colorado',
|
|
602
|
+
'Connecticut',
|
|
603
|
+
'District of Columbia',
|
|
604
|
+
'Delaware',
|
|
605
|
+
'Florida',
|
|
606
|
+
'Georgia',
|
|
607
|
+
'Hawaii',
|
|
608
|
+
'Idaho',
|
|
609
|
+
'Illinois',
|
|
610
|
+
'Indiana',
|
|
611
|
+
'Iowa',
|
|
612
|
+
'Kansas',
|
|
613
|
+
'Kentucky',
|
|
614
|
+
'Louisiana',
|
|
615
|
+
'Maine',
|
|
616
|
+
'Maryland',
|
|
617
|
+
'Massachusetts',
|
|
618
|
+
'Michigan',
|
|
619
|
+
'Minnesota',
|
|
620
|
+
'Mississippi',
|
|
621
|
+
'Missouri',
|
|
622
|
+
'Montana',
|
|
623
|
+
'Nebraska',
|
|
624
|
+
'Nevada',
|
|
625
|
+
'New Hampshire',
|
|
626
|
+
'New Jersey',
|
|
627
|
+
'New Mexico',
|
|
628
|
+
'New York',
|
|
629
|
+
'North Carolina',
|
|
630
|
+
'North Dakota',
|
|
631
|
+
'Ohio',
|
|
632
|
+
'Oklahoma',
|
|
633
|
+
'Oregon',
|
|
634
|
+
'Pennsylvania',
|
|
635
|
+
'Rhode Island',
|
|
636
|
+
'South Carolina',
|
|
637
|
+
'South Dakota',
|
|
638
|
+
'Tennessee',
|
|
639
|
+
'Texas',
|
|
640
|
+
'Utah',
|
|
641
|
+
'Vermont',
|
|
642
|
+
'Virginia',
|
|
643
|
+
'Washington',
|
|
644
|
+
'West Virginia',
|
|
645
|
+
'Wisconsin',
|
|
646
|
+
'Wyoming'
|
|
647
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
export const updatePreliminaryDataSeriesKeys = config => {
|
|
4
|
+
if (config.type === 'chart') {
|
|
5
|
+
;(config.preliminaryData || []).forEach(pd => {
|
|
6
|
+
if (!pd.seriesKeys) {
|
|
7
|
+
pd.seriesKeys = pd.seriesKey ? [pd.seriesKey] : []
|
|
8
|
+
delete pd.seriesKey
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
} else if (config.type === 'dashboard') {
|
|
12
|
+
Object.values(config.visualizations).forEach(visualization => {
|
|
13
|
+
updatePreliminaryDataSeriesKeys(visualization)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const update_4_25_7 = config => {
|
|
19
|
+
const ver = '4.25.7'
|
|
20
|
+
const newConfig = _.cloneDeep(config)
|
|
21
|
+
updatePreliminaryDataSeriesKeys(newConfig)
|
|
22
|
+
newConfig.version = ver
|
|
23
|
+
return newConfig
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default update_4_25_7
|