@cdc/chart 4.23.11 → 4.24.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 (103) hide show
  1. package/dist/cdcchart.js +30220 -29764
  2. package/examples/feature/bar/additional-column-tooltip.json +446 -0
  3. package/examples/feature/bar/tall-data.json +98 -0
  4. package/examples/feature/forest-plot/forest-plot.json +63 -19
  5. package/examples/feature/forest-plot/linear.json +52 -3
  6. package/examples/feature/forest-plot/log.json +26 -0
  7. package/examples/feature/forest-plot/logarithmic.json +0 -35
  8. package/examples/feature/line/line-chart-preliminary.json +346 -0
  9. package/examples/feature/scatterplot/scatterplot.json +272 -33
  10. package/examples/private/chart-t.json +3740 -0
  11. package/examples/private/combo.json +369 -0
  12. package/examples/private/epi-data.csv +13 -0
  13. package/examples/private/epi-data.json +62 -0
  14. package/examples/private/epi.json +403 -0
  15. package/examples/private/occupancy.json +109283 -0
  16. package/examples/private/prod-line-config.json +401 -0
  17. package/examples/private/region-data.json +822 -0
  18. package/examples/private/region-testing.json +312 -0
  19. package/examples/private/scaling.json +45325 -0
  20. package/examples/private/testing-data.json +1739 -0
  21. package/examples/private/testing.json +816 -0
  22. package/index.html +7 -7
  23. package/package.json +2 -2
  24. package/src/CdcChart.tsx +29 -210
  25. package/src/ConfigContext.tsx +6 -0
  26. package/src/_stories/ChartEditor.stories.tsx +22 -0
  27. package/src/_stories/ChartLine.preliminary.tsx +19 -0
  28. package/src/_stories/_mock/pie_config.json +191 -0
  29. package/src/_stories/_mock/pie_data.json +218 -0
  30. package/src/_stories/_mock/preliminary_mock.json +346 -0
  31. package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +2 -2
  32. package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +1 -1
  33. package/src/components/AreaChart/index.tsx +4 -0
  34. package/src/components/{BarChart.Horizontal.tsx → BarChart/components/BarChart.Horizontal.tsx} +8 -8
  35. package/src/components/{BarChart.StackedHorizontal.tsx → BarChart/components/BarChart.StackedHorizontal.tsx} +37 -7
  36. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +106 -0
  37. package/src/components/{BarChart.Vertical.tsx → BarChart/components/BarChart.Vertical.tsx} +41 -57
  38. package/src/components/BarChart/components/BarChart.jsx +39 -0
  39. package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
  40. package/src/components/BarChart/components/context.tsx +13 -0
  41. package/src/components/BarChart/index.tsx +3 -0
  42. package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +1 -1
  43. package/src/components/BoxPlot/index.tsx +3 -0
  44. package/src/components/{EditorPanel.jsx → EditorPanel/EditorPanel.tsx} +667 -851
  45. package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +109 -0
  46. package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panel.ForestPlotSettings.tsx} +87 -166
  47. package/src/components/EditorPanel/components/Panel.Regions.tsx +168 -0
  48. package/src/components/{Series.jsx → EditorPanel/components/Panel.Series.tsx} +1 -1
  49. package/src/components/EditorPanel/components/PanelProps.ts +3 -0
  50. package/src/components/EditorPanel/components/Panels.tsx +13 -0
  51. package/src/components/EditorPanel/components/panels.scss +72 -0
  52. package/src/components/EditorPanel/editor-panel.scss +751 -0
  53. package/src/components/EditorPanel/index.tsx +3 -0
  54. package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +29 -2
  55. package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
  56. package/src/components/Forecasting/index.tsx +3 -0
  57. package/src/components/ForestPlot/ForestPlot.tsx +254 -0
  58. package/src/components/ForestPlot/ForestPlotProps.ts +7 -0
  59. package/src/components/ForestPlot/index.tsx +1 -209
  60. package/src/components/{Legend.jsx → Legend/Legend.tsx} +150 -113
  61. package/src/components/Legend/index.tsx +3 -0
  62. package/src/components/LineChart/LineChartProps.ts +29 -0
  63. package/src/components/LineChart/{LineChart.Circle.tsx → components/LineChart.Circle.tsx} +12 -3
  64. package/src/components/LineChart/helpers.ts +45 -0
  65. package/src/components/LineChart/index.tsx +20 -8
  66. package/src/components/LinearChart.jsx +52 -69
  67. package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +16 -7
  68. package/src/components/PieChart/index.tsx +3 -0
  69. package/src/components/Regions/components/Regions.tsx +135 -0
  70. package/src/components/Regions/index.tsx +3 -0
  71. package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
  72. package/src/components/ScatterPlot/index.tsx +3 -0
  73. package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
  74. package/src/components/Sparkline/index.tsx +3 -0
  75. package/src/data/initial-state.js +5 -6
  76. package/src/helpers/abbreviateNumber.ts +17 -0
  77. package/src/helpers/computeMarginBottom.ts +55 -0
  78. package/src/helpers/filterData.ts +18 -0
  79. package/src/helpers/generateColorsArray.ts +8 -0
  80. package/src/helpers/getQuartiles.ts +30 -0
  81. package/src/helpers/handleChartAriaLabels.ts +19 -0
  82. package/src/helpers/handleLineType.ts +18 -0
  83. package/src/helpers/lineOptions.ts +18 -0
  84. package/src/helpers/sort.ts +7 -0
  85. package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
  86. package/src/hooks/useBarChart.js +7 -6
  87. package/src/hooks/useScales.ts +1 -1
  88. package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +23 -21
  89. package/src/scss/main.scss +67 -3
  90. package/src/types/ChartConfig.ts +158 -23
  91. package/src/types/ChartContext.ts +26 -10
  92. package/src/types/ForestPlot.ts +7 -14
  93. package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
  94. package/src/ConfigContext.jsx +0 -5
  95. package/src/components/BarChart.StackedVertical.tsx +0 -91
  96. package/src/components/BarChart.jsx +0 -30
  97. package/src/components/ForestPlot/Readme.md +0 -0
  98. package/src/scss/LinearChart.scss +0 -0
  99. package/src/scss/editor-panel.scss +0 -745
  100. package/src/scss/legend.scss +0 -206
  101. package/src/scss/mixins.scss +0 -0
  102. package/src/scss/variables.scss +0 -1
  103. package/src/types/ChartProps.ts +0 -7
