@cdc/core 4.25.6 → 4.25.7

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.
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/core",
3
- "version": "4.25.6",
3
+ "version": "4.25.7",
4
4
  "description": "Core components, styles, hooks, and helpers, for the CDC Open Visualization project",
5
5
  "moduleName": "CdcCore",
6
6
  "main": "dist/cdccore",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "license": "Apache-2.0",
23
23
  "dependencies": {
24
- "dompurify": "^3.2.6",
24
+ "dompurify": "^3.2.4",
25
25
  "html2canvas": "^1.4.1",
26
26
  "json-edit-react": "^1.27.0",
27
27
  "prop-types": "^15.8.1",
@@ -29,13 +29,15 @@
29
29
  "react-select": "^5.3.1",
30
30
  "react-tooltip": "5.8.2-beta.3",
31
31
  "sass": "^1.89.2",
32
- "use-debounce": "^10.0.5"
32
+ "use-debounce": "^10.0.5",
33
+ "vega": "^6.1.0",
34
+ "vega-lite": "^6.1.0"
33
35
  },
34
36
  "peerDependencies": {
35
37
  "react": "^18.2.0",
36
38
  "react-dom": "^18.2.0"
37
39
  },
38
- "gitHead": "6097de1ff814001880d9ac64bd66becdc092d63c",
40
+ "gitHead": "9062881d50c824ee6cdd71868bafd016a5e5694d",
39
41
  "devDependencies": {
40
42
  "sass": "^1.79.4"
41
43
  }