@cdc/chart 4.25.7 → 4.25.8
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 +26921 -26667
- package/package.json +3 -3
- package/src/CdcChart.tsx +9 -2
- package/src/CdcChartComponent.tsx +17 -3
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +11 -5
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
- package/src/components/BarChart/components/BarChart.Vertical.tsx +21 -4
- package/src/components/Brush/BrushChart.tsx +65 -10
- package/src/components/Brush/BrushController.tsx +37 -5
- package/src/components/Brush/types.tsx +8 -0
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -34
- package/src/components/Legend/Legend.Component.tsx +16 -1
- package/src/components/Legend/Legend.tsx +3 -1
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +27 -26
- package/src/components/LinearChart.tsx +4 -4
- package/src/data/initial-state.js +11 -10
- package/src/hooks/useTooltip.tsx +1 -1
- package/src/index.jsx +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/chart",
|
|
3
|
-
"version": "4.25.
|
|
3
|
+
"version": "4.25.8",
|
|
4
4
|
"description": "React component for visualizing tabular data in various types of charts",
|
|
5
5
|
"moduleName": "CdcChart",
|
|
6
6
|
"main": "dist/cdcchart",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"license": "Apache-2.0",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@cdc/core": "^4.25.
|
|
29
|
+
"@cdc/core": "^4.25.8",
|
|
30
30
|
"@hello-pangea/dnd": "^16.2.0",
|
|
31
31
|
"@react-spring/web": "^9.7.5",
|
|
32
32
|
"@visx/axis": "3.12.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"react": "^18.2.0",
|
|
55
55
|
"react-dom": "^18.2.0"
|
|
56
56
|
},
|
|
57
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "e369994230b5e3facff224e1d89d5937528ac5a0",
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/d3-array": "^3.2.1",
|
|
60
60
|
"@types/d3-format": "^3.0.4",
|
package/src/CdcChart.tsx
CHANGED
|
@@ -14,9 +14,16 @@ interface CdcChartProps {
|
|
|
14
14
|
isEditor?: boolean
|
|
15
15
|
isDebug?: boolean
|
|
16
16
|
config?: ChartConfig
|
|
17
|
+
interactionLabel?: string
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const CdcChartWrapper: React.FC<CdcChartProps> = ({
|
|
20
|
+
const CdcChartWrapper: React.FC<CdcChartProps> = ({
|
|
21
|
+
configUrl,
|
|
22
|
+
isEditor,
|
|
23
|
+
isDebug,
|
|
24
|
+
config: editorsConfig,
|
|
25
|
+
interactionLabel = ''
|
|
26
|
+
}) => {
|
|
20
27
|
const editorContext = useContext(EditorContext)
|
|
21
28
|
const [config, _setConfig] = useState<ChartConfig>({} as ChartConfig)
|
|
22
29
|
const setConfig = newConfig => {
|
|
@@ -89,7 +96,7 @@ const CdcChartWrapper: React.FC<CdcChartProps> = ({ configUrl, isEditor, isDebug
|
|
|
89
96
|
|
|
90
97
|
if (isLoading) return <Loading />
|
|
91
98
|
|
|
92
|
-
return <CdcChart config={config} isEditor={isEditor} isDebug={isDebug} />
|
|
99
|
+
return <CdcChart config={config} isEditor={isEditor} isDebug={isDebug} interactionLabel={interactionLabel} />
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
export default CdcChartWrapper
|
|
@@ -82,6 +82,7 @@ import { VizFilter } from '@cdc/core/types/VizFilter'
|
|
|
82
82
|
import { getNewRuntime } from './helpers/getNewRuntime'
|
|
83
83
|
import FootnotesStandAlone from '@cdc/core/components/Footnotes/FootnotesStandAlone'
|
|
84
84
|
import { Datasets } from '@cdc/core/types/DataSet'
|
|
85
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
85
86
|
|
|
86
87
|
interface CdcChartProps {
|
|
87
88
|
config?: ChartConfig
|
|
@@ -96,6 +97,7 @@ interface CdcChartProps {
|
|
|
96
97
|
setSharedFilterValue?: (value: any) => void
|
|
97
98
|
dashboardConfig?: DashboardConfig
|
|
98
99
|
datasets?: Datasets
|
|
100
|
+
interactionLabel: string
|
|
99
101
|
}
|
|
100
102
|
const CdcChart: React.FC<CdcChartProps> = ({
|
|
101
103
|
config: configObj,
|
|
@@ -108,7 +110,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
108
110
|
setSharedFilter,
|
|
109
111
|
setSharedFilterValue,
|
|
110
112
|
dashboardConfig,
|
|
111
|
-
datasets
|
|
113
|
+
datasets,
|
|
114
|
+
interactionLabel
|
|
112
115
|
}) => {
|
|
113
116
|
const transform = new DataTransform()
|
|
114
117
|
const initialState = getInitialState(configObj)
|
|
@@ -465,6 +468,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
465
468
|
if (container && !isLoading && !_.isEmpty(config) && !coveLoadedEventRan) {
|
|
466
469
|
publish('cove_loaded', { config: config })
|
|
467
470
|
dispatch({ type: 'SET_LOADED_EVENT', payload: true })
|
|
471
|
+
publishAnalyticsEvent('chart_loaded', 'load', interactionLabel, 'chart')
|
|
468
472
|
}
|
|
469
473
|
}, [container, config, isLoading]) // eslint-disable-line
|
|
470
474
|
|
|
@@ -554,6 +558,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
554
558
|
} catch (e) {
|
|
555
559
|
console.error('COVE:', e.message)
|
|
556
560
|
}
|
|
561
|
+
publishAnalyticsEvent('chart_legend_reset', 'click', interactionLabel, 'chart')
|
|
557
562
|
dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: [] })
|
|
558
563
|
}
|
|
559
564
|
|
|
@@ -907,6 +912,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
907
912
|
setFilters={setFilters}
|
|
908
913
|
excludedData={excludedData}
|
|
909
914
|
dimensions={dimensions}
|
|
915
|
+
interactionLabel={interactionLabel}
|
|
910
916
|
/>
|
|
911
917
|
)}
|
|
912
918
|
<SkipTo skipId={handleChartTabbing(config, legendId)} skipMessage='Skip Over Chart Container' />
|
|
@@ -985,6 +991,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
985
991
|
setFilters={setFilters}
|
|
986
992
|
excludedData={excludedData}
|
|
987
993
|
dimensions={dimensions}
|
|
994
|
+
interactionLabel={interactionLabel}
|
|
988
995
|
/>
|
|
989
996
|
{config?.introText && (
|
|
990
997
|
<section className='introText mb-4' style={{ padding: '0px 0 35px' }}>
|
|
@@ -1012,7 +1019,11 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1012
1019
|
{!config.legend.hide &&
|
|
1013
1020
|
config.visualizationType !== 'Spark Line' &&
|
|
1014
1021
|
config.visualizationType !== 'Sankey' && (
|
|
1015
|
-
<Legend
|
|
1022
|
+
<Legend
|
|
1023
|
+
ref={legendRef}
|
|
1024
|
+
skipId={handleChartTabbing(config, legendId)}
|
|
1025
|
+
interactionLabel={interactionLabel}
|
|
1026
|
+
/>
|
|
1016
1027
|
)}
|
|
1017
1028
|
</LegendWrapper>
|
|
1018
1029
|
{/* Link */}
|
|
@@ -1034,6 +1045,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1034
1045
|
type='image'
|
|
1035
1046
|
state={config}
|
|
1036
1047
|
elementToCapture={imageId}
|
|
1048
|
+
interactionLabel={interactionLabel}
|
|
1037
1049
|
/>
|
|
1038
1050
|
)}
|
|
1039
1051
|
{config.table.showDownloadPdfButton && (
|
|
@@ -1043,6 +1055,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1043
1055
|
type='pdf'
|
|
1044
1056
|
state={config}
|
|
1045
1057
|
elementToCapture={imageId}
|
|
1058
|
+
interactionLabel={interactionLabel}
|
|
1046
1059
|
/>
|
|
1047
1060
|
)}
|
|
1048
1061
|
</MediaControls.Section>
|
|
@@ -1054,7 +1067,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1054
1067
|
(config.visualizationType === 'Sankey' && config.table.show)) && (
|
|
1055
1068
|
<DataTable
|
|
1056
1069
|
/* changing the "key" will force the table to re-render
|
|
1057
|
-
|
|
1070
|
+
when the default sort changes while editing */
|
|
1058
1071
|
key={dataTableDefaultSortBy}
|
|
1059
1072
|
config={pivotDynamicSeries(config)}
|
|
1060
1073
|
rawData={
|
|
@@ -1076,6 +1089,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
|
|
|
1076
1089
|
viewport={currentViewport}
|
|
1077
1090
|
tabbingId={handleChartTabbing(config, legendId)}
|
|
1078
1091
|
colorScale={colorScale}
|
|
1092
|
+
interactionLabel={interactionLabel}
|
|
1079
1093
|
/>
|
|
1080
1094
|
)}
|
|
1081
1095
|
{config?.annotations?.length > 0 && <Annotation.Dropdown />}
|
|
@@ -115,9 +115,15 @@ export const BarChartHorizontal = () => {
|
|
|
115
115
|
numbericBarHeight = 25
|
|
116
116
|
}
|
|
117
117
|
let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(scaleVal)
|
|
118
|
-
|
|
118
|
+
let defaultBarWidth = Math.abs(xScale(bar.value) - xScale(scaleVal))
|
|
119
119
|
const isPositiveBar = bar.value >= 0 && isNumber(bar.value)
|
|
120
120
|
|
|
121
|
+
const MINIMUM_BAR_HEIGHT = 3
|
|
122
|
+
if (isPositiveBar && barGroup.bars.length === 1 && defaultBarWidth < MINIMUM_BAR_HEIGHT) {
|
|
123
|
+
defaultBarWidth = MINIMUM_BAR_HEIGHT
|
|
124
|
+
barY = yScale(0) - MINIMUM_BAR_HEIGHT
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(scaleVal)
|
|
122
128
|
const yAxisValue = formatNumber(bar.value, 'left')
|
|
123
129
|
const xAxisValue =
|
|
@@ -166,7 +172,7 @@ export const BarChartHorizontal = () => {
|
|
|
166
172
|
</li></ul>`
|
|
167
173
|
|
|
168
174
|
// configure colors
|
|
169
|
-
let labelColor =
|
|
175
|
+
let labelColor = APP_FONT_COLOR
|
|
170
176
|
labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
|
|
171
177
|
let barColor =
|
|
172
178
|
config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key]
|
|
@@ -184,7 +190,7 @@ export const BarChartHorizontal = () => {
|
|
|
184
190
|
const borderColor = isHighlightedBar
|
|
185
191
|
? highlightedBarColor
|
|
186
192
|
: config.barHasBorder === 'true'
|
|
187
|
-
?
|
|
193
|
+
? APP_FONT_COLOR
|
|
188
194
|
: 'transparent'
|
|
189
195
|
const borderWidth = isHighlightedBar
|
|
190
196
|
? highlightedBar.borderWidth
|
|
@@ -298,7 +304,7 @@ export const BarChartHorizontal = () => {
|
|
|
298
304
|
: pd.symbol === 'Double Asterisk'
|
|
299
305
|
? barHeight
|
|
300
306
|
: barHeight / 1.5
|
|
301
|
-
const fillColor = pd.displayGray ? '#8b8b8a' :
|
|
307
|
+
const fillColor = pd.displayGray ? '#8b8b8a' : APP_FONT_COLOR
|
|
302
308
|
return (
|
|
303
309
|
<Text // prettier-ignore
|
|
304
310
|
key={index}
|
|
@@ -366,7 +372,7 @@ export const BarChartHorizontal = () => {
|
|
|
366
372
|
display={displayBar ? 'block' : 'none'}
|
|
367
373
|
x={bar.y}
|
|
368
374
|
y={0}
|
|
369
|
-
fill={
|
|
375
|
+
fill={APP_FONT_COLOR}
|
|
370
376
|
dx={textPaddingLollipop}
|
|
371
377
|
textAnchor={textAnchorLollipop}
|
|
372
378
|
verticalAnchor='middle'
|
|
@@ -137,7 +137,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
137
137
|
<Text
|
|
138
138
|
x={`${bar.x + (config.isLollipopChart ? 15 : 5)}`} // padding
|
|
139
139
|
y={bar.y + bar.height * 1.2}
|
|
140
|
-
fill={
|
|
140
|
+
fill={APP_FONT_COLOR}
|
|
141
141
|
textAnchor='start'
|
|
142
142
|
verticalAnchor='start'
|
|
143
143
|
>
|
|
@@ -16,6 +16,7 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
|
16
16
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
17
17
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
18
18
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
19
|
+
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
19
20
|
// Third party libraries
|
|
20
21
|
import chroma from 'chroma-js'
|
|
21
22
|
// Types
|
|
@@ -45,6 +46,7 @@ export const BarChartVertical = () => {
|
|
|
45
46
|
const {
|
|
46
47
|
colorScale,
|
|
47
48
|
config,
|
|
49
|
+
currentViewport,
|
|
48
50
|
dashboardConfig,
|
|
49
51
|
tableData,
|
|
50
52
|
formatDate,
|
|
@@ -123,8 +125,20 @@ export const BarChartVertical = () => {
|
|
|
123
125
|
seriesHighlight.indexOf(bar.key) !== -1
|
|
124
126
|
|
|
125
127
|
let barGroupWidth = seriesScale.range()[1] - seriesScale.range()[0]
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
let defaultBarHeight = Math.abs(yScale(bar.value) - yScale(scaleVal))
|
|
129
|
+
let defaultBarY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
|
|
130
|
+
|
|
131
|
+
const MINIMUM_BAR_HEIGHT = 3
|
|
132
|
+
if (
|
|
133
|
+
bar.value >= 0 &&
|
|
134
|
+
isNumber(bar.value) &&
|
|
135
|
+
barGroup.bars.length === 1 &&
|
|
136
|
+
defaultBarHeight < MINIMUM_BAR_HEIGHT
|
|
137
|
+
) {
|
|
138
|
+
defaultBarHeight = MINIMUM_BAR_HEIGHT
|
|
139
|
+
defaultBarY = yScale(0) - MINIMUM_BAR_HEIGHT
|
|
140
|
+
}
|
|
141
|
+
|
|
128
142
|
let barWidth = config.isLollipopChart ? lollipopBarWidth : seriesScale.bandwidth()
|
|
129
143
|
let barX =
|
|
130
144
|
bar.x +
|
|
@@ -159,7 +173,7 @@ export const BarChartVertical = () => {
|
|
|
159
173
|
yAxisValue
|
|
160
174
|
})
|
|
161
175
|
// configure colors
|
|
162
|
-
let labelColor =
|
|
176
|
+
let labelColor = APP_FONT_COLOR
|
|
163
177
|
labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
|
|
164
178
|
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
165
179
|
const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
|
|
@@ -254,6 +268,8 @@ export const BarChartVertical = () => {
|
|
|
254
268
|
|
|
255
269
|
const BAR_LABEL_PADDING = 10
|
|
256
270
|
|
|
271
|
+
const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
|
|
272
|
+
|
|
257
273
|
return (
|
|
258
274
|
<Group display={hideGroup} key={`${barGroup.index}--${index}`}>
|
|
259
275
|
<Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
|
|
@@ -353,6 +369,7 @@ export const BarChartVertical = () => {
|
|
|
353
369
|
y={barY - BAR_LABEL_PADDING}
|
|
354
370
|
fill={labelColor}
|
|
355
371
|
textAnchor='middle'
|
|
372
|
+
fontSize={LABEL_FONT_SIZE}
|
|
356
373
|
>
|
|
357
374
|
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
358
375
|
</Text>
|
|
@@ -363,7 +380,7 @@ export const BarChartVertical = () => {
|
|
|
363
380
|
y={barY - BAR_LABEL_PADDING}
|
|
364
381
|
fill={labelColor}
|
|
365
382
|
textAnchor='middle'
|
|
366
|
-
fontSize={config.isLollipopChart ? null :
|
|
383
|
+
fontSize={config.isLollipopChart ? null : LABEL_FONT_SIZE}
|
|
367
384
|
>
|
|
368
385
|
{absentDataLabel}
|
|
369
386
|
</Text>
|
|
@@ -6,28 +6,36 @@ import ConfigContext from '../../ConfigContext'
|
|
|
6
6
|
import { Text } from '@visx/text'
|
|
7
7
|
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
8
8
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
9
|
-
|
|
10
|
-
export interface ZoomBrushProps {
|
|
9
|
+
export interface BrushChartProps {
|
|
11
10
|
xMax: number
|
|
12
11
|
yMax: number
|
|
13
12
|
brushPosition: { start: { x: number }; end: { x: number } }
|
|
14
13
|
onBrushChange: (bounds: any) => void
|
|
15
14
|
brushKey: number
|
|
15
|
+
brushHandleProps: { startValue: string; endValue: string; endPos: number; startPos: number }
|
|
16
|
+
brushRef: React.RefObject<BrushRef>
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
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)
|
|
21
29
|
const dataKey = config.xAxis.dataKey
|
|
22
30
|
const borderRadius = 15
|
|
23
31
|
const mappedDates: string[] = tableData.map(row => row[dataKey])
|
|
24
32
|
const brushheight = 25
|
|
25
33
|
const DASHBOARD_MARGIN = 50
|
|
26
34
|
const BRUSH_HEIGHT_MULTIPLIER = 1.5
|
|
27
|
-
|
|
35
|
+
const range = config?.xAxis?.sortByRecentDate ? [xMax, 0] : [0, xMax]
|
|
28
36
|
const xScale = scaleBand<string>({
|
|
29
|
-
domain: mappedDates,
|
|
30
|
-
range:
|
|
37
|
+
domain: config?.xAxis?.sortByRecentDate ? mappedDates.reverse() : mappedDates,
|
|
38
|
+
range: range,
|
|
31
39
|
paddingInner: 0.1,
|
|
32
40
|
paddingOuter: 0.1
|
|
33
41
|
})
|
|
@@ -52,6 +60,16 @@ const ZoomBrush: FC<ZoomBrushProps> = ({ xMax, yMax, brushPosition, onBrushChang
|
|
|
52
60
|
<Group left={config.yAxis.size} top={calculateGroupTop()}>
|
|
53
61
|
<rect fill='#949494' width={xMax} height={25} rx={borderRadius} pointerEvents='none' />
|
|
54
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
|
+
)}
|
|
55
73
|
innerRef={brushRef}
|
|
56
74
|
key={brushKey}
|
|
57
75
|
xScale={xScale}
|
|
@@ -64,10 +82,47 @@ const ZoomBrush: FC<ZoomBrushProps> = ({ xMax, yMax, brushPosition, onBrushChang
|
|
|
64
82
|
initialBrushPosition={brushPosition}
|
|
65
83
|
selectedBoxStyle={style}
|
|
66
84
|
onChange={onBrushChange}
|
|
67
|
-
|
|
85
|
+
useWindowMoveEvents={true}
|
|
68
86
|
/>
|
|
69
87
|
</Group>
|
|
70
88
|
)
|
|
71
89
|
}
|
|
72
90
|
|
|
73
|
-
export default
|
|
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,21 +1,51 @@
|
|
|
1
|
-
import { useState, useEffect, useContext } from 'react'
|
|
1
|
+
import { useState, useEffect, useContext, useRef } from 'react'
|
|
2
2
|
import ConfigContext, { ChartDispatchContext } from '../../ConfigContext'
|
|
3
|
-
import
|
|
3
|
+
import BrushChart from './BrushChart'
|
|
4
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
5
|
+
import { BrushRef } from './types'
|
|
6
|
+
|
|
4
7
|
const BrushController = ({ yMax, xMax }) => {
|
|
5
|
-
const { tableData, config, parseDate, dashboardConfig } = useContext(ConfigContext)
|
|
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
|
+
})
|
|
6
16
|
const dataKey = config.xAxis.dataKey
|
|
7
17
|
const [brushKey, setBrushKey] = useState(0)
|
|
8
18
|
const dispatch = useContext(ChartDispatchContext)
|
|
9
19
|
const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
|
|
10
20
|
const isDashboardFilters = sharedFilters?.length > 0
|
|
21
|
+
const brushRef = useRef<BrushRef | null>(null)
|
|
22
|
+
|
|
11
23
|
const [brushPosition, setBrushPosition] = useState({
|
|
12
24
|
start: { x: 0 },
|
|
13
25
|
end: { x: xMax }
|
|
14
26
|
})
|
|
15
27
|
|
|
16
28
|
const handleBrushChange = (bounds: any) => {
|
|
17
|
-
|
|
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
|
+
|
|
18
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
|
+
}))
|
|
19
49
|
dispatch({ type: 'SET_BRUSH_DATA', payload: filteredData })
|
|
20
50
|
}
|
|
21
51
|
|
|
@@ -27,7 +57,9 @@ const BrushController = ({ yMax, xMax }) => {
|
|
|
27
57
|
}, [config.filters, config.exclusions, config.brush?.active, isDashboardFilters])
|
|
28
58
|
|
|
29
59
|
return (
|
|
30
|
-
<
|
|
60
|
+
<BrushChart
|
|
61
|
+
brushRef={brushRef}
|
|
62
|
+
brushHandleProps={brushHandleProps}
|
|
31
63
|
xMax={xMax}
|
|
32
64
|
yMax={yMax}
|
|
33
65
|
brushPosition={brushPosition}
|
|
@@ -67,8 +67,8 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
67
67
|
config.xAxis.type === 'date'
|
|
68
68
|
? new Date(config?.data?.[0]?.[config.xAxis.dataKey]).getTime()
|
|
69
69
|
: config.xAxis.type === 'categorical'
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
? '1/15/2016'
|
|
71
|
+
: '',
|
|
72
72
|
yKey: '',
|
|
73
73
|
dx: 20,
|
|
74
74
|
dy: -20,
|
|
@@ -281,6 +281,7 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
281
281
|
const { config, updateConfig } = useContext(ConfigContext)
|
|
282
282
|
const { series, index } = props
|
|
283
283
|
const { getColumns } = useContext(SeriesContext)
|
|
284
|
+
|
|
284
285
|
if (series.type !== 'Forecasting') return
|
|
285
286
|
|
|
286
287
|
return (
|
|
@@ -289,18 +290,6 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
289
290
|
<fieldset>
|
|
290
291
|
<Accordion allowZeroExpanded>
|
|
291
292
|
{series?.confidenceIntervals?.map((ciGroup, ciIndex) => {
|
|
292
|
-
const showInTooltip = ciGroup.showInTooltip ? ciGroup.showInTooltip : false
|
|
293
|
-
|
|
294
|
-
const updateShowInTooltip = (e, seriesIndex, ciIndex) => {
|
|
295
|
-
e.preventDefault()
|
|
296
|
-
let copiedSeries = [...config.series]
|
|
297
|
-
copiedSeries[seriesIndex].confidenceIntervals[ciIndex].showInTooltip = !showInTooltip
|
|
298
|
-
updateConfig({
|
|
299
|
-
...config,
|
|
300
|
-
series: copiedSeries
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
|
|
304
293
|
return (
|
|
305
294
|
<AccordionItem className='series-item series-item--chart' key={`${ciIndex}`}>
|
|
306
295
|
<AccordionItemHeading className='series-item__title'>
|
|
@@ -312,6 +301,7 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
312
301
|
onClick={e => {
|
|
313
302
|
e.preventDefault()
|
|
314
303
|
const copiedIndex = [...config.series[index].confidenceIntervals]
|
|
304
|
+
|
|
315
305
|
copiedIndex.splice(ciIndex, 1)
|
|
316
306
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
317
307
|
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: [...copiedIndex] }
|
|
@@ -327,28 +317,6 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
327
317
|
</>
|
|
328
318
|
</AccordionItemHeading>
|
|
329
319
|
<AccordionItemPanel>
|
|
330
|
-
<div className='input-group'>
|
|
331
|
-
<label htmlFor='showInTooltip'>Show In Tooltip</label>
|
|
332
|
-
<div
|
|
333
|
-
className={'cove-input__checkbox--small'}
|
|
334
|
-
onClick={e => updateShowInTooltip(e, index, ciIndex)}
|
|
335
|
-
>
|
|
336
|
-
<div
|
|
337
|
-
className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`}
|
|
338
|
-
style={{ backgroundColor: '' }}
|
|
339
|
-
>
|
|
340
|
-
{showInTooltip && <Check className='' style={{ fill: '#025eaa' }} />}
|
|
341
|
-
</div>
|
|
342
|
-
<input
|
|
343
|
-
className='cove-input--hidden'
|
|
344
|
-
type='checkbox'
|
|
345
|
-
name={'showInTooltip'}
|
|
346
|
-
checked={showInTooltip ? showInTooltip : false}
|
|
347
|
-
readOnly
|
|
348
|
-
/>
|
|
349
|
-
</div>
|
|
350
|
-
</div>
|
|
351
|
-
|
|
352
320
|
<InputSelect
|
|
353
321
|
initial='Select an option'
|
|
354
322
|
value={
|
|
@@ -17,6 +17,7 @@ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
|
17
17
|
import LegendLineShape from './LegendLine.Shape'
|
|
18
18
|
import LegendGroup from './LegendGroup'
|
|
19
19
|
import { getSeriesWithData } from '../../helpers/dataHelpers'
|
|
20
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
20
21
|
|
|
21
22
|
const LEGEND_PADDING = 36
|
|
22
23
|
|
|
@@ -32,6 +33,7 @@ export interface LegendProps {
|
|
|
32
33
|
skipId: string
|
|
33
34
|
dimensions: DimensionsType // for responsive width legend
|
|
34
35
|
transformedData: any
|
|
36
|
+
interactionLabel: string
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
@@ -47,7 +49,8 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
47
49
|
formatLabels,
|
|
48
50
|
skipId = 'legend',
|
|
49
51
|
dimensions,
|
|
50
|
-
transformedData: data
|
|
52
|
+
transformedData: data,
|
|
53
|
+
interactionLabel = ''
|
|
51
54
|
},
|
|
52
55
|
ref
|
|
53
56
|
) => {
|
|
@@ -137,11 +140,23 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
137
140
|
onKeyDown={e => {
|
|
138
141
|
if (e.key === 'Enter') {
|
|
139
142
|
e.preventDefault()
|
|
143
|
+
publishAnalyticsEvent(
|
|
144
|
+
`chart_legend_item_toggled--${legend.behavior}-mode`,
|
|
145
|
+
'keydown',
|
|
146
|
+
`${interactionLabel}|${label.text}`,
|
|
147
|
+
'chart'
|
|
148
|
+
)
|
|
140
149
|
highlight(label)
|
|
141
150
|
}
|
|
142
151
|
}}
|
|
143
152
|
onClick={e => {
|
|
144
153
|
e.preventDefault()
|
|
154
|
+
publishAnalyticsEvent(
|
|
155
|
+
`chart_legend_item_toggled--${legend.behavior}-mode`,
|
|
156
|
+
'click',
|
|
157
|
+
`${interactionLabel}|${label.text}`,
|
|
158
|
+
'chart'
|
|
159
|
+
)
|
|
145
160
|
highlight(label)
|
|
146
161
|
}}
|
|
147
162
|
role='button'
|
|
@@ -21,7 +21,8 @@ const Legend = forwardRef((props, ref) => {
|
|
|
21
21
|
transformedData
|
|
22
22
|
} = useContext(ConfigContext)
|
|
23
23
|
if (!config.legend) return null
|
|
24
|
-
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default
|
|
24
|
+
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default
|
|
25
|
+
const { interactionLabel } = props
|
|
25
26
|
|
|
26
27
|
const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
|
|
27
28
|
|
|
@@ -40,6 +41,7 @@ const Legend = forwardRef((props, ref) => {
|
|
|
40
41
|
handleShowAll={handleShowAll}
|
|
41
42
|
currentViewport={currentViewport}
|
|
42
43
|
formatLabels={createLegendLabels}
|
|
44
|
+
interactionLabel={interactionLabel}
|
|
43
45
|
/>
|
|
44
46
|
</Fragment>
|
|
45
47
|
)
|