@@ -9,11 +9,10 @@ import { Tooltip as ReactTooltip } from 'react-tooltip'
9
9
  import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
10
 
11
11
  // CDC Components
12
- import AreaChart from './AreaChart'
13
- import AreaChartStacked from './AreaChart.Stacked'
12
+ import { AreaChart, AreaChartStacked } from './AreaChart'
14
13
  import BarChart from './BarChart'
15
14
  import ConfigContext from '../ConfigContext'
16
- import CoveBoxPlot from './BoxPlot'
15
+ import BoxPlot from './BoxPlot'
17
16
  import ScatterPlot from './ScatterPlot'
18
17
  import DeviationBar from './DeviationBar'
19
18
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
@@ -22,6 +21,7 @@ import LineChart from './LineChart'
22
21
  import ForestPlot from './ForestPlot'
23
22
  import PairedBarChart from './PairedBarChart'
24
23
  import useIntersectionObserver from './../hooks/useIntersectionObserver'
24
+ import Regions from './Regions'
25
25
 
26
26
  // Hooks
27
27
  import useMinMax from '../hooks/useMinMax'
@@ -30,14 +30,33 @@ import useRightAxis from '../hooks/useRightAxis'
30
30
  import useScales from '../hooks/useScales'
31
31
  import useTopAxis from '../hooks/useTopAxis'
32
32
  import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
33
- import { useEditorPermissions } from '../hooks/useEditorPermissions'
33
+ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
34
34
 
35
35
  // styles
36
- import '../scss/LinearChart.scss'
37
36
  import ZoomBrush from './ZoomBrush'
38
37
 
