@cdc/core 4.23.5 → 4.23.6

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 @@
1
+ <?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96.31 82.55"><path d="M18.29,39.57c0-1.03,.23-1.79,.68-2.28,.45-.49,1.06-.74,1.83-.74s1.42,.25,1.87,.74c.45,.49,.68,1.25,.68,2.29s-.23,1.79-.68,2.28c-.45,.5-1.06,.74-1.83,.74s-1.42-.25-1.87-.74c-.45-.49-.68-1.25-.68-2.29Zm1.71-.02c0,.75,.09,1.25,.26,1.5,.13,.19,.31,.28,.54,.28s.42-.09,.55-.28c.17-.25,.25-.75,.25-1.5s-.08-1.24-.25-1.49c-.13-.19-.31-.29-.55-.29s-.41,.09-.54,.28c-.17,.26-.26,.76-.26,1.5Zm2.47,9.08h-1.62l6.1-12.09h1.58l-6.05,12.09Zm3.55-3.01c0-1.03,.23-1.79,.68-2.28,.45-.49,1.07-.74,1.85-.74s1.4,.25,1.86,.74c.45,.5,.68,1.26,.68,2.28s-.23,1.79-.68,2.29c-.45,.49-1.06,.74-1.83,.74s-1.42-.25-1.87-.74c-.45-.49-.68-1.26-.68-2.29Zm1.71,0c0,.75,.09,1.24,.26,1.49,.13,.19,.31,.29,.54,.29s.41-.09,.54-.28c.17-.25,.26-.75,.26-1.5s-.08-1.24-.25-1.5c-.13-.19-.31-.28-.55-.28s-.41,.09-.54,.28c-.17,.26-.26,.76-.26,1.5Z"/><path d="M92.33,52.83v-20.42c0-1.61-1.31-2.93-2.93-2.93H6.65c-1.61,0-2.93,1.31-2.93,2.93v20.42c0,1.61,1.31,2.93,2.93,2.93H89.4c1.61,0,2.93-1.31,2.93-2.93Zm-52.34-2.93H9.58v-14.56h30.41v14.56Zm5.86-14.56h40.63v14.56H45.85v-14.56Z"/></svg>
@@ -8,7 +8,12 @@ export const AdvancedEditor = ({ loadConfig, state, convertStateToConfig }) => {
8
8
  const [configTextboxValue, setConfigTextbox] = useState({})
9
9
 
10
10
  useEffect(() => {
11
- const parsedData = convertStateToConfig()
11
+ let parsedData = state
12
+ if (state.type !== 'dashboard') {
13
+ parsedData = convertStateToConfig()
14
+ } else {
15
+ parsedData = JSON.parse(JSON.stringify(parsedData))
16
+ }
12
17
  const formattedData = JSON.stringify(parsedData, undefined, 2)
13
18
 
14
19
  setConfigTextbox(formattedData)
@@ -16,6 +21,7 @@ export const AdvancedEditor = ({ loadConfig, state, convertStateToConfig }) => {
16
21
 
17
22
  const typeLookup = {
18
23
  chart: ['Charts', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
24
+ dashboard: ['Dashboard', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html', <ChartIcon />],
19
25
  map: ['Maps', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html', <MapIcon />],
20
26
  'markup-include': ['Markup Include', 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html', <MarkupIncludeIcon />]
21
27
  }
@@ -1,22 +1,38 @@
1
1
  import React, { useEffect, useState, memo, useMemo } from 'react'
2
2
 
3
3
  import Papa from 'papaparse'
4
- import ExternalIcon from '../assets/external-link.svg' // TODO: Move to Icon component
4
+ import ExternalIcon from '../assets/external-link.svg'
5
5
  import Icon from '@cdc/core/components/ui/Icon'
6
6
 
7
7
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
8
8
  import LegendCircle from '@cdc/core/components/LegendCircle'
9
- import CoveMediaControls from '@cdc/core/components/CoveMediaControls'
9
+ import MediaControls from '@cdc/core/components/MediaControls'
10
+
11
+ import { parseDate, formatDate } from '@cdc/core/helpers/cove/date'
12
+ import { formatNumber } from '@cdc/core/helpers/cove/number'
10
13
 
11
14
  import Loading from '@cdc/core/components/Loading'
12
15
 
16
+ // FILE REVIEW
17
+ // TODO: Remove eslint-disable jsx/a11y/non-interactive-tabindex and handle appropriately
18
+ // TODO: Move ExternalIcon to core Icon component
19
+ // TODO: use destructuring
20
+ // TODO: @tturnerswdev33 - It looks like there's an unused variable setFilteredCountryCode that was added
21
+ // TODO: @tturnerswdev33 - change function declarations to arrow functions
22
+ // TODO: @tturnerswdev33 - move caption so that useMemo is not rendered conditionally
23
+
13
24
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
14
25
  const DataTable = props => {
15
- const { config, tableTitle, indexTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, displayDataAsText, formatNumber, applyLegendToRow, displayGeoName, navigationHandler, viewport, formatLegendLocation, tabbingId, parseDate, formatDate, isDebug } = props
16
- if (isDebug) console.log('core/DataTable: props=', props)
17
- if (isDebug) console.log('core/DataTable: runtimeData=', runtimeData)
18
- if (isDebug) console.log('core/DataTable: rawData=', rawData)
19
- if (isDebug) console.log('core/DataTable: config=', config)
26
+ const { config, tableTitle, indexTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, displayDataAsText, applyLegendToRow, displayGeoName, navigationHandler, viewport, formatLegendLocation, tabbingId, isDebug } = props
27
+
28
+ /* eslint-disable no-console */
29
+ if (isDebug) {
30
+ console.log('core/DataTable: props=', props)
31
+ console.log('core/DataTable: runtimeData=', runtimeData)
32
+ console.log('core/DataTable: rawData=', rawData)
33
+ console.log('core/DataTable: config=', config)
34
+ }
35
+ /* eslint-enable no-console */
20
36
 
21
37
  const [expanded, setExpanded] = useState(expandDataTable)
22
38
 
@@ -137,7 +153,7 @@ const DataTable = props => {
137
153
  const DownloadButton = memo(() => {
138
154
  if (rawData !== undefined) {
139
155
  let csvData
140
- if (config.type === 'chart' || config.general.type === 'bubble') {
156
+ if (config.type === 'chart' || config.general.type === 'bubble' || !config.table.showFullGeoNameInCSV) {
141
157
  // Just Unparse
142
158
  csvData = Papa.unparse(rawData)
143
159
  } else if ((config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') || config.general.type === 'us-geocode') {
@@ -299,12 +315,17 @@ const DataTable = props => {
299
315
  }
300
316
  }
301
317
 
302
- function genChartHeader(columns, data) {
318
+ const genChartHeader = (columns, data) => {
303
319
  return (
304
320
  <tr>
305
321
  {dataSeriesColumns().map(column => {
306
322
  let custLabel = getLabel(column) ? getLabel(column) : column
307
323
  let text = column === config.xAxis.dataKey ? config.table.indexLabel : custLabel
324
+
325
+ // If a user sets the name on a series use that.
326
+ let userUpdatedSeriesName = config.series.filter(series => series.dataKey === column)?.[0]?.name
327
+ if (userUpdatedSeriesName) text = userUpdatedSeriesName
328
+
308
329
  return (
309
330
  <th
310
331
  key={`col-header-${column}`}
@@ -334,38 +355,44 @@ const DataTable = props => {
334
355
  )
335
356
  }
336
357
 
337
- function genChartRows(rows) {
338
- const allrows = rows.map(row => {
358
+ const genChartRows = rows => {
359
+ const allRows = rows.map(row => {
339
360
  return (
340
361
  <tr role='row'>
341
- {dataSeriesColumns()
342
- //.filter(column => columns[column].dataTable === true && columns[column].name)
343
- .map(column => {
344
- let cellValue
345
- if (column === config.xAxis.dataKey) {
346
- const rowObj = runtimeData[row]
347
- //const legendColor = applyLegendToRow(rowObj)
348
- var labelValue = rowObj[column] // just raw X axis string
349
- labelValue = getCellAnchor(labelValue, rowObj)
350
- // no colors on row headers for charts bc it's Date not data
351
- // Remove this - <LegendCircle fill={legendColor[row]} />
352
- cellValue = <>{labelValue}</>
353
- } else {
354
- cellValue = displayDataAsText(runtimeData[row][column], column)
355
- }
362
+ {dataSeriesColumns().map(column => {
363
+ const rowObj = runtimeData[row]
364
+ let cellValue // placeholder for formatting below
365
+ let labelValue = rowObj[column] // just raw X axis string
366
+ if (column === config.xAxis.dataKey) {
367
+ // not the prettiest, but helper functions work nicely here.
368
+ cellValue = <>{config.xAxis.type === 'date' ? formatDate(config.xAxis.dateDisplayFormat, parseDate(config.xAxis.dateParseFormat, labelValue)) : labelValue}</>
369
+ } else {
370
+ let resolvedAxis = ''
371
+ let leftAxisItems = config.series.filter(item => item?.axis === 'Left')
372
+ let rightAxisItems = config.series.filter(item => item?.axis === 'Right')
373
+ console.log('column', column)
374
+
375
+ leftAxisItems.map(leftSeriesItem => {
376
+ if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
377
+ })
378
+
379
+ rightAxisItems.map(rightSeriesItem => {
380
+ if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
381
+ })
382
+
383
+ cellValue = formatNumber(runtimeData[row][column], resolvedAxis, true, config)
384
+ }
356
385
 
357
- //MAP SPECIFIC- change to CHART specific
358
- // onClick = { e => (config.general.type === 'bubble' && config.general.allowMapZoom && config.general.geoType === 'world' ? setFilteredCountryCode(row) : true)}
359
- return (
360
- <td tabIndex='0' role='gridcell' id={`${runtimeData[config.runtime.originalXAxis.dataKey]}--${row}`}>
361
- {cellValue}
362
- </td>
363
- )
364
- })}
386
+ return (
387
+ <td tabIndex='0' role='gridcell' id={`${runtimeData[config.runtime.originalXAxis.dataKey]}--${row}`}>
388
+ {cellValue}
389
+ </td>
390
+ )
391
+ })}
365
392
  </tr>
366
393
  )
367
394
  })
368
- return allrows
395
+ return allRows
369
396
  }
370
397
 
371
398
  const limitHeight = {
@@ -437,10 +464,10 @@ const DataTable = props => {
437
464
 
438
465
  return (
439
466
  <ErrorBoundary component='DataTable'>
440
- <CoveMediaControls.Section classes={['download-links']}>
441
- <CoveMediaControls.Link config={config} />
467
+ <MediaControls.Section classes={['download-links']}>
468
+ <MediaControls.Link config={config} />
442
469
  {(config.table.download || config.general.showDownloadButton) && <DownloadButton />}
443
- </CoveMediaControls.Section>
470
+ </MediaControls.Section>
444
471
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
445
472
  <a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
446
473
  Skip Navigation or Skip to Content
@@ -590,10 +617,10 @@ const DataTable = props => {
590
617
  return (
591
618
  <ErrorBoundary component='DataTable'>
592
619
  {/* cove media results in error so disabling for now (TT)
593
- <CoveMediaControls.Section classes={['download-links']}>
594
- <CoveMediaControls.Link config={config} />
620
+ <MediaControls.Section classes={['download-links']}>
621
+ <MediaControls.Link config={config} />
595
622
  {config.general.showDownloadButton && <DownloadButton />}
596
- </CoveMediaControls.Section>
623
+ </MediaControls.Section>
597
624
  */}
598
625
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
599
626
  <a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
@@ -312,6 +312,8 @@ const Filters = props => {
312
312
  delete filtersToLoop.fromHash
313
313
 
314
314
  return filtersToLoop.map((singleFilter, outerIndex) => {
315
+ if(singleFilter.showDropdown === false) return
316
+
315
317
  const values = []
316
318
  const pillValues = []
317
319
  const tabValues = []
@@ -134,11 +134,11 @@ const Section = ({ children, classes }) => {
134
134
  )
135
135
  }
136
136
 
137
- const CoveMediaControls = () => null
137
+ const MediaControls = () => null
138
138
 
139
- CoveMediaControls.Section = Section
140
- CoveMediaControls.Link = Link
141
- CoveMediaControls.Button = Button
142
- CoveMediaControls.generateMedia = generateMedia
139
+ MediaControls.Section = Section
140
+ MediaControls.Link = Link
141
+ MediaControls.Button = Button
142
+ MediaControls.generateMedia = generateMedia
143
143
 
144
- export default CoveMediaControls
144
+ export default MediaControls
@@ -0,0 +1,37 @@
1
+ import React from 'react'
2
+ import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
3
+ import { Draggable } from '@hello-pangea/dnd'
4
+ import Icon from '@cdc/core/components/ui/Icon'
5
+
6
+ const RepeatableGroupSection = props => {
7
+ return <div className='repeatable-group'>{props.children}</div>
8
+ }
9
+
10
+ export const RepeatableGroupItem = props => {
11
+ const { item, index, title } = props
12
+
13
+ return (
14
+ <Draggable key={item} draggableId={`draggable-repeatable-item-${item}`} index={index}>
15
+ {(provided, snapshot) => (
16
+ <div key={index} className={snapshot.isDragging ? 'currently-dragging' : ''} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
17
+ <Accordion allowZeroExpanded>
18
+ <AccordionItem className='series-item series-item--chart'>
19
+ <AccordionItemHeading className='series-item__title'>
20
+ <AccordionItemButton className={''}>
21
+ <Icon display='move' size={15} style={{ cursor: 'default' }} />
22
+ Title Section
23
+ </AccordionItemButton>
24
+ </AccordionItemHeading>
25
+ <AccordionItemPanel>panel section to be built</AccordionItemPanel>
26
+ </AccordionItem>
27
+ </Accordion>
28
+ </div>
29
+ )}
30
+ </Draggable>
31
+ )
32
+ }
33
+
34
+ export const RepeatableGroup = {
35
+ Section: RepeatableGroupSection,
36
+ Item: RepeatableGroupItem
37
+ }
@@ -33,7 +33,7 @@ const InputSelect = memo(({ label, value, options, fieldName, section = null, su
33
33
  }
34
34
 
35
35
  return (
36
- <label>
36
+ <label style={{ width: '100%', display: 'block' }}>
37
37
  {label && <span className='edit-label cove-input__label'>{label}</span>}
38
38
  <select
39
39
  className={required && !value ? 'warning' : ''}
@@ -21,6 +21,7 @@ const Accordion = ({ children }) => {
21
21
  <AccordionItem className='cove-accordion__item' key={index}>
22
22
  <AccordionItemHeading className='cove-accordion__heading'>
23
23
  <AccordionItemButton className='cove-accordion__button'>
24
+ {section.props.icon}
24
25
  {section.props.title}
25
26
  {section.props.tooltipText ? (
26
27
  <Tooltip position='bottom'>
@@ -43,6 +44,8 @@ const Accordion = ({ children }) => {
43
44
  Accordion.Section = AccordionSection
44
45
 
45
46
  Accordion.Section.propTypes = {
47
+ /* Icon for the accordion label */
48
+ icon: PropTypes.node,
46
49
  /* Title for the accordion label*/
47
50
  title: PropTypes.string,
48
51
  /* Tooltip for the accordion label*/
@@ -11,109 +11,109 @@ export class DataTransform {
11
11
 
12
12
  //Performs standardizations that can be completed automatically without use input
13
13
  autoStandardize(data) {
14
- const errorsFound = [];
14
+ const errorsFound = []
15
15
 
16
16
  // Empty data
17
17
  if (0 === data.length) {
18
- errorsFound.push(this.constants.errorMessageEmptyData);
18
+ errorsFound.push(this.constants.errorMessageEmptyData)
19
19
  }
20
20
 
21
21
  // Does it have the correct data structure?
22
22
  if (!data.filter || data.filter(row => typeof row !== 'object').length > 0) {
23
- errorsFound.push(this.constants.errorMessageFormat);
23
+ errorsFound.push(this.constants.errorMessageFormat)
24
24
  }
25
25
 
26
26
  if (errorsFound.length > 0) {
27
- console.error(errorsFound);
28
- return undefined;
27
+ console.error(errorsFound)
28
+ return undefined
29
29
  }
30
30
 
31
31
  //Convert array of arrays, to array of objects
32
32
  if (data.filter(row => row.constructor !== Object).length > 0) {
33
- let standardizedData = [];
33
+ let standardizedData = []
34
34
  for (let row = 1; row < data.length; row++) {
35
- let standardizedRow = {};
35
+ let standardizedRow = {}
36
36
  data[row].forEach((datum, col) => {
37
- standardizedRow[data[0][col]] = datum;
37
+ standardizedRow[data[0][col]] = datum
38
38
  })
39
- standardizedData.push(standardizedRow);
39
+ standardizedData.push(standardizedRow)
40
40
  }
41
- data = standardizedData;
41
+ data = standardizedData
42
42
  }
43
43
 
44
- return data;
44
+ return data
45
45
  }
46
46
 
47
47
  //Performs standardizations based on developer provided description of the data
48
48
  developerStandardize(data, description) {
49
49
  //Validate the description object
50
50
  if (!description) {
51
- return undefined;
51
+ return undefined
52
52
  }
53
53
 
54
54
  if (description.horizontal === undefined || description.series === undefined) {
55
- return undefined;
55
+ return undefined
56
56
  }
57
57
 
58
58
  if (description.series === true && description.horizontal === false && description.singleRow === undefined) {
59
- return undefined;
59
+ return undefined
60
60
  }
61
61
 
62
62
  if (description.horizontal === true) {
63
63
  if (description.series === true) {
64
64
  if (!description.seriesKey) {
65
- return undefined;
65
+ return undefined
66
66
  }
67
67
 
68
- let standardizedMapped = {};
69
- let standardized = [];
68
+ let standardizedMapped = {}
69
+ let standardized = []
70
70
  data.forEach(row => {
71
- let nonNumericKeys = [];
71
+ let nonNumericKeys = []
72
72
  Object.keys(row).forEach(key => {
73
73
  if (key !== description.seriesKey && isNaN(parseFloat(row[key]))) {
74
- nonNumericKeys.push(key);
74
+ nonNumericKeys.push(key)
75
75
  }
76
76
  })
77
77
 
78
78
  Object.keys(row).forEach(key => {
79
79
  if (key !== description.seriesKey && nonNumericKeys.indexOf(key) === -1) {
80
- let uniqueKey = key + '|' + nonNumericKeys.map(nonNumericKey => nonNumericKey + '=' + row[nonNumericKey]);
80
+ let uniqueKey = key + '|' + nonNumericKeys.map(nonNumericKey => nonNumericKey + '=' + row[nonNumericKey])
81
81
  if (!standardizedMapped[uniqueKey]) {
82
- standardizedMapped[uniqueKey] = { [row[description.seriesKey]]: row[key], key };
82
+ standardizedMapped[uniqueKey] = { [row[description.seriesKey]]: row[key], key }
83
83
  nonNumericKeys.forEach(nonNumericKey => {
84
- standardizedMapped[uniqueKey][nonNumericKey] = row[nonNumericKey];
84
+ standardizedMapped[uniqueKey][nonNumericKey] = row[nonNumericKey]
85
85
  })
86
86
  }
87
- standardizedMapped[uniqueKey][row[description.seriesKey]] = row[key];
87
+ standardizedMapped[uniqueKey][row[description.seriesKey]] = row[key]
88
88
  }
89
89
  })
90
90
  })
91
91
 
92
92
  Object.keys(standardizedMapped).forEach(key => {
93
- standardized.push(standardizedMapped[key]);
93
+ standardized.push(standardizedMapped[key])
94
94
  })
95
95
 
96
- return standardized;
96
+ return standardized
97
97
  } else {
98
- let standardized = [];
98
+ let standardized = []
99
99
 
100
100
  data.forEach(row => {
101
- let nonNumericKeys = [];
101
+ let nonNumericKeys = []
102
102
  Object.keys(row).forEach(key => {
103
103
  if (isNaN(parseFloat(row[key]))) {
104
- nonNumericKeys.push(key);
104
+ nonNumericKeys.push(key)
105
105
  }
106
106
  })
107
107
 
108
108
  Object.keys(row).forEach(key => {
109
109
  if (nonNumericKeys.indexOf(key) === -1) {
110
- let newRow = { key, value: row[key] };
110
+ let newRow = { key, value: row[key] }
111
111
 
112
112
  nonNumericKeys.forEach(nonNumericKey => {
113
- newRow[nonNumericKey] = row[nonNumericKey];
113
+ newRow[nonNumericKey] = row[nonNumericKey]
114
114
  })
115
115
 
116
- standardized.push(newRow);
116
+ standardized.push(newRow)
117
117
  }
118
118
  })
119
119
  })
@@ -122,43 +122,43 @@ export class DataTransform {
122
122
  }
123
123
  } else if (description.series === true && description.singleRow === false) {
124
124
  if (description.seriesKey !== undefined && description.xKey !== undefined && (description.valueKey !== undefined || (description.valueKeys !== undefined && description.valueKeys.length > 0))) {
125
- if(description.valueKeys !== undefined){
126
- let standardizedMapped = {};
127
- let standardized = [];
128
- let valueKeys = description.valueKeys;
129
- if(description.ignoredKeys && description.ignoredKeys.length > 0){
130
- valueKeys = valueKeys.concat(description.ignoredKeys);
125
+ if (description.valueKeys !== undefined) {
126
+ let standardizedMapped = {}
127
+ let standardized = []
128
+ let valueKeys = description.valueKeys
129
+ if (description.ignoredKeys && description.ignoredKeys.length > 0) {
130
+ valueKeys = valueKeys.concat(description.ignoredKeys)
131
131
  }
132
132
 
133
133
  data.forEach(row => {
134
134
  valueKeys.forEach(valueKey => {
135
- let extraKeys = [];
136
- let uniqueKey = row[description.xKey] + '|' + valueKey;
135
+ let extraKeys = []
136
+ let uniqueKey = row[description.xKey] + '|' + valueKey
137
137
  Object.keys(row).forEach(key => {
138
138
  if (key !== description.xKey && key !== description.seriesKey && valueKeys.indexOf(key) === -1) {
139
- uniqueKey += '|' + key + '=' + row[key];
140
- extraKeys.push(key);
139
+ uniqueKey += '|' + key + '=' + row[key]
140
+ extraKeys.push(key)
141
141
  }
142
142
  })
143
143
 
144
- if(!standardizedMapped[uniqueKey]){
145
- standardizedMapped[uniqueKey] = { [description.xKey]: row[description.xKey], '**Numeric Value Property**': valueKey };
144
+ if (!standardizedMapped[uniqueKey]) {
145
+ standardizedMapped[uniqueKey] = { [description.xKey]: row[description.xKey], '**Numeric Value Property**': valueKey }
146
146
  extraKeys.forEach(key => {
147
- standardizedMapped[uniqueKey][key] = row[key];
147
+ standardizedMapped[uniqueKey][key] = row[key]
148
148
  })
149
149
  }
150
150
 
151
- standardizedMapped[uniqueKey][row[description.seriesKey]] = row[valueKey];
152
- });
151
+ standardizedMapped[uniqueKey][row[description.seriesKey]] = row[valueKey]
152
+ })
153
153
  })
154
154
 
155
155
  Object.keys(standardizedMapped).forEach(key => {
156
- if(!description.ignoredKeys || description.ignoredKeys.indexOf(standardizedMapped[key]['**Numeric Value Property**']) === -1){
157
- standardized.push(standardizedMapped[key]);
156
+ if (!description.ignoredKeys || description.ignoredKeys.indexOf(standardizedMapped[key]['**Numeric Value Property**']) === -1) {
157
+ standardized.push(standardizedMapped[key])
158
158
  }
159
159
  })
160
160
 
161
- return standardized;
161
+ return standardized
162
162
  } else {
163
163
  let standardizedMapped = {}
164
164
  let standardized = []
@@ -190,14 +190,14 @@ export class DataTransform {
190
190
  return standardized
191
191
  }
192
192
  } else {
193
- return undefined;
193
+ return undefined
194
194
  }
195
195
  }
196
196
 
197
- return data;
197
+ return data
198
198
  }
199
199
 
200
- /**
200
+ /**
201
201
  * cleanData
202
202
  *
203
203
  // This cleans a data set by:
@@ -207,21 +207,21 @@ export class DataTransform {
207
207
  *
208
208
  * Inputs: data as array, excludeKey indicates which key to use to NOT clean
209
209
  * Example: "Date" should not be cleaned if part of the data
210
- *
210
+ *
211
211
  * Output: returns the cleanedData
212
- *
212
+ *
213
213
  * Set testing = true if you need to see before and after data
214
- *
214
+ *
215
215
  */
216
- cleanData (data, excludeKey, testing = false) {
216
+ cleanData(data, excludeKey, testing = false) {
217
217
  let cleanedupData = []
218
218
  if (testing) console.log('## Data to clean=', data)
219
219
  if (excludeKey === undefined) {
220
220
  console.log('COVE: cleanData excludeKey undefined')
221
- return data // because no excludeKey
221
+ return data // because no excludeKey
222
222
  }
223
223
  data.forEach(function (d, i) {
224
- if (testing) console.log("clean", i, " d", d);
224
+ if (testing) console.log('clean', i, ' d', d)
225
225
  let cleaned = {}
226
226
  Object.keys(d).forEach(function (key) {
227
227
  if (key === excludeKey) {
@@ -229,27 +229,27 @@ export class DataTransform {
229
229
  cleaned[key] = d[key]
230
230
  } else {
231
231
  // remove comma and dollar signs
232
- if (testing) console.log("typeof d[key] is ", typeof d[key]);
233
- let tmp = "";
232
+ if (testing) console.log('typeof d[key] is ', typeof d[key])
233
+ let tmp = ''
234
234
  if (typeof d[key] === 'string') {
235
235
  tmp = d[key] !== null && d[key] !== '' ? d[key].replace(/[,\$]/g, '') : ''
236
236
  } else {
237
- tmp = d[key] !== null && d[key] !== '' ? d[key] : ''
237
+ tmp = d[key] !== null && d[key] !== '' ? d[key] : ''
238
238
  }
239
239
  if ((tmp !== '' && tmp !== null && !isNaN(tmp)) || (tmp !== '' && tmp !== null && /\d+\.?\d*/.test(tmp))) {
240
240
  cleaned[key] = tmp
241
- } else { cleaned[key] = '' }
241
+ } else {
242
+ cleaned[key] = ''
243
+ }
242
244
  // if you get here, then return nothing to skip bad data point
243
245
  }
244
246
  })
245
- if (testing) console.log("cleaned=", cleaned)
247
+ if (testing) console.log('cleaned=', cleaned)
246
248
  cleanedupData.push(cleaned)
247
249
  })
248
250
  if (testing) console.log('## cleanedData =', cleanedupData)
249
251
  return cleanedupData
250
252
  }
251
-
252
-
253
253
  }
254
254
 
255
255
  export default DataTransform
@@ -0,0 +1,9 @@
1
+ import { timeFormat, timeParse } from 'd3-time-format'
2
+
3
+ export function formatDate(format = undefined, date) {
4
+ return timeFormat(format)(date)
5
+ }
6
+
7
+ export function parseDate(format = undefined, dateString) {
8
+ return timeParse(format)(dateString) || new Date()
9
+ }
@@ -0,0 +1,139 @@
1
+ import numberFromString from '@cdc/core/helpers/numberFromString'
2
+
3
+ const abbreviateNumber = num => {
4
+ let unit = ''
5
+ let absNum = Math.abs(num)
6
+
7
+ if (absNum >= 1e9) {
8
+ unit = 'B'
9
+ num = num / 1e9
10
+ } else if (absNum >= 1e6) {
11
+ unit = 'M'
12
+ num = num / 1e6
13
+ } else if (absNum >= 1e3) {
14
+ unit = 'K'
15
+ num = num / 1e3
16
+ }
17
+
18
+ return num + unit
19
+ }
20
+
21
+ // Format numeric data based on settings in config
22
+ const formatNumber = (num, axis, shouldAbbreviate = false, config = null) => {
23
+ if (!config) console.error('no config found in formatNumber')
24
+ // if num is NaN return num
25
+ if (isNaN(num) || !num) return num
26
+ // Check if the input number is negative
27
+ const isNegative = num < 0
28
+
29
+ // If the input number is negative, take the absolute value
30
+ if (isNegative) {
31
+ num = Math.abs(num)
32
+ }
33
+
34
+ // destructure dataFormat values
35
+ let {
36
+ dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, bottomRoundTo, rightPrefix, rightSuffix, bottomPrefix, bottomSuffix, bottomAbbreviated }
37
+ } = config
38
+
39
+ // check if value contains comma and remove it. later will add comma below.
40
+ if (String(num).indexOf(',') !== -1) num = num.replaceAll(',', '')
41
+
42
+ let original = num
43
+ let stringFormattingOptions
44
+ if (axis === 'left') {
45
+ stringFormattingOptions = {
46
+ useGrouping: config.dataFormat.commas ? true : false,
47
+ minimumFractionDigits: roundTo ? Number(roundTo) : 0,
48
+ maximumFractionDigits: roundTo ? Number(roundTo) : 0
49
+ }
50
+ }
51
+
52
+ if (axis === 'right') {
53
+ stringFormattingOptions = {
54
+ useGrouping: config.dataFormat.rightCommas ? true : false,
55
+ minimumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0,
56
+ maximumFractionDigits: rightRoundTo ? Number(rightRoundTo) : 0
57
+ }
58
+ }
59
+
60
+ if (axis === 'bottom') {
61
+ stringFormattingOptions = {
62
+ useGrouping: config.dataFormat.bottomCommas ? true : false,
63
+ minimumFractionDigits: bottomRoundTo ? Number(bottomRoundTo) : 0,
64
+ maximumFractionDigits: bottomRoundTo ? Number(bottomRoundTo) : 0
65
+ }
66
+ }
67
+
68
+ num = numberFromString(num)
69
+
70
+ if (isNaN(num)) {
71
+ config.runtime.editorErrorMessage = `Unable to parse number from data ${original}. Try reviewing your data and selections in the Data Series section.`
72
+ return original
73
+ }
74
+
75
+ if (!config.dataFormat) return num
76
+ if (config.dataCutoff) {
77
+ let cutoff = numberFromString(config.dataCutoff)
78
+
79
+ if (num < cutoff) {
80
+ num = cutoff
81
+ }
82
+ }
83
+
84
+ // When we're formatting the left axis
85
+ // Use commas also updates bars and the data table
86
+ // We can't use commas when we're formatting the dataFormatted number
87
+ // Example: commas -> 12,000; abbreviated -> 12k (correct); abbreviated & commas -> 12 (incorrect)
88
+ //
89
+ // Edge case for small numbers with decimals
90
+ // - if roundTo undefined which means it is blank, then do not round
91
+
92
+ if ((axis === 'left' && commas && abbreviated && shouldAbbreviate) || (axis === 'bottom' && commas && abbreviated && shouldAbbreviate)) {
93
+ num = num // eslint-disable-line
94
+ } else {
95
+ num = num.toLocaleString('en-US', stringFormattingOptions)
96
+ }
97
+ let result = ''
98
+
99
+ if (abbreviated && axis === 'left' && shouldAbbreviate) {
100
+ num = abbreviateNumber(parseFloat(num))
101
+ }
102
+
103
+ if (bottomAbbreviated && axis === 'bottom' && shouldAbbreviate) {
104
+ num = abbreviateNumber(parseFloat(num))
105
+ }
106
+
107
+ if (prefix && axis === 'left') {
108
+ result += prefix
109
+ }
110
+
111
+ if (rightPrefix && axis === 'right') {
112
+ result += rightPrefix
113
+ }
114
+
115
+ if (bottomPrefix && axis === 'bottom') {
116
+ result += bottomPrefix
117
+ }
118
+
119
+ result += num
120
+
121
+ if (suffix && axis === 'left') {
122
+ result += suffix
123
+ }
124
+
125
+ if (rightSuffix && axis === 'right') {
126
+ result += rightSuffix
127
+ }
128
+
129
+ if (bottomSuffix && axis === 'bottom') {
130
+ result += bottomSuffix
131
+ }
132
+ if (isNegative) {
133
+ result = '-' + result
134
+ }
135
+
136
+ return String(result)
137
+ }
138
+
139
+ export { formatNumber }
@@ -0,0 +1,15 @@
1
+ // If config key names or position in the config have been changed with a version change,
2
+ // process those config entries and format old values into new
3
+ import update_4_23 from './ver/4.23'
4
+
5
+ // 4.23.6 ------------------------------------------------------
6
+ const coveUpdateWorker = async config => {
7
+ let genConfig = config
8
+
9
+ // v4.23
10
+ genConfig = await update_4_23(genConfig)
11
+
12
+ return genConfig
13
+ }
14
+
15
+ export default coveUpdateWorker
@@ -14,10 +14,20 @@ export default async function (url, visualizationType = '') {
14
14
  data = await fetch(url.href)
15
15
  .then(response => response.text())
16
16
  .then(responseText => {
17
+ // for every comma NOT inside quotes, replace with a pipe delimiter
18
+ // - this will let commas inside the quotes not be parsed as a new column
19
+ // - Limitation: if a delimiter other than comma is used in the csv this will break
20
+ // Examples of other delimiters that would break: tab
21
+ responseText = responseText.replace(/(".*?")|,/g, (...m) => m[1] || '|')
22
+ // now strip the double quotes
23
+ responseText = responseText.replace(/["]+/g, '')
17
24
  const parsedCsv = Papa.parse(responseText, {
25
+ //quotes: "true", // dont need these
26
+ //quoteChar: "'", // has no effect that I can tell
18
27
  header: true,
19
- dynamicTyping: true,
20
- skipEmptyLines: true
28
+ skipEmptyLines: true,
29
+ delimiter: '|', // we are using pipe symbol as delimiter so setting this explicitly for Papa.parse
30
+ dynamicTyping: false
21
31
  })
22
32
  return parsedCsv.data
23
33
  })
@@ -0,0 +1,10 @@
1
+ const update_4_23 = async config => {
2
+ const ver = 4.23
3
+
4
+ let newConfig = { ...config }
5
+
6
+ newConfig.validated = ver
7
+ return newConfig
8
+ }
9
+
10
+ export default update_4_23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/core",
3
- "version": "4.23.5",
3
+ "version": "4.23.6",
4
4
  "description": "Core components, styles, hooks, and helpers, for the CDC Open Visualization project",
5
5
  "moduleName": "CdcCore",
6
6
  "main": "dist/cdccore",
@@ -30,5 +30,5 @@
30
30
  "react": "^18.2.0",
31
31
  "react-dom": "^18.2.0"
32
32
  },
33
- "gitHead": "34add3436994ca3cf13e51f313add4d70377f53e"
33
+ "gitHead": "aaed0388b487adfeb3e7e278b4ce74df09cbaade"
34
34
  }
@@ -2,9 +2,9 @@
2
2
  display: block;
3
3
  position: relative;
4
4
  padding: 0.5em 0.5em;
5
- border-width: 1px;
6
- border-style: solid;
7
- border-color: #cbcbcb;
5
+ border-width: 1px !important; // intentional use of !important for CDC
6
+ border-style: solid !important; // intentional use of !important for CDC
7
+ border-color: #cbcbcb !important; // intentional use of !important for CDC
8
8
  border-radius: 0.1875rem;
9
9
  transition: border-color 200ms $transition-expo-out;
10
10
  font-family: sans-serif;
@@ -21,7 +21,7 @@
21
21
  border-radius: 0 0 3px 3px;
22
22
 
23
23
  &.no-borders {
24
- border: none;
24
+ border: none !important;
25
25
  }
26
26
 
27
27
  .cdc-data-bite-inner-container:not(.component--has-title) .cove-component__content:not(.no-borders) {