@cdc/chart 4.24.5 → 4.24.9
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 +44197 -38258
- package/examples/cases-year.json +13379 -0
- package/examples/feature/annotations/index.json +542 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
- package/examples/xaxis.json +493 -0
- package/index.html +20 -10
- package/package.json +5 -4
- package/src/CdcChart.tsx +462 -172
- package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
- package/src/_stories/Chart.stories.tsx +18 -171
- package/src/_stories/ChartAnnotation.stories.tsx +32 -0
- package/src/_stories/_mock/annotation_category_mock.json +473 -0
- package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
- package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
- package/src/_stories/_mock/legend.gradient_mock.json +236 -0
- package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
- package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
- package/src/_stories/_mock/lollipop.json +171 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
- package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
- package/src/components/Annotations/components/AnnotationList.tsx +42 -0
- package/src/components/Annotations/components/findNearestDatum.ts +138 -0
- package/src/components/Annotations/components/helpers/index.tsx +46 -0
- package/src/components/Annotations/index.tsx +13 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +145 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
- package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
- package/src/components/BarChart/helpers/index.ts +91 -0
- package/src/components/BrushChart.tsx +205 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/components/panels.scss +4 -0
- package/src/components/EditorPanel/editor-panel.scss +35 -3
- package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
- package/src/components/Legend/Legend.Component.tsx +185 -194
- package/src/components/Legend/Legend.Suppression.tsx +146 -0
- package/src/components/Legend/Legend.tsx +21 -5
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +35 -0
- package/src/components/LegendWrapper.tsx +26 -0
- package/src/components/LineChart/LineChartProps.ts +1 -15
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
- package/src/components/LineChart/helpers.ts +72 -14
- package/src/components/LineChart/index.tsx +117 -42
- package/src/components/LinearChart.jsx +179 -136
- package/src/components/LinearChart.tsx +1366 -0
- package/src/components/PairedBarChart.jsx +9 -9
- package/src/components/PieChart/PieChart.tsx +75 -18
- package/src/components/Sankey/index.tsx +89 -30
- package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
- package/src/components/Sparkline/components/SparkLine.tsx +2 -2
- package/src/components/ZoomBrush.tsx +90 -44
- package/src/data/initial-state.js +25 -7
- package/src/helpers/handleChartTabbing.ts +8 -0
- package/src/helpers/isConvertLineToBarGraph.ts +4 -0
- package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
- package/src/hooks/useColorScale.ts +1 -1
- package/src/hooks/useLegendClasses.ts +68 -0
- package/src/hooks/useMinMax.ts +12 -7
- package/src/hooks/useScales.ts +58 -26
- package/src/hooks/useTooltip.tsx +135 -25
- package/src/scss/DataTable.scss +2 -1
- package/src/scss/main.scss +128 -28
- package/src/types/ChartConfig.ts +83 -10
- package/src/types/ChartContext.ts +14 -4
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- package/src/components/BrushHandle.jsx +0 -17
- package/src/components/LineChart/index.scss +0 -1
- package/src/helpers/filterData.ts +0 -18
- package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
- package/src/hooks/useLegendClasses.js +0 -31
- /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Group } from '@visx/group'
|
|
2
|
+
import { useContext, useEffect, useRef, useState } from 'react'
|
|
3
|
+
import ConfigContext from '../ConfigContext'
|
|
4
|
+
import * as d3 from 'd3'
|
|
5
|
+
import { invertValue } from '@cdc/core/helpers/scaling'
|
|
6
|
+
import { Text } from '@visx/text'
|
|
7
|
+
interface BrushChartProps {
|
|
8
|
+
xMax: number
|
|
9
|
+
yMax: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
|
|
13
|
+
const { tableData, config, setBrushConfig, getTextWidth, dashboardConfig, formatDate } = useContext(ConfigContext)
|
|
14
|
+
const [brushState, setBrushState] = useState({ isBrushing: false, selection: [] })
|
|
15
|
+
const [brushKey, setBrushKey] = useState(0)
|
|
16
|
+
const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
|
|
17
|
+
const isDashboardFilters = sharedFilters?.length > 0
|
|
18
|
+
const [tooltip, showTooltip] = useState(false)
|
|
19
|
+
const svgRef = useRef()
|
|
20
|
+
const brushheight = 25
|
|
21
|
+
const borderRadius = 15
|
|
22
|
+
const xDomain = d3.extent(tableData, d => new Date(d[config.runtime.originalXAxis.dataKey]))
|
|
23
|
+
|
|
24
|
+
const xScale = d3.scaleTime().domain(xDomain).range([0, xMax])
|
|
25
|
+
|
|
26
|
+
const tooltipText = 'Drag edges to focus on a specific segment '
|
|
27
|
+
const textWidth = getTextWidth(tooltipText, `normal ${16 / 1.1}px sans-serif`)
|
|
28
|
+
|
|
29
|
+
const calculateGroupTop = (): number => {
|
|
30
|
+
return Number(yMax) + config.xAxis.axisBBox + brushheight * 1.5
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const handleMouseOver = () => {
|
|
34
|
+
// show tooltip text only once before brush triggered
|
|
35
|
+
if (brushState.selection[0] === 0 && xMax === brushState.selection[1]) {
|
|
36
|
+
showTooltip(true)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const handleMouseLeave = () => {
|
|
40
|
+
// hide tooltip text if brush was triggered
|
|
41
|
+
if (brushState.selection[0] !== 0 || brushState.selection[1] !== xMax) {
|
|
42
|
+
showTooltip(false)
|
|
43
|
+
}
|
|
44
|
+
showTooltip(false)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const brushHandle = (g, selection, firstDate, lastDate) => {
|
|
48
|
+
const textWidth = getTextWidth(firstDate, `normal ${16 / 1.1}px sans-serif`)
|
|
49
|
+
return g
|
|
50
|
+
.selectAll('.handle--custom')
|
|
51
|
+
.data([{ side: 'left' }, { side: 'right' }])
|
|
52
|
+
.join(enter => {
|
|
53
|
+
const handleGroup = enter.append('g').attr('class', 'handle--custom')
|
|
54
|
+
handleGroup
|
|
55
|
+
.append('text')
|
|
56
|
+
.attr('x', d => (d.side === 'left' ? 0 : -textWidth))
|
|
57
|
+
.attr('y', 30)
|
|
58
|
+
.text(d => (d.side === 'left' ? firstDate : lastDate))
|
|
59
|
+
.attr('font-size', '13px')
|
|
60
|
+
return handleGroup
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
.attr('display', 'block')
|
|
64
|
+
.attr('transform', selection === null ? null : (_, i) => `translate(${selection[i]},${'10'})`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const initializeBrush = () => {
|
|
68
|
+
const svg = d3.select(svgRef.current).attr('overflow', 'visible')
|
|
69
|
+
|
|
70
|
+
svg
|
|
71
|
+
.append('rect') // prettier-ignore
|
|
72
|
+
.attr('fill', '#949494')
|
|
73
|
+
.attr('stroke', '#c5c5c5')
|
|
74
|
+
.attr('stroke-width', 2)
|
|
75
|
+
.attr('ry', borderRadius)
|
|
76
|
+
.attr('rx', borderRadius)
|
|
77
|
+
.attr('height', brushheight)
|
|
78
|
+
.attr('width', xMax)
|
|
79
|
+
|
|
80
|
+
const brushHandler = event => {
|
|
81
|
+
const selection = event?.selection
|
|
82
|
+
//if (!selection) return
|
|
83
|
+
let isUserBrushing = event.type === 'brush' && selection && selection.length > 0
|
|
84
|
+
|
|
85
|
+
const [x0, x1] = selection.map(value => xScale.invert(value))
|
|
86
|
+
|
|
87
|
+
// filter and update brush state directly
|
|
88
|
+
const newFilteredData = tableData.filter(d => {
|
|
89
|
+
const dateValue = d[config.runtime.originalXAxis.dataKey]
|
|
90
|
+
// Check if the date value exists and is valid
|
|
91
|
+
if (!dateValue) {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const parsedDate = new Date(dateValue)
|
|
96
|
+
|
|
97
|
+
// Check if parsedDate is a valid date
|
|
98
|
+
if (isNaN(parsedDate.getTime())) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check if the date falls within the selection range
|
|
103
|
+
if (parsedDate >= x0 && parsedDate <= x1) {
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const firstDate = (newFilteredData.length && newFilteredData[0][config?.runtime?.originalXAxis?.dataKey]) ?? ''
|
|
109
|
+
const lastDate =
|
|
110
|
+
(newFilteredData.length &&
|
|
111
|
+
newFilteredData[newFilteredData.length - 1][config?.runtime?.originalXAxis?.dataKey]) ??
|
|
112
|
+
''
|
|
113
|
+
// add custom blue colored handlers to each corners of brush
|
|
114
|
+
svg.selectAll('.handle--custom').remove()
|
|
115
|
+
// append handler
|
|
116
|
+
svg.call(brushHandle, selection, firstDate, lastDate)
|
|
117
|
+
|
|
118
|
+
setBrushConfig({
|
|
119
|
+
active: config.brush.active,
|
|
120
|
+
isBrushing: isUserBrushing,
|
|
121
|
+
data: newFilteredData
|
|
122
|
+
})
|
|
123
|
+
setBrushState({
|
|
124
|
+
isBrushing: true,
|
|
125
|
+
selection
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const brush = d3
|
|
130
|
+
.brushX()
|
|
131
|
+
.extent([
|
|
132
|
+
[0, 0],
|
|
133
|
+
[xMax, 25]
|
|
134
|
+
]) // brush extent
|
|
135
|
+
.on('start brush end', brushHandler)
|
|
136
|
+
|
|
137
|
+
const defaultSelection = [0, xMax]
|
|
138
|
+
let brushGroup = svg.append('g').call(brush).call(brush.move, defaultSelection)
|
|
139
|
+
brushGroup.select('.overlay').style('pointer-events', 'none')
|
|
140
|
+
|
|
141
|
+
brushGroup
|
|
142
|
+
.selectAll('.selection')
|
|
143
|
+
.attr('fill', '#474747')
|
|
144
|
+
.attr('fill-opacity', 1)
|
|
145
|
+
.attr('rx', borderRadius)
|
|
146
|
+
.attr('ry', borderRadius)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
const isFiltersActive = config.filters?.some(filter => filter.active)
|
|
151
|
+
const isExclusionsActive = config.exclusions?.active
|
|
152
|
+
|
|
153
|
+
if ((isFiltersActive || isExclusionsActive || isDashboardFilters) && config.brush?.active) {
|
|
154
|
+
setBrushKey(prevKey => prevKey + 1)
|
|
155
|
+
setBrushConfig(prev => {
|
|
156
|
+
return {
|
|
157
|
+
...prev,
|
|
158
|
+
data: tableData
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
return () =>
|
|
163
|
+
setBrushConfig(prev => {
|
|
164
|
+
return {
|
|
165
|
+
...prev,
|
|
166
|
+
data: []
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
}, [config.filters, config.exclusions, config.brush?.active, isDashboardFilters])
|
|
170
|
+
// Initialize brush when component is first rendered
|
|
171
|
+
|
|
172
|
+
// reset brush on keychange
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (brushKey) {
|
|
175
|
+
initializeBrush()
|
|
176
|
+
}
|
|
177
|
+
}, [brushKey])
|
|
178
|
+
|
|
179
|
+
if (!brushState.isBrushing) {
|
|
180
|
+
initializeBrush()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<Group
|
|
185
|
+
onMouseLeave={handleMouseLeave}
|
|
186
|
+
onMouseOver={handleMouseOver}
|
|
187
|
+
className='brush-container'
|
|
188
|
+
left={Number(config.runtime.yAxis.size)}
|
|
189
|
+
top={calculateGroupTop()}
|
|
190
|
+
>
|
|
191
|
+
<Text
|
|
192
|
+
pointerEvents='visiblePainted'
|
|
193
|
+
display={tooltip ? 'block' : 'none'}
|
|
194
|
+
fontSize={16}
|
|
195
|
+
x={(Number(xMax) - Number(textWidth)) / 2}
|
|
196
|
+
y={-10}
|
|
197
|
+
>
|
|
198
|
+
Drag edges to focus on a specific segment
|
|
199
|
+
</Text>
|
|
200
|
+
<svg width={'100%'} height={brushheight * 3} ref={svgRef}></svg>
|
|
201
|
+
</Group>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default BrushChart
|