@cdc/chart 4.25.11 → 4.26.1

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.
Files changed (77) hide show
  1. package/dist/cdcchart.js +38898 -40013
  2. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  3. package/examples/private/DEV-12100.json +1303 -0
  4. package/examples/private/cat-y.json +1235 -0
  5. package/examples/private/data-points.json +228 -0
  6. package/examples/private/height.json +3915 -0
  7. package/examples/private/links.json +569 -0
  8. package/examples/private/quadrant.txt +30 -0
  9. package/examples/private/test-forecast.json +5510 -0
  10. package/examples/private/warming-stripe-test.json +2578 -0
  11. package/examples/private/warming-stripes.json +4763 -0
  12. package/examples/tech-adoption-with-links.json +560 -0
  13. package/index.html +15 -20
  14. package/package.json +5 -4
  15. package/preview.html +1616 -0
  16. package/src/CdcChartComponent.tsx +111 -75
  17. package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
  18. package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
  19. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
  20. package/src/_stories/Chart.stories.tsx +8 -0
  21. package/src/_stories/ChartBar.Editor.stories.tsx +11 -6
  22. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  23. package/src/_stories/ChartBrush.stories.tsx +50 -0
  24. package/src/_stories/ChartEditor.Editor.stories.tsx +3 -5
  25. package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
  26. package/src/_stories/_mock/brush_enabled.json +326 -0
  27. package/src/_stories/_mock/brush_mock.json +2 -69
  28. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  29. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -2
  30. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  31. package/src/components/BarChart/components/BarChart.Horizontal.tsx +178 -24
  32. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  33. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  34. package/src/components/BarChart/components/BarChart.Vertical.tsx +6 -8
  35. package/src/components/BarChart/components/context.tsx +1 -0
  36. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  37. package/src/components/Brush/BrushSelector.tsx +1258 -0
  38. package/src/components/Brush/MiniChartPreview.tsx +283 -0
  39. package/src/components/DeviationBar.jsx +9 -7
  40. package/src/components/EditorPanel/EditorPanel.tsx +2711 -2586
  41. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  42. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +57 -30
  43. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +2 -0
  44. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +30 -25
  45. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +21 -27
  46. package/src/components/EditorPanel/useEditorPermissions.ts +31 -18
  47. package/src/components/Legend/Legend.tsx +3 -2
  48. package/src/components/Legend/helpers/createFormatLabels.tsx +151 -2
  49. package/src/components/Legend/helpers/index.ts +10 -6
  50. package/src/components/LinearChart.tsx +495 -430
  51. package/src/components/PairedBarChart.jsx +20 -3
  52. package/src/components/Regions/components/Regions.tsx +365 -122
  53. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  54. package/src/components/SmallMultiples/SmallMultipleTile.tsx +5 -1
  55. package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
  56. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  57. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  58. package/src/components/WarmingStripes/index.tsx +3 -0
  59. package/src/data/initial-state.js +3 -1
  60. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  61. package/src/helpers/getMinMax.ts +12 -7
  62. package/src/helpers/sizeHelpers.ts +0 -20
  63. package/src/helpers/smallMultiplesHelpers.ts +1 -1
  64. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  65. package/src/hooks/useScales.ts +11 -1
  66. package/src/hooks/useTooltip.tsx +31 -10
  67. package/src/scss/DataTable.scss +0 -4
  68. package/src/scss/main.scss +17 -3
  69. package/src/test/CdcChart.test.jsx +1 -1
  70. package/src/types/ChartConfig.ts +3 -0
  71. package/src/types/Label.ts +1 -0
  72. package/src/utils/analyticsTracking.ts +19 -0
  73. package/LICENSE +0 -201
  74. package/src/components/Brush/BrushChart.tsx +0 -128
  75. package/src/components/Brush/BrushController.tsx +0 -71
  76. package/src/components/Brush/types.tsx +0 -8
  77. package/src/components/BrushChart.tsx +0 -223
@@ -13,7 +13,6 @@ import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
13
13
  // CDC core components and helpers
14
14
  import { getColorContrast, getContrastColor } from '@cdc/core/helpers/cove/accessibility'
15
15
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
16
- import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
17
16
  import createBarElement from '@cdc/core/components/createBarElement'
18
17
  import { getBarConfig, testZeroValue, getLollipopStemColor, getLollipopHeadColor } from '../helpers'
