@cdc/core 4.23.6 → 4.23.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.
@@ -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="M89.56,65.41H16.36V8.8c0-1.1-.9-2-2-2h-5.48c-1.1,0-2,.9-2,2V72.89c0,1.1,.9,2,2,2H89.56c1.1,0,2-.9,2-2v-5.48c0-1.1-.9-2-2-2Z"/><path d="M54.66,55.34c-17.76,0-33.46-17.8-34.12-18.56l-.98-1.13v-4.55l3.25,3.72c.15,.18,15.47,17.53,31.86,17.53h0c7.31,0,12.37-1.68,17.27-3.3,4.31-1.42,8.77-2.9,14.44-3.05l1.5-.04v3l-1.42,.04c-5.24,.14-9.29,1.48-13.59,2.9-5.13,1.69-10.43,3.45-18.22,3.45h0Z"/><path d="M87.84,59.04c-4.73-1.24-7.41-1.66-13.05-1.93-2.82-.14-5.22,.16-8,.5-2.73,.33-5.81,.71-10.06,.79-13.13,.27-23.77-4.63-37.17-17.31v-1.57c13.19,12.48,24.31,18.13,37.15,17.88,4.2-.08,7.26-.46,9.96-.79,2.83-.34,5.27-.64,8.17-.5,5.73,.28,8.2,.62,13,1.88v1.05Z"/><path d="M57.02,49.28c-19.55,0-30.91-14.65-37.69-23.41v-1.54c6.67,8.61,18.64,23.95,37.69,23.95,5.87,0,14.11-3.23,18.39-6.23,2.43-1.7,3.84-2.77,4.87-3.55,2.24-1.7,2.7-2.05,7.71-4.44l-.03,1.12c-4.92,2.34-4.91,2.47-7.08,4.12-1.04,.79-2.45,1.86-4.9,3.57-4.4,3.08-12.9,6.41-18.96,6.41Z"/></svg>
@@ -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"><defs><style>.b{stroke-width:3px;}.b,.c,.d{stroke-linecap:round;}.b,.c,.d,.e{stroke:#000;}.c,.d,.e{fill:none;}.d{stroke-dasharray:0 0 0 4.07;}.e{stroke-width:2px;}</style></defs><path d="M14.15,8.39v63.29c0,.87,.89,1.59,1.98,1.59h5.42c1.09,0,1.98-.72,1.98-1.59l-.13-63.29c0-.87-.89-1.59-1.98-1.59h-5.28c-1.09,0-1.98,.72-1.98,1.59Z"/><g><line class="c" x1="53.51" y1="10.09" x2="53.51" y2="10.09"/><line class="d" x1="53.51" y1="14.16" x2="53.51" y2="69.05"/><line class="c" x1="53.51" y1="71.08" x2="53.51" y2="71.08"/></g><g><path class="b" d="M29.78,16.97h34.43"/><path class="b" d="M29.78,21.48V12.45"/><path class="b" d="M65.64,21.48V12.45"/></g><g><path class="b" d="M50.62,33.5h28.85"/><path class="b" d="M50.62,38.02v-9.03"/><path class="b" d="M80.67,38.02v-9.03"/></g><g><path class="b" d="M28.21,50.03h19.26"/><path class="b" d="M28.21,54.55v-9.03"/><path class="b" d="M48.27,54.55v-9.03"/></g><g><path class="b" d="M40.69,66.57h33.99"/><path class="b" d="M40.69,71.08v-9.03"/><path class="b" d="M76.1,71.08v-9.03"/></g><circle class="e" cx="47.47" cy="16.97" r="2.2"/><circle class="e" cx="65.64" cy="33.5" r="2.2"/><circle class="e" cx="38.49" cy="50.03" r="2.2"/><circle class="e" cx="58.31" cy="66.57" r="2.2"/></svg>
@@ -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"><defs><style>.b{fill:none;}.b,.c{stroke:#000;stroke-linejoin:round;}</style></defs><path d="M89.93,65.41H16.73V8.8c0-1.1-.9-2-2-2h-5.48c-1.1,0-2,.9-2,2V72.89c0,1.1,.9,2,2,2H89.93c1.1,0,2-.9,2-2v-5.48c0-1.1-.9-2-2-2Z"/><polygon points="22.7 54.03 22.7 59.31 89.66 59.31 89.66 37.5 70.66 47.11 52.54 42.02 43.1 48.03 35.85 44.59 22.7 54.03"/><polygon class="b" points="22.7 47.3 22.7 59.31 89.66 59.31 89.66 25.48 70.66 35.35 52.54 35.3 43.1 40.03 36.46 31.3 22.7 47.3"/><polygon class="c" points="70.66 23.24 52.54 15.09 43.1 21.1 35.85 25.48 22.7 27.11 22.7 47.3 36.46 31.3 43.1 40.03 52.54 35.3 70.66 35.35 89.66 25.48 89.66 10.58 70.66 23.24"/></svg>
@@ -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="M87.87,5.89H11.35c-1.56,0-2.84,1.28-2.84,2.84v14.78c0,1.56,1.28,2.84,2.84,2.84l76.52-.19c1.56,0,2.84-1.28,2.84-2.84V8.73c0-1.56-1.28-2.84-2.84-2.84ZM25.95,14.33l-5.19,5.19c-.29,.29-.77,.29-1.06,0l-5.19-5.19c-.29-.29-.29-.77,0-1.06h11.43c.29,.29,.29,.77,0,1.06Zm60.97,10.22H32.18V7.7h54.73V24.55Z"/><path transform="translate(0, 30)" d="M87.87,5.89H11.35c-1.56,0-2.84,1.28-2.84,2.84v14.78c0,1.56,1.28,2.84,2.84,2.84l76.52-.19c1.56,0,2.84-1.28,2.84-2.84V8.73c0-1.56-1.28-2.84-2.84-2.84ZM25.95,14.33l-5.19,5.19c-.29,.29-.77,.29-1.06,0l-5.19-5.19c-.29-.29-.29-.77,0-1.06h11.43c.29,.29,.29,.77,0,1.06Zm60.97,10.22H32.18V7.7h54.73V24.55Z"/></svg>
@@ -13,14 +13,6 @@ import { formatNumber } from '@cdc/core/helpers/cove/number'
13
13
 
