@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.
Files changed (135) hide show
  1. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  2. package/dist/cdcchart.js +44003 -43518
  3. package/examples/feature/__data__/planet-example-data.json +1 -1
  4. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  5. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  6. package/examples/private/DEV-11825.json +573 -0
  7. package/examples/private/DEV-12100.json +1303 -0
  8. package/examples/private/cat-y.json +1235 -0
  9. package/examples/private/data-points.json +228 -0
  10. package/examples/private/height.json +3915 -0
  11. package/examples/private/links.json +569 -0
  12. package/examples/private/na.json +913 -0
  13. package/examples/private/quadrant.txt +30 -0
  14. package/examples/private/test-data.csv +28 -0
  15. package/examples/private/test-forecast.json +5510 -0
  16. package/examples/private/warming-stripe-test.json +2578 -0
  17. package/examples/private/warming-stripes.json +4763 -0
  18. package/examples/tech-adoption-with-links.json +560 -0
  19. package/index.html +16 -140
  20. package/package.json +6 -5
  21. package/preview.html +1616 -0
  22. package/src/CdcChart.tsx +8 -11
  23. package/src/CdcChartComponent.tsx +329 -124
  24. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  25. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  26. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  27. package/src/_stories/Chart.Patterns.stories.tsx +2 -1
  28. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  29. package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
  30. package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
  31. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
  32. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  33. package/src/_stories/Chart.stories.tsx +8 -0
  34. package/src/_stories/ChartAnnotation.stories.tsx +6 -3
  35. package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
  36. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  37. package/src/_stories/ChartBrush.stories.tsx +50 -0
  38. package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
  39. package/src/_stories/ChartEditor.stories.tsx +1 -2
  40. package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
  41. package/src/_stories/_mock/brush_enabled.json +326 -0
  42. package/src/_stories/_mock/brush_mock.json +2 -69
  43. package/src/_stories/_mock/combo.json +451 -0
  44. package/src/_stories/_mock/editor-test-configs.json +376 -0
  45. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  46. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  47. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  48. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  49. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  50. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  51. package/src/_stories/_mock/pie_config.json +257 -62
  52. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  53. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  54. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  55. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  56. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  57. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  58. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -7
  59. package/src/components/AreaChart/index.tsx +1 -2
  60. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  61. package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
  62. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  63. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  64. package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
  65. package/src/components/BarChart/components/context.tsx +1 -0
  66. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  67. package/src/components/BoxPlot/helpers/index.ts +3 -3
  68. package/src/components/Brush/BrushSelector.tsx +1258 -0
  69. package/src/components/Brush/MiniChartPreview.tsx +283 -0
  70. package/src/components/DeviationBar.jsx +9 -7
  71. package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
  72. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  73. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  74. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
  75. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
  76. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
  77. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
  78. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
  79. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  80. package/src/components/EditorPanel/editor-panel.scss +0 -20
  81. package/src/components/EditorPanel/useEditorPermissions.ts +36 -31
  82. package/src/components/Forecasting/Forecasting.tsx +139 -21
  83. package/src/components/Legend/Legend.Component.tsx +16 -9
  84. package/src/components/Legend/Legend.tsx +3 -2
  85. package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
  86. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  87. package/src/components/Legend/helpers/index.ts +10 -6
  88. package/src/components/LineChart/LineChartProps.ts +0 -3
  89. package/src/components/LineChart/helpers.ts +1 -1
  90. package/src/components/LineChart/index.tsx +36 -13
  91. package/src/components/LinearChart.tsx +559 -499
  92. package/src/components/PairedBarChart.jsx +20 -3
  93. package/src/components/Regions/components/Regions.tsx +366 -144
  94. package/src/components/Sankey/types/index.ts +1 -1
  95. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  96. package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -0
  97. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  98. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  99. package/src/components/SmallMultiples/index.ts +2 -0
  100. package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
  101. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  102. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  103. package/src/components/WarmingStripes/index.tsx +3 -0
  104. package/src/data/initial-state.js +16 -2
  105. package/src/helpers/buildForecastPaletteOptions.ts +0 -38
  106. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  107. package/src/helpers/getColorScale.ts +10 -0
  108. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
  109. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  110. package/src/helpers/sizeHelpers.ts +0 -20
  111. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  112. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  113. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  114. package/src/hooks/useScales.ts +98 -34
  115. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  116. package/src/hooks/useTooltip.tsx +91 -25
  117. package/src/scss/DataTable.scss +0 -4
  118. package/src/scss/main.scss +18 -83
  119. package/src/store/chart.actions.ts +2 -0
  120. package/src/store/chart.reducer.ts +4 -0
  121. package/src/test/CdcChart.test.jsx +1 -1
  122. package/src/types/ChartConfig.ts +27 -6
  123. package/src/types/ChartContext.ts +3 -0
  124. package/src/types/Label.ts +1 -0
  125. package/src/utils/analyticsTracking.ts +19 -0
  126. package/LICENSE +0 -201
  127. package/src/_stories/_mock/pie_data.json +0 -218
  128. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  129. package/src/components/Brush/BrushChart.tsx +0 -128
  130. package/src/components/Brush/BrushController.tsx +0 -71
  131. package/src/components/Brush/types.tsx +0 -8
  132. package/src/components/BrushChart.tsx +0 -223
  133. package/src/helpers/sort.ts +0 -7
  134. package/src/hooks/useActiveElement.js +0 -19
  135. 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,8 +0,0 @@
1
- export type BrushRef = {
2
- reset: () => void
3
- state: {
4
- start: { x: number; y: number }
5
- end: { x: number; y: number }
6
- isBrushing: boolean
7
- }
8
- }
@@ -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
@@ -1,7 +0,0 @@
1
- export const sortAsc = (a, b) => {
2
- return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
3
- }
4
-
5
- export const sortDesc = (a, b) => {
6
- return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
7
- }
@@ -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
- }