19
18
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
@@ -36,6 +35,7 @@ const BarChartHorizontal = () => {
36
35
  isLabelBelowBar,
37
36
  lollipopBarWidth,
38
37
  lollipopShapeSize,
38
+ labelFontSize,
39
39
  getHighlightedBarColorByValue,
40
40
  getHighlightedBarByValue,
41
41
  getAdditionalColumn,
@@ -59,8 +59,6 @@ const BarChartHorizontal = () => {
59
59
 
60
60
  const { HighLightedBarUtils } = useHighlightedBars(config)
61
61
 
62
- const LABEL_FONT_SIZE = isMobileFontViewport(vizViewport) ? 13 : 16
63
-
64
62
  const hasConfidenceInterval = [config.confidenceKeys?.upper, config.confidenceKeys?.lower].every(
65
63
  v => v != null && String(v).trim() !== ''
66
64
  )
@@ -209,7 +207,7 @@ const BarChartHorizontal = () => {
209
207
  isVertical: false,
210
208
  yAxisValue,
211
209
  barWidth: 0,
212
- labelFontSize: LABEL_FONT_SIZE
210
+ labelFontSize: labelFontSize
213
211
  })
214
212
 
215
213
  const barPosition = !isPositiveBar ? 'below' : 'above'
@@ -217,7 +215,7 @@ const BarChartHorizontal = () => {
217
215
  const barDefaultLabel = !config.yAxis.displayNumbersOnBar || absentDataLabel ? '' : yAxisValue
218
216
 
219
217
  // check if bar text/value string fits into each bars.
220
- const textWidth = getTextWidth(barDefaultLabel)
218
+ const textWidth = getTextWidth(barDefaultLabel, `normal ${labelFontSize}px sans-serif`)
221
219
  const textFits = Number(textWidth) < defaultBarWidth - 5
222
220
 
223
221
  // control text position
@@ -447,7 +445,7 @@ const BarChartHorizontal = () => {
447
445
  return (
448
446
  <Text // prettier-ignore
449
447
  key={index}
450
- fontSize={LABEL_FONT_SIZE}
448
+ fontSize={labelFontSize}
451
449
  display={displayBar ? 'block' : 'none'}
452
450
  opacity={transparentBar ? 0.5 : 1}
453
451
  x={barX}
@@ -473,6 +471,7 @@ const BarChartHorizontal = () => {
473
471
  dx={textPadding}
474
472
  verticalAnchor='middle'
475
473
  textAnchor={textAnchor}
474
+ fontSize={labelFontSize}
476
475
  >
477
476
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
478
477
  </Text>
@@ -488,6 +487,7 @@ const BarChartHorizontal = () => {
488
487
  dx={-textPadding}
489
488
  verticalAnchor='middle'
490
489
  textAnchor={bar.value < 0 ? 'end' : 'start'}
490
+ fontSize={labelFontSize}
491
491
  >
492
492
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
493
493
  </Text>
@@ -496,12 +496,17 @@ const BarChartHorizontal = () => {
496
496
  display={displayBar ? 'block' : 'none'}
497
497
  x={bar.y}
498
498
  opacity={transparentBar ? 0.5 : 1}
499
- y={config.barHeight / 2 + config.barHeight * bar.index}
499
+ y={
500
+ config.isLollipopChart
501
+ ? barHeight * bar.index + lollipopBarWidth / 2
502
+ : config.barHeight / 2 + config.barHeight * bar.index
503
+ }
500
504
  fill={labelColor}
501
505
  dx={absentDataLabel === 'N/A' ? 20 : textPadding}
502
- dy={config.isLollipopChart ? -10 : 0}
506
+ dy={0}
503
507
  verticalAnchor='middle'
504
508
  textAnchor={absentDataLabel === 'N/A' ? 'middle' : textAnchor}
509
+ fontSize={labelFontSize}
505
510
  >
506
511
  {absentDataLabel}
507
512
  </Text>
@@ -510,31 +515,180 @@ const BarChartHorizontal = () => {
510
515
  <Text // prettier-ignore
511
516
  display={displayBar ? 'block' : 'none'}
512
517
  x={bar.y}
513
- y={0}
518
+ y={barHeight * bar.index + lollipopBarWidth / 2}
514
519
  fill={APP_FONT_COLOR}
515
520
  dx={textPaddingLollipop}
516
521
  textAnchor={textAnchorLollipop}
517
522
  verticalAnchor='middle'
518
523
  fontWeight={'normal'}
524
+ fontSize={labelFontSize}
519
525
  >
520
526
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
521
527
  </Text>
522
528
  )}
523
- {isLabelBelowBar && !config.yAxis.hideLabel && (
524
- <Text
525
- x={config.yAxis.hideAxis ? 0 : 5}
526
- y={barGroup.height}
527
- dy={4}
528
- verticalAnchor={'start'}
529
- textAnchor={'start'}
530
- >
531
- {config.runtime.yAxis.type === 'date'
532
- ? formatDate(parseDate(dataValue))
533
- : isHorizontal
534
- ? dataValue
535
- : formatNumber(dataValue)}
536
- </Text>
537
- )}
529
+ {isLabelBelowBar &&
530
+ !config.yAxis.hideLabel &&
531
+ (() => {
532
+ const label =
533
+ config.runtime.yAxis.type === 'date'
534
+ ? formatDate(parseDate(dataValue))
535
+ : isHorizontal
536
+ ? dataValue
537
+ : formatNumber(dataValue)
538
+ if (typeof label === 'string') {
539
+ // 1. Check for HTML <a> tag and extract its text and href
540
+ const aTagMatch = label.match(/<a [^>]*href=["']?([^"'>\s]+)["']?[^>]*>(.*?)<\/a>/i)
541
+ if (aTagMatch) {
542
+ const href = aTagMatch[1].startsWith('http') ? aTagMatch[1] : `https://${aTagMatch[1]}`
543
+ const linkText = aTagMatch[2]
544
+ return (
545
+ <foreignObject
546
+ x={config.yAxis.hideAxis ? 0 : 5}
547
+ y={barGroup.height}
548
+ width={120}
549
+ height={24}
550
+ style={{ overflow: 'visible' }}
551
+ >
552
+ <a
553
+ href={href}
554
+ target='_blank'
555
+ rel='noopener noreferrer'
556
+ style={
557
+ config.tooltips.singleSeries
558
+ ? {
559
+ color: '#0071bc',
560
+ textDecoration: 'underline',
561
+ fontSize: 12,
562
+ fontFamily: 'inherit',
563
+ display: 'inline-block',
564
+ width: '100%'
565
+ }
566
+ : {
567
+ color: 'inherit',
568
+ textDecoration: 'none',
569
+ fontSize: 12,
570
+ fontFamily: 'inherit',
571
+ display: 'inline-block',
572
+ width: '100%'
573
+ }
574
+ }
575
+ >
576
+ {linkText}
577
+ </a>
578
+ </foreignObject>
579
+ )
580
+ }
581
+ // 2. Check for markdown link
582
+ const mdMatch = label.match(/\[([^\]]+)\]\((https?:\/\/[^\s]+|www\.[^\s]+)\)/i)
583
+ if (mdMatch) {
584
+ const href = mdMatch[2].startsWith('http') ? mdMatch[2] : `https://${mdMatch[2]}`
585
+ const linkText = mdMatch[1]
586
+ return (
587
+ <foreignObject
588
+ x={config.yAxis.hideAxis ? 0 : 5}
589
+ y={barGroup.height}
590
+ width={120}
591
+ height={24}
592
+ style={{ overflow: 'visible' }}
593
+ >
594
+ <a
595
+ href={href}
596
+ target='_blank'
597
+ rel='noopener noreferrer'
598
+ style={
599
+ config.tooltips.singleSeries
600
+ ? {
601
+ color: '#0071bc',
602
+ textDecoration: 'underline',
603
+ fontSize: 12,
604
+ fontFamily: 'inherit',
605
+ display: 'inline-block',
606
+ width: '100%'
607
+ }
608
+ : {
609
+ color: 'inherit',
610
+ textDecoration: 'none',
611
+ fontSize: 12,
612
+ fontFamily: 'inherit',
613
+ display: 'inline-block',
614
+ width: '100%'
615
+ }
616
+ }
617
+ >
618
+ {linkText}
619
+ </a>
620
+ </foreignObject>
621
+ )
622
+ }
623
+ // 3. Check for plain URL
624
+ if (/(https?:\/\/|www\.)/i.test(label)) {
625
+ try {
626
+ const urlObj = new URL(label.startsWith('http') ? label : `https://${label}`)
627
+ const linkText = urlObj.hostname.replace(/^www\./, '')
628
+ return (
629
+ <foreignObject
630
+ x={config.yAxis.hideAxis ? 0 : 5}
631
+ y={barGroup.height}
632
+ width={120}
633
+ height={24}
634
+ style={{ overflow: 'visible' }}
635
+ >
636
+ <a
637
+ href={urlObj.href}
638
+ target='_blank'
639
+ rel='noopener noreferrer'
640
+ style={
641
+ config.tooltips.singleSeries
642
+ ? {
643
+ color: '#0071bc',
644
+ textDecoration: 'underline',
645
+ fontSize: 12,
646
+ fontFamily: 'inherit',
647
+ display: 'inline-block',
648
+ width: '100%'
649
+ }
650
+ : {
651
+ color: 'inherit',
652
+ textDecoration: 'none',
653
+ fontSize: 12,
654
+ fontFamily: 'inherit',
655
+ display: 'inline-block',
656
+ width: '100%'
657
+ }
658
+ }
659
+ >
660
+ {linkText}
661
+ </a>
662
+ </foreignObject>
663
+ )
664
+ } catch {
665
+ return (
666
+ <Text
667
+ x={config.yAxis.hideAxis ? 0 : 5}
668
+ y={barGroup.height}
669
+ dy={4}
670
+ verticalAnchor={'start'}
671
+ textAnchor={'start'}
672
+ >
673
+ {label}
674
+ </Text>
675
+ )
676
+ }
677
+ }
678
+ }
679
+ // Not a link, render as normal
680
+ return (
681
+ <Text
682
+ x={config.yAxis.hideAxis ? 0 : 5}
683
+ y={barGroup.height}
684
+ dy={4}
685
+ verticalAnchor={'start'}
686
+ textAnchor={'start'}
687
+ >
688
+ {label}
689
+ </Text>
690
+ )
691
+ })()}
538
692
 
539
693
  {config.isLollipopChart && config.lollipopShape === 'circle' && (
540
694
  <circle
@@ -38,6 +38,7 @@ const BarChartStackedHorizontal = () => {
38
38
  hoveredBar,
39
39
  isHorizontal,
40
40
  isLabelBelowBar,
41
+ labelFontSize,
41
42
  onMouseLeaveBar,
42
43
  onMouseOverBar,
43
44
  barStackedSeriesKeys
@@ -155,7 +156,7 @@ const BarChartStackedHorizontal = () => {
155
156
  const yAxisTooltip = config.runtime.yAxis.label
156
157
  ? `${config.runtime.yAxis.label}: ${yAxisValue}`
157
158
  : yAxisValue
158
- const textWidth = getTextWidth(xAxisValue)
159
+ const textWidth = getTextWidth(xAxisValue, `normal ${labelFontSize}px sans-serif`)
159
160
  const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
160
161
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${xAxisValue}`
161
162
  const tooltip = `<ul>
@@ -286,6 +287,7 @@ const BarChartStackedHorizontal = () => {
286
287
  fill={labelColor}
287
288
  textAnchor='middle'
288
289
  verticalAnchor='middle'
290
+ fontSize={labelFontSize}
289
291
  >
290
292
  {xAxisValue}
291
293
  </Text>
@@ -259,6 +259,7 @@ const BarChartStackedVertical = () => {
259
259
  yMax={yMax}
260
260
  barWidth={barWidth}
261
261
  totalBarsInGroup={1}
262
+ xMax={xMax}
262
263
  handleTooltipMouseOff={() => {}}
263
264
  handleTooltipMouseOver={() => {}}
264
265
  handleTooltipClick={() => {}}
@@ -17,7 +17,6 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
17
17
  import isNumber from '@cdc/core/helpers/isNumber'
18
18
  import createBarElement from '@cdc/core/components/createBarElement'
19
19
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
20
- import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
21
20
  // Types
22
21
  import { type ChartContext } from '../../../types/ChartContext'
23
22
  import _ from 'lodash'
@@ -32,6 +31,7 @@ const BarChartVertical = () => {
32
31
  getAdditionalColumn,
33
32
  getHighlightedBarByValue,
34
33
  getHighlightedBarColorByValue,
34
+ labelFontSize,
35
35
  lollipopBarWidth,
36
36
  lollipopShapeSize,
37
37
  onMouseLeaveBar,
@@ -59,8 +59,6 @@ const BarChartVertical = () => {
59
59
 
60
60
  const { HighLightedBarUtils } = useHighlightedBars(config)
61
61
 
62
- const LABEL_FONT_SIZE = isMobileFontViewport(vizViewport) ? 13 : 16
63
-
64
62
  const root = document.documentElement
65
63
 
66
64
  let data = transformedData
@@ -242,7 +240,7 @@ const BarChartVertical = () => {
242
240
  barWidth,
243
241
  isVertical: true,
244
242
  yAxisValue,
245
- labelFontSize: LABEL_FONT_SIZE
243
+ labelFontSize: labelFontSize
246
244
  })
247
245
  // configure colors
248
246
  let labelColor = APP_FONT_COLOR
@@ -481,7 +479,7 @@ const BarChartVertical = () => {
481
479
  verticalAnchor={verticalAnchor}
482
480
  fill={fillColor}
483
481
  textAnchor='middle'
484
- fontSize={LABEL_FONT_SIZE}
482
+ fontSize={labelFontSize}
485
483
  >
486
484
  {pd.iconCode}
487
485
  </Text>
@@ -494,7 +492,7 @@ const BarChartVertical = () => {
494
492
  y={barY - BAR_LABEL_PADDING}
495
493
  fill={labelColor}
496
494
  textAnchor='middle'
497
- fontSize={LABEL_FONT_SIZE}
495
+ fontSize={labelFontSize}
498
496
  >
499
497
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
500
498
  </Text>
@@ -505,7 +503,7 @@ const BarChartVertical = () => {
505
503
  y={barY - BAR_LABEL_PADDING}
506
504
  fill={labelColor}
507
505
  textAnchor='middle'
508
- fontSize={config.isLollipopChart ? null : LABEL_FONT_SIZE}
506
+ fontSize={config.isLollipopChart ? null : labelFontSize}
509
507
  >
510
508
  {absentDataLabel}
511
509
  </Text>
@@ -568,7 +566,7 @@ const BarChartVertical = () => {
568
566
  }}
569
567
  </BarGroup>
570
568
 
571
- <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} />
569
+ <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} xMax={xMax} />
572
570
  </Group>
573
571
  )
574
572
  )
@@ -18,6 +18,7 @@ export type BarChartContextValues = {
18
18
  getHighlightedBarColorByValue: Function
19
19
  lollipopBarWidth: number
20
20
  lollipopShapeSize: number
21
+ labelFontSize: number
21
22
  onMouseLeaveBar: Function
22
23
  onMouseOverBar: Function
23
24
  section: string
@@ -5,10 +5,20 @@ import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
5
5
  import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
6
6
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
7
7
  import { getVizSubType, getVizTitle } from '@cdc/core/helpers/metrics/utils'
8
+ import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
8
9
 
9
10
  export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, configContext) => {
10
- const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, seriesHighlight, interactionLabel } =
11
- configContext
11
+ const {
12
+ config,
13
+ colorPalettes,
14
+ tableData,
15
+ updateConfig,
16
+ parseDate,
17
+ formatDate,
18
+ seriesHighlight,
19
+ interactionLabel,
20
+ vizViewport
21
+ } = configContext
12
22
  const { orientation } = config
13
23
  const dispatch = useContext(ChartDispatchContext)
14
24
  const [hoveredBar, setHoveredBar] = useState(null)
@@ -20,6 +30,7 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
20
30
  const isLabelBelowBar = config.yAxis.labelPlacement === 'Below Bar'
21
31
  const displayNumbersOnBar = config.yAxis.displayNumbersOnBar
22
32
  const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
33
+ const labelFontSize = isMobileFontViewport(vizViewport) ? 13 : 16
23
34
 
24
35
  const isRounded = config.barStyle === 'rounded'
25
36
  const isStacked = config.visualizationSubType === 'stacked'
@@ -241,6 +252,7 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
241
252
  stackCount,
242
253
  barStackedSeriesKeys,
243
254
  hasMultipleSeries,
255
+ labelFontSize,
244
256
  applyRadius,
245
257
  assignColorsToValues,
246
258
  getHighlightedBarColorByValue,