@cdc/chart 4.24.2 → 4.24.4

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 (60) hide show
  1. package/dist/cdcchart.js +47933 -36918
  2. package/examples/chart-regression-1.json +378 -0
  3. package/examples/chart-regression-2.json +2360 -0
  4. package/examples/feature/filters/url-filter.json +1076 -0
  5. package/examples/feature/line/line-chart.json +362 -37
  6. package/examples/feature/regions/index.json +50 -4
  7. package/examples/feature/sankey/sankey-example-data.json +1364 -0
  8. package/examples/feature/sankey/sankey_chart_data.csv +20 -0
  9. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +306 -19
  10. package/examples/region-issue.json +2065 -0
  11. package/examples/sparkline.json +868 -0
  12. package/examples/test.json +5409 -0
  13. package/index.html +130 -123
  14. package/package.json +4 -2
  15. package/src/CdcChart.tsx +178 -94
  16. package/src/_stories/ChartEditor.stories.tsx +14 -3
  17. package/src/_stories/_mock/url_filter.json +1076 -0
  18. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +2 -1
  19. package/src/components/AreaChart/components/AreaChart.jsx +2 -1
  20. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -63
  21. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +36 -56
  22. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +32 -39
  23. package/src/components/BarChart/components/BarChart.Vertical.tsx +44 -59
  24. package/src/components/BoxPlot/BoxPlot.jsx +2 -1
  25. package/src/components/DeviationBar.jsx +3 -3
  26. package/src/components/EditorPanel/EditorPanel.tsx +1684 -1564
  27. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +1 -1
  28. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +107 -0
  29. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +48 -4
  30. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +41 -0
  31. package/src/components/EditorPanel/components/Panels/index.tsx +9 -7
  32. package/src/components/EditorPanel/components/panels.scss +11 -0
  33. package/src/components/EditorPanel/editor-panel.scss +0 -724
  34. package/src/components/EditorPanel/useEditorPermissions.js +40 -14
  35. package/src/components/Legend/Legend.Component.tsx +43 -63
  36. package/src/components/Legend/Legend.tsx +8 -4
  37. package/src/components/LineChart/LineChartProps.ts +1 -0
  38. package/src/components/LineChart/helpers.ts +2 -2
  39. package/src/components/LineChart/index.tsx +7 -7
  40. package/src/components/LinearChart.jsx +11 -31
  41. package/src/components/PairedBarChart.jsx +6 -10
  42. package/src/components/PieChart/PieChart.tsx +3 -3
  43. package/src/components/Regions/components/Regions.tsx +120 -78
  44. package/src/components/Sankey/index.tsx +434 -0
  45. package/src/components/Sankey/sankey.scss +153 -0
  46. package/src/components/Sankey/types/index.ts +16 -0
  47. package/src/components/ScatterPlot/ScatterPlot.jsx +1 -0
  48. package/src/components/Sparkline/{SparkLine.jsx → components/SparkLine.tsx} +14 -30
  49. package/src/components/Sparkline/index.scss +3 -0
  50. package/src/components/Sparkline/index.tsx +1 -1
  51. package/src/components/ZoomBrush.tsx +2 -1
  52. package/src/data/initial-state.js +46 -2
  53. package/src/helpers/computeMarginBottom.ts +2 -1
  54. package/src/helpers/tests/computeMarginBottom.test.ts +2 -1
  55. package/src/hooks/useBarChart.js +5 -2
  56. package/src/hooks/useScales.ts +47 -18
  57. package/src/hooks/useTooltip.tsx +9 -8
  58. package/src/scss/main.scss +33 -29
  59. package/src/types/ChartConfig.ts +32 -14
  60. package/src/types/ChartContext.ts +7 -0
@@ -1,6 +1,5 @@
1
1
  import React, { useContext, useState, useEffect, useRef, useMemo } from 'react'
2
2
  import { animated, useTransition, interpolate } from 'react-spring'
3
- import chroma from 'chroma-js'
4
3
 
5
4
  // visx
6
5
  import { Pie } from '@visx/shape'