14
14
  import Loading from '@cdc/core/components/Loading'
15
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
-
24
16
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
25
17
  const DataTable = props => {
26
18
  const { config, tableTitle, indexTitle, vizTitle, rawData, runtimeData, headerColor, expandDataTable, columns, displayDataAsText, applyLegendToRow, displayGeoName, navigationHandler, viewport, formatLegendLocation, tabbingId, isDebug } = props
@@ -153,15 +145,13 @@ const DataTable = props => {
153
145
  const DownloadButton = memo(() => {
154
146
  if (rawData !== undefined) {
155
147
  let csvData
156
- if (config.type === 'chart' || config.general.type === 'bubble' || !config.table.showFullGeoNameInCSV) {
157
- // Just Unparse
158
- csvData = Papa.unparse(rawData)
159
- } else if ((config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') || config.general.type === 'us-geocode') {
160
- // Unparse + Add column for full Geo name
161
- csvData = Papa.unparse(rawData.map(row => ({ FullGeoName: displayGeoName(row[config.columns.geo.name]), ...row })))
162
- } else {
148
+ // only use fullGeoName on County maps and no other
149
+ if (config.general.geoType === 'us-county') {
163
150
  // Unparse + Add column for full Geo name along with State
164
151
  csvData = Papa.unparse(rawData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row })))
152
+ } else {
153
+ // Just Unparse
154
+ csvData = Papa.unparse(rawData)
165
155
  }
166
156
 
167
157
  const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' })
@@ -213,16 +203,19 @@ const DataTable = props => {
213
203
 
214
204
  const rows = Object.keys(runtimeData).sort((a, b) => {
215
205
  let sortVal
216
- if (config.columns.length > 0) {
206
+ if (config.type === 'map' && config.columns) {
217
207
  sortVal = customSort(runtimeData[a][config.columns[sortBy.column].name], runtimeData[b][config.columns[sortBy.column].name])
218
208
  }
209
+ if (config.type === 'chart') {
210
+ sortVal = customSort(runtimeData[a][sortBy.column], runtimeData[b][sortBy.column])
211
+ }
219
212
  if (!sortBy.asc) return sortVal
220
213
  if (sortVal === 0) return 0
221
214
  if (sortVal < 0) return 1
222
215
  return -1
223
216
  })
224
217
 
225
- function genMapRows(rows) {
218
+ const genMapRows = rows => {
226
219
  const allrows = rows.map(row => {
227
220
  return (
228
221
  <tr role='row'>
@@ -316,6 +309,7 @@ const DataTable = props => {
316
309
  }
317
310
 
318
311
  const genChartHeader = (columns, data) => {
312
+ if (!data) return
319
313
  return (
320
314
  <tr>
321
315
  {dataSeriesColumns().map(column => {
@@ -345,6 +339,7 @@ const DataTable = props => {
345
339
  {...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
346
340
  >
347
341
  {text}
342
+ {sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? upIcon : downIcon}</span>}
348
343
  <button>
349
344
  <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
350
345
  </button>
@@ -355,6 +350,24 @@ const DataTable = props => {
355
350
  )
356
351
  }
357
352
 
353
+ // if its additional column, return formatting params
354
+ const isAdditionalColumn = column => {
355
+ let inthere = false
356
+ let formattingParams = {}
357
+ Object.keys(config.columns).forEach(keycol => {
358
+ if (config.columns[keycol].name === column) {
359
+ inthere = true
360
+ formattingParams = {
361
+ addColPrefix: config.columns[keycol].prefix,
362
+ addColSuffix: config.columns[keycol].suffix,
363
+ addColRoundTo: config.columns[keycol].roundToPlace ? config.columns[keycol].roundToPlace : '',
364
+ addColCommas: config.columns[keycol].commas
365
+ }
366
+ }
367
+ })
368
+ return formattingParams
369
+ }
370
+
358
371
  const genChartRows = rows => {
359
372
  const allRows = rows.map(row => {
360
373
  return (
@@ -367,10 +380,9 @@ const DataTable = props => {
367
380
  // not the prettiest, but helper functions work nicely here.
368
381
  cellValue = <>{config.xAxis.type === 'date' ? formatDate(config.xAxis.dateDisplayFormat, parseDate(config.xAxis.dateParseFormat, labelValue)) : labelValue}</>
369
382
  } else {
370
- let resolvedAxis = ''
383
+ let resolvedAxis = 'left'
371
384
  let leftAxisItems = config.series.filter(item => item?.axis === 'Left')
372
385
  let rightAxisItems = config.series.filter(item => item?.axis === 'Right')
373
- console.log('column', column)
374
386
 
375
387
  leftAxisItems.map(leftSeriesItem => {
376
388
  if (leftSeriesItem.dataKey === column) resolvedAxis = 'left'
@@ -380,7 +392,12 @@ const DataTable = props => {
380
392
  if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
381
393
  })
382
394
 
383
- cellValue = formatNumber(runtimeData[row][column], resolvedAxis, true, config)
395
+ let addColParams = isAdditionalColumn(column)
396
+ if (addColParams) {
397
+ cellValue = formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
398
+ } else {
399
+ cellValue = formatNumber(runtimeData[row][column], resolvedAxis, false, config)
400
+ }
384
401
  }
385
402
 
386
403
  return (
@@ -395,6 +412,17 @@ const DataTable = props => {
395
412
  return allRows
396
413
  }
397
414
 
415
+ const upIcon = (
416
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
417
+ <path d='M0 5l5-5 5 5z' />
418
+ </svg>
419
+ )
420
+ const downIcon = (
421
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'>
422
+ <path d='M0 0l5 5 5-5z' />
423
+ </svg>
424
+ )
425
+
398
426
  const limitHeight = {
399
427
  maxHeight: config.table.limitHeight && `${config.table.height}px`,
400
428
  overflowY: 'scroll'
@@ -418,7 +446,7 @@ const DataTable = props => {
418
446
  [config.runtime.seriesKeys]) // eslint-disable-line
419
447
 
420
448
  if (config.visualizationType !== 'Box Plot') {
421
- function genMapHeader(columns) {
449
+ const genMapHeader = columns => {
422
450
  return (
423
451
  <tr>
424
452
  {Object.keys(columns)
@@ -433,9 +461,11 @@ const DataTable = props => {
433
461
  if (config.type === 'map' && (text === undefined || text === '')) {
434
462
  text = 'Location'
435
463
  }
464
+
436
465
  return (
437
466
  <th
438
467
  key={`col-header-${column}`}
468
+ id={column}
439
469
  tabIndex='0'
440
470
  title={text}
441
471
  role='columnheader'
@@ -452,6 +482,7 @@ const DataTable = props => {
452
482
  {...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
453
483
  >
454
484
  {text}
485
+ {sortBy.column === column && <span className={'sort-icon'}>{!sortBy.asc ? upIcon : downIcon}</span>}
455
486
  <button>
456
487
  <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
457
488
  </button>
@@ -530,7 +561,7 @@ const DataTable = props => {
530
561
  )
531
562
  } else {
532
563
  // Render Data Table for Box Plots
533
- function genBoxplotHeader(categories) {
564
+ const genBoxplotHeader = categories => {
534
565
  let columns = ['Measures', ...categories]
535
566
  return (
536
567
  <tr>
@@ -584,7 +615,7 @@ const DataTable = props => {
584
615
  if (Number(rowid) === 10) return plot.values.length > 0 ? plot.values.toString() : '-'
585
616
  return <p>-</p>
586
617
  }
587
- function genBoxplotRows(rows) {
618
+ const genBoxplotRows = rows => {
588
619
  // get list of data keys for each row
589
620
  let dataKeys = rows.map(row => {
590
621
  return row[0]
@@ -7,8 +7,7 @@ import { DATA_TABLE_VERTICAL, DATA_TABLE_HORIZONTAL, DATA_TABLE_SINGLE_ROW, DATA
7
7
  import '../../styles/v2/components/data-designer.scss'
8
8
 
9
9
  const DataDesigner = props => {
10
- const { configureData, updateDescriptionProp, visualizationKey, dataKey } = props
11
-
10
+ const { configureData, updateDescriptionProp, visualizationKey, dataKey, config, setConfig } = props
12
11
 
13
12
  return (
14
13
  <div className='cove-data-designer__container'>
@@ -166,66 +165,203 @@ const DataDesigner = props => {
166
165
  </select>
167
166
  </div>
168
167
  <div className='mb-2'>
169
- <div className='mb-1'>Which properties in the dataset represent the numeric value? (all remaining properties will be treated as filters)</div>
168
+ <div className='mb-1'>Which properties in the dataset represent the numeric value? (all remaining properties will be treated as filters)</div>
170
169
  {configureData.dataDescription.valueKeys && configureData.dataDescription.valueKeys.length > 0 && (
171
- <ul className="value-list">
170
+ <ul className='value-list'>
172
171
  {configureData.dataDescription.valueKeys.map((valueKey, index) => (
173
- <li key={`value-keys-list-${index}`}>{valueKey}<button onClick={() => {
174
- let newValueKeys = configureData.dataDescription.valueKeys;
175
- newValueKeys.splice(index, 1);
176
- updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', newValueKeys)
177
- }}>X</button></li>
172
+ <li key={`value-keys-list-${index}`}>
173
+ {valueKey}
174
+ <button
175
+ onClick={() => {
176
+ let newValueKeys = configureData.dataDescription.valueKeys
177
+ newValueKeys.splice(index, 1)
178
+ updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', newValueKeys)
179
+ }}
180
+ >
181
+ X
182
+ </button>
183
+ </li>
178
184
  ))}
179
185
  </ul>
180
186
  )}
181
187
  <select
182
188
  onChange={e => {
183
- if(e.target.value && (!configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(e.target.value) === -1)){
189
+ if (e.target.value && (!configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(e.target.value) === -1)) {
184
190
  updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', [...(configureData.dataDescription.valueKeys || []), e.target.value])
185
191
  }
186
192
  }}
187
193
  >
188
194
  <option value=''>Choose an option</option>
189
- {Object.keys(configureData.data[0]).filter(value => !configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(value) === -1).map((value, index) => (
190
- <option value={value} key={`value-keys-option-${index}`}>
191
- {value}
192
- </option>
193
- ))}
195
+ {Object.keys(configureData.data[0])
196
+ .filter(value => !configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(value) === -1)
197
+ .map((value, index) => (
198
+ <option value={value} key={`value-keys-option-${index}`}>
199
+ {value}
200
+ </option>
201
+ ))}
194
202
  </select>
195
203
  </div>
196
204
  <div className='mb-2'>
197
- <div className='mb-1'>(Optional) Which properties in the dataset should be ignored? (will not be used or treated as filters)</div>
205
+ <div className='mb-1'>(Optional) Which properties in the dataset should be ignored? (will not be used or treated as filters)</div>
198
206
  {configureData.dataDescription.ignoredKeys && configureData.dataDescription.ignoredKeys.length > 0 && (
199
- <ul className="value-list">
207
+ <ul className='value-list'>
200
208
  {configureData.dataDescription.ignoredKeys.map((ignoredKey, index) => (
201
- <li key={`value-keys-list-${index}`}>{ignoredKey}<button onClick={() => {
202
- let newIgnoredKeys = configureData.dataDescription.ignoredKeys;
203
- newIgnoredKeys.splice(index, 1);
204
- updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', newIgnoredKeys)
205
- }}>X</button></li>
209
+ <li key={`value-keys-list-${index}`}>
210
+ {ignoredKey}
211
+ <button
212
+ onClick={() => {
213
+ let newIgnoredKeys = configureData.dataDescription.ignoredKeys
214
+ newIgnoredKeys.splice(index, 1)
215
+ updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', newIgnoredKeys)
216
+ }}
217
+ >
218
+ X
219
+ </button>
220
+ </li>
206
221
  ))}
207
222
  </ul>
208
223
  )}
209
224
  <select
210
225
  onChange={e => {
211
- if(e.target.value){
226
+ if (e.target.value) {
212
227
  updateDescriptionProp(visualizationKey, dataKey, 'ignoredKeys', [...(configureData.dataDescription.ignoredKeys || []), e.target.value])
213
228
  }
214
- e.target.value = '';
229
+ e.target.value = ''
215
230
  }}
216
231
  >
217
232
  <option value=''>Choose an option</option>
218
- {Object.keys(configureData.data[0]).filter(value => !configureData.dataDescription.ignoredKeys || configureData.dataDescription.ignoredKeys.indexOf(value) === -1).map((value, index) => (
219
- <option value={value} key={`ignored-keys-option-${index}`}>
220
- {value}
221
- </option>
222
- ))}
233
+ {Object.keys(configureData.data[0])
234
+ .filter(value => !configureData.dataDescription.ignoredKeys || configureData.dataDescription.ignoredKeys.indexOf(value) === -1)
235
+ .map((value, index) => (
236
+ <option value={value} key={`ignored-keys-option-${index}`}>
237
+ {value}
238
+ </option>
239
+ ))}
223
240
  </select>
224
241
  </div>
225
242
  </>
226
243
  )}
227
244
  </>
228
245
  )}
246
+
247
+ {config?.visualizationType === 'Forest Plot' && (
248
+ <>
249
+ <div className='mb-2'>
250
+ <div className='mb-1'>Which column represents the date/category column?</div>
251
+ <select
252
+ onChange={e => {
253
+ setConfig({
254
+ ...config,
255
+ xAxis: {
256
+ ...config.xAxis,
257
+ dataKey: e.target.value
258
+ }
259
+ })
260
+ }}
261
+ defaultValue={'Select'}
262
+ >
263
+ <option value=''>Choose an option</option>
264
+ {Object.keys(configureData.data[0]).map((value, index) => (
265
+ <option value={value} key={index}>
266
+ {value}
267
+ </option>
268
+ ))}
269
+ </select>
270
+ </div>
271
+
272
+ <div className='mb-2'>
273
+ <div className='mb-1'>Which column represents your estimate field?</div>
274
+ <select
275
+ onChange={e => {
276
+ setConfig({
277
+ ...config,
278
+ forestPlot: {
279
+ ...config.forestPlot,
280
+ estimateField: e.target.value
281
+ }
282
+ })
283
+ }}
284
+ defaultValue={'Select'}
285
+ >
286
+ <option value=''>Choose an option</option>
287
+ {Object.keys(configureData.data[0]).map((value, index) => (
288
+ <option value={value} key={index}>
289
+ {value}
290
+ </option>
291
+ ))}
292
+ </select>
293
+ </div>
294
+
295
+ <div className='mb-2'>
296
+ <div className='mb-1'>Which column represents the low confidence interval?</div>
297
+ <select
298
+ onChange={e => {
299
+ setConfig({
300
+ ...config,
301
+ forestPlot: {
302
+ ...config.forestPlot,
303
+ lower: e.target.value
304
+ }
305
+ })
306
+ }}
307
+ defaultValue={'Select'}
308
+ >
309
+ <option value=''>Choose an option</option>
310
+ {Object.keys(configureData.data[0]).map((value, index) => (
311
+ <option value={value} key={index}>
312
+ {value}
313
+ </option>
314
+ ))}
315
+ </select>
316
+ </div>
317
+
318
+ <div className='mb-2'>
319
+ <div className='mb-1'>Which column represents the high confidence interval?</div>
320
+ <select
321
+ onChange={e => {
322
+ setConfig({
323
+ ...config,
324
+ forestPlot: {
325
+ ...config.forestPlot,
326
+ upper: e.target.value
327
+ }
328
+ })
329
+ }}
330
+ defaultValue={'Select'}
331
+ >
332
+ <option value=''>Choose an option</option>
333
+ {Object.keys(configureData.data[0]).map((value, index) => (
334
+ <option value={value} key={index}>
335
+ {value}
336
+ </option>
337
+ ))}
338
+ </select>
339
+ </div>
340
+
341
+ <div className='mb-2'>
342
+ <div className='mb-1'>Which shape do you want to use in your forest plot?</div>
343
+ <select
344
+ onChange={e => {
345
+ setConfig({
346
+ ...config,
347
+ forestPlot: {
348
+ ...config.forestPlot,
349
+ shape: e.target.value
350
+ }
351
+ })
352
+ }}
353
+ defaultValue={'Select'}
354
+ >
355
+ <option value=''>Choose an option</option>
356
+ {['text', 'circle', 'square', 'diamond'].map((value, index) => (
357
+ <option value={value} key={index}>
358
+ {value}
359
+ </option>
360
+ ))}
361
+ </select>
362
+ </div>
363
+ </>
364
+ )}
229
365
  </>
230
366
  )}
231
367
  {configureData.dataDescription && configureData.formattedData && <div>Data configured successfully</div>}
@@ -27,7 +27,8 @@ import iconWarningCircle from '../../assets/icon-warning-circle.svg'
27
27
  import iconWarningTriangle from '../../assets/icon-warning-triangle.svg'
28
28
  import iconGear from '../../assets/icon-gear.svg'
29
29
  import iconTools from '../../assets/icon-tools.svg'
30
- import iconText from '../../assets/filtered-text.svg'
30
+ import iconText from '../../assets/icon-filtered-text.svg'
31
+ import iconDropdowns from '../../assets/icon-filter-dropdowns.svg'
31
32
  import iconPlus from '../../assets/icon-plus.svg'
32
33
  import iconMinus from '../../assets/icon-minus.svg'
33
34
 
@@ -62,7 +63,8 @@ const iconHash = {
62
63
  tools: iconTools,
63
64
  plus: iconPlus,
64
65
  minus: iconMinus,
65
- 'filtered-text': iconText
66
+ 'filtered-text': iconText,
67
+ 'filter-dropdowns': iconDropdowns
66
68
  }
67
69
 
68
70
  const Icon = ({ display = null, base, alt = '', size, color, style, ...attributes }) => {
@@ -9,42 +9,30 @@ import '../../styles/v2/components/ui/tooltip.scss'
9
9
  const TooltipTarget = () => null
10
10
  const TooltipContent = () => null
11
11
 
12
- const Tooltip = ({
13
- place = 'top',
14
- trigger = 'hover',
15
- float = false,
16
- shadow = true,
17
- border = false,
18
- children,
19
- style,
20
- ...attributes
21
- }) => {
22
-
12
+ const Tooltip = ({ place = 'top', trigger = 'hover', float = false, shadow = true, border = false, children, style, ...attributes }) => {
23
13
  const tooltipTargetChildren = children.find(el => el.type === TooltipTarget)
24
14
  const tooltipContentChildren = children.find(el => el.type === TooltipContent)
25
15
 
26
16
  const uid = 'tooltip-' + useId()
27
17
 
28
- const generateTriggerEvent = (trigger) => {
18
+ const generateTriggerEvent = trigger => {
29
19
  const eventList = {
30
- 'hover': null,
31
- 'focus': 'focus',
32
- 'click': 'click focus'
20
+ hover: null,
21
+ focus: 'focus',
22
+ click: 'click focus'
33
23
  }
34
24
  return eventList[trigger]
35
25
  }
36
26
 
37
27
  return (
38
- <span className="cove-tooltip" style={style} {...attributes}>
39
- <a id={uid} className="cove-tooltip--target"
40
- data-tooltip-float={float}
41
- data-tooltip-place={place}
42
- data-tooltip-events={generateTriggerEvent()}
43
- >
28
+ <span className='cove-tooltip' style={style} {...attributes}>
29
+ <a id={uid} className='cove-tooltip--target' data-tooltip-float={float} data-tooltip-place={place} data-tooltip-events={generateTriggerEvent()}>
44
30
  {tooltipTargetChildren ? tooltipTargetChildren.props.children : null}
45
31
  </a>
32
+ {/* prettier-ignore */}
46
33
  <ReactTooltip
47
- id={uid} anchorId={uid}
34
+ id={uid}
35
+ anchorId={uid}
48
36
  className={'cove-tooltip__content' + (' place-' + place) + (!float ? ' cove-tooltip__content--animated' : '') + (trigger === 'click' ? ' interactive' : '') + (border ? (' cove-tooltip--border') : '') + (shadow ? ' has-shadow' : '')}
49
37
  globalEventOff="click"
50
38
  >
@@ -250,6 +250,26 @@ export class DataTransform {
250
250
  if (testing) console.log('## cleanedData =', cleanedupData)
251
251
  return cleanedupData
252
252
  }
253
+
254
+ // clean out %, $, commas from numbers when needing to do sorting!
255
+ cleanDataPoint(data, testing = false) {
256
+ if (testing) console.log('clean', data)
257
+ let cleaned = ''
258
+
259
+ // remove comma and dollar signs
260
+ if (testing) console.log('typeof data is ', typeof data)
261
+ let tmp = ''
262
+ if (typeof data === 'string') {
263
+ tmp = data!== null && data !== '' ? data.replace(/[,\$\%]/g, '') : ''
264
+ } else {
265
+ tmp = data !== null && data !== '' ? data : ''
266
+ }
267
+
268
+ cleaned = tmp
269
+
270
+ if (testing) console.log('## cleanedData =', cleaned)
271
+ return cleaned
272
+ }
253
273
  }
254
274
 
255
275
  export default DataTransform
@@ -19,7 +19,7 @@ const abbreviateNumber = num => {
19
19
  }
20
20
 
21
21
  // Format numeric data based on settings in config
22
- const formatNumber = (num, axis, shouldAbbreviate = false, config = null) => {
22
+ const formatNumber = (num, axis, shouldAbbreviate = false, config = null, addColParams = null) => {
23
23
  if (!config) console.error('no config found in formatNumber')
24
24
  // if num is NaN return num
25
25
  if (isNaN(num) || !num) return num
@@ -30,22 +30,41 @@ const formatNumber = (num, axis, shouldAbbreviate = false, config = null) => {
30
30
  if (isNegative) {
31
31
  num = Math.abs(num)
32
32
  }
33
-
33
+
34
34
  // destructure dataFormat values
35
35
  let {
36
36
  dataFormat: { commas, abbreviated, roundTo, prefix, suffix, rightRoundTo, bottomRoundTo, rightPrefix, rightSuffix, bottomPrefix, bottomSuffix, bottomAbbreviated }
37
37
  } = config
38
38
 
39
+ // destructure Additional Col dataformat values
40
+ const { addColCommas, addColRoundTo, addColPrefix, addColSuffix } = addColParams
41
+
39
42
  // check if value contains comma and remove it. later will add comma below.
40
43
  if (String(num).indexOf(',') !== -1) num = num.replaceAll(',', '')
41
44
 
42
45
  let original = num
43
46
  let stringFormattingOptions
44
47
  if (axis === 'left') {
48
+ let roundToPlace
49
+ if (addColRoundTo !== undefined) {
50
+ // if its an Additional Column
51
+ roundToPlace = addColRoundTo ? Number(addColRoundTo) : 0
52
+ } else {
53
+ roundToPlace = roundTo ? Number(roundTo) : 0
54
+ }
55
+ // Need to prevent negative values in rounding
56
+ if (roundToPlace < 0) roundToPlace = 0
57
+ let useCommas
58
+ if (addColCommas !== undefined) {
59
+ // if its an Additional Column
60
+ useCommas = addColCommas ? true : false
61
+ } else {
62
+ useCommas = config.dataFormat.commas ? true : false
63
+ }
45
64
  stringFormattingOptions = {
46
- useGrouping: config.dataFormat.commas ? true : false,
47
- minimumFractionDigits: roundTo ? Number(roundTo) : 0,
48
- maximumFractionDigits: roundTo ? Number(roundTo) : 0
65
+ useGrouping: useCommas,
66
+ minimumFractionDigits: roundToPlace,
67
+ maximumFractionDigits: roundToPlace
49
68
  }
50
69
  }
51
70
 
@@ -104,8 +123,12 @@ const formatNumber = (num, axis, shouldAbbreviate = false, config = null) => {
104
123
  num = abbreviateNumber(parseFloat(num))
105
124
  }
106
125
 
107
- if (prefix && axis === 'left') {
108
- result += prefix
126
+ if (addColPrefix !== undefined && axis === 'left') {
127
+ result = addColPrefix + result
128
+ } else {
129
+ if (prefix && axis === 'left') {
130
+ result = prefix + result
131
+ }
109
132
  }
110
133
 
111
134
  if (rightPrefix && axis === 'right') {
@@ -118,8 +141,12 @@ const formatNumber = (num, axis, shouldAbbreviate = false, config = null) => {
118
141
 
119
142
  result += num
120
143
 
121
- if (suffix && axis === 'left') {
122
- result += suffix
144
+ if (addColSuffix !== undefined && axis === 'left') {
145
+ result += addColSuffix
146
+ } else {
147
+ if (suffix && axis === 'left') {
148
+ result += suffix
149
+ }
123
150
  }
124
151
 
125
152
  if (rightSuffix && axis === 'right') {
@@ -136,4 +163,10 @@ const formatNumber = (num, axis, shouldAbbreviate = false, config = null) => {
136
163
  return String(result)
137
164
  }
138
165
 
139
- export { formatNumber }
166
+ const getFontSize = (size = 'medium') => {
167
+ const fontSize = { small: 16, medium: 18, large: 20 }
168
+
169
+ return fontSize[size]
170
+ }
171
+
172
+ export { formatNumber, getFontSize }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/core",
3
- "version": "4.23.6",
3
+ "version": "4.23.8",
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": "aaed0388b487adfeb3e7e278b4ce74df09cbaade"
33
+ "gitHead": "ba0a072a40c430baf121ad5ece0165f52a414b86"
34
34
  }
@@ -94,6 +94,7 @@ table.data-table {
94
94
  background-size: 10px 5px;
95
95
  }
96
96
 
97
+ /* doesnt work
97
98
  th.sort-asc,
98
99
  td.sort-asc {
99
100
  background-image: url(../assets/icon-caret-filled-up.svg);
@@ -103,6 +104,19 @@ table.data-table {
103
104
  td.sort-desc {
104
105
  background-image: url(../assets/icon-caret-filled-down.svg);
105
106
  }
107
+ */
108
+
109
+ // format the white triangle sort icon in data table headers
110
+ .sort-icon {
111
+ fill: white;
112
+ width: 30px;
113
+ float: right;
114
+ align-self: center;
115
+ position: absolute;
116
+ right: 10px;
117
+ top: 50%;
118
+ transform: translateY(-50%);
119
+ }
106
120
 
107
121
  th:last-child,
108
122
  td:last-child {
@@ -1,17 +1,34 @@
1
- $cove-tooltip-bg: #fff;
2
- $cove-tooltip-color: #333;
3
- $cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
4
-
5
- .cove-tooltip {
6
- display: inline-block;
7
- position: relative;
8
- line-height: 1em;
1
+ // Reusable variables, nice for JS manipulations
2
+ :root {
3
+ --cove-tooltip-bg: #fff;
4
+ --cove-tooltip-color: black;
5
+ --cove-tooltip-font-size: 14px;
6
+ --cove-tooltip-border-color: #bdbdbd;
7
+ --cove-tooltip-heading-fontWeight: bold;
8
+ --cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
9
+ --cove-tooltip-maxWidth: 280px;
10
+ }
9
11
 
10
- &.cove-tooltip--border {
11
- border: 1px solid #bdbdbd;
12
+ // Namespace to cove project, inconveniently using two different classes.
13
+ .cove,
14
+ .cdc-open-viz-module {
15
+ // todo: move to a utils folder.
16
+ .capitalize {
17
+ text-transform: capitalize;
18
+ }
19
+
20
+ // react-tooltip library
21
+ .react-tooltip {
22
+ .styles-module_arrow,
23
+ .react-tooltip-arrow {
24
+ border-bottom: var(--cove-tooltip-border-color) 1px solid;
25
+ border-right: var(--cove-tooltip-border-color) 1px solid;
26
+ backface-visibility: hidden;
27
+ }
12
28
  }
13
29
 
14
- @at-root {
30
+ // editor-panel
31
+ .editor-panel {
15
32
  .cove-label + .cove-tooltip {
16
33
  top: 1px;
17
34
  margin-left: 0.5rem;
@@ -30,6 +47,61 @@ $cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
30
47
  line-height: inherit;
31
48
  }
32
49
  }
50
+
51
+ // begin actual tooltip styles
52
+ .react-tooltip,
53
+ .tooltip {
54
+ color: var(--cove-tooltip-color);
55
+ border: 1px solid var(--cove-tooltip-border-color);
56
+ padding: 0.3rem 0.5rem;
57
+ z-index: 1000;
58
+ opacity: 1;
59
+
60
+ .interactive {
61
+ a {
62
+ pointer-events: all;
63
+ }
64
+ }
65
+
66
+ ul {
67
+ list-style: none;
68
+
69
+ li {
70
+ font-size: var(--cove-tooltip-font-size) !important;
71
+ }
72
+
73
+ // under the heading
74
+ > li.tooltip-body {
75
+ border-top: 1px solid black;
76
+ padding-top: 5px;
77
+ }
78
+
79
+ li.tooltip-body:first-of-type {
80
+ padding-top: 5px;
81
+ border-top: 1px solid black;
82
+ }
83
+
84
+ // undo previous except first class
85
+ > li.tooltip-body ~ li.tooltip-body {
86
+ border-top: initial;
87
+ padding-top: initial;
88
+ }
89
+ }
90
+
91
+ .tooltip-heading {
92
+ display: block;
93
+ font-weight: var(--cove-tooltip-heading-fontWeight) !important;
94
+ padding-bottom: 3px;
95
+ padding-top: 3px;
96
+ font-size: var(--cove-tooltip-font-size) !important;
97
+ }
98
+ }
99
+ }
100
+
101
+ .cove-tooltip {
102
+ display: inline-block;
103
+ position: relative;
104
+ line-height: 1em;
33
105
  }
34
106
 
35
107
  .cove-tooltip--target {
@@ -45,16 +117,15 @@ $cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
45
117
  }
46
118
 
47
119
  .cove-tooltip__content {
48
- max-width: 280px;
120
+ max-width: var(--cove-tooltip-maxWidth);
49
121
  padding: 10px 8px;
50
122
  font-size: 0.875rem;
51
123
  line-height: 1.125rem;
52
124
  text-align: left;
53
- color: $cove-tooltip-color;
54
- background-color: $cove-tooltip-bg;
125
+ color: var(--cove-tooltip-color);
126
+ background-color: var(--cove-tooltip-bg);
55
127
  border-radius: 5px;
56
128
  user-select: none;
57
- opacity: 0;
58
129
  cursor: default;
59
130
  z-index: 1;
60
131
 
@@ -64,7 +135,7 @@ $cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
64
135
  }
65
136
 
66
137
  &.cove-tooltip__content--animated[class*='styles-module_show__'] {
67
- animation: tooltip-btt $cove-tooltip-animation;
138
+ animation: tooltip-btt var(--cove-tooltip-animation);
68
139
  }
69
140
  }
70
141
 
@@ -74,7 +145,7 @@ $cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
74
145
  }
75
146
 
76
147
  &.cove-tooltip__content--animated[class*='styles-module_show__'] {
77
- animation: tooltip-ltr $cove-tooltip-animation;
148
+ animation: tooltip-ltr var(--cove-tooltip-animation);
78
149
  }
79
150
  }
80
151
 
@@ -84,7 +155,7 @@ $cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
84
155
  }
85
156
 
86
157
  &.cove-tooltip__content--animated[class*='styles-module_show__'] {
87
- animation: tooltip-ttb $cove-tooltip-animation;
158
+ animation: tooltip-ttb var(--cove-tooltip-animation);
88
159
  }
89
160
  }
90
161
 
@@ -94,61 +165,7 @@ $cove-tooltip-animation: 500ms cubic-bezier(0.16, 1, 0.3, 1) 50ms 1 forwards;
94
165
  }
95
166
 
96
167
  &.cove-tooltip__content--animated[class*='styles-module_show__'] {
97
- animation: tooltip-rtl $cove-tooltip-animation;
168
+ animation: tooltip-rtl var(--cove-tooltip-animation);
98
169
  }
99
170
  }
100
171
  }
101
-
102
- .interactive {
103
- a {
104
- pointer-events: all;
105
- }
106
- }
107
-
108
- @keyframes tooltip-ltr {
109
- 0% {
110
- opacity: 0;
111
- transform: translateX(-8px);
112
- }
113
-
114
- 100% {
115
- opacity: 1;
116
- transform: translateX(0);
117
- }
118
- }
119
-
120
- @keyframes tooltip-rtl {
121
- 0% {
122
- opacity: 0;
123
- transform: translateX(8px);
124
- }
125
-
126
- 100% {
127
- opacity: 1;
128
- transform: translateX(0);
129
- }
130
- }
131
-
132
- @keyframes tooltip-ttb {
133
- 0% {
134
- opacity: 0;
135
- transform: translateY(-8px);
136
- }
137
-
138
- 100% {
139
- opacity: 1;
140
- transform: translateY(0);
141
- }
142
- }
143
-
144
- @keyframes tooltip-btt {
145
- 0% {
146
- opacity: 0;
147
- transform: translateY(8px);
148
- }
149
-
150
- 100% {
151
- opacity: 1;
152
- transform: translateY(0);
153
- }
154
- }
@@ -150,16 +150,6 @@
150
150
  background-size: 10px 5px;
151
151
  }
152
152
 
153
- .sort-asc {
154
- //TODO find fix
155
- //background-image: url(~@cdc/core/assets/icon-caret-filled-up.svg);
156
- }
157
-
158
- .sort-desc {
159
- //TODO find fix
160
- //background-image: url(~@cdc/core/assets/icon-caret-filled-down.svg);
161
- }
162
-
163
153
  .resizer {
164
154
  cursor: e-resize;
165
155
  width: 10px;
@@ -171,6 +161,7 @@
171
161
  }
172
162
  }
173
163
 
164
+
174
165
  .cove-data-table__footer {
175
166
  margin-top: 1rem;
176
167
  }