@cdc/chart 4.23.8 → 4.23.10

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 (34) hide show
  1. package/dist/cdcchart.js +44114 -44410
  2. package/examples/feature/__data__/area-chart-date-apple.json +1 -5073
  3. package/examples/feature/area/area-chart-date-apple.json +73 -10316
  4. package/examples/feature/area/area-chart-date-city-temperature.json +204 -80
  5. package/examples/{private/confidence_interval_test.json → feature/area/area-chart-stacked.json} +65 -74
  6. package/examples/feature/filters/bar-filter.json +5027 -0
  7. package/examples/feature/legend-highlights/highlights.json +567 -0
  8. package/index.html +28 -7
  9. package/package.json +3 -2
  10. package/src/{CdcChart.jsx → CdcChart.tsx} +77 -71
  11. package/src/components/AreaChart.Stacked.jsx +73 -0
  12. package/src/components/AreaChart.jsx +24 -26
  13. package/src/components/BarChart.StackedVertical.jsx +2 -0
  14. package/src/components/DeviationBar.jsx +67 -13
  15. package/src/components/EditorPanel.jsx +493 -454
  16. package/src/components/Forecasting.jsx +5 -5
  17. package/src/components/Legend.jsx +17 -8
  18. package/src/components/LineChart.Circle.tsx +108 -0
  19. package/src/components/{LineChart.jsx → LineChart.tsx} +10 -42
  20. package/src/components/LinearChart.jsx +460 -443
  21. package/src/components/PieChart.jsx +54 -25
  22. package/src/components/Series.jsx +63 -17
  23. package/src/components/SparkLine.jsx +7 -19
  24. package/src/data/initial-state.js +10 -1
  25. package/src/hooks/useEditorPermissions.js +87 -24
  26. package/src/hooks/useLegendClasses.js +14 -11
  27. package/src/hooks/useReduceData.js +6 -1
  28. package/src/hooks/useScales.js +2 -2
  29. package/src/hooks/useTooltip.jsx +21 -8
  30. package/src/scss/legend.scss +206 -0
  31. package/src/scss/main.scss +25 -24
  32. package/examples/private/tooltip-issue.json +0 -45275
  33. package/src/components/DataTable.jsx +0 -374
  34. /package/src/{components → hooks}/useIntersectionObserver.jsx +0 -0
@@ -3,7 +3,7 @@ import React, { useContext } from 'react'
3
3
  // cdc
4
4
  import ConfigContext from '../ConfigContext'
5
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
- import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
6
+ import { colorPalettesChart, sequentialPalettes } from '@cdc/core/data/colorPalettes'
7
7
 
8
8
  // visx & d3
9
9
  import { curveMonotoneX } from '@visx/curve'
