@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.
- package/dist/cdcchart.js +29056 -27475
- package/examples/feature/__data__/planet-example-data.json +15 -16
- package/examples/feature/bar/new.json +561 -0
- package/examples/feature/combo/right-issues.json +190 -0
- package/examples/feature/forecasting/combo-forecasting.json +245 -0
- package/examples/feature/forecasting/forecasting.json +5325 -0
- package/examples/feature/forecasting/index.json +203 -0
- package/examples/feature/line/line-chart.json +1 -1
- package/examples/gallery/line/line.json +173 -1
- package/index.html +12 -6
- package/package.json +2 -2
- package/src/CdcChart.jsx +48 -11
- package/src/components/AreaChart.jsx +8 -23
- package/src/components/BarChart.jsx +65 -3
- package/src/components/DataTable.jsx +30 -12
- package/src/components/EditorPanel.jsx +1803 -1948
- package/src/components/Forecasting.jsx +147 -0
- package/src/components/Legend.jsx +188 -274
- package/src/components/LineChart.jsx +3 -1
- package/src/components/LinearChart.jsx +145 -18
- package/src/components/Series.jsx +518 -0
- package/src/components/SparkLine.jsx +3 -3
- package/src/data/initial-state.js +7 -3
- package/src/hooks/useMinMax.js +36 -0
- package/src/hooks/useRightAxis.js +8 -2
- package/src/hooks/useScales.js +7 -13
|
@@ -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[
|
|
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
|
|
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
|
-
{/*
|
|
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
|
-
{
|
|
439
|
-
{
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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' &&
|
|
449
|
-
|
|
450
|
-
|
|
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 &&
|