@cdc/waffle-chart 4.25.11 → 4.26.2

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/package.json CHANGED
@@ -1,49 +1,47 @@
1
1
  {
2
2
  "name": "@cdc/waffle-chart",
3
- "version": "4.25.11",
3
+ "version": "4.26.2",
4
4
  "description": "React component for displaying a single piece of data in a card module",
5
- "moduleName": "CdcWaffleChart",
6
- "main": "dist/cdcwafflechart",
7
- "type": "module",
8
- "scripts": {
9
- "start": "vite --open",
10
- "build": "vite build",
11
- "preview": "vite preview",
12
- "graph": "nx graph",
13
- "prepublishOnly": "lerna run --scope @cdc/waffle-chart build",
14
- "test": "vitest run --reporter verbose",
15
- "test-watch": "vitest watch --reporter verbose",
16
- "test-watch:ui": "vitest --ui"
17
- },
18
- "repository": {
19
- "type": "git",
20
- "url": "git+https://github.com/CDCgov/cdc-open-viz",
21
- "directory": "packages/waffle-chart"
22
- },
23
- "author": "rshelnutt <qyu6@cdc.gov>",
24
- "bugs": {
25
- "url": "https://github.com/CDCgov/cdc-open-viz/issues"
26
- },
27
5
  "license": "Apache-2.0",
28
- "homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
6
+ "author": "rshelnutt <qyu6@cdc.gov>",
7
+ "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
29
8
  "dependencies": {
30
- "@cdc/core": "^4.25.11",
9
+ "@cdc/core": "^4.26.2",
31
10
  "@visx/shape": "^3.12.0",
32
11
  "@visx/text": "^3.12.0",
33
- "chroma": "0.0.1",
34
- "html-react-parser": "5.2.3",
12
+ "html-react-parser": "^5.2.3",
35
13
  "react-accessible-accordion": "^5.0.1",
36
14
  "resize-observer-polyfill": "^1.5.1"
37
15
  },
16
+ "devDependencies": {
17
+ "@rollup/plugin-dsv": "^3.0.2",
18
+ "@vitejs/plugin-react": "^5.1.2",
19
+ "sass": "^1.89.2",
20
+ "vite-plugin-css-injected-by-js": "^2.4.0",
21
+ "vite-plugin-svgr": "^4.2.0"
22
+ },
23
+ "gitHead": "be3413e8e1149abf94225108f86a7910f56e0616",
24
+ "homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
25
+ "main": "dist/cdcwafflechart",
26
+ "moduleName": "CdcWaffleChart",
38
27
  "peerDependencies": {
39
28
  "react": "^18.2.0",
40
29
  "react-dom": "^18.2.0"
41
30
  },
42
- "devDependencies": {
43
- "@vitejs/plugin-react": "^4.3.4",
44
- "vite": "^4.4.11",
45
- "vite-plugin-css-injected-by-js": "^2.4.0",
46
- "vite-plugin-svgr": "^2.4.0"
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/CDCgov/cdc-open-viz",
34
+ "directory": "packages/waffle-chart"
35
+ },
36
+ "scripts": {
37
+ "build": "vite build",
38
+ "graph": "nx graph",
39
+ "prepublishOnly": "lerna run --scope @cdc/waffle-chart build",
40
+ "preview": "vite preview",
41
+ "start": "vite --open",
42
+ "test": "vitest run --reporter verbose",
43
+ "test-watch": "vitest watch --reporter verbose",
44
+ "test-watch:ui": "vitest --ui"
47
45
  },
48
- "gitHead": "5f09a137c22f454111ab5f4cd7fdf1d2d58e31bd"
46
+ "type": "module"
49
47
  }
@@ -30,6 +30,13 @@ import './scss/main.scss'
30
30
  import Title from '@cdc/core/components/ui/Title'
31
31
  import Layout from '@cdc/core/components/Layout'
32
32
 
