@cdc/chart 4.23.5 → 4.23.6

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.
@@ -62,6 +62,8 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
62
62
  ${yAxisTooltip}<br />
63
63
  ${xAxisTooltip}
64
64
  </div>`
65
+
66
+ // TODO: move all instances of circleRadii to state.
65
67
  let circleRadii = 4.5
66
68
 
67
69
  return (
@@ -107,7 +109,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
107
109
  strokeOpacity={1}
108
110
  strokeDasharray={lineType ? handleLineType(lineType) : 0}
109
111
  defined={(item, i) => {
110
- return item[config.runtime.seriesLabels[seriesKey]] !== '' && item[config.runtime.seriesLabels[seriesKey]] !== null && item[config.runtime.seriesLabels[seriesKey]] !== undefined
112
+ return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
111
113
  }}
112
114
  />
113
115
  {config.animate && (
@@ -5,6 +5,8 @@ import { Group } from '@visx/group'
5
5
  import { Line } from '@visx/shape'
6
6
  import { Text } from '@visx/text'
7
7
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
8
+ import { localPoint } from '@visx/event'
9
+ import { useTooltip } from '@visx/tooltip'
8
10
 
9
11
  import BarChart from './BarChart'
10
12
  import ConfigContext from '../ConfigContext'
@@ -23,9 +25,10 @@ import useScales from '../hooks/useScales'
23
25
  import useMinMax from '../hooks/useMinMax'
24
26
  import useRightAxis from '../hooks/useRightAxis'
25
27
  import useTopAxis from '../hooks/useTopAxis'
28
+ import Forecasting from './Forecasting'
26
29
 
27
30
  export default function LinearChart() {
28
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType } = useContext(ConfigContext)
31
+ const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData } = useContext(ConfigContext)
29
32
 
30
33
  // getters & functions
31
34
  const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
@@ -52,7 +55,7 @@ export default function LinearChart() {
52
55
  const [animatedChart, setAnimatedChart] = useState(false)
53
56
  const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
54
57
  const { min, max } = useMinMax(properties)
55
- const { xScale, yScale, seriesScale, g1xScale, g2xScale } = useScales({ ...properties, min, max })
58
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
56
59
 
57
60
  // refs
58
61
  const triggerRef = useRef()
@@ -63,7 +66,7 @@ export default function LinearChart() {
63
66
 
64
67
  const handleLeftTickFormatting = tick => {
65
68
  if (config.useLogScale && tick === 0.1) {
66
- //when logaritmic scale applyed change value of FIRST tick
69
+ //when logarithmic scale applied change value of first tick
67
70
  tick = 0
68
71
  }
69
72
  if (config.runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
@@ -118,6 +121,109 @@ export default function LinearChart() {
118
121
  return tickCount
119
122
  }
120
123
 
124
+ // Tooltip helper for getting data to the closest date/category hovered.
125
+ const getXValueFromCoordinate = x => {
126
+ if (xScale.type === 'point') {
127
+ // Find the closest x value by calculating the minimum distance
128
+ let closestX = null
129
+ let minDistance = Number.MAX_VALUE
130
+ let offset = x - yAxis.size
131
+
132
+ data.forEach(d => {
133
+ const xPosition = xAxis.type === 'date' ? xScale(parseDate(d[xAxis.dataKey])) : xScale(d[xAxis.dataKey])
134
+ const distance = Math.abs(Number(xPosition - offset))
135
+
136
+ if (distance < minDistance) {
137
+ minDistance = distance
138
+ closestX = xAxis.type === 'date' ? parseDate(d[xAxis.dataKey]) : d[xAxis.dataKey]
139
+ }
140
+ })
141
+ return closestX
142
+ }
143
+ }
144
+
145
+ // import tooltip helpers
146
+ const { tooltipData, showTooltip, hideTooltip } = useTooltip()
147
+
148
+ const handleTooltipMouseOver = (e, data) => {
149
+ // get the svg coordinates of the mouse
150
+ // and get the closest values
151
+ const eventSvgCoords = localPoint(e)
152
+ const { x, y } = eventSvgCoords
153
+
154
+ const { runtime } = config
155
+
156
+ let closestXScaleValue = getXValueFromCoordinate(x)
157
+ let formattedDate = formatDate(closestXScaleValue)
158
+
159
+ let yScaleValues
160
+ if (xAxis.type === 'categorical') {
161
+ yScaleValues = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
162
+ } else {
163
+ yScaleValues = rawData.filter(d => formatDate(parseDate(d[xAxis.dataKey])) === formattedDate)
164
+ }
165
+
166
+ let seriesToInclude = []
167
+ let stageColumns = []
168
+ let ciItems = []
169
+
170
+ // loop through series for items to add to tooltip.
171
+ // there is probably a better way of doing this.
172
+ config.series?.map(s => {
173
+ if (s.type === 'Forecasting') {
174
+ stageColumns.push(s.stageColumn)
175
+
176
+ // greedy fn 😭
177
+ s?.confidenceIntervals.map(ci => {
178
+ if (ci.showInTooltip === true) {
179
+ ciItems.push(ci.low)
180
+ ciItems.push(ci.high)
181
+ }
182
+ })
183
+ }
184
+ })
185
+
186
+ let standardLoopItems = []
187
+
188
+ if (config.visualizationType === 'Combo') {
189
+ standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.barSeriesKeys, ...stageColumns, ...ciItems]
190
+ } else {
191
+ standardLoopItems = [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
192
+ }
193
+
194
+ standardLoopItems.map(seriesKey => {
195
+ if (!seriesKey) return false
196
+ if (!yScaleValues[0]) return false
197
+ for (const item of Object.entries(yScaleValues[0])) {
198
+ if (item[0] === seriesKey) {
199
+ seriesToInclude.push(item)
200
+ }
201
+ }
202
+ })
203
+
204
+ // filter out the series that aren't added to the map.
205
+ if (!seriesToInclude) return
206
+ let initialTooltipData = Object.fromEntries(seriesToInclude) ? Object.fromEntries(seriesToInclude) : {}
207
+
208
+ let tooltipData = {}
209
+ tooltipData.data = initialTooltipData
210
+ tooltipData.dataXPosition = x + 10
211
+ tooltipData.dataYPosition = y
212
+
213
+ let tooltipInformation = {
214
+ tooltipData: tooltipData,
215
+ tooltipTop: 0,
216
+ tooltipValues: yScaleValues,
217
+ tooltipLeft: x
218
+ }
219
+
220
+ showTooltip(tooltipInformation)
221
+ }
222
+
223
+ const handleTooltipMouseOff = () => {
224
+ hideTooltip()
225
+ }
226
+
121
227
  // Make sure the chart is visible if in the editor
122
228
  /* eslint-disable react-hooks/exhaustive-deps */
123
229
  useEffect(() => {
@@ -144,7 +250,7 @@ export default function LinearChart() {
144
250
  ) : (
145
251
  <ErrorBoundary component='LinearChart'>
146
252
  <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
147
- {/* Higlighted regions */}
253
+ {/* Highlighted regions */}
148
254
  {config.regions
149
255
  ? config.regions.map(region => {
150
256
  if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
@@ -288,7 +394,7 @@ export default function LinearChart() {
288
394
  )
289
395
  })}
290
396
  {!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
291
- <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0}, ${axisCenter}) rotate(90)`} fontWeight='bold' fill={config.yAxis.rightAxisLabelColor}>
397
+ <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.rightAxisLabelColor}>
292
398
  {props.label}