@@ -30,15 +30,15 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
30
30
  return (
31
31
  <Group className={`forecasting-areas-combo-${index}`} key={`forecasting-areas--stage-${stage.key.replaceAll(' ', '-')}-${index}`}>
32
32
  {group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
33
- const palette = colorPalettesChart[stage.color]
33
+ const palette = sequentialPalettes[stage.color] || colorPalettesChart[stage.color] || false
34
34
 
35
35
  const getFill = () => {
36
- if (displayArea) return palette[ciGroupIndex] ? palette[ciGroupIndex] : 'transparent'
36
+ if (displayArea) return palette[2] ? palette[2] : 'transparent'
37
37
  return 'transparent'
38
38
  }
39
39
 
40
40
  const getStroke = () => {
41
- if (displayArea) return palette[2] ? palette[2] : 'transparent'
41
+ if (displayArea) return palette[1] ? palette[1] : 'transparent'
42
42
  return 'transparent'
43
43
  }
44
44
 
@@ -50,7 +50,7 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
50
50
  curve={curveMonotoneX}
51
51
  data={groupData}
52
52
  fill={getFill()}
53
- opacity={transparentArea ? 0.1 : 0.5}
53
+ opacity={transparentArea ? 0.1 : .5}
54
54
  x={d => xScale(Date.parse(d[xAxis.dataKey]))}
55
55
  y0={d => yScale(d[ciGroup.low])}
56
56
  y1={d => yScale(d[ciGroup.high])}
@@ -6,6 +6,8 @@ import LegendCircle from '@cdc/core/components/LegendCircle'
6
6
 
7
7
  import useLegendClasses from './../hooks/useLegendClasses'
8
8
  import { useHighlightedBars } from '../hooks/useHighlightedBars'
9
+ import { Line } from '@visx/shape'
10
+ import { sequentialPalettes } from '@cdc/core/data/colorPalettes'
9
11
 
10
12
  // * FILE REVIEW *
11
13
  // TODO: fix eslint-disable jsxa11y issues
@@ -28,7 +30,8 @@ const Legend = () => {
28
30
  highlightReset,
29
31
  transformedData: data,
30
32
  colorPalettes,
31
- currentViewport
33
+ currentViewport,
34
+ handleLineType
32
35
  } = useContext(ConfigContext)
33
36
 
34
37
  const { innerClasses, containerClasses } = useLegendClasses(config)
@@ -90,7 +93,7 @@ const Legend = () => {
90
93
  // loop through each stage/group/area on the chart and create a label
91
94
  config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
92
95
  return outerGroup?.stages?.map((stage, index) => {
93
- let colorValue = colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
96
+ let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
94
97
 
95
98
  const newLabel = {
96
99
  datum: stage.key,
@@ -159,13 +162,12 @@ const Legend = () => {
159
162
 
160
163
  const legendClasses = {
161
164
  marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
162
- marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `0px`
165
+ marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `${config.dynamicMarginTop + 15}px`
163
166
  }
164
167
 
165
168
  const { HighLightedBarUtils } = useHighlightedBars(config)
166
169
 
167
170
  let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
168
-
169
171
  if (!legend) return null
170
172
 
171
173
  return (
@@ -178,7 +180,7 @@ const Legend = () => {
178
180
  return (
179
181
  <div className={innerClasses.join(' ')}>
180
182
  {createLegendLabels(labels).map((label, i) => {
181
- let className = 'legend-item'
183
+ let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
182
184
  let itemName = label.datum
183
185
 
184
186
  // Filter excluded data keys from legend
@@ -196,12 +198,12 @@ const Legend = () => {
196
198
  }
197
199
 
198
200
  if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
199
- className += ' inactive'
201
+ className.push('inactive')
200
202
  }
201
203
 
202
204
  return (
203
205
  <LegendItem
204
- className={className}
206
+ className={className.join(' ')}
205
207
  tabIndex={0}
206
208
  key={`legend-quantile-${i}`}
207
209
  onKeyPress={e => {
@@ -213,7 +215,14 @@ const Legend = () => {
213
215
  highlight(label)
214
216
  }}
215
217
  >
216
- <LegendCircle fill={label.value} />
218
+ {config.visualizationType === 'Line' && config.legend.lineMode ? (
219
+ <svg width={40} height={20}>
220
+ <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
221
+ </svg>
222
+ ) : (
223
+ <LegendCircle fill={label.value} />
224
+ )}
225
+
217
226
  <LegendLabel align='left' margin='0 0 0 4px'>
218
227
  {label.text}
219
228
  </LegendLabel>
@@ -0,0 +1,108 @@
1
+ import React from 'react'
2
+
3
+ // todo: change this config obj to ChartConfig once its created
4
+ type LineChartCircleProps = {
5
+ config: {
6
+ xAxis: string
7
+ data: Object[]
8
+ lineDatapointStyle: string
9
+ runtime: Object
10
+ }
11
+ d?: Object
12
+ displayArea: boolean
13
+ seriesKey: string
14
+ tooltipData: {
15
+ data: []
16
+ tooltipDataX: number
17
+ tooltipDataY: number
18
+ }
19
+ xScale: any
20
+ yScale: any
21
+ yScaleRight: any
22
+ colorScale: any
23
+ parseDate: any
24
+ seriesAxis: string
25
+ }
26
+
27
+ const LineChartCircle = (props: LineChartCircleProps) => {
28
+ const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight } = props
29
+ const { lineDatapointStyle } = config
30
+ const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
31
+
32
+ if (lineDatapointStyle === 'hidden') return null
33
+
34
+ const getColor = (displayArea, colorScale, config, seriesIndex, hoveredKey, seriesKey) => {
35
+ const customColors = config.customColors || []
36
+ const seriesLabels = config.runtime.seriesLabels || []
37
+ let color
38
+
39
+ const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
40
+
41
+ if (displayArea) {
42
+ if (colorScale) {
43
+ if (getIndex(hoveredKey) === false) return
44
+ color = customColors.length > 0 ? customColors[getIndex(hoveredKey)] : colorScale(seriesLabels[hoveredKey] || seriesKey)
45
+ } else if (customColors) {
46
+ color = customColors.length > 0 ? customColors[getIndex(hoveredKey)] : 'transparent'
47
+ }
48
+ } else {
49
+ color = 'transparent'
50
+ }
51
+ console.log('color', color)
52
+ return color
53
+ }
54
+
55
+ if (lineDatapointStyle === 'always show') {
56
+ return (
57
+ <circle
58
+ cx={config.xAxis.type === 'categorical' ? xScale(d[config.xAxis.dataKey]) : xScale(parseDate(d[config.xAxis.dataKey]))}
59
+ cy={filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered.dataKey])}
60
+ r={4.5}
61
+ opacity={d[seriesKey] ? 1 : 0}
62
+ fillOpacity={1}
63
+ fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000') : 'transparent'}
64
+ style={{ filter: 'unset', opacity: 1 }}
65
+ />
66
+ )
67
+ }
68
+
69
+ if (lineDatapointStyle === 'hover') {
70
+ if (!tooltipData) return
71
+ if (!tooltipData.data) return
72
+ let hoveredXValue = tooltipData?.data?.[0]?.[1]
73
+ if (!hoveredXValue) return
74
+ let hoveredSeriesValue
75
+ let hoveredSeriesIndex
76
+ let hoveredSeriesData = tooltipData.data.filter(d => d[0] === seriesKey)
77
+ let hoveredSeriesKey = hoveredSeriesData?.[0]?.[0]
78
+ let hoveredSeriesAxis = hoveredSeriesData?.[0]?.[2]
79
+ hoveredSeriesIndex = tooltipData.data.indexOf(hoveredSeriesKey)
80
+ hoveredSeriesValue = hoveredSeriesData?.[0]?.[1]
81
+
82
+ console.log('hoveredSeriesKey', hoveredSeriesKey)
83
+ console.log('hoveredSeriesAxis', hoveredSeriesAxis)
84
+ console.log('hoveredSeriesValue', hoveredSeriesValue)
85
+
86
+ console.log('hoveredSeriesData', hoveredSeriesData)
87
+ return tooltipData.data.map((tooltipItem, index) => {
88
+ let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
89
+
90
+ if (isNaN(hoveredSeriesValue)) return <></>
91
+ return (
92
+ <circle
93
+ cx={config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))}
94
+ cy={hoveredSeriesAxis === 'right' ? yScaleRight(hoveredSeriesValue) : yScale(hoveredSeriesValue)}
95
+ r={4.5}
96
+ opacity={1}
97
+ fillOpacity={1}
98
+ fill={getColor(displayArea, colorScale, config, seriesIndex, hoveredSeriesKey, seriesKey)}
99
+ style={{ filter: 'unset', opacity: 1 }}
100
+ />
101
+ )
102
+ })
103
+ }
104
+
105
+ return null
106
+ }
107
+
108
+ export default LineChartCircle
@@ -8,6 +8,7 @@ import { Text } from '@visx/text'
8
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
9
  import ConfigContext from '../ConfigContext'
10
10
  import useRightAxis from '../hooks/useRightAxis'
11
+ import LineChartCircle from './LineChart.Circle'
11
12
 
12
13
  const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, handleTooltipMouseOver, handleTooltipMouseOff, showTooltip, seriesStyle = 'Line', svgRef, handleTooltipClick, tooltipData }) => {
13
14
  // Not sure why there's a redraw here.
@@ -27,6 +28,7 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
27
28
  }
28
29
 
29
30
  const DEBUG = false
31
+
30
32
  return (
31
33
  <ErrorBoundary component='LineChart'>
32
34
  <Group left={config.runtime.yAxis.size ? parseInt(config.runtime.yAxis.size) : 66}>
@@ -58,18 +60,6 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
58
60
  // if has muiltiple series dont show legend value on tooltip
59
61
  if (!hasMultipleSeries) label = config.isLegendValue ? config.runtime.seriesLabels[seriesKey] : label
60
62
 
61
- let yAxisTooltip = handleAxisFormating(axis, label, yAxisValue)
62
- let xAxisTooltip = handleAxisFormating(axis, config.runtime.xAxis.label, xAxisValue)
63
-
64
- const tooltip = `<div>
65
- ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && Object.keys(config.runtime.seriesLabels).length > 1 ? `${config.runtime.seriesLabels[seriesKey] || ''}<br/>` : ''}
66
- ${yAxisTooltip}<br />
67
- ${xAxisTooltip}
68
- </div>`
69
-
70
- // TODO: move all instances of circleRadii to state.
71
- let circleRadii = 4.5
72
-
73
63
  return (
74
64
  d[seriesKey] !== undefined &&
75
65
  d[seriesKey] !== '' &&
@@ -84,46 +74,24 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
84
74
  {formatNumber(d[seriesKey], 'left')}
85
75
  </Text>
86
76
 
87
- <circle
88
- key={`${seriesKey}-${dataIndex}`}
89
- r={circleRadii}
90
- cx={Number(xScale(getXAxisData(d)))}
91
- cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))}
92
- fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
93
- style={{
94
- fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'
95
- }}
96
- data-tooltip-html={tooltip}
97
- data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
98
- />
99
-
100
- {/* circles that appear on hover */}
101
- {/* todo: circle radii used here should be global with other circle radii */}
102
- {/* {tooltipData && Object.entries(tooltipData.data).length > 0 && isNumber(tooltipData.data[seriesKey]) && config.lineDatapointStyle === 'hover' && config.series.filter(s => s.type === 'Line') && (
103
- <circle
104
- cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
105
- cy={yScale(tooltipData.data[seriesKey])}
106
- r={4.5}
107
- opacity={tooltipData[seriesKey] ? 1 : 0}
108
- fillOpacity={1}
109
- fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000') : 'transparent'}
110
- style={{ filter: 'unset', opacity: 1 }}
111
- />
112
- )} */}
77
+ {config.lineDatapointStyle === 'hidden' ||
78
+ (config.lineDatapointStyle === 'always show' && <LineChartCircle d={d} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} />)}
113
79
  </Group>
114
80
  )
115
81
  )
116
82
  })}
83
+
84
+ <>{config.lineDatapointStyle === 'hover' && <LineChartCircle config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />}</>
85
+
117
86
  <LinePath
118
87
  curve={allCurves[seriesData[0].lineType]}
119
88
  data={data}
120
89
  x={d => xScale(getXAxisData(d))}
121
90
  y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
122
91
  stroke={
123
- colorScale && !config.legend.dynamicLegend
124
- ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey)
125
- : // is dynamic legend
126
- config.legend.dynamicLegend
92
+ config.customColors
93
+ ? config.customColors[index]
94
+ : colorScale
127
95
  ? colorPalettes[config.palette][index]
128
96
  : // fallback
129
97
  '#000'