33
+ // images
34
+ import CalloutFlag from './images/callout-flag.svg?url'
35
+
36
+ // TP5 Style Constants
37
+ const TP5_NODE_WIDTH = 13
38
+ const TP5_NODE_SPACER = 3
39
+
33
40
  type CdcWaffleChartProps = {
34
41
  configUrl?: string
35
42
  config?: Config
@@ -256,8 +263,10 @@ const WaffleChart = ({ config, isEditor, link = '', showConfigConfirm, updateCon
256
263
 
257
264
  const buildWaffle = useCallback(() => {
258
265
  let waffleData = []
259
- let nodeWidthNum = parseInt(nodeWidth, 10)
260
- let nodeSpacerNum = parseInt(nodeSpacer, 10)
266
+ // Use standardized values for TP5 style
267
+ const isTP5 = config.visualizationType === 'TP5 Waffle'
268
+ let nodeWidthNum = isTP5 ? TP5_NODE_WIDTH : parseInt(nodeWidth, 10)
269
+ let nodeSpacerNum = isTP5 ? TP5_NODE_SPACER : parseInt(nodeSpacer, 10)
261
270
 
262
271
  const calculatePos = (shape, axis, index, width, spacer) => {
263
272
  let mod = axis === 'x' ? index % 10 : axis === 'y' ? Math.floor(index / 10) : null
@@ -270,7 +279,8 @@ const WaffleChart = ({ config, isEditor, link = '', showConfigConfirm, updateCon
270
279
  x: calculatePos(shape, 'x', i, nodeWidthNum, nodeSpacerNum),
271
280
  y: calculatePos(shape, 'y', i, nodeWidthNum, nodeSpacerNum),
272
281
  color: config.visual.colors[theme],
273
- opacity: i + 1 > 100 - Math.round(dataPercentage) ? 1 : 0.35
282
+ opacity: i + 1 > 100 - Math.round(dataPercentage) ? 1 : 0.2,
283
+ isFilled: i + 1 > 100 - Math.round(dataPercentage)
274
284
  }
275
285
  waffleData.push(newNode)
276
286
  }
@@ -280,25 +290,37 @@ const WaffleChart = ({ config, isEditor, link = '', showConfigConfirm, updateCon
280
290
  <Bar
281
291
  className='cdc-waffle-chart__node'
282
292
  style={{ transitionDelay: `${0.1 * key}ms` }}
283
- x={node.x}
284
- y={node.y}
285
- width={nodeWidthNum}
286
- height={nodeWidthNum}
287
- fill={node.color}
288
- fillOpacity={node.opacity}
293
+ x={isTP5 && !node.isFilled ? node.x + 1 : node.x}
294
+ y={isTP5 && !node.isFilled ? node.y + 1 : node.y}
295
+ width={isTP5 && !node.isFilled ? nodeWidthNum - 2 : nodeWidthNum}
296
+ height={isTP5 && !node.isFilled ? nodeWidthNum - 2 : nodeWidthNum}
297
+ fill={isTP5 ? (node.isFilled ? '#009EC1' : '#DFF2F6') : node.color}
298
+ fillOpacity={isTP5 ? 1 : node.opacity}
299
+ stroke={isTP5 ? (!node.isFilled ? '#009EC1' : undefined) : undefined}
300
+ strokeWidth={isTP5 ? (!node.isFilled ? 1 : 0) : 0}
289
301
  key={key}
290
302
  />
291
303
  ) : node.shape === 'person' ? (
292
304
  <path
305
+ className='cdc-waffle-chart__node'
293
306
  style={{
294
- transform: `translateX(${node.x + nodeWidthNum / 4}px) translateY(${node.y}px) scale(${nodeWidthNum / 20})`
307
+ transform: isTP5
308
+ ? `translateX(${node.x}px) translateY(${node.y + nodeWidthNum * 0.1}px) scale(${
309
+ (nodeWidthNum * 0.8) / 448
310
+ })`
311
+ : `translateX(${node.x + nodeWidthNum / 4}px) translateY(${node.y}px) scale(${nodeWidthNum / 20})`,
312
+ transitionDelay: `${0.1 * key}ms`
295
313
  }}
296
- fill={node.color}
297
- fillOpacity={node.opacity}
314
+ fill={isTP5 ? (node.isFilled ? '#009EC1' : 'transparent') : node.color}
315
+ fillOpacity={isTP5 ? 1 : node.opacity}
316
+ stroke={isTP5 ? (!node.isFilled ? '#009EC1' : undefined) : undefined}
317
+ strokeWidth={isTP5 ? (!node.isFilled ? 448 / nodeWidthNum : 0) : 0}
298
318
  key={key}
299
- d='M3.75,0a2.5,2.5,0,1,1-2.5,2.5A2.5,2.5,0,0,1,3.75,0M5.625,5.625H5.18125a3.433,3.433,0,0,1-2.8625,0H1.875A1.875,1.875,
300
- 0,0,0,0,7.5v5.3125a.9375.9375,0,0,0,.9375.9375h.625v5.3125A.9375.9375,0,0,0,2.5,20H5a.9375.9375,0,0,0,
301
- .9375-.9375V13.75h.625A.9375.9375,0,0,0,7.5,12.8125V7.5A1.875,1.875,0,0,0,5.625,5.625Z'
319
+ d={
320
+ isTP5
321
+ ? 'M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512l388.6 0c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304l-91.4 0z'
322
+ : 'M3.75,0a2.5,2.5,0,1,1-2.5,2.5A2.5,2.5,0,0,1,3.75,0M5.625,5.625H5.18125a3.433,3.433,0,0,1-2.8625,0H1.875A1.875,1.875,0,0,0,0,7.5v5.3125a.9375.9375,0,0,0,.9375.9375h.625v5.3125A.9375.9375,0,0,0,2.5,20H5a.9375.9375,0,0,0,.9375-.9375V13.75h.625A.9375.9375,0,0,0,7.5,12.8125V7.5A1.875,1.875,0,0,0,5.625,5.625Z'
323
+ }
302
324
  ></path>
303
325
  ) : (
304
326
  <Circle
@@ -306,18 +328,28 @@ const WaffleChart = ({ config, isEditor, link = '', showConfigConfirm, updateCon
306
328
  style={{ transitionDelay: `${0.1 * key}ms` }}
307
329
  cx={node.x}
308
330
  cy={node.y}
309
- r={nodeWidthNum / 2}
310
- fill={node.color}
311
- fillOpacity={node.opacity}
331
+ r={isTP5 && !node.isFilled ? nodeWidthNum / 2 - 1 : nodeWidthNum / 2}
332
+ fill={isTP5 ? (node.isFilled ? '#009EC1' : '#DFF2F6') : node.color}
333
+ fillOpacity={isTP5 ? 1 : node.opacity}
334
+ stroke={isTP5 ? (!node.isFilled ? '#009EC1' : undefined) : undefined}
335
+ strokeWidth={isTP5 ? (!node.isFilled ? 1 : 0) : 0}
312
336
  key={key}
313
337
  />
314
338
  )
315
339
  )
316
- }, [theme, dataPercentage, shape, nodeWidth, nodeSpacer])
340
+ }, [theme, dataPercentage, shape, nodeWidth, nodeSpacer, config.visualizationType, config.visual?.whiteBackground])
317
341
 
318
342
  const setRatio = useCallback(() => {
319
- return nodeWidth * 10 + nodeSpacer * 9
320
- }, [nodeWidth, nodeSpacer])
343
+ const isTP5 = config.visualizationType === 'TP5 Waffle'
344
+ const width = isTP5 ? TP5_NODE_WIDTH : nodeWidth
345
+ const spacer = isTP5 ? TP5_NODE_SPACER : nodeSpacer
346
+ return width * 10 + spacer * 9
347
+ }, [nodeWidth, nodeSpacer, config.visualizationType])
348
+
349
+ const setSvgSize = useCallback(() => {
350
+ // Add 2px padding to account for strokes on edges
351
+ return setRatio() + 2
352
+ }, [nodeWidth, nodeSpacer, config.visualizationType])
321
353
 
322
354
  const { innerContainerClasses, contentClasses } = useDataVizClasses(config)
323
355
 
@@ -358,76 +390,181 @@ const WaffleChart = ({ config, isEditor, link = '', showConfigConfirm, updateCon
358
390
  )
359
391
  }
360
392
 
361
- return (
362
- <div className='cove-component__content'>
363
- <Title
364
- showTitle={config.showTitle}
365
- title={title}
366
- config={config}
367
- classes={['chart-title', `${config.theme}`, 'mb-0']}
368
- />
369
- <div className={contentClasses.join(' ')}>
370
- {!config.newViz && config.runtime && config.runtime.editorErrorMessage && (
371
- <Error updateConfig={updateConfig} config={config} />
372
- )}
373
- {config.newViz && showConfigConfirm && <Confirm updateConfig={updateConfig} config={config} />}
374
- <div className='cove-component__content-wrap'>
375
- {config.visualizationType === 'Gauge' && (
376
- <div className={`cove-gauge-chart${config.overallFontSize ? ' font-' + config.overallFontSize : ''}`}>
377
- <div className='cove-gauge-chart__chart'>
378
- <div className='cove-waffle-chart__data--primary' style={dataFontSize}>
379
- {prefix ? prefix : ' '}
380
- {config.showPercent ? dataPercentage : waffleNumerator}
381
- {suffix ? suffix + ' ' : ' '} {config.valueDescription}{' '}
382
- {config.showDenominator && waffleDenominator ? waffleDenominator : ' '}
383
- </div>
384
- <div className='cove-waffle-chart__data--text'>{parse(content)}</div>
385
- <svg height={config.gauge.height} width={'100%'}>
386
- <Group>
387
- <foreignObject
388
- style={{ border: '1px solid black' }}
389
- x={0}
390
- y={0}
391
- width={config.gauge.width}
392
- height={config.gauge.height}
393
- fill='#fff'
394
- />
395
- <Bar x={0} y={0} width={xScale(waffleNumerator)} height={config.gauge.height} fill={gaugeColor} />
396
- </Group>
397
- </svg>
398
- <div className={'cove-waffle-chart__subtext subtext'}>{parse(subtext)}</div>
399
- </div>
400
- </div>
401
- )}
402
- {config.visualizationType !== 'Gauge' && (
403
- <div
404
- className={`cove-waffle-chart${orientation === 'vertical' ? ' cove-waffle-chart--verical' : ''}${
405
- config.overallFontSize ? ' font-' + config.overallFontSize : ''
406
- }`}
407
- >
408
- <div className='cove-waffle-chart__chart' style={{ width: setRatio() }}>
409
- <svg width={setRatio()} height={setRatio()}>
410
- <Group>{buildWaffle()}</Group>
411
- </svg>
412
- </div>
413
- {(dataPercentage || content) && (
414
- <div className='cove-waffle-chart__data'>
415
- {dataPercentage && (
416
- <div className='cove-waffle-chart__data--primary' style={dataFontSize}>
417
- {prefix ? prefix : null}
418
- {dataPercentage}
419
- {suffix ? suffix : null}
393
+ // Render waffle chart content (without title)
394
+ const renderChartContent = () => (
395
+ <>
396
+ {!config.newViz && config.runtime && config.runtime.editorErrorMessage && (
397
+ <Error updateConfig={updateConfig} config={config} />
398
+ )}
399
+ {config.newViz && showConfigConfirm && <Confirm updateConfig={updateConfig} config={config} />}
400
+ <div className='cove-component__content-wrap p-0'>
401
+ {(config.visualizationType === 'Gauge' || config.visualizationType === 'TP5 Gauge') && (
402
+ <div className={`cove-gauge-chart${config.overallFontSize ? ' font-' + config.overallFontSize : ''}`}>
403
+ <div className='cove-gauge-chart__chart'>
404
+ {config.visualizationType === 'TP5 Gauge' ? (
405
+ <>
406
+ <div
407
+ className={`cove-gauge-chart__body d-flex flex-row align-items-start flex-grow-1${
408
+ !content ? ' justify-content-center' : ''
409
+ }`}
410
+ >
411
+ <div className='cove-gauge-chart__value-section flex-shrink-0'>
412
+ <div className='cove-waffle-chart__data--primary' style={dataFontSize}>
413
+ {prefix ? prefix : ' '}
414
+ {config.showPercent ? dataPercentage : waffleNumerator}
415
+ {suffix ? suffix + ' ' : ' '} {config.valueDescription}{' '}
416
+ {config.showDenominator && waffleDenominator ? waffleDenominator : ' '}
417
+ </div>
420
418
  </div>
419
+ <div className='cove-gauge-chart__content flex-grow-1 d-flex flex-column min-w-0'>
420
+ {content ? (
421
+ <div className='cove-waffle-chart__data--text'>{parse(content)}</div>
422
+ ) : (
423
+ <div className='cove-waffle-chart__data--text' aria-hidden='true'>
424
+ &nbsp;
425
+ </div>
426
+ )}
427
+ </div>
428
+ </div>
429
+ <svg
430
+ height={config.gauge.height + 2}
431
+ width={'100%'}
432
+ className='mt-2'
433
+ style={{ overflow: 'visible', padding: '1px' }}
434
+ >
435
+ <Group>
436
+ <Bar
437
+ x={0}
438
+ y={0}
439
+ width={config.gauge.width}
440
+ height={config.gauge.height}
441
+ fill='#dff2f6'
442
+ stroke='#007A99'
443
+ strokeWidth={1}
444
+ rx={10}
445
+ ry={10}
446
+ />
447
+ <Bar
448
+ x={0}
449
+ y={0}
450
+ width={xScale(waffleNumerator)}
451
+ height={config.gauge.height}
452
+ fill='#007A99'
453
+ rx={10}
454
+ ry={10}
455
+ />
456
+ </Group>
457
+ </svg>
458
+ {subtext && (
459
+ <div className='cove-waffle-chart__subtext subtext fst-italic mt-2'>{parse(subtext)}</div>
421
460
  )}
461
+ </>
462
+ ) : (
463
+ <>
464
+ <div className='cove-waffle-chart__data--primary' style={dataFontSize}>
465
+ {prefix ? prefix : ' '}
466
+ {config.showPercent ? dataPercentage : waffleNumerator}
467
+ {suffix ? suffix + ' ' : ' '} {config.valueDescription}{' '}
468
+ {config.showDenominator && waffleDenominator ? waffleDenominator : ' '}
469
+ </div>
422
470
  <div className='cove-waffle-chart__data--text'>{parse(content)}</div>
423
-
424
- {subtext && <div className='cove-waffle-chart__subtext subtext'>{parse(subtext)}</div>}
425
- </div>
471
+ <svg height={config.gauge.height} width={'100%'}>
472
+ <Group>
473
+ <Bar
474
+ x={0}
475
+ y={0}
476
+ width={config.gauge.width}
477
+ height={config.gauge.height}
478
+ fill='#e0e0e0'
479
+ stroke='#999'
480
+ strokeWidth={1}
481
+ rx={4}
482
+ ry={4}
483
+ />
484
+ <Bar
485
+ x={0}
486
+ y={0}
487
+ width={xScale(waffleNumerator)}
488
+ height={config.gauge.height}
489
+ fill={gaugeColor}
490
+ rx={4}
491
+ ry={4}
492
+ />
493
+ </Group>
494
+ </svg>
495
+ <div className={'cove-waffle-chart__subtext subtext'}>{parse(subtext)}</div>
496
+ </>
426
497
  )}
427
498
  </div>
499
+ </div>
500
+ )}
501
+ {config.visualizationType !== 'Gauge' && config.visualizationType !== 'TP5 Gauge' && (
502
+ <div
503
+ className={`cove-waffle-chart${orientation === 'vertical' ? ' cove-waffle-chart--verical' : ''}${
504
+ config.overallFontSize ? ' font-' + config.overallFontSize : ''
505
+ }`}
506
+ >
507
+ <div className='cove-waffle-chart__chart' style={{ width: setRatio() }}>
508
+ <svg width={setSvgSize()} height={setSvgSize()} style={{ display: 'block' }}>
509
+ <Group top={1} left={1}>
510
+ {buildWaffle()}
511
+ </Group>
512
+ </svg>
513
+ </div>
514
+ {(dataPercentage || content) && (
515
+ <div className='cove-waffle-chart__data'>
516
+ {dataPercentage && (
517
+ <div className='cove-waffle-chart__data--primary' style={dataFontSize}>
518
+ {prefix ? prefix : null}
519
+ {dataPercentage}
520
+ {suffix ? suffix : null}
521
+ </div>
522
+ )}
523
+ {content && <div className='cove-waffle-chart__data--text'>{parse(content)}</div>}
524
+
525
+ {subtext && <div className='cove-waffle-chart__subtext subtext fst-italic'>{parse(subtext)}</div>}
526
+ </div>
527
+ )}
528
+ </div>
529
+ )}
530
+ </div>
531
+ </>
532
+ )
533
+
534
+ // TP5 Style: render with callout wrapper inside cove-component__content
535
+ if (config.visualizationType === 'TP5 Waffle' || config.visualizationType === 'TP5 Gauge') {
536
+ const calloutClasses = ['cdc-callout', 'd-flex', 'flex-column']
537
+ if (!config.visual?.whiteBackground) {
538
+ calloutClasses.push('dfe-block', 'cdc-callout--data')
539
+ }
540
+
541
+ return (
542
+ <div className='cove-component__content border-0'>
543
+ <div className={calloutClasses.join(' ')}>
544
+ {!config.visual?.whiteBackground && (
545
+ <img src={CalloutFlag} alt='' className='cdc-callout__flag' aria-hidden='true' />
428
546
  )}
547
+ {config.showTitle && title && title.trim() && (
548
+ <h3 className='cdc-callout__heading fw-bold flex-shrink-0'>{parse(title)}</h3>
549
+ )}
550
+ <div className='w-100 mw-100 overflow-hidden'>{renderChartContent()}</div>
429
551
  </div>
552
+ {link && link}
430
553
  </div>
554
+ )
555
+ }
556
+
557
+ // Original Style: Regular title and content
558
+ return (
559
+ <div className='cove-component__content'>
560
+ <Title
561
+ showTitle={config.showTitle}
562
+ title={title}
563
+ titleStyle='legacy'
564
+ config={config}
565
+ classes={['chart-title', `${config.theme}`, 'mb-0']}
566
+ />
567
+ <div className={contentClasses.join(' ')}>{renderChartContent()}</div>
431
568
  {link && link}
432
569
  </div>
433
570
  )
@@ -500,8 +637,7 @@ const CdcWaffleChart = ({
500
637
 
501
638
  //Load initial config
502
639
  useEffect(() => {
503
- // eslint-disable-next-line no-console
504
- loadConfig().catch(err => console.log(err))
640
+ loadConfig().catch(err => console.warn(err))
505
641
  }, [])
506
642
 
507
643
  useEffect(() => {
@@ -521,21 +657,17 @@ const CdcWaffleChart = ({
521
657
  let content = <Loading />
522
658
 
523
659
  if (loading === false) {
524
- let body = (
525
- <Layout.Responsive isEditor={isEditor}>
526
- <WaffleChart
527
- config={config}
528
- isEditor={isEditor}
529
- showConfigConfirm={showConfigConfirm}
530
- updateConfig={updateConfig}
531
- />
532
- </Layout.Responsive>
533
- )
534
-
535
660
  content = (
536
661
  <>
537
- {isEditor && <EditorPanel showConfigConfirm={showConfigConfirm}>{body}</EditorPanel>}
538
- {!isEditor && body}
662
+ {isEditor && <EditorPanel showConfigConfirm={showConfigConfirm} />}
663
+ <Layout.Responsive isEditor={isEditor}>
664
+ <WaffleChart
665
+ config={config}
666
+ isEditor={isEditor}
667
+ showConfigConfirm={showConfigConfirm}
668
+ updateConfig={updateConfig}
669
+ />
670
+ </Layout.Responsive>
539
671
  </>
540
672
  )
541
673
  }