293
399
  </Text>
294
400
  </Group>
@@ -428,28 +534,49 @@ export default function LinearChart() {
428
534
  </AxisBottom>
429
535
  </>
430
536
  )}
431
-
432
537
  {config.visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
433
538
  {config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
434
539
  {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
435
540
  {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
436
541
  {(config.visualizationType === 'Area Chart' || config.visualizationType === 'Combo') && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} />}
437
-
438
- {/* Bar chart */}
439
- {/* TODO: Make this just bar or combo? */}
440
- {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
441
- <>
442
- <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
443
- </>
542
+ {(config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />}
543
+ {(config.visualizationType === 'Line' || config.visualizationType === 'Combo') && <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />}
544
+ {(config.visualizationType === 'Forecasting' || config.visualizationType === 'Combo') && (
545
+ <Forecasting
546
+ hideTooltip={hideTooltip}
547
+ showTooltip={showTooltip}
548
+ tooltipData={tooltipData}
549
+ xScale={xScale}
550
+ yScale={yScale}
551
+ width={xMax}
552
+ height={yMax}
553
+ xScaleNoPadding={xScaleNoPadding}
554
+ chartRef={svgRef}
555
+ getXValueFromCoordinate={getXValueFromCoordinate}
556
+ handleTooltipMouseOver={handleTooltipMouseOver}
557
+ handleTooltipMouseOff={handleTooltipMouseOff}
558
+ />
444
559
  )}
445
560
 
561
+ {/* y anchors */}
562
+ {config.yAxis.anchors &&
563
+ config.yAxis.anchors.map(anchor => {
564
+ return <Line strokeDasharray={handleLineType(anchor.lineStyle)} stroke='rgba(0,0,0,1)' className='customAnchor' from={{ x: 0 + config.yAxis.size, y: yScale(anchor.value) }} to={{ x: xMax, y: yScale(anchor.value) }} display={config.runtime.horizontal ? 'none' : 'block'} />
565
+ })}
566
+
446
567
  {/* Line chart */}
447
568
  {/* TODO: Make this just line or combo? */}
448
- {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
449
- <>
450
- <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
451
- </>
452
- )}
569
+ {config.visualizationType !== 'Bar' &&
570
+ config.visualizationType !== 'Paired Bar' &&
571
+ config.visualizationType !== 'Box Plot' &&
572
+ config.visualizationType !== 'Area Chart' &&
573
+ config.visualizationType !== 'Scatter Plot' &&
574
+ config.visualizationType !== 'Deviation Bar' &&
575
+ config.visualizationType !== 'Forecasting' && (
576
+ <>
577
+ <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
578
+ </>
579
+ )}
453
580
 
454
581
  {/* y anchors */}
455
582
  {config.yAxis.anchors &&