@cdc/chart 4.23.10 → 4.24.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.js +34606 -32218
- package/examples/feature/bar/additional-column-tooltip.json +446 -0
- package/examples/feature/bar/example-bar-chart.json +1 -46
- package/examples/feature/bar/lollipop.json +156 -0
- package/examples/feature/bar/tall-data.json +98 -0
- package/examples/feature/combo/planet-combo-example-config.json +99 -9
- package/examples/feature/dev-4261.json +399 -0
- package/examples/feature/forest-plot/forest-plot.json +63 -19
- package/examples/feature/forest-plot/{broken.json → linear.json} +77 -23
- package/examples/feature/forest-plot/log.json +26 -0
- package/examples/feature/forest-plot/logarithmic.json +271 -0
- package/examples/feature/line/line-chart-preliminary.json +346 -0
- package/examples/feature/line/line-points.json +340 -0
- package/examples/feature/regions/index.json +462 -0
- package/examples/feature/scatterplot/scatterplot.json +272 -33
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
- package/examples/private/chart-t.json +3740 -0
- package/examples/private/combo.json +369 -0
- package/examples/private/epi-data.csv +13 -0
- package/examples/private/epi-data.json +62 -0
- package/examples/private/epi.json +403 -0
- package/examples/private/occupancy.json +109283 -0
- package/examples/private/prod-line-config.json +401 -0
- package/examples/private/region-data.json +822 -0
- package/examples/private/region-testing.json +312 -0
- package/examples/private/scaling.json +45325 -0
- package/examples/private/testing-data.json +1739 -0
- package/examples/private/testing.json +816 -0
- package/examples/sparkline-multilple.json +846 -0
- package/index.html +12 -8
- package/package.json +3 -3
- package/src/CdcChart.tsx +42 -211
- package/src/ConfigContext.tsx +6 -0
- package/src/_stories/Chart.stories.tsx +188 -0
- package/src/_stories/Chart.tooltip.stories.tsx +305 -0
- package/src/_stories/ChartBrush.stories.tsx +19 -0
- package/src/_stories/ChartEditor.stories.tsx +22 -0
- package/src/_stories/ChartLine.preliminary.tsx +19 -0
- package/src/_stories/ChartSuppress.stories.tsx +19 -0
- package/src/_stories/_mock/brush_mock.json +393 -0
- package/src/_stories/_mock/pie_config.json +191 -0
- package/src/_stories/_mock/pie_data.json +218 -0
- package/src/_stories/_mock/preliminary_mock.json +346 -0
- package/src/_stories/_mock/suppress_mock.json +911 -0
- package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +6 -7
- package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +7 -36
- package/src/components/AreaChart/index.tsx +4 -0
- package/src/components/{BarChart.Horizontal.jsx → BarChart/components/BarChart.Horizontal.tsx} +111 -34
- package/src/components/{BarChart.StackedHorizontal.jsx → BarChart/components/BarChart.StackedHorizontal.tsx} +55 -20
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +106 -0
- package/src/components/{BarChart.Vertical.jsx → BarChart/components/BarChart.Vertical.tsx} +162 -34
- package/src/components/BarChart/components/BarChart.jsx +39 -0
- package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
- package/src/components/BarChart/components/context.tsx +13 -0
- package/src/components/BarChart/index.tsx +3 -0
- package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +1 -1
- package/src/components/BoxPlot/index.tsx +3 -0
- package/src/components/DeviationBar.jsx +4 -3
- package/src/components/{EditorPanel.jsx → EditorPanel/EditorPanel.tsx} +807 -865
- package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +109 -0
- package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panel.ForestPlotSettings.tsx} +190 -220
- package/src/components/EditorPanel/components/Panel.Regions.tsx +168 -0
- package/src/components/{Series.jsx → EditorPanel/components/Panel.Series.tsx} +23 -4
- package/src/components/EditorPanel/components/PanelProps.ts +3 -0
- package/src/components/EditorPanel/components/Panels.tsx +13 -0
- package/src/components/EditorPanel/components/panels.scss +72 -0
- package/src/components/EditorPanel/editor-panel.scss +751 -0
- package/src/components/EditorPanel/index.tsx +3 -0
- package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +50 -5
- package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
- package/src/components/Forecasting/index.tsx +3 -0
- package/src/components/ForestPlot/ForestPlot.tsx +254 -0
- package/src/components/ForestPlot/ForestPlotProps.ts +18 -0
- package/src/components/ForestPlot/index.scss +1 -0
- package/src/components/ForestPlot/index.tsx +3 -0
- package/src/components/Legend/Legend.tsx +347 -0
- package/src/components/Legend/index.tsx +3 -0
- package/src/components/LineChart/LineChartProps.ts +46 -0
- package/src/components/{LineChart.Circle.tsx → LineChart/components/LineChart.Circle.tsx} +36 -30
- package/src/components/LineChart/helpers.ts +45 -0
- package/src/components/LineChart/index.scss +1 -0
- package/src/components/{LineChart.tsx → LineChart/index.tsx} +83 -42
- package/src/components/LinearChart.jsx +125 -82
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +16 -7
- package/src/components/PieChart/index.tsx +3 -0
- package/src/components/Regions/components/Regions.tsx +135 -0
- package/src/components/Regions/index.tsx +3 -0
- package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
- package/src/components/ScatterPlot/index.tsx +3 -0
- package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
- package/src/components/Sparkline/index.tsx +3 -0
- package/src/components/ZoomBrush.tsx +168 -0
- package/src/data/initial-state.js +30 -16
- package/src/helpers/abbreviateNumber.ts +17 -0
- package/src/helpers/computeMarginBottom.ts +55 -0
- package/src/helpers/filterData.ts +18 -0
- package/src/helpers/generateColorsArray.ts +8 -0
- package/src/helpers/getQuartiles.ts +30 -0
- package/src/helpers/handleChartAriaLabels.ts +19 -0
- package/src/helpers/handleLineType.ts +18 -0
- package/src/helpers/lineOptions.ts +18 -0
- package/src/helpers/sort.ts +7 -0
- package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
- package/src/hooks/useBarChart.js +72 -7
- package/src/hooks/useColorScale.ts +50 -0
- package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
- package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
- package/src/hooks/{useScales.js → useScales.ts} +64 -17
- package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +84 -55
- package/src/scss/main.scss +70 -38
- package/src/types/ChartConfig.ts +178 -0
- package/src/types/ChartContext.ts +54 -0
- package/src/types/ForestPlot.ts +53 -0
- package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
- package/src/ConfigContext.jsx +0 -5
- package/src/components/BarChart.StackedVertical.jsx +0 -95
- package/src/components/BarChart.jsx +0 -30
- package/src/components/ForestPlot.jsx +0 -191
- package/src/components/Legend.jsx +0 -277
- package/src/scss/LinearChart.scss +0 -0
- package/src/scss/editor-panel.scss +0 -745
- package/src/scss/legend.scss +0 -206
- package/src/scss/mixins.scss +0 -0
- package/src/scss/variables.scss +0 -1
|
@@ -9,9 +9,10 @@ import { Text } from '@visx/text'
|
|
|
9
9
|
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
10
10
|
|
|
11
11
|
// cove
|
|
12
|
-
import ConfigContext from '
|
|
13
|
-
import { useTooltip as useCoveTooltip } from '
|
|
14
|
-
import useIntersectionObserver from '
|
|
12
|
+
import ConfigContext from '../../ConfigContext'
|
|
13
|
+
import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
|
|
14
|
+
import useIntersectionObserver from '../../hooks/useIntersectionObserver'
|
|
15
|
+
import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
|
|
15
16
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
16
17
|
|
|
17
18
|
const enterUpdateTransition = ({ startAngle, endAngle }) => ({
|
|
@@ -19,9 +20,17 @@ const enterUpdateTransition = ({ startAngle, endAngle }) => ({
|
|
|
19
20
|
endAngle
|
|
20
21
|
})
|
|
21
22
|
|
|
23
|
+
type TooltipData = {
|
|
24
|
+
data: {
|
|
25
|
+
[key: string]: string | number
|
|
26
|
+
}
|
|
27
|
+
dataXPosition: number
|
|
28
|
+
dataYPosition: number
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
const PieChart = props => {
|
|
23
|
-
const { transformedData: data, config, dimensions, seriesHighlight, colorScale,
|
|
24
|
-
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
|
|
32
|
+
const { transformedData: data, config, dimensions, seriesHighlight, colorScale, currentViewport } = useContext(ConfigContext)
|
|
33
|
+
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
|
|
25
34
|
const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
|
|
26
35
|
xScale: false,
|
|
27
36
|
yScale: false,
|
|
@@ -94,7 +103,7 @@ const PieChart = props => {
|
|
|
94
103
|
</Group>
|
|
95
104
|
)
|
|
96
105
|
})}
|
|
97
|
-
{transitions.map(({ item: arc, key }) => {
|
|
106
|
+
{transitions.map(({ item: arc, key }, i) => {
|
|
98
107
|
const [centroidX, centroidY] = path.centroid(arc)
|
|
99
108
|
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
|
|
100
109
|
|
|
@@ -104,7 +113,7 @@ const PieChart = props => {
|
|
|
104
113
|
}
|
|
105
114
|
|
|
106
115
|
return (
|
|
107
|
-
<animated.g key={key}>
|
|
116
|
+
<animated.g key={`${key}${i}`}>
|
|
108
117
|
{hasSpaceForLabel && (
|
|
109
118
|
<Text style={{ fill: textColor }} x={centroidX} y={centroidY} dy='.33em' textAnchor='middle' pointerEvents='none'>
|
|
110
119
|
{Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import { ChartConfig } from '../../../types/ChartConfig'
|
|
3
|
+
import ConfigContext from '../../../ConfigContext'
|
|
4
|
+
import { ChartContext } from '../../../types/ChartContext'
|
|
5
|
+
import { Text } from '@visx/text'
|
|
6
|
+
import { Group } from '@visx/group'
|
|
7
|
+
import * as d3 from 'd3'
|
|
8
|
+
|
|
9
|
+
type RegionsProps = {
|
|
10
|
+
xScale: Function
|
|
11
|
+
barWidth: number
|
|
12
|
+
totalBarsInGroup: number
|
|
13
|
+
yMax: number
|
|
14
|
+
barWidth?: number
|
|
15
|
+
totalBarsInGroup?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleTooltipMouseOff, handleTooltipMouseOver, handleTooltipClick, tooltipData, showTooltip, hideTooltip }: RegionsProps) => {
|
|
19
|
+
const { parseDate, config } = useContext<ChartContext>(ConfigContext)
|
|
20
|
+
|
|
21
|
+
const { runtime, regions, visualizationType, orientation, xAxis } = config
|
|
22
|
+
|
|
23
|
+
let from
|
|
24
|
+
let to
|
|
25
|
+
let width
|
|
26
|
+
|
|
27
|
+
if (regions && orientation === 'vertical') {
|
|
28
|
+
return regions.map(region => {
|
|
29
|
+
if (xAxis.type === 'date' && region.fromType !== 'Previous Days') {
|
|
30
|
+
from = xScale(parseDate(region.from).getTime())
|
|
31
|
+
to = xScale(parseDate(region.to).getTime())
|
|
32
|
+
width = to - from
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (xAxis.type === 'categorical') {
|
|
36
|
+
from = xScale(region.from)
|
|
37
|
+
to = xScale(region.to)
|
|
38
|
+
width = to - from
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && xAxis.type === 'date') {
|
|
42
|
+
from = region.fromType !== 'Previous Days' ? xScale(parseDate(region.from).getTime()) - (barWidth * totalBarsInGroup) / 2 : null
|
|
43
|
+
to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
|
|
44
|
+
|
|
45
|
+
width = to - from
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.xAxis.type === 'categorical') {
|
|
49
|
+
from = xScale(region.from)
|
|
50
|
+
to = xScale(region.to)
|
|
51
|
+
width = to - from
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (region.fromType === 'Previous Days') {
|
|
55
|
+
to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
|
|
56
|
+
|
|
57
|
+
let domain = xScale.domain()
|
|
58
|
+
let bisectDate = d3.bisector(d => d).left
|
|
59
|
+
let closestValue
|
|
60
|
+
|
|
61
|
+
let previousDays = Number(region.from)
|
|
62
|
+
let lastDate = region.toType === 'Last Date' ? domain[domain.length - 1] : region.to
|
|
63
|
+
let fromDate = new Date(lastDate)
|
|
64
|
+
|
|
65
|
+
from = new Date(fromDate.setDate(fromDate.getDate() - previousDays)).getTime()
|
|
66
|
+
let targetValue = from
|
|
67
|
+
|
|
68
|
+
let index = bisectDate(domain, targetValue)
|
|
69
|
+
if (index === 0) {
|
|
70
|
+
closestValue = domain[0]
|
|
71
|
+
} else if (index === domain.length) {
|
|
72
|
+
closestValue = domain[domain.length - 1]
|
|
73
|
+
} else {
|
|
74
|
+
let d0 = domain[index - 1]
|
|
75
|
+
let d1 = domain[index]
|
|
76
|
+
closestValue = targetValue - d0 > d1 - targetValue ? d1 : d0
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
from = Number(xScale(closestValue) - (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
|
|
80
|
+
|
|
81
|
+
width = to - from
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// set the region max to the charts max range.
|
|
85
|
+
if (region.toType === 'Last Date') {
|
|
86
|
+
let domainValues = xScale.domain()
|
|
87
|
+
let lastDate = domainValues[domainValues.length - 1]
|
|
88
|
+
to = Number(xScale(lastDate) + (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
|
|
89
|
+
width = to - from
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!from) return null
|
|
93
|
+
if (!to) return null
|
|
94
|
+
|
|
95
|
+
const TopRegionBorderShape = () => {
|
|
96
|
+
return (
|
|
97
|
+
<path
|
|
98
|
+
stroke='#333'
|
|
99
|
+
d={`M${from} -5
|
|
100
|
+
L${from} 5
|
|
101
|
+
M${from} 0
|
|
102
|
+
L${to} 0
|
|
103
|
+
M${to} -5
|
|
104
|
+
L${to} 5`}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const HighlightedArea = () => {
|
|
110
|
+
return <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Group
|
|
115
|
+
className='regions regions-group--line'
|
|
116
|
+
left={config.visualizationType === 'Bar' || config.visualizationType === 'Combo' ? 0 : config?.visualizationType === 'Line' ? Number(runtime.yAxis.size) : 0}
|
|
117
|
+
key={region.label}
|
|
118
|
+
onMouseMove={handleTooltipMouseOver}
|
|
119
|
+
onMouseLeave={handleTooltipMouseOff}
|
|
120
|
+
handleTooltipClick={handleTooltipClick}
|
|
121
|
+
tooltipData={JSON.stringify(tooltipData)}
|
|
122
|
+
showTooltip={showTooltip}
|
|
123
|
+
>
|
|
124
|
+
<TopRegionBorderShape />
|
|
125
|
+
<HighlightedArea />
|
|
126
|
+
<Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
|
|
127
|
+
{region.label}
|
|
128
|
+
</Text>
|
|
129
|
+
</Group>
|
|
130
|
+
)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default Regions
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
|
-
import ConfigContext from '
|
|
2
|
+
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
import { Group } from '@visx/group'
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const ScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
|
|
6
6
|
const { colorScale, transformedData: data, config, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
|
|
7
7
|
|
|
8
8
|
// TODO: copied from line chart should probably be a constant somewhere.
|
|
@@ -48,4 +48,4 @@ const CoveScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
|
|
|
48
48
|
</Group>
|
|
49
49
|
)
|
|
50
50
|
}
|
|
51
|
-
export default
|
|
51
|
+
export default ScatterPlot
|
|
@@ -11,9 +11,9 @@ import { MarkerArrow } from '@visx/marker'
|
|
|
11
11
|
|
|
12
12
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
13
13
|
|
|
14
|
-
import useReduceData from '
|
|
14
|
+
import useReduceData from '../../hooks/useReduceData'
|
|
15
15
|
|
|
16
|
-
import ConfigContext from '
|
|
16
|
+
import ConfigContext from '../../ConfigContext'
|
|
17
17
|
|
|
18
18
|
const SparkLine = props => {
|
|
19
19
|
const { width: parentWidth, height: parentHeight } = props
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Brush } from '@visx/brush'
|
|
2
|
+
import { Group } from '@visx/group'
|
|
3
|
+
import { Text } from '@visx/text'
|
|
4
|
+
import { useBarChart } from '../hooks/useBarChart'
|
|
5
|
+
import { FC, useContext, useEffect, useRef, useState } from 'react'
|
|
6
|
+
import ConfigContext from '../ConfigContext'
|
|
7
|
+
import { ScaleLinear, ScaleBand } from 'd3-scale'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
xScaleBrush: ScaleLinear<number, number>
|
|
11
|
+
yScale: ScaleBand<string>
|
|
12
|
+
xMax: number
|
|
13
|
+
yMax: number
|
|
14
|
+
}
|
|
15
|
+
const ZoomBrush: FC<Props> = props => {
|
|
16
|
+
const { transformedData: data, config, parseDate, formatDate, updateConfig } = useContext(ConfigContext)
|
|
17
|
+
const { fontSize } = useBarChart()
|
|
18
|
+
|
|
19
|
+
const [filteredData, setFilteredData] = useState([...data])
|
|
20
|
+
const brushRef = useRef(null)
|
|
21
|
+
const radius = 15
|
|
22
|
+
|
|
23
|
+
const [textProps, setTextProps] = useState({
|
|
24
|
+
startPosition: 0,
|
|
25
|
+
endPosition: 0,
|
|
26
|
+
startValue: '',
|
|
27
|
+
endValue: ''
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const initialPosition = {
|
|
31
|
+
start: { x: 0 },
|
|
32
|
+
end: { x: props.xMax }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const style = {
|
|
36
|
+
fill: '#ddd',
|
|
37
|
+
stroke: 'blue',
|
|
38
|
+
fillOpacity: 0.8,
|
|
39
|
+
strokeOpacity: 0,
|
|
40
|
+
rx: radius
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const onBrushChange = event => {
|
|
44
|
+
if (!event) return
|
|
45
|
+
|
|
46
|
+
const { xValues } = event
|
|
47
|
+
|
|
48
|
+
const dataKey = config.xAxis?.dataKey
|
|
49
|
+
|
|
50
|
+
const filteredData = data.filter(item => xValues.includes(item[dataKey]))
|
|
51
|
+
|
|
52
|
+
const endValue = xValues
|
|
53
|
+
.slice()
|
|
54
|
+
.reverse()
|
|
55
|
+
.find(item => item !== undefined)
|
|
56
|
+
const startValue = xValues.find(item => item !== undefined)
|
|
57
|
+
|
|
58
|
+
const formatIfDate = value => (config.runtime.xAxis.type === 'date' ? formatDate(parseDate(value)) : value)
|
|
59
|
+
|
|
60
|
+
setTextProps(prev => ({
|
|
61
|
+
...prev,
|
|
62
|
+
startPosition: brushRef.current?.state.start.x,
|
|
63
|
+
endPosition: brushRef.current?.state.end.x,
|
|
64
|
+
endValue: formatIfDate(endValue),
|
|
65
|
+
startValue: formatIfDate(startValue)
|
|
66
|
+
}))
|
|
67
|
+
|
|
68
|
+
setFilteredData(filteredData)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
updateConfig({
|
|
73
|
+
...config,
|
|
74
|
+
brush: {
|
|
75
|
+
...config.brush,
|
|
76
|
+
data: filteredData
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}, [filteredData])
|
|
80
|
+
|
|
81
|
+
//reset filters if brush is off
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!config.brush.active) {
|
|
84
|
+
setFilteredData(data)
|
|
85
|
+
}
|
|
86
|
+
}, [config.brush.active])
|
|
87
|
+
|
|
88
|
+
const calculateTop = (): number => {
|
|
89
|
+
const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
|
|
90
|
+
let top = 0
|
|
91
|
+
const offSet = 20
|
|
92
|
+
if (!config.xAxis.label) {
|
|
93
|
+
if (!config.isResponsiveTicks && tickRotation) {
|
|
94
|
+
top = Number(tickRotation + config.xAxis.tickWidthMax) / 1.6
|
|
95
|
+
}
|
|
96
|
+
if (!config.isResponsiveTicks && !tickRotation) {
|
|
97
|
+
top = Number(config.xAxis.labelOffset) - offSet
|
|
98
|
+
}
|
|
99
|
+
if (config.isResponsiveTicks && config.dynamicMarginTop) {
|
|
100
|
+
top = Number(config.xAxis.labelOffset + config.xAxis.tickWidthMax / 1.6)
|
|
101
|
+
}
|
|
102
|
+
if (config.isResponsiveTicks && !config.dynamicMarginTop) {
|
|
103
|
+
top = Number(config.xAxis.labelOffset - offSet)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (config.xAxis.label) {
|
|
107
|
+
if (!config.isResponsiveTicks && tickRotation) {
|
|
108
|
+
top = Number(config.xAxis.tickWidthMax + tickRotation)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!config.isResponsiveTicks && !tickRotation) {
|
|
112
|
+
top = config.xAxis.labelOffset + offSet
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (config.isResponsiveTicks && !tickRotation) {
|
|
116
|
+
top = Number(config.dynamicMarginTop ? config.dynamicMarginTop : config.xAxis.labelOffset) + offSet
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return top
|
|
121
|
+
}
|
|
122
|
+
if (!['Line', 'Bar', 'Area Chart', 'Combo'].includes(config.visualizationType)) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
return (
|
|
126
|
+
<Group display={config.brush.active ? 'block' : 'none'} top={Number(props.yMax) + calculateTop()} left={Number(config.runtime.yAxis.size)} pointerEvents='fill'>
|
|
127
|
+
<rect fill='#eee' width={props.xMax} height={config.brush.height} rx={radius} />
|
|
128
|
+
<Brush
|
|
129
|
+
renderBrushHandle={props => <BrushHandle textProps={textProps} fontSize={fontSize[config.fontSize]} {...props} isBrushing={brushRef.current?.state.isBrushing} />}
|
|
130
|
+
innerRef={brushRef}
|
|
131
|
+
useWindowMoveEvents={true}
|
|
132
|
+
selectedBoxStyle={style}
|
|
133
|
+
xScale={props.xScaleBrush}
|
|
134
|
+
yScale={props.yScale}
|
|
135
|
+
width={props.xMax}
|
|
136
|
+
resizeTriggerAreas={['left', 'right']}
|
|
137
|
+
height={config.brush.height}
|
|
138
|
+
handleSize={8}
|
|
139
|
+
brushDirection='horizontal'
|
|
140
|
+
initialBrushPosition={initialPosition}
|
|
141
|
+
onChange={onBrushChange}
|
|
142
|
+
/>
|
|
143
|
+
</Group>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const BrushHandle = props => {
|
|
148
|
+
const { x, isBrushActive, isBrushing, className, textProps } = props
|
|
149
|
+
const pathWidth = 8
|
|
150
|
+
if (!isBrushActive) {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
// Flip the SVG path horizontally for the left handle
|
|
154
|
+
const isLeft = className.includes('left')
|
|
155
|
+
const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
|
|
156
|
+
const textAnchor = isLeft ? 'end' : 'start'
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<Group left={x + pathWidth / 2} top={-2}>
|
|
160
|
+
<Text pointerEvents='visiblePainted' dominantBaseline='hanging' x={0} verticalAnchor='start' textAnchor={textAnchor} fontSize={props.fontSize / 1.4} dy={10} y={15}>
|
|
161
|
+
{isLeft ? textProps.startValue : textProps.endValue}
|
|
162
|
+
</Text>
|
|
163
|
+
<path cursor='ew-resize' d='M0.5,10A6,6 0 0 1 6.5,16V14A6,6 0 0 1 0.5,20ZM2.5,18V12M4.5,18V12' fill={!isBrushing ? '#666' : '#297EF1'} strokeWidth='1' transform={transform}></path>
|
|
164
|
+
</Group>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default ZoomBrush
|
|
@@ -7,11 +7,11 @@ export default {
|
|
|
7
7
|
title: '',
|
|
8
8
|
showTitle: true,
|
|
9
9
|
showDownloadMediaButton: false,
|
|
10
|
-
showChartBrush: false,
|
|
11
10
|
theme: 'theme-blue',
|
|
12
11
|
animate: false,
|
|
13
12
|
fontSize: 'medium',
|
|
14
13
|
lineDatapointStyle: 'hover',
|
|
14
|
+
lineDatapointColor: 'Same as Line',
|
|
15
15
|
barHasBorder: 'false',
|
|
16
16
|
isLollipopChart: false,
|
|
17
17
|
lollipopShape: 'circle',
|
|
@@ -28,6 +28,9 @@ export default {
|
|
|
28
28
|
left: 5,
|
|
29
29
|
right: 5
|
|
30
30
|
},
|
|
31
|
+
suppressedData: [],
|
|
32
|
+
preliminaryData: [],
|
|
33
|
+
|
|
31
34
|
yAxis: {
|
|
32
35
|
hideAxis: false,
|
|
33
36
|
displayNumbersOnBar: false,
|
|
@@ -134,8 +137,9 @@ export default {
|
|
|
134
137
|
// start with a blank list
|
|
135
138
|
},
|
|
136
139
|
legend: {
|
|
140
|
+
hide: false,
|
|
137
141
|
behavior: 'isolate',
|
|
138
|
-
singleRow:
|
|
142
|
+
singleRow: true,
|
|
139
143
|
colorCode: '',
|
|
140
144
|
reverseLabelOrder: false,
|
|
141
145
|
description: '',
|
|
@@ -145,7 +149,13 @@ export default {
|
|
|
145
149
|
dynamicLegendItemLimitMessage: 'Dynamic Legend Item Limit Hit.',
|
|
146
150
|
dynamicLegendChartMessage: 'Select Options from the Legend',
|
|
147
151
|
lineMode: false,
|
|
148
|
-
verticalSorted: false
|
|
152
|
+
verticalSorted: false,
|
|
153
|
+
highlightOnHover: false
|
|
154
|
+
},
|
|
155
|
+
brush: {
|
|
156
|
+
height: 25,
|
|
157
|
+
data: [],
|
|
158
|
+
active: false
|
|
149
159
|
},
|
|
150
160
|
exclusions: {
|
|
151
161
|
active: false,
|
|
@@ -180,22 +190,27 @@ export default {
|
|
|
180
190
|
highlightedBarValues: [],
|
|
181
191
|
series: [],
|
|
182
192
|
tooltips: {
|
|
183
|
-
opacity: 90
|
|
193
|
+
opacity: 90,
|
|
194
|
+
singleSeries: false
|
|
184
195
|
},
|
|
185
196
|
forestPlot: {
|
|
186
197
|
startAt: 0,
|
|
187
|
-
width: 'auto',
|
|
188
198
|
colors: {
|
|
189
199
|
line: '',
|
|
190
200
|
shape: ''
|
|
191
201
|
},
|
|
202
|
+
lineOfNoEffect: {
|
|
203
|
+
show: true
|
|
204
|
+
},
|
|
205
|
+
type: '',
|
|
206
|
+
pooledResult: {
|
|
207
|
+
diamondHeight: 5,
|
|
208
|
+
column: ''
|
|
209
|
+
},
|
|
192
210
|
estimateField: '',
|
|
193
211
|
estimateRadius: '',
|
|
194
|
-
|
|
195
|
-
upperCiField: '',
|
|
196
|
-
shape: '',
|
|
212
|
+
shape: 'square',
|
|
197
213
|
rowHeight: 20,
|
|
198
|
-
showZeroLine: false,
|
|
199
214
|
description: {
|
|
200
215
|
show: true,
|
|
201
216
|
text: 'description',
|
|
@@ -207,8 +222,8 @@ export default {
|
|
|
207
222
|
location: 100
|
|
208
223
|
},
|
|
209
224
|
radius: {
|
|
210
|
-
min:
|
|
211
|
-
max:
|
|
225
|
+
min: 2,
|
|
226
|
+
max: 10,
|
|
212
227
|
scalingColumn: ''
|
|
213
228
|
},
|
|
214
229
|
regression: {
|
|
@@ -217,11 +232,10 @@ export default {
|
|
|
217
232
|
estimateField: 0
|
|
218
233
|
},
|
|
219
234
|
leftWidthOffset: 0,
|
|
220
|
-
rightWidthOffset: 0
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
accent_color: '#ddd'
|
|
235
|
+
rightWidthOffset: 0,
|
|
236
|
+
showZeroLine: false,
|
|
237
|
+
leftLabel: '',
|
|
238
|
+
rightLabel: ''
|
|
225
239
|
},
|
|
226
240
|
area: {
|
|
227
241
|
isStacked: false
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const abbreviateNumber = num => {
|
|
2
|
+
let unit = ''
|
|
3
|
+
let absNum = Math.abs(num)
|
|
4
|
+
|
|
5
|
+
if (absNum >= 1e9) {
|
|
6
|
+
unit = 'B'
|
|
7
|
+
num = num / 1e9
|
|
8
|
+
} else if (absNum >= 1e6) {
|
|
9
|
+
unit = 'M'
|
|
10
|
+
num = num / 1e6
|
|
11
|
+
} else if (absNum >= 1e3) {
|
|
12
|
+
unit = 'K'
|
|
13
|
+
num = num / 1e3
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return num + unit
|
|
17
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ChartConfig, Legend } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export const computeMarginBottom = (config: ChartConfig, legend: Legend, currentViewport: string): string | number => {
|
|
4
|
+
const isLegendBottom = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
|
|
5
|
+
const isHorizontal = config.orientation === 'horizontal'
|
|
6
|
+
const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
|
|
7
|
+
const isBrush = config.brush.active
|
|
8
|
+
const offset = 20
|
|
9
|
+
const brushHeight = config.brush.height
|
|
10
|
+
let bottom = 0
|
|
11
|
+
if (!isLegendBottom && isHorizontal && !config.yAxis.label) {
|
|
12
|
+
bottom = Number(config.xAxis.labelOffset)
|
|
13
|
+
}
|
|
14
|
+
if (!isLegendBottom && isHorizontal && config.yAxis.label && !config.isResponsiveTicks) {
|
|
15
|
+
bottom = Number(config.runtime.xAxis.size) + Number(config.xAxis.labelOffset)
|
|
16
|
+
}
|
|
17
|
+
if (!isLegendBottom && isHorizontal && config.yAxis.label && config.isResponsiveTicks) {
|
|
18
|
+
bottom = config.dynamicMarginTop + offset
|
|
19
|
+
}
|
|
20
|
+
if (!isLegendBottom && isHorizontal && !config.yAxis.label && config.isResponsiveTicks) {
|
|
21
|
+
bottom = config.dynamicMarginTop ? config.dynamicMarginTop - offset : Number(config.xAxis.labelOffset) - offset
|
|
22
|
+
}
|
|
23
|
+
if (!isLegendBottom && isHorizontal && config.yAxis.label && config.isResponsiveTicks) {
|
|
24
|
+
bottom = config.dynamicMarginTop ? config.dynamicMarginTop + offset : Number(config.xAxis.labelOffset)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!isHorizontal && !isLegendBottom && config.xAxis.label && tickRotation && !config.isResponsiveTicks) {
|
|
28
|
+
bottom = isBrush ? brushHeight + config.xAxis.tickWidthMax + -config.xAxis.size + config.xAxis.labelOffset + offset : config.xAxis.tickWidthMax + offset + -config.xAxis.size + config.xAxis.labelOffset
|
|
29
|
+
}
|
|
30
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && tickRotation && !config.isResponsiveTicks) {
|
|
31
|
+
}
|
|
32
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && tickRotation && !config.dynamicMarginTop && !config.isResponsiveTicks) {
|
|
33
|
+
bottom = isBrush ? config.xAxis.tickWidthMax + brushHeight + offset + -config.xAxis.size : 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!isHorizontal && !isLegendBottom && config.xAxis.label && !tickRotation && !config.isResponsiveTicks) {
|
|
37
|
+
bottom = isBrush ? brushHeight + -config.xAxis.size + config.xAxis.labelOffset + offset : -config.xAxis.size + config.xAxis.labelOffset + offset
|
|
38
|
+
}
|
|
39
|
+
if (!isHorizontal && !isLegendBottom && config.xAxis.label && config.dynamicMarginTop && config.isResponsiveTicks) {
|
|
40
|
+
bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + config.xAxis.tickWidthMax : config.dynamicMarginTop + -config.xAxis.size + offset
|
|
41
|
+
}
|
|
42
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && config.dynamicMarginTop && config.isResponsiveTicks) {
|
|
43
|
+
bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + config.xAxis.tickWidthMax : config.dynamicMarginTop + -config.xAxis.size - offset
|
|
44
|
+
}
|
|
45
|
+
if (!isHorizontal && !isLegendBottom && config.xAxis.label && !config.dynamicMarginTop && config.isResponsiveTicks) {
|
|
46
|
+
bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + 25 : config.xAxis.labelOffset + -config.xAxis.size + offset
|
|
47
|
+
}
|
|
48
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && !config.dynamicMarginTop && config.isResponsiveTicks) {
|
|
49
|
+
bottom = -config.xAxis.size + offset + config.xAxis.labelOffset
|
|
50
|
+
}
|
|
51
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && !tickRotation && !config.dynamicMarginTop && !config.isResponsiveTicks) {
|
|
52
|
+
bottom = isBrush ? brushHeight + -config.xAxis.size + config.xAxis.labelOffset : 0
|
|
53
|
+
}
|
|
54
|
+
return `${bottom}px`
|
|
55
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const filterData = (filters, data) => {
|
|
2
|
+
let filteredData: any[] = []
|
|
3
|
+
|
|
4
|
+
data.forEach(row => {
|
|
5
|
+
let add = true
|
|
6
|
+
filters
|
|
7
|
+
.filter(filter => filter.type !== 'url')
|
|
8
|
+
.forEach(filter => {
|
|
9
|
+
if (row[filter.columnName] != filter.active) {
|
|
10
|
+
add = false
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
if (add) filteredData.push(row)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return filteredData
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import chroma from 'chroma-js'
|
|
2
|
+
|
|
3
|
+
export const generateColorsArray = (color = '#000000', special = false) => {
|
|
4
|
+
let colorObj = chroma(color)
|
|
5
|
+
let hoverColor = special ? colorObj.brighten(0.5).hex() : colorObj.saturate(1.3).hex()
|
|
6
|
+
|
|
7
|
+
return [color, hoverColor, colorObj.darken(0.3).hex()]
|
|
8
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the first quartile (q1) and third quartile (q3) from an array of integers or decimals.
|
|
3
|
+
*
|
|
4
|
+
* @param {Array} arr - The array of integers or decimals.
|
|
5
|
+
* @returns {Object} An object containing the q1 and q3 values.
|
|
6
|
+
*/
|
|
7
|
+
export const getQuartiles = arr => {
|
|
8
|
+
arr.sort((a, b) => a - b)
|
|
9
|
+
|
|
10
|
+
// Calculate the index of the median value of the array
|
|
11
|
+
const medianIndex = Math.floor(arr.length / 2)
|
|
12
|
+
|
|
13
|
+
// Check if the length of the array is even or odd
|
|
14
|
+
const isEvenLength = arr.length % 2 === 0
|
|
15
|
+
|
|
16
|
+
// Split the array into two subarrays based on the median index
|
|
17
|
+
const q1Array = isEvenLength ? arr.slice(0, medianIndex) : arr.slice(0, medianIndex + 1)
|
|
18
|
+
const q3Array = isEvenLength ? arr.slice(medianIndex) : arr.slice(medianIndex + 1)
|
|
19
|
+
|
|
20
|
+
// Calculate the median of the first subarray to get the q1 value
|
|
21
|
+
const q1Index = Math.floor(q1Array.length / 2)
|
|
22
|
+
const q1 = isEvenLength ? (q1Array[q1Index - 1] + q1Array[q1Index]) / 2 : q1Array[q1Index]
|
|
23
|
+
|
|
24
|
+
// Calculate the median of the second subarray to get the q3 value
|
|
25
|
+
const q3Index = Math.floor(q3Array.length / 2)
|
|
26
|
+
const q3 = isEvenLength ? (q3Array[q3Index - 1] + q3Array[q3Index]) / 2 : q3Array[q3Index]
|
|
27
|
+
|
|
28
|
+
// Return an object containing the q1 and q3 values
|
|
29
|
+
return { q1, q3 }
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const handleChartAriaLabels = (state, testing = false) => {
|
|
2
|
+
if (testing) console.log(`handleChartAriaLabels Testing On:`, state) // eslint-disable-line
|
|
3
|
+
try {
|
|
4
|
+
if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
|
|
5
|
+
let ariaLabel = ''
|
|
6
|
+
|
|
7
|
+
if (state.visualizationType) {
|
|
8
|
+
ariaLabel += `${state.visualizationType} chart`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (state.title && state.visualizationType) {
|
|
12
|
+
ariaLabel += ` with the title: ${state.title}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return ariaLabel
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.error('COVE: ', e.message) // eslint-disable-line
|
|
18
|
+
}
|
|
19
|
+
}
|