@cdc/chart 4.25.10 → 4.26.1
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-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
- package/dist/cdcchart.js +44003 -43518
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/feature/pie/planet-pie-example-config.json +48 -2
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/DEV-12100.json +1303 -0
- package/examples/private/cat-y.json +1235 -0
- package/examples/private/data-points.json +228 -0
- package/examples/private/height.json +3915 -0
- package/examples/private/links.json +569 -0
- package/examples/private/na.json +913 -0
- package/examples/private/quadrant.txt +30 -0
- package/examples/private/test-data.csv +28 -0
- package/examples/private/test-forecast.json +5510 -0
- package/examples/private/warming-stripe-test.json +2578 -0
- package/examples/private/warming-stripes.json +4763 -0
- package/examples/tech-adoption-with-links.json +560 -0
- package/index.html +16 -140
- package/package.json +6 -5
- package/preview.html +1616 -0
- package/src/CdcChart.tsx +8 -11
- package/src/CdcChartComponent.tsx +329 -124
- package/src/_stories/Chart.Combo.stories.tsx +18 -0
- package/src/_stories/Chart.Forecast.stories.tsx +36 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
- package/src/_stories/Chart.Patterns.stories.tsx +2 -1
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/Chart.stories.tsx +8 -0
- package/src/_stories/ChartAnnotation.stories.tsx +6 -3
- package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
- package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
- package/src/_stories/ChartBrush.stories.tsx +50 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
- package/src/_stories/ChartEditor.stories.tsx +1 -2
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
- package/src/_stories/_mock/brush_enabled.json +326 -0
- package/src/_stories/_mock/brush_mock.json +2 -69
- package/src/_stories/_mock/combo.json +451 -0
- package/src/_stories/_mock/editor-test-configs.json +376 -0
- package/src/_stories/_mock/editor-test-datasets.json +477 -0
- package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
- package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
- package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
- package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
- package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
- package/src/_stories/_mock/pie_config.json +257 -62
- package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
- package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
- package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
- package/src/components/Annotations/components/findNearestDatum.ts +6 -41
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -7
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/Axis/Categorical.Axis.tsx +6 -7
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
- package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
- package/src/components/BarChart/components/context.tsx +1 -0
- package/src/components/BarChart/helpers/useBarChart.ts +14 -2
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushSelector.tsx +1258 -0
- package/src/components/Brush/MiniChartPreview.tsx +283 -0
- package/src/components/DeviationBar.jsx +9 -7
- package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/editor-panel.scss +0 -20
- package/src/components/EditorPanel/useEditorPermissions.ts +36 -31
- package/src/components/Forecasting/Forecasting.tsx +139 -21
- package/src/components/Legend/Legend.Component.tsx +16 -9
- package/src/components/Legend/Legend.tsx +3 -2
- package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/Legend/helpers/index.ts +10 -6
- package/src/components/LineChart/LineChartProps.ts +0 -3
- package/src/components/LineChart/helpers.ts +1 -1
- package/src/components/LineChart/index.tsx +36 -13
- package/src/components/LinearChart.tsx +559 -499
- package/src/components/PairedBarChart.jsx +20 -3
- package/src/components/Regions/components/Regions.tsx +366 -144
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
- package/src/components/SmallMultiples/index.ts +2 -0
- package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
- package/src/components/WarmingStripes/index.tsx +3 -0
- package/src/data/initial-state.js +16 -2
- package/src/helpers/buildForecastPaletteOptions.ts +0 -38
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
- package/src/helpers/getColorScale.ts +10 -0
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/sizeHelpers.ts +0 -20
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useChartHoverAnalytics.tsx +10 -9
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useScales.ts +98 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +91 -25
- package/src/scss/DataTable.scss +0 -4
- package/src/scss/main.scss +18 -83
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +4 -0
- package/src/test/CdcChart.test.jsx +1 -1
- package/src/types/ChartConfig.ts +27 -6
- package/src/types/ChartContext.ts +3 -0
- package/src/types/Label.ts +1 -0
- package/src/utils/analyticsTracking.ts +19 -0
- package/LICENSE +0 -201
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/components/Brush/BrushChart.tsx +0 -128
- package/src/components/Brush/BrushController.tsx +0 -71
- package/src/components/Brush/types.tsx +0 -8
- package/src/components/BrushChart.tsx +0 -223
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import React, { useContext, memo } from 'react'
|
|
2
|
-
|
|
3
|
-
// cdc
|
|
4
|
-
import ConfigContext from '../../../ConfigContext'
|
|
5
|
-
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
|
-
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
7
|
-
|
|
8
|
-
// visx & d3
|
|
9
|
-
import * as allCurves from '@visx/curve'
|
|
10
|
-
import { AreaClosed, LinePath, Bar } from '@visx/shape'
|
|
11
|
-
import { Group } from '@visx/group'
|
|
12
|
-
|
|
13
|
-
const AreaChart = props => {
|
|
14
|
-
const { xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff } = props
|
|
15
|
-
// import data from context
|
|
16
|
-
let {
|
|
17
|
-
transformedData: data,
|
|
18
|
-
config,
|
|
19
|
-
handleLineType,
|
|
20
|
-
parseDate,
|
|
21
|
-
|
|
22
|
-
seriesHighlight,
|
|
23
|
-
colorScale,
|
|
24
|
-
rawData
|
|
25
|
-
} = useContext(ConfigContext)
|
|
26
|
-
|
|
27
|
-
if (!data) return
|
|
28
|
-
|
|
29
|
-
const handleX = d => {
|
|
30
|
-
return (
|
|
31
|
-
(isDateScale(config.xAxis)
|
|
32
|
-
? xScale(parseDate(d[config.xAxis.dataKey], false))
|
|
33
|
-
: xScale(d[config.xAxis.dataKey])) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const handleY = (d, index, s = undefined) => {
|
|
38
|
-
return yScale(d[s.dataKey])
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
data && (
|
|
43
|
-
<svg>
|
|
44
|
-
<ErrorBoundary component='AreaChart'>
|
|
45
|
-
<Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size)}>
|
|
46
|
-
{(config.runtime.areaSeriesKeys || config.series).map((s, index) => {
|
|
47
|
-
let seriesData = data.map(d => {
|
|
48
|
-
return {
|
|
49
|
-
[config.xAxis.dataKey]: d[config.xAxis.dataKey],
|
|
50
|
-
[s.dataKey]: d[s.dataKey]
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
let curveType = allCurves[s.lineType]
|
|
55
|
-
let transparentArea =
|
|
56
|
-
config.legend.behavior === 'highlight' &&
|
|
57
|
-
seriesHighlight.length > 0 &&
|
|
58
|
-
seriesHighlight.indexOf(s.dataKey) === -1
|
|
59
|
-
let displayArea =
|
|
60
|
-
config.legend.behavior === 'highlight' ||
|
|
61
|
-
seriesHighlight.length === 0 ||
|
|
62
|
-
seriesHighlight.indexOf(s.dataKey) !== -1
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<React.Fragment key={index}>
|
|
66
|
-
{/* prettier-ignore */}
|
|
67
|
-
<LinePath
|
|
68
|
-
data={seriesData}
|
|
69
|
-
x={d => handleX(d)}
|
|
70
|
-
y={d => handleY(d, index, s)}
|
|
71
|
-
stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
|
|
72
|
-
strokeWidth={2}
|
|
73
|
-
strokeOpacity={1}
|
|
74
|
-
shapeRendering='geometricPrecision'
|
|
75
|
-
curve={curveType}
|
|
76
|
-
strokeDasharray={s.type ? handleLineType(s.type) : 0}
|
|
77
|
-
/>
|
|
78
|
-
|
|
79
|
-
{/* prettier-ignore */}
|
|
80
|
-
<AreaClosed
|
|
81
|
-
key={'area-chart'}
|
|
82
|
-
fill={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
|
|
83
|
-
fillOpacity={transparentArea ? 0.25 : 0.5}
|
|
84
|
-
data={seriesData}
|
|
85
|
-
x={d => handleX(d)}
|
|
86
|
-
y={d => handleY(d, index, s)}
|
|
87
|
-
yScale={yScale}
|
|
88
|
-
curve={curveType}
|
|
89
|
-
strokeDasharray={s.type ? handleLineType(s.type) : 0}
|
|
90
|
-
/>
|
|
91
|
-
</React.Fragment>
|
|
92
|
-
)
|
|
93
|
-
})}
|
|
94
|
-
<Bar
|
|
95
|
-
width={Number(xMax)}
|
|
96
|
-
height={Number(yMax)}
|
|
97
|
-
fill={'transparent'}
|
|
98
|
-
fillOpacity={0.05}
|
|
99
|
-
onMouseMove={e => handleTooltipMouseOver(e, rawData)}
|
|
100
|
-
onMouseLeave={handleTooltipMouseOff}
|
|
101
|
-
/>
|
|
102
|
-
</Group>
|
|
103
|
-
</ErrorBoundary>
|
|
104
|
-
</svg>
|
|
105
|
-
)
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export default memo(AreaChart)
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import React, { FC, useContext, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { Brush } from '@visx/brush'
|
|
3
|
-
import { Group } from '@visx/group'
|
|
4
|
-
import { scaleBand, scaleLinear } from '@visx/scale'
|
|
5
|
-
import ConfigContext from '../../ConfigContext'
|
|
6
|
-
import { Text } from '@visx/text'
|
|
7
|
-
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
8
|
-
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
9
|
-
export interface BrushChartProps {
|
|
10
|
-
xMax: number
|
|
11
|
-
yMax: number
|
|
12
|
-
brushPosition: { start: { x: number }; end: { x: number } }
|
|
13
|
-
onBrushChange: (bounds: any) => void
|
|
14
|
-
brushKey: number
|
|
15
|
-
brushHandleProps: { startValue: string; endValue: string; endPos: number; startPos: number }
|
|
16
|
-
brushRef: React.RefObject<BrushRef>
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const BrushChart: FC<BrushChartProps> = ({
|
|
20
|
-
xMax,
|
|
21
|
-
yMax,
|
|
22
|
-
brushPosition,
|
|
23
|
-
onBrushChange,
|
|
24
|
-
brushKey,
|
|
25
|
-
brushHandleProps,
|
|
26
|
-
brushRef
|
|
27
|
-
}) => {
|
|
28
|
-
const { tableData, config, dashboardConfig } = useContext(ConfigContext)
|
|
29
|
-
const dataKey = config.xAxis.dataKey
|
|
30
|
-
const borderRadius = 15
|
|
31
|
-
const mappedDates: string[] = tableData.map(row => row[dataKey])
|
|
32
|
-
const brushheight = 25
|
|
33
|
-
const DASHBOARD_MARGIN = 50
|
|
34
|
-
const BRUSH_HEIGHT_MULTIPLIER = 1.5
|
|
35
|
-
const range = config?.xAxis?.sortByRecentDate ? [xMax, 0] : [0, xMax]
|
|
36
|
-
const xScale = scaleBand<string>({
|
|
37
|
-
domain: config?.xAxis?.sortByRecentDate ? mappedDates.reverse() : mappedDates,
|
|
38
|
-
range: range,
|
|
39
|
-
paddingInner: 0.1,
|
|
40
|
-
paddingOuter: 0.1
|
|
41
|
-
})
|
|
42
|
-
const yScale = scaleLinear({ domain: [0, yMax], range: [0, yMax] })
|
|
43
|
-
|
|
44
|
-
const calculateGroupTop = (): number => {
|
|
45
|
-
if (dashboardConfig?.type === 'dashboard') {
|
|
46
|
-
return Number(yMax) + config.xAxis.axisBBox + brushheight * BRUSH_HEIGHT_MULTIPLIER + DASHBOARD_MARGIN
|
|
47
|
-
} else {
|
|
48
|
-
return Number(yMax) + config.xAxis.axisBBox + brushheight * BRUSH_HEIGHT_MULTIPLIER
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const style = {
|
|
53
|
-
fill: '#474747',
|
|
54
|
-
stroke: 'blue',
|
|
55
|
-
strokeOpacity: 0,
|
|
56
|
-
rx: borderRadius
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<Group left={config.yAxis.size} top={calculateGroupTop()}>
|
|
61
|
-
<rect fill='#949494' width={xMax} height={25} rx={borderRadius} pointerEvents='none' />
|
|
62
|
-
<Brush
|
|
63
|
-
disableDraggingOverlay={false}
|
|
64
|
-
renderBrushHandle={props => (
|
|
65
|
-
<BrushHandle
|
|
66
|
-
left={Number(config.runtime.yAxis.size)}
|
|
67
|
-
pixelDistance={brushHandleProps.endPos - brushHandleProps.startPos}
|
|
68
|
-
textProps={brushHandleProps}
|
|
69
|
-
isBrushing={brushRef.current?.state.isBrushing}
|
|
70
|
-
{...props}
|
|
71
|
-
/>
|
|
72
|
-
)}
|
|
73
|
-
innerRef={brushRef}
|
|
74
|
-
key={brushKey}
|
|
75
|
-
xScale={xScale}
|
|
76
|
-
yScale={yScale}
|
|
77
|
-
width={xMax}
|
|
78
|
-
height={25}
|
|
79
|
-
brushDirection='horizontal'
|
|
80
|
-
handleSize={8}
|
|
81
|
-
resizeTriggerAreas={['left', 'right']}
|
|
82
|
-
initialBrushPosition={brushPosition}
|
|
83
|
-
selectedBoxStyle={style}
|
|
84
|
-
onChange={onBrushChange}
|
|
85
|
-
useWindowMoveEvents={true}
|
|
86
|
-
/>
|
|
87
|
-
</Group>
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export default BrushChart
|
|
92
|
-
|
|
93
|
-
const BrushHandle = props => {
|
|
94
|
-
const { x, y, isBrushing, className, textProps } = props
|
|
95
|
-
const pathWidth = 8
|
|
96
|
-
|
|
97
|
-
// Flip the SVG path horizontally for the left handle
|
|
98
|
-
const isLeft = className.includes('left')
|
|
99
|
-
const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
|
|
100
|
-
const textAnchor = isLeft ? 'end' : 'start'
|
|
101
|
-
const tooltipText = isLeft ? ` Drag edges to focus on a specific segment ` : ''
|
|
102
|
-
const textFontSize = APP_FONT_SIZE / 1.4
|
|
103
|
-
const textWidth = getTextWidth(textProps.startValue, `${textFontSize}px`)
|
|
104
|
-
const textPosLeft = x > 0 ? 0 : 55
|
|
105
|
-
const textPosRight = y < textProps.xMax ? 0 : -50
|
|
106
|
-
return (
|
|
107
|
-
<Group left={x + pathWidth / 2} top={-2}>
|
|
108
|
-
<Text
|
|
109
|
-
pointerEvents='visiblePainted'
|
|
110
|
-
dominantBaseline='hanging'
|
|
111
|
-
x={isLeft ? textPosLeft : textPosRight}
|
|
112
|
-
y={25}
|
|
113
|
-
verticalAnchor='start'
|
|
114
|
-
textAnchor={textAnchor}
|
|
115
|
-
fontSize={textFontSize}
|
|
116
|
-
>
|
|
117
|
-
{isLeft ? textProps.startValue : textProps.endValue}
|
|
118
|
-
</Text>
|
|
119
|
-
<path
|
|
120
|
-
cursor='ew-resize'
|
|
121
|
-
d='M0.5,10A6,6 0 0 1 6.5,16V14A6,6 0 0 1 0.5,20ZM2.5,18V12M4.5,18V12'
|
|
122
|
-
fill='#297EF1'
|
|
123
|
-
strokeWidth='1'
|
|
124
|
-
transform={transform}
|
|
125
|
-
/>
|
|
126
|
-
</Group>
|
|
127
|
-
)
|
|
128
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useContext, useRef } from 'react'
|
|
2
|
-
import ConfigContext, { ChartDispatchContext } from '../../ConfigContext'
|
|
3
|
-
import BrushChart from './BrushChart'
|
|
4
|
-
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
5
|
-
import { BrushRef } from './types'
|
|
6
|
-
|
|
7
|
-
const BrushController = ({ yMax, xMax }) => {
|
|
8
|
-
const { tableData, config, parseDate, dashboardConfig, formatDate } = useContext(ConfigContext)
|
|
9
|
-
const [brushHandleProps, setBrushHandleProps] = useState({
|
|
10
|
-
startPos: 0,
|
|
11
|
-
endPos: 0,
|
|
12
|
-
startValue: '',
|
|
13
|
-
endValue: '',
|
|
14
|
-
xMax: xMax
|
|
15
|
-
})
|
|
16
|
-
const dataKey = config.xAxis.dataKey
|
|
17
|
-
const [brushKey, setBrushKey] = useState(0)
|
|
18
|
-
const dispatch = useContext(ChartDispatchContext)
|
|
19
|
-
const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
|
|
20
|
-
const isDashboardFilters = sharedFilters?.length > 0
|
|
21
|
-
const brushRef = useRef<BrushRef | null>(null)
|
|
22
|
-
|
|
23
|
-
const [brushPosition, setBrushPosition] = useState({
|
|
24
|
-
start: { x: 0 },
|
|
25
|
-
end: { x: xMax }
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
const handleBrushChange = (bounds: any) => {
|
|
29
|
-
if (!bounds) return dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
|
|
30
|
-
const filteredValues = bounds?.xValues?.filter(val => val !== undefined)
|
|
31
|
-
if (filteredValues?.length === 0) dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
|
|
32
|
-
const selected = bounds?.xValues || []
|
|
33
|
-
|
|
34
|
-
const filteredData = tableData.filter(row => selected.includes(row[dataKey]))
|
|
35
|
-
const endValue = filteredValues
|
|
36
|
-
.slice()
|
|
37
|
-
.reverse()
|
|
38
|
-
.find(item => item !== undefined)
|
|
39
|
-
const startValue = filteredValues.find(item => item !== undefined)
|
|
40
|
-
const formatIfDate = value => (isDateScale(config.runtime.xAxis) ? formatDate(parseDate(value)) : value)
|
|
41
|
-
|
|
42
|
-
setBrushHandleProps(prev => ({
|
|
43
|
-
...prev,
|
|
44
|
-
startPos: brushRef.current?.state.start.x,
|
|
45
|
-
endPos: brushRef.current?.state.end.x,
|
|
46
|
-
endValue: formatIfDate(endValue),
|
|
47
|
-
startValue: formatIfDate(startValue)
|
|
48
|
-
}))
|
|
49
|
-
dispatch({ type: 'SET_BRUSH_DATA', payload: filteredData })
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// whenever your other filters change:
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
setBrushPosition({ start: { x: 0 }, end: { x: xMax } })
|
|
55
|
-
dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
|
|
56
|
-
setBrushKey(k => k + 1)
|
|
57
|
-
}, [config.filters, config.exclusions, config.brush?.active, isDashboardFilters])
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<BrushChart
|
|
61
|
-
brushRef={brushRef}
|
|
62
|
-
brushHandleProps={brushHandleProps}
|
|
63
|
-
xMax={xMax}
|
|
64
|
-
yMax={yMax}
|
|
65
|
-
brushPosition={brushPosition}
|
|
66
|
-
onBrushChange={handleBrushChange}
|
|
67
|
-
brushKey={brushKey}
|
|
68
|
-
/>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
export default BrushController
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { Group } from '@visx/group'
|
|
2
|
-
import { useContext, useEffect, useRef, useState } from 'react'
|
|
3
|
-
import ConfigContext, { ChartDispatchContext } from '../ConfigContext'
|
|
4
|
-
import * as d3 from 'd3'
|
|
5
|
-
import { Text } from '@visx/text'
|
|
6
|
-
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
7
|
-
import _ from 'lodash'
|
|
8
|
-
|
|
9
|
-
interface BrushChartProps {
|
|
10
|
-
xMax: number
|
|
11
|
-
yMax: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
|
|
15
|
-
const { tableData, config, dashboardConfig, formatDate, parseDate } = useContext(ConfigContext)
|
|
16
|
-
const dispatch = useContext(ChartDispatchContext)
|
|
17
|
-
const [brushState, setBrushState] = useState({ isBrushing: false, selection: [] })
|
|
18
|
-
const [brushKey, setBrushKey] = useState(0)
|
|
19
|
-
const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
|
|
20
|
-
const isDashboardFilters = sharedFilters?.length > 0
|
|
21
|
-
const [tooltip, showTooltip] = useState(false)
|
|
22
|
-
const svgRef = useRef()
|
|
23
|
-
const brushheight = 25
|
|
24
|
-
const borderRadius = 15
|
|
25
|
-
const xDomain = d3.extent(tableData, d => new Date(d[config.runtime.originalXAxis.dataKey]))
|
|
26
|
-
|
|
27
|
-
const xScale = d3.scaleTime().domain(xDomain).range([0, xMax])
|
|
28
|
-
|
|
29
|
-
const tooltipText = 'Drag edges to focus on a specific segment '
|
|
30
|
-
const textWidth = getTextWidth(tooltipText, `normal ${16 / 1.1}px sans-serif`)
|
|
31
|
-
const DASHBOARD_MARGIN = 50
|
|
32
|
-
const BRUSH_HEIGHT_MULTIPLIER = 1.5
|
|
33
|
-
|
|
34
|
-
const calculateGroupTop = (): number => {
|
|
35
|
-
if (dashboardConfig?.type === 'dashboard') {
|
|
36
|
-
return Number(yMax) + config.xAxis.axisBBox + brushheight * BRUSH_HEIGHT_MULTIPLIER + DASHBOARD_MARGIN
|
|
37
|
-
} else {
|
|
38
|
-
return Number(yMax) + config.xAxis.axisBBox + brushheight * BRUSH_HEIGHT_MULTIPLIER
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const handleMouseOver = () => {
|
|
43
|
-
// show tooltip text only once before brush triggered
|
|
44
|
-
if (brushState.selection[0] === 0 && xMax === brushState.selection[1]) {
|
|
45
|
-
showTooltip(true)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const handleMouseLeave = () => {
|
|
49
|
-
// hide tooltip text if brush was triggered
|
|
50
|
-
if (brushState.selection[0] !== 0 || brushState.selection[1] !== xMax) {
|
|
51
|
-
showTooltip(false)
|
|
52
|
-
}
|
|
53
|
-
showTooltip(false)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const brushHandle = (g, selection, firstDate, lastDate) => {
|
|
57
|
-
const textWidth = getTextWidth(firstDate, `normal ${16 / 1.1}px sans-serif`)
|
|
58
|
-
const textPositionLeft = selection[0] < textWidth ? 0 : -textWidth
|
|
59
|
-
const textPositionRight = xMax - selection[1] < textWidth ? -textWidth : 0
|
|
60
|
-
|
|
61
|
-
return g
|
|
62
|
-
.selectAll('.handle--custom')
|
|
63
|
-
.data([{ side: 'left' }, { side: 'right' }])
|
|
64
|
-
.join(enter => {
|
|
65
|
-
const handleGroup = enter.append('g').attr('class', 'handle--custom')
|
|
66
|
-
handleGroup
|
|
67
|
-
.append('text')
|
|
68
|
-
.attr('x', d => (d.side === 'left' ? textPositionLeft : textPositionRight))
|
|
69
|
-
.attr('y', 30)
|
|
70
|
-
.text(d => (d.side === 'left' ? firstDate : lastDate))
|
|
71
|
-
.attr('font-size', '13px')
|
|
72
|
-
return handleGroup
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
.attr('display', 'block')
|
|
76
|
-
.attr('transform', selection === null ? null : (_, i) => `translate(${selection[i]},${'10'})`)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const initializeBrush = () => {
|
|
80
|
-
const svg = d3.select(svgRef.current).attr('overflow', 'visible')
|
|
81
|
-
|
|
82
|
-
svg
|
|
83
|
-
.append('rect') // prettier-ignore
|
|
84
|
-
.attr('fill', '#949494')
|
|
85
|
-
.attr('stroke', '#c5c5c5')
|
|
86
|
-
.attr('stroke-width', 2)
|
|
87
|
-
.attr('ry', borderRadius)
|
|
88
|
-
.attr('rx', borderRadius)
|
|
89
|
-
.attr('height', brushheight)
|
|
90
|
-
.attr('width', xMax)
|
|
91
|
-
|
|
92
|
-
const brushHandler = event => {
|
|
93
|
-
const selection = event?.selection
|
|
94
|
-
//if (!selection) return
|
|
95
|
-
let isUserBrushing = event.type === 'brush' && selection && selection.length > 0
|
|
96
|
-
|
|
97
|
-
const [x0, x1] = selection.map(value => xScale.invert(value))
|
|
98
|
-
|
|
99
|
-
const newFilteredData = _.filter(tableData, d => {
|
|
100
|
-
const parsedDate = new Date(d[config.xAxis.dataKey])
|
|
101
|
-
return parsedDate && !isNaN(parsedDate.getTime()) && parsedDate >= x0 && parsedDate <= x1
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
const sortByRecentDate = config.xAxis.sortByRecentDate
|
|
105
|
-
|
|
106
|
-
const sortedData = _.sortBy(newFilteredData, item => new Date(item[config.xAxis.dataKey]))
|
|
107
|
-
|
|
108
|
-
// If ascending is false, reverse the sorted array
|
|
109
|
-
const finalData: object[] = !sortByRecentDate ? sortedData : sortedData.reverse()
|
|
110
|
-
|
|
111
|
-
// Retrieve the start and end dates based on the sorted data array
|
|
112
|
-
const startDate: string = _.get(_.first(finalData), config.xAxis.dataKey, '')
|
|
113
|
-
const endDate: string = _.get(_.last(finalData), config.xAxis.dataKey, '')
|
|
114
|
-
// add custom blue colored handlers to each corners of brush
|
|
115
|
-
svg.selectAll('.handle--custom').remove()
|
|
116
|
-
// Parse and format the dates, setting them to an empty string if undefined
|
|
117
|
-
const parseAndFormatDate = date => (date ? formatDate(parseDate(date)) : '')
|
|
118
|
-
const formattedStartDate = parseAndFormatDate(startDate)
|
|
119
|
-
const formattedEndDate = parseAndFormatDate(endDate)
|
|
120
|
-
svg.call(brushHandle, selection, formattedStartDate, formattedEndDate)
|
|
121
|
-
|
|
122
|
-
const payload = {
|
|
123
|
-
active: config.xAxis.brushActive,
|
|
124
|
-
isBrushing: isUserBrushing,
|
|
125
|
-
data: finalData
|
|
126
|
-
}
|
|
127
|
-
dispatch({ type: 'SET_BRUSH_CONFIG', payload: payload })
|
|
128
|
-
setBrushState({
|
|
129
|
-
isBrushing: true,
|
|
130
|
-
selection
|
|
131
|
-
})
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const brush = d3
|
|
135
|
-
.brushX()
|
|
136
|
-
.extent([
|
|
137
|
-
[0, 0],
|
|
138
|
-
[xMax, 25]
|
|
139
|
-
]) // brush extent
|
|
140
|
-
.on('start brush end', brushHandler)
|
|
141
|
-
|
|
142
|
-
const defaultSelection = [0, xMax]
|
|
143
|
-
let brushGroup = svg.append('g').call(brush).call(brush.move, defaultSelection)
|
|
144
|
-
brushGroup.select('.overlay').style('pointer-events', 'none')
|
|
145
|
-
|
|
146
|
-
brushGroup
|
|
147
|
-
.selectAll('.selection')
|
|
148
|
-
.attr('fill', '#474747')
|
|
149
|
-
.attr('fill-opacity', 1)
|
|
150
|
-
.attr('rx', borderRadius)
|
|
151
|
-
.attr('ry', borderRadius)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
const isFiltersActive = config.filters?.some(filter => filter.active)
|
|
156
|
-
const isExclusionsActive = config.exclusions?.active
|
|
157
|
-
|
|
158
|
-
if ((isFiltersActive || isExclusionsActive || isDashboardFilters) && config.brush?.active) {
|
|
159
|
-
setBrushKey(prevKey => prevKey + 1)
|
|
160
|
-
}
|
|
161
|
-
dispatch({ type: 'SET_BRUSH_CONFIG', payload: { ...config.brush, data: tableData } })
|
|
162
|
-
|
|
163
|
-
return () => dispatch({ type: 'SET_BRUSH_CONFIG', payload: { ...config.brush, data: [] } })
|
|
164
|
-
}, [config.filters, config.exclusions, config.brush?.active, isDashboardFilters])
|
|
165
|
-
|
|
166
|
-
// this effect handles where brush chart is missing on production. it helpes re render
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
let timeoutId = null
|
|
169
|
-
|
|
170
|
-
const checkAndInitializeBrush = () => {
|
|
171
|
-
if (xMax > 0) {
|
|
172
|
-
initializeBrush()
|
|
173
|
-
} else {
|
|
174
|
-
// Clear the existing timeout and set a new one
|
|
175
|
-
clearTimeout(timeoutId)
|
|
176
|
-
timeoutId = setTimeout(checkAndInitializeBrush, 500)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
checkAndInitializeBrush()
|
|
181
|
-
|
|
182
|
-
// Cleanup function to clear timeout
|
|
183
|
-
return () => {
|
|
184
|
-
if (timeoutId) {
|
|
185
|
-
clearTimeout(timeoutId)
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}, [xMax])
|
|
189
|
-
|
|
190
|
-
// reset brush on keychange
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
if (brushKey) {
|
|
193
|
-
initializeBrush()
|
|
194
|
-
}
|
|
195
|
-
}, [brushKey])
|
|
196
|
-
|
|
197
|
-
if (!brushState.isBrushing) {
|
|
198
|
-
initializeBrush()
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<Group
|
|
203
|
-
onMouseLeave={handleMouseLeave}
|
|
204
|
-
onMouseOver={handleMouseOver}
|
|
205
|
-
className='brush-container'
|
|
206
|
-
left={Number(config.runtime.yAxis.size)}
|
|
207
|
-
top={calculateGroupTop()}
|
|
208
|
-
>
|
|
209
|
-
<Text
|
|
210
|
-
pointerEvents='visiblePainted'
|
|
211
|
-
display={tooltip ? 'block' : 'none'}
|
|
212
|
-
fontSize={16}
|
|
213
|
-
x={(Number(xMax) - Number(textWidth)) / 2}
|
|
214
|
-
y={-10}
|
|
215
|
-
>
|
|
216
|
-
Drag edges to focus on a specific segment
|
|
217
|
-
</Text>
|
|
218
|
-
<svg width={'100%'} height={brushheight * 3} ref={svgRef}></svg>
|
|
219
|
-
</Group>
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export default BrushChart
|
package/src/helpers/sort.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
2
|
-
// Use for accessibility testing
|
|
3
|
-
const useActiveElement = () => {
|
|
4
|
-
const [active, setActive] = useState(document.activeElement)
|
|
5
|
-
|
|
6
|
-
const handleFocusIn = e => {
|
|
7
|
-
setActive(document.activeElement)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
document.addEventListener('focusin', handleFocusIn)
|
|
12
|
-
return () => {
|
|
13
|
-
document.removeEventListener('focusin', handleFocusIn)
|
|
14
|
-
}
|
|
15
|
-
}, [])
|
|
16
|
-
|
|
17
|
-
return active
|
|
18
|
-
}
|
|
19
|
-
export default useActiveElement
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export default function useChartClasses(config) {
|
|
2
|
-
let lineDatapointClass = ''
|
|
3
|
-
let barBorderClass = ''
|
|
4
|
-
|
|
5
|
-
if (config.lineDatapointStyle === 'hover') {
|
|
6
|
-
lineDatapointClass = ' chart-line--hover'
|
|
7
|
-
}
|
|
8
|
-
if (config.lineDatapointStyle === 'always show') {
|
|
9
|
-
lineDatapointClass = ' chart-line--always'
|
|
10
|
-
}
|
|
11
|
-
if (config.barHasBorder === 'false') {
|
|
12
|
-
barBorderClass = ' chart-bar--no-border'
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let innerContainerClasses = ['cove-component__inner']
|
|
16
|
-
config.title && innerContainerClasses.push('component--has-title')
|
|
17
|
-
config.subtext && innerContainerClasses.push('component--has-subtext')
|
|
18
|
-
config.biteStyle && innerContainerClasses.push(`bite__style--${config.biteStyle}`)
|
|
19
|
-
config.general?.isCompactStyle && innerContainerClasses.push(`component--isCompactStyle`)
|
|
20
|
-
|
|
21
|
-
let contentClasses = ['cove-component__content']
|
|
22
|
-
config.visualizationType === 'Spark Line' && contentClasses.push('sparkline')
|
|
23
|
-
!config.visual?.border && contentClasses.push('no-borders')
|
|
24
|
-
config.visual?.borderColorTheme && contentClasses.push('component--has-borderColorTheme')
|
|
25
|
-
config.visual?.accent && contentClasses.push('component--has-accent')
|
|
26
|
-
config.visual?.background && contentClasses.push('component--has-background')
|
|
27
|
-
config.visual?.hideBackgroundColor && contentClasses.push('component--hideBackgroundColor')
|
|
28
|
-
|
|
29
|
-
let sparkLineStyles = {
|
|
30
|
-
width: '100%',
|
|
31
|
-
height: '100px'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
barBorderClass,
|
|
36
|
-
lineDatapointClass,
|
|
37
|
-
contentClasses,
|
|
38
|
-
innerContainerClasses,
|
|
39
|
-
sparkLineStyles
|
|
40
|
-
}
|
|
41
|
-
}
|