@@ -18,6 +17,7 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
18
17
  import LegendComponent from '../Legend/Legend.Component'
19
18
  import { createFormatLabels } from '../Legend/helpers/createFormatLabels'
20
19
  import { scaleOrdinal } from '@visx/scale'
20
+ import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
21
21
 
22
22
  const enterUpdateTransition = ({ startAngle, endAngle }) => ({
23
23
  startAngle,
@@ -156,8 +156,8 @@ const PieChart = props => {
156
156
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
157
157
 
158
158
  let textColor = '#FFF'
159
- if (_colorScale(arc.data[config.runtime.xAxis.dataKey]) && chroma.contrast(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey])) < 3.5) {
160
- textColor = '000'
159
+ if (_colorScale(arc.data[config.runtime.xAxis.dataKey])) {
160
+ textColor = getContrastColor(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey]))
161
161
  }
162
162
 
163
163
  return (
@@ -1,102 +1,153 @@
1
- import React, { useContext } from 'react'
2
- import { ChartConfig } from '../../../types/ChartConfig'
1
+ import React, { MouseEventHandler, useContext } from 'react'
3
2
  import ConfigContext from '../../../ConfigContext'
4
3
  import { ChartContext } from '../../../types/ChartContext'
5
4
  import { Text } from '@visx/text'
6
5
  import { Group } from '@visx/group'
7
- import * as d3 from 'd3'
8
- import { formatDate } from '@cdc/core/helpers/cove/date.js'
6
+ import { formatDate, isDateScale } from '@cdc/core/helpers/cove/date.js'
9
7
 
10
8
  type RegionsProps = {
11
9
  xScale: Function
12
10
  yMax: number
13
11
  barWidth?: number
14
12
  totalBarsInGroup?: number
13
+ handleTooltipMouseOff: MouseEventHandler<SVGElement>
14
+ handleTooltipMouseOver: MouseEventHandler<SVGElement>
15
+ handleTooltipClick: MouseEventHandler<SVGElement>
16
+ tooltipData: unknown
17
+ showTooltip: Function
18
+ hideTooltip: Function
15
19
  }
16
20
 
17
- const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleTooltipMouseOff, handleTooltipMouseOver, handleTooltipClick, tooltipData, showTooltip, hideTooltip }: RegionsProps) => {
21
+ // TODO: should regions be removed on categorical axis?
22
+ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleTooltipMouseOff, handleTooltipMouseOver, handleTooltipClick, tooltipData, showTooltip, hideTooltip }) => {
18
23
  const { parseDate, config } = useContext<ChartContext>(ConfigContext)
19
24
 
20
25
  const { runtime, regions, visualizationType, orientation, xAxis } = config
26
+ const domain = xScale.domain()
21
27
 
22
- let from
23
- let to
24
- let width
28
+ const getFromValue = region => {
29
+ let from
25
30
 
26
- if (regions && orientation === 'vertical') {
27
- return regions.map(region => {
28
- if (xAxis.type === 'date' && region.fromType !== 'Previous Days') {
29
- from = xScale(parseDate(region.from).getTime())
30
- to = xScale(parseDate(region.to).getTime())
31
- width = to - from
31
+ // Fixed Date
32
+ if (!region?.fromType || region.fromType === 'Fixed') {
33
+ const date = new Date(region.from)
34
+ const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
35
+ from = xScale(parsedDate)
36
+
37
+ if (visualizationType === 'Bar' && xAxis.type === 'date-time') {
38
+ from = from - (barWidth * totalBarsInGroup) / 2
39
+ }
40
+ }
41
+
42
+ // Previous Date
43
+ if (region.fromType === 'Previous Days') {
44
+ const previousDays = Number(region.from) || 0
45
+ const categoricalDomain = domain.map(d => formatDate(config.xAxis.dateParseFormat, new Date(d)))
46
+ const d = region.toType === 'Last Date' ? new Date(domain[domain.length - 1]).getTime() : new Date(region.to) // on categorical charts force leading zero 03/15/2016 vs 3/15/2016 for valid date format
47
+ const to = config.xAxis.type === 'categorical' ? formatDate(config.xAxis.dateParseFormat, d) : formatDate(config.xAxis.dateParseFormat, d)
48
+ const toDate = new Date(to)
49
+ from = new Date(toDate.setDate(toDate.getDate() - Number(previousDays)))
50
+
51
+ if (xAxis.type === 'date') {
52
+ from = new Date(formatDate(xAxis.dateParseFormat, from)).getTime()
53
+
54
+ let closestDate = domain[0]
55
+ let minDiff = Math.abs(from - closestDate)
56
+
57
+ for (let i = 1; i < domain.length; i++) {
58
+ const diff = Math.abs(from - domain[i])
59
+ if (diff < minDiff) {
60
+ minDiff = diff
61
+ closestDate = domain[i]
62
+ }
63
+ }
64
+ from = closestDate
32
65
  }
33
66
 
67
+ // Here the domain is in the xScale.dateParseFormat
34
68
  if (xAxis.type === 'categorical') {
35
- from = xScale(region.from)
36
- to = xScale(region.to)
37
- width = to - from
69
+ let closestDate = domain[0]
70
+ let minDiff = Math.abs(new Date(from).getTime() - new Date(closestDate).getTime())
71
+
72
+ for (let i = 1; i < domain.length; i++) {
73
+ const diff = Math.abs(new Date(from).getTime() - new Date(domain[i]).getTime())
74
+ if (diff < minDiff) {
75
+ minDiff = diff
76
+ closestDate = domain[i]
77
+ }
78
+ }
79
+ from = closestDate
38
80
  }
39
81
 
40
- if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && xAxis.type === 'date') {
41
- from = region.fromType !== 'Previous Days' ? xScale(parseDate(region.from).getTime()) - (barWidth * totalBarsInGroup) / 2 : null
42
- to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
82
+ from = xScale(from)
83
+ }
43
84
 
44
- width = to - from
45
- }
85
+ if (xAxis.type === 'categorical' && region.fromType !== 'Previous Days') {
86
+ from = xScale(region.from)
87
+ }
46
88
 
47
- if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.xAxis.type === 'categorical') {
48
- from = xScale(region.from)
49
- to = xScale(region.to)
50
- width = to - from
89
+ if (visualizationType === 'Line' || visualizationType === 'Area Chart') {
90
+ let scalePadding = Number(config.yAxis.size)
91
+ if (xScale.bandwidth) {
92
+ scalePadding += xScale.bandwidth() / 2
51
93
  }
94
+ from = from + scalePadding
95
+ }
52
96
 
53
- if (region.fromType === 'Previous Days') {
54
- to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
55
-
56
- let domain = xScale.domain()
57
- let bisectDate = d3.bisector(d => d).left
58
- let closestValue
59
-
60
- let previousDays = Number(region.from)
61
- let lastDate = region.toType === 'Last Date' ? domain[domain.length - 1] : region.to
62
- let toDate = new Date(lastDate)
63
-
64
- from = new Date(toDate.setDate(toDate.getDate() - previousDays)).getTime()
65
- let targetValue = from
66
-
67
- let index = bisectDate(domain, targetValue)
68
- if (index === 0) {
69
- closestValue = domain[0]
70
- } else if (index === domain.length) {
71
- closestValue = domain[domain.length - 1]
72
- } else {
73
- let d0 = domain[index - 1]
74
- let d1 = domain[index]
75
- closestValue = targetValue - d0 > d1 - targetValue ? d1 : d0
76
- }
77
- from = Number(xScale(closestValue) - (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
97
+ if (visualizationType === 'Bar' && config.xAxis.type === 'date-time' && region.fromType === 'Previous Days') {
98
+ from = from - (barWidth * totalBarsInGroup) / 2
99
+ }
78
100
 
79
- width = to - from
80
- }
101
+ return from
102
+ }
81
103
 
82
- // set the region max to the charts max range.
83
- if (region.toType === 'Last Date') {
84
- let domainValues = xScale.domain()
85
- let lastDate = domainValues[domainValues.length - 1]
86
- to = Number(xScale(lastDate) + (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
87
- width = to - from
104
+ const getToValue = region => {
105
+ let to
106
+
107
+ // when xScale is categorical leading zeros are removed, ie. 03/15/2016 is 3/15/2016
108
+ if (xAxis.type === 'categorical') {
109
+ to = xScale(region.to)
110
+ }
111
+
112
+ if (isDateScale(xAxis)) {
113
+ if (!region?.toType || region.toType === 'Fixed') {
114
+ to = xScale(parseDate(region.to).getTime())
88
115
  }
89
116
 
90
- if (region.fromType === 'Previous Days' && xAxis.type === 'date' && xAxis.sortDates && config.visualizationType === 'Line') {
91
- let domain = xScale.domain()
92
- let previousDays = Number(region.from)
93
- let to = region.toType === 'Last Date' ? formatDate(config.xAxis.dateParseFormat, domain[domain.length - 1]) : region.to
94
- let toDate = new Date(to)
95
- from = new Date(toDate.setDate(toDate.getDate() - previousDays)).getTime()
96
- from = xScale(from)
97
- to = xScale(parseDate(to))
98
- width = to - from
117
+ if (visualizationType === 'Bar' || config.visualizationType === 'Combo') {
118
+ to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + barWidth * totalBarsInGroup : to
99
119
  }
120
+ }
121
+ if (region.toType === 'Last Date') {
122
+ const lastDate = domain[domain.length - 1]
123
+ to = Number(xScale(lastDate) + ((visualizationType === 'Bar' || visualizationType === 'Combo') && config.xAxis.type === 'date' ? barWidth * totalBarsInGroup : 0))
124
+ }
125
+
126
+ if (visualizationType === 'Line' || visualizationType === 'Area Chart') {
127
+ let scalePadding = Number(config.yAxis.size)
128
+ if (xScale.bandwidth) {
129
+ scalePadding += xScale.bandwidth() / 2
130
+ }
131
+ to = to + scalePadding
132
+ }
133
+
134
+ if (visualizationType === 'Bar' && config.xAxis.type === 'date-time' && region.toType !== 'Last Date') {
135
+ to = to - (barWidth * totalBarsInGroup) / 2
136
+ }
137
+
138
+ if ((visualizationType === 'Bar' || visualizationType === 'Combo') && xAxis.type === 'categorical') {
139
+ to = to + (visualizationType === 'Bar' || visualizationType === 'Combo' ? barWidth * totalBarsInGroup : 0)
140
+ }
141
+ return to
142
+ }
143
+
144
+ const getWidth = (to, from) => to - from
145
+
146
+ if (regions && orientation === 'vertical') {
147
+ return regions.map(region => {
148
+ const from = getFromValue(region)
149
+ const to = getToValue(region)
150
+ const width = getWidth(to, from)
100
151
 
101
152
  if (!from) return null
102
153
  if (!to) return null
@@ -120,16 +171,7 @@ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleToolt
120
171
  }
121
172
 
122
173
  return (
123
- <Group
124
- className='regions regions-group--line'
125
- left={config.visualizationType === 'Bar' || config.visualizationType === 'Combo' ? 0 : config?.visualizationType === 'Line' ? Number(runtime.yAxis.size) : 0}
126
- key={region.label}
127
- onMouseMove={handleTooltipMouseOver}
128
- onMouseLeave={handleTooltipMouseOff}
129
- handleTooltipClick={handleTooltipClick}
130
- tooltipData={JSON.stringify(tooltipData)}
131
- showTooltip={showTooltip}
132
- >
174
+ <Group height={100} fill='red' className='regions regions-group--line zzz' key={region.label} onMouseMove={handleTooltipMouseOver} onMouseLeave={handleTooltipMouseOff} handleTooltipClick={handleTooltipClick} tooltipData={JSON.stringify(tooltipData)} showTooltip={showTooltip}>
133
175
  <TopRegionBorderShape />
134
176
  <HighlightedArea />
135
177
  <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>