39
38
  const LinearChart = props => {
40
- const { isEditor, isDashboard, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth, isDebug } = useContext(ConfigContext)
39
+ const {
40
+ isEditor,
41
+ isDashboard,
42
+ computeMarginBottom,
43
+ transformedData: data,
44
+ dimensions,
45
+ config,
46
+ parseDate,
47
+ formatDate,
48
+ currentViewport,
49
+ formatNumber,
50
+ handleChartAriaLabels,
51
+ updateConfig,
52
+ handleLineType,
53
+ rawData,
54
+ capitalize,
55
+ setSharedFilter,
56
+ setSharedFilterValue,
57
+ getTextWidth,
58
+ isDebug
59
+ } = useContext(ConfigContext)
41
60
  // todo: start destructuring this file for conciseness
42
61
  const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
43
62
 
@@ -50,7 +69,7 @@ const LinearChart = props => {
50
69
  }
51
70
  // configure height , yMax, xMax
52
71
  const { horizontal: heightHorizontal } = config.heights
53
- const isHorizontal = orientation === 'horizontal'
72
+ const isHorizontal = orientation === 'horizontal' || config.visualizationType === 'Forest Plot'
54
73
  const shouldAbbreviate = true
55
74
  let height = config.aspectRatio ? width * config.aspectRatio : config.visualizationType === 'Forest Plot' ? config.heights['vertical'] : config.heights[orientation]
56
75
  const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
@@ -59,6 +78,7 @@ const LinearChart = props => {
59
78
  if (config.visualizationType === 'Forest Plot') {
60
79
  height = height + config.data.length * config.forestPlot.rowHeight
61
80
  yMax = yMax + config.data.length * config.forestPlot.rowHeight
81
+ width = dimensions[0]
62
82
  }
63
83
  if (config.brush.active) {
64
84
  height = height + config.brush.height
@@ -82,11 +102,11 @@ const LinearChart = props => {
82
102
  const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
83
103
  const getYAxisData = (d, seriesKey) => d[seriesKey]
84
104
  const xAxisDataMapped = config.brush.active && config.brush.data?.length ? config.brush.data.map(d => getXAxisData(d)) : data.map(d => getXAxisData(d))
85
- const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
105
+ const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
86
106
  const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
87
107
  const { min, max, leftMax, rightMax } = useMinMax(properties)
88
108
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
89
- const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleBrush } = useScales({ ...properties, min, max, leftMax, rightMax })
109
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleBrush } = useScales({ ...properties, min, max, leftMax, rightMax, dimensions })
90
110
 
91
111
  // sets the portal x/y for where tooltips should appear on the page.
92
112
  const [chartPosition, setChartPosition] = useState(null)
@@ -113,10 +133,10 @@ const LinearChart = props => {
113
133
  tick = 0
114
134
  }
115
135
 
116
- if (config.visualizationType === 'Forest Plot') return formatNumber(tick, 'bottom', false, false, false, true)
117
- if (runtime.xAxis.type === 'date') return formatDate(tick)
118
- if (orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
119
- if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
136
+ if (runtime.xAxis.type === 'date' && config.visualizationType !== 'Forest Plot') return formatDate(tick)
137
+ if (orientation === 'horizontal' && config.visualizationType !== 'Forest Plot') return formatNumber(tick, 'left', shouldAbbreviate)
138
+ if (config.xAxis.type === 'continuous' && config.visualizationType !== 'Forest Plot') return formatNumber(tick, 'bottom', shouldAbbreviate)
139
+ if (config.visualizationType === 'Forest Plot') return formatNumber(tick, 'left', config.dataFormat.abbreviated, config.runtime.xAxis.prefix, config.runtime.xAxis.suffix, Number(config.dataFormat.roundTo))
120
140
  return tick
121
141
  }
122
142
 
@@ -154,7 +174,7 @@ const LinearChart = props => {
154
174
  }
155
175
 
156
176
  if (config.visualizationType === 'Forest Plot') {
157
- tickCount = config.xAxis.numTicks !== '' ? config.xAxis.numTicks : 4
177
+ tickCount = config.yAxis.numTicks !== '' ? config.yAxis.numTicks : 4
158
178
  }
159
179
  }
160
180
 
@@ -249,7 +269,7 @@ const LinearChart = props => {
249
269
  {!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
250
270
  <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
251
271
  {props => {
252
- const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
272
+ const axisCenter = config.orientation === 'horizontal' ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
253
273
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
254
274
  return (
255
275
  <Group className='left-axis'>
@@ -262,7 +282,7 @@ const LinearChart = props => {
262
282
 
263
283
  return (
264
284
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
265
- {!runtime.yAxis.hideTicks && <Line key={`${tick.value}--hide-hideTicks`} from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={runtime.horizontal ? 'none' : 'block'} />}
285
+ {!runtime.yAxis.hideTicks && <Line key={`${tick.value}--hide-hideTicks`} from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={orientation === 'horizontal' ? 'none' : 'block'} />}
266
286
 
267
287
  {runtime.yAxis.gridLines ? <Line key={`${tick.value}--hide-hideGridLines`} display={(config.useLogScale && showTicks).toString()} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
268
288
 
@@ -325,7 +345,7 @@ const LinearChart = props => {
325
345
  {hasRightAxis && (
326
346
  <AxisRight scale={yScaleRight} left={Number(width - config.yAxis.rightAxisSize)} label={config.yAxis.rightLabel} tickFormat={tick => formatNumber(tick, 'right')} numTicks={runtime.yAxis.rightNumTicks || undefined} labelOffset={45}>
327
347
  {props => {
328
- const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
348
+ const axisCenter = config.orientation === 'horizontal' ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
329
349
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
330
350
  return (
331
351
  <Group className='right-axis'>
@@ -369,7 +389,7 @@ const LinearChart = props => {
369
389
  {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
370
390
  <AxisBottom
371
391
  top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
372
- left={Number(runtime.yAxis.size)}
392
+ left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
373
393
  label={runtime.xAxis.label}
374
394
  tickFormat={handleBottomTickFormatting}
375
395
  scale={xScale}
@@ -378,7 +398,7 @@ const LinearChart = props => {
378
398
  tickStroke='#333'
379
399
  >
380
400
  {props => {
381
- const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : width / 2
401
+ const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : dimensions[0] / 2
382
402
  const containsMultipleWords = inputString => /\s/.test(inputString)
383
403
  const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
384
404
 
@@ -418,7 +438,7 @@ const LinearChart = props => {
418
438
  config.xAxis.tickWidthMax = tickWidthMax
419
439
 
420
440
  return (
421
- <Group className='bottom-axis'>
441
+ <Group className='bottom-axis' width={dimensions[0]}>
422
442
  {props.ticks.map((tick, i, propsTicks) => {
423
443
  // when using LogScale show major ticks values only
424
444
  const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
@@ -435,7 +455,7 @@ const LinearChart = props => {
435
455
 
436
456
  return (
437
457
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
438
- {!config.xAxis.hideTicks && <Line from={tick.from} to={orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' ? 1.3 : 1} />}
458
+ {!config.xAxis.hideTicks && <Line from={tick.from} to={orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' && config.useLogScale ? 1.3 : 1} />}
439
459
  {!config.xAxis.hideLabel && (
440
460
  <Text
441
461
  dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
@@ -458,7 +478,9 @@ const LinearChart = props => {
458
478
  <Text
459
479
  x={axisCenter}
460
480
  y={
461
- config.orientation === 'horizontal'
481
+ config.visualizationType === 'Forest Plot'
482
+ ? config.xAxis.tickWidthMax + 40
483
+ : config.orientation === 'horizontal'
462
484
  ? dynamicMarginTop || config.xAxis.labelOffset
463
485
  : config.isResponsiveTicks && dynamicMarginTop && !isHorizontal
464
486
  ? dynamicMarginTop
@@ -467,6 +489,7 @@ const LinearChart = props => {
467
489
  : Number(config.xAxis.labelOffset)
468
490
  }
469
491
  textAnchor='middle'
492
+ verticalAnchor='start'
470
493
  fontWeight='bold'
471
494
  fill={config.xAxis.labelColor}
472
495
  >
@@ -560,7 +583,7 @@ const LinearChart = props => {
560
583
  showTooltip={showTooltip}
561
584
  />
562
585
  )}
563
- {visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
586
+ {visualizationType === 'Box Plot' && <BoxPlot xScale={xScale} yScale={yScale} />}
564
587
  {((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') || visualizationType === 'Combo') && (
565
588
  <AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
566
589
  )}
@@ -630,10 +653,8 @@ const LinearChart = props => {
630
653
  xScale={xScale}
631
654
  yScale={yScale}
632
655
  seriesScale={seriesScale}
633
- width={xMax}
634
- height={yMax}
635
- maxWidth={width}
636
- maxHeight={height}
656
+ width={width}
657
+ height={height}
637
658
  getXAxisData={getXAxisData}
638
659
  getYAxisData={getYAxisData}
639
660
  animatedChart={animatedChart}
@@ -708,48 +729,10 @@ const LinearChart = props => {
708
729
  )
709
730
  })}
710
731
  {/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
711
- {config.regions && config.visualizationType !== 'Bar' && config.orientation === 'vertical'
712
- ? config.regions.map(region => {
713
- if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
714
-
715
- let from
716
- let to
717
- let width
718
-
719
- if (config.xAxis.type === 'date') {
720
- from = xScale(parseDate(region.from).getTime())
721
- to = xScale(parseDate(region.to).getTime())
722
- width = to - from
723
- }
724
-
725
- if (config.xAxis.type === 'categorical') {
726
- from = xScale(region.from)
727
- to = xScale(region.to)
728
- width = to - from
729
- }
730
-
731
- if (!from) return null
732
- if (!to) return null
733
-
734
- return (
735
- <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label} onMouseMove={handleTooltipMouseOver} onMouseLeave={handleTooltipMouseOff} handleTooltipClick={handleTooltipClick} tooltipData={JSON.stringify(tooltipData)} showTooltip={showTooltip}>
736
- <path
737
- stroke='#333'
738
- d={`M${from} -5
739
- L${from} 5
740
- M${from} 0
741
- L${to} 0
742
- M${to} -5
743
- L${to} 5`}
744
- />
745
- <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
746
- <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
747
- {region.label}
748
- </Text>
749
- </Group>
750
- )
751
- })
752
- : ''}
732
+ {/* prettier-ignore */}
733
+ {config.visualizationType !== 'Bar' && config.visualizationType !== 'Combo' && (
734
+ <Regions xScale={xScale} handleTooltipClick={handleTooltipClick} handleTooltipMouseOff={handleTooltipMouseOff} handleTooltipMouseOver={handleTooltipMouseOver} showTooltip={showTooltip} hideTooltip={hideTooltip} tooltipData={tooltipData} yMax={yMax} width={width} />
735
+ )}
753
736
  {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
754
737
  <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
755
738
  <Line from={{ x: tooltipData.dataXPosition - 10, y: 0 }} to={{ x: tooltipData.dataXPosition - 10, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
@@ -9,9 +9,10 @@ import { Text } from '@visx/text'
9
9
  import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
10
 
11
11
  // cove
12
- import ConfigContext from '../ConfigContext'
13
- import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
14
- import useIntersectionObserver from '../hooks/useIntersectionObserver'
12
+ import ConfigContext from '../../ConfigContext'
13
+ import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
14
+ import useIntersectionObserver from '../../hooks/useIntersectionObserver'
15
+ import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
15
16
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
16
17
 
17
18
  const enterUpdateTransition = ({ startAngle, endAngle }) => ({
@@ -19,9 +20,17 @@ const enterUpdateTransition = ({ startAngle, endAngle }) => ({
19
20
  endAngle
20
21
  })
21
22
 
23
+ type TooltipData = {
24
+ data: {
25
+ [key: string]: string | number
26
+ }
27
+ dataXPosition: number
28
+ dataYPosition: number
29
+ }
30
+
22
31
  const PieChart = props => {
23
- const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels, isEditor } = useContext(ConfigContext)
24
- const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
32
+ const { transformedData: data, config, dimensions, seriesHighlight, colorScale, currentViewport } = useContext(ConfigContext)
33
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
25
34
  const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
26
35
  xScale: false,
27
36
  yScale: false,
@@ -94,7 +103,7 @@ const PieChart = props => {
94
103
  </Group>
95
104
  )
96
105
  })}
97
- {transitions.map(({ item: arc, key }) => {
106
+ {transitions.map(({ item: arc, key }, i) => {
98
107
  const [centroidX, centroidY] = path.centroid(arc)
99
108
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
100
109
 
@@ -104,7 +113,7 @@ const PieChart = props => {
104
113
  }
105
114
 
106
115
  return (
107
- <animated.g key={key}>
116
+ <animated.g key={`${key}${i}`}>
108
117
  {hasSpaceForLabel && (
109
118
  <Text style={{ fill: textColor }} x={centroidX} y={centroidY} dy='.33em' textAnchor='middle' pointerEvents='none'>
110
119
  {Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
@@ -0,0 +1,3 @@
1
+ import PieChart from './PieChart'
2
+
3
+ export default PieChart
@@ -0,0 +1,135 @@
1
+ import React, { useContext } from 'react'
2
+ import { ChartConfig } from '../../../types/ChartConfig'
3
+ import ConfigContext from '../../../ConfigContext'
4
+ import { ChartContext } from '../../../types/ChartContext'
5
+ import { Text } from '@visx/text'
6
+ import { Group } from '@visx/group'
7
+ import * as d3 from 'd3'
8
+
9
+ type RegionsProps = {
10
+ xScale: Function
11
+ barWidth: number
12
+ totalBarsInGroup: number
13
+ yMax: number
14
+ barWidth?: number
15
+ totalBarsInGroup?: number
16
+ }
17
+
18
+ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleTooltipMouseOff, handleTooltipMouseOver, handleTooltipClick, tooltipData, showTooltip, hideTooltip }: RegionsProps) => {
19
+ const { parseDate, config } = useContext<ChartContext>(ConfigContext)
20
+
21
+ const { runtime, regions, visualizationType, orientation, xAxis } = config
22
+
23
+ let from
24
+ let to
25
+ let width
26
+
27
+ if (regions && orientation === 'vertical') {
28
+ return regions.map(region => {
29
+ if (xAxis.type === 'date' && region.fromType !== 'Previous Days') {
30
+ from = xScale(parseDate(region.from).getTime())
31
+ to = xScale(parseDate(region.to).getTime())
32
+ width = to - from
33
+ }
34
+
35
+ if (xAxis.type === 'categorical') {
36
+ from = xScale(region.from)
37
+ to = xScale(region.to)
38
+ width = to - from
39
+ }
40
+
41
+ if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && xAxis.type === 'date') {
42
+ from = region.fromType !== 'Previous Days' ? xScale(parseDate(region.from).getTime()) - (barWidth * totalBarsInGroup) / 2 : null
43
+ to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
44
+
45
+ width = to - from
46
+ }
47
+
48
+ if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.xAxis.type === 'categorical') {
49
+ from = xScale(region.from)
50
+ to = xScale(region.to)
51
+ width = to - from
52
+ }
53
+
54
+ if (region.fromType === 'Previous Days') {
55
+ to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
56
+
57
+ let domain = xScale.domain()
58
+ let bisectDate = d3.bisector(d => d).left
59
+ let closestValue
60
+
61
+ let previousDays = Number(region.from)
62
+ let lastDate = region.toType === 'Last Date' ? domain[domain.length - 1] : region.to
63
+ let fromDate = new Date(lastDate)
64
+
65
+ from = new Date(fromDate.setDate(fromDate.getDate() - previousDays)).getTime()
66
+ let targetValue = from
67
+
68
+ let index = bisectDate(domain, targetValue)
69
+ if (index === 0) {
70
+ closestValue = domain[0]
71
+ } else if (index === domain.length) {
72
+ closestValue = domain[domain.length - 1]
73
+ } else {
74
+ let d0 = domain[index - 1]
75
+ let d1 = domain[index]
76
+ closestValue = targetValue - d0 > d1 - targetValue ? d1 : d0
77
+ }
78
+
79
+ from = Number(xScale(closestValue) - (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
80
+
81
+ width = to - from
82
+ }
83
+
84
+ // set the region max to the charts max range.
85
+ if (region.toType === 'Last Date') {
86
+ let domainValues = xScale.domain()
87
+ let lastDate = domainValues[domainValues.length - 1]
88
+ to = Number(xScale(lastDate) + (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
89
+ width = to - from
90
+ }
91
+
92
+ if (!from) return null
93
+ if (!to) return null
94
+
95
+ const TopRegionBorderShape = () => {
96
+ return (
97
+ <path
98
+ stroke='#333'
99
+ d={`M${from} -5
100
+ L${from} 5
101
+ M${from} 0
102
+ L${to} 0
103
+ M${to} -5
104
+ L${to} 5`}
105
+ />
106
+ )
107
+ }
108
+
109
+ const HighlightedArea = () => {
110
+ return <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
111
+ }
112
+
113
+ return (
114
+ <Group
115
+ className='regions regions-group--line'
116
+ left={config.visualizationType === 'Bar' || config.visualizationType === 'Combo' ? 0 : config?.visualizationType === 'Line' ? Number(runtime.yAxis.size) : 0}
117
+ key={region.label}
118
+ onMouseMove={handleTooltipMouseOver}
119
+ onMouseLeave={handleTooltipMouseOff}
120
+ handleTooltipClick={handleTooltipClick}
121
+ tooltipData={JSON.stringify(tooltipData)}
122
+ showTooltip={showTooltip}
123
+ >
124
+ <TopRegionBorderShape />
125
+ <HighlightedArea />
126
+ <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
127
+ {region.label}
128
+ </Text>
129
+ </Group>
130
+ )
131
+ })
132
+ }
133
+ }
134
+
135
+ export default Regions
@@ -0,0 +1,3 @@
1
+ import Regions from './components/Regions'
2
+
3
+ export default Regions
@@ -1,8 +1,8 @@
1
1
  import React, { useContext } from 'react'
2
- import ConfigContext from '../ConfigContext'
2
+ import ConfigContext from '../../ConfigContext'
3
3
  import { Group } from '@visx/group'
4
4
 
5
- const CoveScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
5
+ const ScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
6
6
  const { colorScale, transformedData: data, config, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
7
7
 
8
8
  // TODO: copied from line chart should probably be a constant somewhere.
@@ -48,4 +48,4 @@ const CoveScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
48
48
  </Group>
49
49
  )
50
50
  }
51
- export default CoveScatterPlot
51
+ export default ScatterPlot
@@ -0,0 +1,3 @@
1
+ import ScatterPlot from './ScatterPlot.jsx'
2
+
3
+ export default ScatterPlot
@@ -11,9 +11,9 @@ import { MarkerArrow } from '@visx/marker'
11
11
 
12
12
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
13
13
 
14
- import useReduceData from '../hooks/useReduceData'
14
+ import useReduceData from '../../hooks/useReduceData'
15
15
 
16
- import ConfigContext from '../ConfigContext'
16
+ import ConfigContext from '../../ConfigContext'
17
17
 
18
18
  const SparkLine = props => {
19
19
  const { width: parentWidth, height: parentHeight } = props
@@ -0,0 +1,3 @@
1
+ import SparkLine from './SparkLine.jsx'
2
+
3
+ export default SparkLine
@@ -29,6 +29,7 @@ export default {
29
29
  right: 5
30
30
  },
31
31
  suppressedData: [],
32
+ preliminaryData: [],
32
33
 
33
34
  yAxis: {
34
35
  hideAxis: false,
@@ -138,7 +139,7 @@ export default {
138
139
  legend: {
139
140
  hide: false,
140
141
  behavior: 'isolate',
141
- singleRow: false,
142
+ singleRow: true,
142
143
  colorCode: '',
143
144
  reverseLabelOrder: false,
144
145
  description: '',
@@ -208,7 +209,7 @@ export default {
208
209
  },
209
210
  estimateField: '',
210
211
  estimateRadius: '',
211
- shape: '',
212
+ shape: 'square',
212
213
  rowHeight: 20,
213
214
  description: {
214
215
  show: true,
@@ -221,8 +222,8 @@ export default {
221
222
  location: 100
222
223
  },
223
224
  radius: {
224
- min: 1,
225
- max: 8,
225
+ min: 2,
226
+ max: 10,
226
227
  scalingColumn: ''
227
228
  },
228
229
  regression: {
@@ -233,11 +234,9 @@ export default {
233
234
  leftWidthOffset: 0,
234
235
  rightWidthOffset: 0,
235
236
  showZeroLine: false,
236
- hideDateCategoryCol: false,
237
237
  leftLabel: '',
238
238
  rightLabel: ''
239
239
  },
240
-
241
240
  area: {
242
241
  isStacked: false
243
242
  }
@@ -0,0 +1,17 @@
1
+ export const abbreviateNumber = num => {
2
+ let unit = ''
3
+ let absNum = Math.abs(num)
4
+
5
+ if (absNum >= 1e9) {
6
+ unit = 'B'
7
+ num = num / 1e9
8
+ } else if (absNum >= 1e6) {
9
+ unit = 'M'
10
+ num = num / 1e6
11
+ } else if (absNum >= 1e3) {
12
+ unit = 'K'
13
+ num = num / 1e3
14
+ }
15
+
16
+ return num + unit
17
+ }
@@ -0,0 +1,55 @@
1
+ import { ChartConfig, Legend } from '../types/ChartConfig'
2
+
3
+ export const computeMarginBottom = (config: ChartConfig, legend: Legend, currentViewport: string): string | number => {
4
+ const isLegendBottom = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
5
+ const isHorizontal = config.orientation === 'horizontal'
6
+ const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
7
+ const isBrush = config.brush.active
8
+ const offset = 20
9
+ const brushHeight = config.brush.height
10
+ let bottom = 0
11
+ if (!isLegendBottom && isHorizontal && !config.yAxis.label) {
12
+ bottom = Number(config.xAxis.labelOffset)
13
+ }
14
+ if (!isLegendBottom && isHorizontal && config.yAxis.label && !config.isResponsiveTicks) {
15
+ bottom = Number(config.runtime.xAxis.size) + Number(config.xAxis.labelOffset)
16
+ }
17
+ if (!isLegendBottom && isHorizontal && config.yAxis.label && config.isResponsiveTicks) {
18
+ bottom = config.dynamicMarginTop + offset
19
+ }
20
+ if (!isLegendBottom && isHorizontal && !config.yAxis.label && config.isResponsiveTicks) {
21
+ bottom = config.dynamicMarginTop ? config.dynamicMarginTop - offset : Number(config.xAxis.labelOffset) - offset
22
+ }
23
+ if (!isLegendBottom && isHorizontal && config.yAxis.label && config.isResponsiveTicks) {
24
+ bottom = config.dynamicMarginTop ? config.dynamicMarginTop + offset : Number(config.xAxis.labelOffset)
25
+ }
26
+
27
+ if (!isHorizontal && !isLegendBottom && config.xAxis.label && tickRotation && !config.isResponsiveTicks) {
28
+ bottom = isBrush ? brushHeight + config.xAxis.tickWidthMax + -config.xAxis.size + config.xAxis.labelOffset + offset : config.xAxis.tickWidthMax + offset + -config.xAxis.size + config.xAxis.labelOffset
29
+ }
30
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && tickRotation && !config.isResponsiveTicks) {
31
+ }
32
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && tickRotation && !config.dynamicMarginTop && !config.isResponsiveTicks) {
33
+ bottom = isBrush ? config.xAxis.tickWidthMax + brushHeight + offset + -config.xAxis.size : 0
34
+ }
35
+
36
+ if (!isHorizontal && !isLegendBottom && config.xAxis.label && !tickRotation && !config.isResponsiveTicks) {
37
+ bottom = isBrush ? brushHeight + -config.xAxis.size + config.xAxis.labelOffset + offset : -config.xAxis.size + config.xAxis.labelOffset + offset
38
+ }
39
+ if (!isHorizontal && !isLegendBottom && config.xAxis.label && config.dynamicMarginTop && config.isResponsiveTicks) {
40
+ bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + config.xAxis.tickWidthMax : config.dynamicMarginTop + -config.xAxis.size + offset
41
+ }
42
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && config.dynamicMarginTop && config.isResponsiveTicks) {
43
+ bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + config.xAxis.tickWidthMax : config.dynamicMarginTop + -config.xAxis.size - offset
44
+ }
45
+ if (!isHorizontal && !isLegendBottom && config.xAxis.label && !config.dynamicMarginTop && config.isResponsiveTicks) {
46
+ bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + 25 : config.xAxis.labelOffset + -config.xAxis.size + offset
47
+ }
48
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && !config.dynamicMarginTop && config.isResponsiveTicks) {
49
+ bottom = -config.xAxis.size + offset + config.xAxis.labelOffset
50
+ }
51
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && !tickRotation && !config.dynamicMarginTop && !config.isResponsiveTicks) {
52
+ bottom = isBrush ? brushHeight + -config.xAxis.size + config.xAxis.labelOffset : 0
53
+ }
54
+ return `${bottom}px`
55
+ }
@@ -0,0 +1,18 @@
1
+ export const filterData = (filters, data) => {
2
+ let filteredData: any[] = []
3
+
4
+ data.forEach(row => {
5
+ let add = true
6
+ filters
7
+ .filter(filter => filter.type !== 'url')
8
+ .forEach(filter => {
9
+ if (row[filter.columnName] != filter.active) {
10
+ add = false
11
+ }
12
+ })
13
+
14
+ if (add) filteredData.push(row)
15
+ })
16
+
17
+ return filteredData
18
+ }
@@ -0,0 +1,8 @@
1
+ import chroma from 'chroma-js'
2
+
3
+ export const generateColorsArray = (color = '#000000', special = false) => {
4
+ let colorObj = chroma(color)
5
+ let hoverColor = special ? colorObj.brighten(0.5).hex() : colorObj.saturate(1.3).hex()
6
+
7
+ return [color, hoverColor, colorObj.darken(0.3).hex()]
8
+ }