@cdc/chart 4.24.4 → 4.24.7
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 +39611 -36038
- package/examples/feature/annotations/index.json +542 -0
- package/examples/xaxis.json +493 -0
- package/index.html +9 -8
- package/package.json +5 -4
- package/src/CdcChart.tsx +115 -71
- package/src/_stories/Chart.stories.tsx +26 -171
- package/src/_stories/ChartAnnotation.stories.tsx +32 -0
- package/src/_stories/_mock/annotation_category_mock.json +473 -0
- package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
- package/src/_stories/_mock/annotation_date-time_mock.json +530 -0
- package/src/_stories/_mock/bar-chart-suppressed.json +474 -0
- package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
- package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
- package/src/_stories/_mock/lollipop.json +171 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +154 -0
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
- package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
- package/src/components/Annotations/components/AnnotationList.tsx +42 -0
- package/src/components/Annotations/components/findNearestDatum.ts +138 -0
- package/src/components/Annotations/components/helpers/index.tsx +46 -0
- package/src/components/Annotations/index.tsx +13 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +78 -71
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -2
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -11
- package/src/components/BarChart/components/BarChart.Vertical.tsx +100 -87
- package/src/components/BarChart/helpers/index.ts +102 -0
- package/src/components/DeviationBar.jsx +4 -2
- package/src/components/EditorPanel/EditorPanel.tsx +435 -613
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +306 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +135 -7
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +2 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/components/panels.scss +4 -0
- package/src/components/EditorPanel/editor-panel.scss +19 -0
- package/src/components/EditorPanel/useEditorPermissions.js +23 -3
- package/src/components/Legend/Legend.Component.tsx +66 -15
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +5 -0
- package/src/components/LineChart/LineChartProps.ts +16 -6
- package/src/components/LineChart/components/LineChart.Circle.tsx +22 -11
- package/src/components/LineChart/helpers.ts +148 -10
- package/src/components/LineChart/index.tsx +71 -44
- package/src/components/LinearChart.jsx +184 -125
- package/src/components/PairedBarChart.jsx +9 -9
- package/src/components/PieChart/PieChart.tsx +4 -4
- package/src/components/Sankey/index.tsx +73 -20
- package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
- package/src/components/ZoomBrush.tsx +120 -55
- package/src/data/initial-state.js +14 -6
- package/src/helpers/handleChartTabbing.ts +8 -0
- package/src/helpers/isConvertLineToBarGraph.ts +4 -0
- package/src/hooks/{useBarChart.js → useBarChart.ts} +9 -22
- package/src/hooks/useColorScale.ts +1 -1
- package/src/hooks/useMinMax.ts +29 -5
- package/src/hooks/useScales.ts +48 -26
- package/src/hooks/useTooltip.tsx +62 -15
- package/src/scss/main.scss +69 -12
- package/src/types/ChartConfig.ts +53 -16
- package/src/types/ChartContext.ts +13 -0
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- package/src/_stories/ChartLine.preliminary.tsx +0 -19
- package/src/_stories/ChartSuppress.stories.tsx +0 -19
- package/src/_stories/_mock/suppress_mock.json +0 -911
- package/src/helpers/computeMarginBottom.ts +0 -56
- package/src/helpers/filterData.ts +0 -18
- package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
- /package/src/hooks/{useLegendClasses.js → useLegendClasses.ts} +0 -0
- /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
|
@@ -13,13 +13,16 @@ import 'react-tooltip/dist/react-tooltip.css'
|
|
|
13
13
|
import ConfigContext from '@cdc/chart/src/ConfigContext'
|
|
14
14
|
import { ChartContext } from '../../types/ChartContext'
|
|
15
15
|
import type { SankeyNode, SankeyProps } from './types'
|
|
16
|
+
import { SankeyChartConfig, AllChartsConfig } from '../../types/ChartConfig'
|
|
16
17
|
|
|
17
18
|
const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
18
|
-
const DEBUG = true
|
|
19
19
|
const { config } = useContext<ChartContext>(ConfigContext)
|
|
20
20
|
const { sankey: sankeyConfig } = config
|
|
21
|
-
|
|
22
|
-
const
|
|
21
|
+
|
|
22
|
+
const isSankeyChartConfig = (config: AllChartsConfig | SankeyChartConfig): config is SankeyChartConfig => {
|
|
23
|
+
return config.visualizationType === 'Sankey'
|
|
24
|
+
}
|
|
25
|
+
|
|
23
26
|
const [largestGroupWidth, setLargestGroupWidth] = useState(0)
|
|
24
27
|
const groupRefs = useRef([])
|
|
25
28
|
|
|
@@ -60,6 +63,9 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
60
63
|
setLargestGroupWidth(largest)
|
|
61
64
|
}, [groupRefs, sankeyConfig, window.innerWidth])
|
|
62
65
|
|
|
66
|
+
if (!isSankeyChartConfig(config)) return
|
|
67
|
+
const data = config?.data[0]
|
|
68
|
+
|
|
63
69
|
//Retrieve all the unique values for the Nodes
|
|
64
70
|
const uniqueNodes = Array.from(new Set(data?.links?.flatMap(link => [link.source, link.target])))
|
|
65
71
|
|
|
@@ -109,6 +115,8 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
const activeConnection = (id: String) => {
|
|
118
|
+
if (!sankeyData?.nodes) return { sourceNodes: [], activeLinks: [] }
|
|
119
|
+
|
|
112
120
|
const currentNode = sankeyData.nodes.find(node => node.id === id)
|
|
113
121
|
|
|
114
122
|
const sourceNodes = []
|
|
@@ -137,12 +145,12 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
137
145
|
return { sourceNodes, activeLinks }
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
const tooltipVal = `${(data?.tooltips
|
|
141
|
-
const tooltipSummary = `${(data?.tooltips
|
|
142
|
-
const tooltipColumn1Label = (data?.tooltips
|
|
143
|
-
const tooltipColumn2Label = (data?.tooltips
|
|
144
|
-
const tooltipColumn1 = (data?.tooltips
|
|
145
|
-
const tooltipColumn2 = (data?.tooltips
|
|
148
|
+
const tooltipVal = `${(data?.tooltips?.find(item => item.node === tooltipID) || {}).value}`
|
|
149
|
+
const tooltipSummary = `${(data?.tooltips?.find(item => item.node === tooltipID) || {}).summary}`
|
|
150
|
+
const tooltipColumn1Label = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column1Label
|
|
151
|
+
const tooltipColumn2Label = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column2Label
|
|
152
|
+
const tooltipColumn1 = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column1
|
|
153
|
+
const tooltipColumn2 = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column2
|
|
146
154
|
|
|
147
155
|
const ColumnList = ({ columnData }) => {
|
|
148
156
|
return (
|
|
@@ -229,12 +237,27 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
229
237
|
*/
|
|
230
238
|
fill={sankeyConfig.nodeFontColor}
|
|
231
239
|
fontWeight='bold' // font weight
|
|
232
|
-
style={{ pointerEvents: 'none' }}
|
|
233
240
|
className='node-text'
|
|
241
|
+
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
242
|
+
onClick={() => handleNodeClick(node.id)}
|
|
243
|
+
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
244
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
234
245
|
>
|
|
235
246
|
{(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
|
|
236
247
|
</Text>
|
|
237
|
-
<Text
|
|
248
|
+
<Text
|
|
249
|
+
verticalAnchor='end'
|
|
250
|
+
className={classStyle}
|
|
251
|
+
x={node.x0! + textPositionHorizontal}
|
|
252
|
+
y={(node.y1! + node.y0! + 25) / 2}
|
|
253
|
+
fill={sankeyConfig.storyNodeFontColor || sankeyConfig.nodeFontColor}
|
|
254
|
+
fontWeight='bold'
|
|
255
|
+
textAnchor='start'
|
|
256
|
+
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
257
|
+
onClick={() => handleNodeClick(node.id)}
|
|
258
|
+
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
259
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
260
|
+
>
|
|
238
261
|
{typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
|
|
239
262
|
</Text>
|
|
240
263
|
<Text
|
|
@@ -244,20 +267,32 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
244
267
|
fill={sankeyConfig.nodeFontColor}
|
|
245
268
|
fontWeight='bold'
|
|
246
269
|
textAnchor={sankeyData.nodes.length === i ? 'end' : 'start'}
|
|
247
|
-
style={{ pointerEvents: 'none' }}
|
|
248
270
|
className='node-text'
|
|
249
271
|
verticalAnchor='end'
|
|
272
|
+
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
273
|
+
onClick={() => handleNodeClick(node.id)}
|
|
274
|
+
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
275
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
250
276
|
>
|
|
251
277
|
{(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
|
|
252
278
|
</Text>
|
|
253
279
|
</>
|
|
254
280
|
) : (
|
|
255
281
|
<>
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
282
|
+
<Text
|
|
283
|
+
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
284
|
+
onClick={() => handleNodeClick(node.id)}
|
|
285
|
+
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
286
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
287
|
+
x={node.x0! + textPositionHorizontal}
|
|
288
|
+
y={(node.y1! + node.y0!) / 2 + textPositionVertical}
|
|
289
|
+
dominantBaseline='text-before-edge'
|
|
290
|
+
fill={sankeyConfig.nodeFontColor}
|
|
291
|
+
fontWeight='bold'
|
|
292
|
+
textAnchor='start'
|
|
293
|
+
>
|
|
294
|
+
{node.id}
|
|
295
|
+
</Text>
|
|
261
296
|
<text
|
|
262
297
|
x={node.x0! + textPositionHorizontal}
|
|
263
298
|
/* adding 30 allows the node value to be on the next line underneath the node id */
|
|
@@ -267,7 +302,10 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
267
302
|
//fontSize={16}
|
|
268
303
|
fontWeight='bold'
|
|
269
304
|
textAnchor='start'
|
|
270
|
-
style={{ pointerEvents: '
|
|
305
|
+
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
306
|
+
onClick={() => handleNodeClick(node.id)}
|
|
307
|
+
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
308
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
271
309
|
>
|
|
272
310
|
<tspan className={classStyle}>{sankeyConfig.nodeValueStyle.textBefore + (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) + sankeyConfig.nodeValueStyle.textAfter}</tspan>
|
|
273
311
|
</text>
|
|
@@ -291,7 +329,20 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
291
329
|
opacityValue = sankeyConfig.opacity.LinkOpacityInactive
|
|
292
330
|
}
|
|
293
331
|
|
|
294
|
-
return
|
|
332
|
+
return (
|
|
333
|
+
<path
|
|
334
|
+
key={i}
|
|
335
|
+
d={path!}
|
|
336
|
+
stroke={strokeColor}
|
|
337
|
+
fill='none'
|
|
338
|
+
strokeOpacity={opacityValue}
|
|
339
|
+
strokeWidth={link.width! + 2}
|
|
340
|
+
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
341
|
+
onClick={() => handleNodeClick(link.target.id || null)}
|
|
342
|
+
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
343
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
344
|
+
/>
|
|
345
|
+
)
|
|
295
346
|
})
|
|
296
347
|
|
|
297
348
|
// max depth - calculates how many nodes deep the chart goes.
|
|
@@ -393,7 +444,9 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
393
444
|
textAnchor='start'
|
|
394
445
|
style={{ pointerEvents: 'none' }}
|
|
395
446
|
>
|
|
396
|
-
<tspan
|
|
447
|
+
<tspan onClick={() => handleNodeClick(node.id)} className={classStyle}>
|
|
448
|
+
{sankeyConfig.nodeValueStyle.textBefore + (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) + sankeyConfig.nodeValueStyle.textAfter}
|
|
449
|
+
</tspan>
|
|
397
450
|
</text>
|
|
398
451
|
</>
|
|
399
452
|
)}
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
import { Group } from '@visx/group'
|
|
4
|
+
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
4
5
|
|
|
5
|
-
const ScatterPlot = ({ xScale, yScale
|
|
6
|
-
const {
|
|
6
|
+
const ScatterPlot = ({ xScale, yScale }) => {
|
|
7
|
+
const { transformedData: data, config, tableData, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
|
|
7
8
|
|
|
8
9
|
// TODO: copied from line chart should probably be a constant somewhere.
|
|
9
|
-
|
|
10
|
+
const circleRadii = 4.5
|
|
10
11
|
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
11
|
-
|
|
12
|
-
const
|
|
12
|
+
// tooltips for additional columns
|
|
13
|
+
const additionalColumns = Object.entries(config.columns)
|
|
14
|
+
.filter(([_, value]) => value.tooltips)
|
|
15
|
+
.map(([_, value]) => [
|
|
16
|
+
value.label || value.name,
|
|
17
|
+
value.name,
|
|
18
|
+
{
|
|
19
|
+
addColPrefix: value.prefix,
|
|
20
|
+
addColSuffix: value.suffix,
|
|
21
|
+
addColRoundTo: value.roundToPlace,
|
|
22
|
+
addColCommas: value.commas
|
|
23
|
+
}
|
|
24
|
+
])
|
|
25
|
+
const handleTooltip = (item, s, dataIndex) => `<div>
|
|
13
26
|
${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[s] || ''}<br/>` : ''}
|
|
14
27
|
${config.xAxis.label}: ${formatNumber(item[config.xAxis.dataKey], 'bottom')} <br/>
|
|
15
|
-
${config.yAxis.label}: ${formatNumber(item[s], 'left')}
|
|
28
|
+
${config.yAxis.label}: ${formatNumber(item[s], 'left')}<br/>
|
|
29
|
+
${additionalColumns.map(([label, name, options]) => `${label} : ${formatColNumber(tableData[dataIndex][name], 'left', false, config, options)}<br/>`).join('')}
|
|
16
30
|
</div>`
|
|
17
31
|
|
|
18
32
|
return (
|
|
@@ -37,9 +51,9 @@ const ScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
|
|
|
37
51
|
cx={xScale(item[config.xAxis.dataKey])}
|
|
38
52
|
cy={yScale(item[s])}
|
|
39
53
|
fill={displayArea ? seriesColor : 'transparent'}
|
|
40
|
-
fillOpacity={transparentArea ? .25 : 1}
|
|
54
|
+
fillOpacity={transparentArea ? 0.25 : 1}
|
|
41
55
|
style={pointStyles}
|
|
42
|
-
data-tooltip-html={handleTooltip(item, s)}
|
|
56
|
+
data-tooltip-html={handleTooltip(item, s, dataIndex)}
|
|
43
57
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
44
58
|
tabIndex={-1}
|
|
45
59
|
/>
|
|
@@ -6,6 +6,7 @@ import { FC, useContext, useEffect, useRef, useState } from 'react'
|
|
|
6
6
|
import ConfigContext from '../ConfigContext'
|
|
7
7
|
import { ScaleLinear, ScaleBand } from 'd3-scale'
|
|
8
8
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
9
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
9
10
|
|
|
10
11
|
interface Props {
|
|
11
12
|
xScaleBrush: ScaleLinear<number, number>
|
|
@@ -14,10 +15,12 @@ interface Props {
|
|
|
14
15
|
yMax: number
|
|
15
16
|
}
|
|
16
17
|
const ZoomBrush: FC<Props> = props => {
|
|
17
|
-
const {
|
|
18
|
+
const { tableData, config, parseDate, formatDate, setBrushConfig, getTextWidth, dashboardConfig } = useContext(ConfigContext)
|
|
19
|
+
const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
|
|
20
|
+
const isDashboardFilters = sharedFilters?.length > 0
|
|
18
21
|
const { fontSize } = useBarChart()
|
|
19
|
-
|
|
20
|
-
const [
|
|
22
|
+
const [showTooltip, setShowTooltip] = useState(false)
|
|
23
|
+
const [brushKey, setBrushKey] = useState(0)
|
|
21
24
|
const brushRef = useRef(null)
|
|
22
25
|
const radius = 15
|
|
23
26
|
|
|
@@ -25,16 +28,16 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
25
28
|
startPosition: 0,
|
|
26
29
|
endPosition: 0,
|
|
27
30
|
startValue: '',
|
|
28
|
-
endValue: ''
|
|
31
|
+
endValue: '',
|
|
32
|
+
xMax: props.xMax
|
|
29
33
|
})
|
|
30
34
|
|
|
31
35
|
const initialPosition = {
|
|
32
36
|
start: { x: 0 },
|
|
33
37
|
end: { x: props.xMax }
|
|
34
38
|
}
|
|
35
|
-
|
|
36
39
|
const style = {
|
|
37
|
-
fill: '#
|
|
40
|
+
fill: '#474747',
|
|
38
41
|
stroke: 'blue',
|
|
39
42
|
fillOpacity: 0.8,
|
|
40
43
|
strokeOpacity: 0,
|
|
@@ -42,19 +45,19 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
const onBrushChange = event => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
setShowTooltip(false)
|
|
49
|
+
const filteredValues = event?.xValues?.filter(val => val !== undefined)
|
|
50
|
+
if (filteredValues?.length === 0) return
|
|
48
51
|
|
|
49
52
|
const dataKey = config.xAxis?.dataKey
|
|
50
53
|
|
|
51
|
-
const
|
|
54
|
+
const brushedData = tableData.filter(item => filteredValues.includes(item[dataKey]))
|
|
52
55
|
|
|
53
|
-
const endValue =
|
|
56
|
+
const endValue = filteredValues
|
|
54
57
|
.slice()
|
|
55
58
|
.reverse()
|
|
56
59
|
.find(item => item !== undefined)
|
|
57
|
-
const startValue =
|
|
60
|
+
const startValue = filteredValues.find(item => item !== undefined)
|
|
58
61
|
|
|
59
62
|
const formatIfDate = value => (isDateScale(config.runtime.xAxis) ? formatDate(parseDate(value)) : value)
|
|
60
63
|
|
|
@@ -63,33 +66,58 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
63
66
|
startPosition: brushRef.current?.state.start.x,
|
|
64
67
|
endPosition: brushRef.current?.state.end.x,
|
|
65
68
|
endValue: formatIfDate(endValue),
|
|
66
|
-
startValue: formatIfDate(startValue)
|
|
69
|
+
startValue: formatIfDate(startValue),
|
|
70
|
+
xMax: props.xMax
|
|
67
71
|
}))
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
...config,
|
|
75
|
-
brush: {
|
|
76
|
-
...config.brush,
|
|
77
|
-
data: filteredData
|
|
73
|
+
setBrushConfig(prev => {
|
|
74
|
+
return {
|
|
75
|
+
...prev,
|
|
76
|
+
isBrushing: brushRef.current?.state.isBrushing,
|
|
77
|
+
data: brushedData
|
|
78
78
|
}
|
|
79
79
|
})
|
|
80
|
-
}
|
|
80
|
+
}
|
|
81
|
+
// reset brush if brush is off.
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!config.brush?.active) {
|
|
84
|
+
setBrushKey(prevKey => prevKey + 1)
|
|
85
|
+
setBrushConfig({
|
|
86
|
+
data: [],
|
|
87
|
+
isActive: false,
|
|
88
|
+
isBrushing: false
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}, [config.brush?.active])
|
|
92
|
+
|
|
93
|
+
// reset brush if filters or exclusions are ON each time
|
|
81
94
|
|
|
82
|
-
//reset filters if brush is off
|
|
83
95
|
useEffect(() => {
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
const isFiltersActive = config.filters?.some(filter => filter.active)
|
|
97
|
+
const isExclusionsActive = config.exclusions?.active
|
|
98
|
+
|
|
99
|
+
if ((isFiltersActive || isExclusionsActive || isDashboardFilters) && config.brush?.active) {
|
|
100
|
+
setBrushKey(prevKey => prevKey + 1)
|
|
101
|
+
setBrushConfig(prev => {
|
|
102
|
+
return {
|
|
103
|
+
...prev,
|
|
104
|
+
data: tableData
|
|
105
|
+
}
|
|
106
|
+
})
|
|
86
107
|
}
|
|
87
|
-
|
|
108
|
+
return () =>
|
|
109
|
+
setBrushConfig(prev => {
|
|
110
|
+
return {
|
|
111
|
+
...prev,
|
|
112
|
+
data: []
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}, [config.filters, config.exclusions, config.brush?.active, isDashboardFilters])
|
|
88
116
|
|
|
89
117
|
const calculateTop = (): number => {
|
|
90
118
|
const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
|
|
91
119
|
let top = 0
|
|
92
|
-
const offSet =
|
|
120
|
+
const offSet = 30
|
|
93
121
|
if (!config.xAxis.label) {
|
|
94
122
|
if (!config.isResponsiveTicks && tickRotation) {
|
|
95
123
|
top = Number(tickRotation + config.xAxis.tickWidthMax) / 1.6
|
|
@@ -106,7 +134,7 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
106
134
|
}
|
|
107
135
|
if (config.xAxis.label) {
|
|
108
136
|
if (!config.isResponsiveTicks && tickRotation) {
|
|
109
|
-
top = Number(config.xAxis.tickWidthMax + tickRotation)
|
|
137
|
+
top = Number(config.xAxis.tickWidthMax + tickRotation) + offSet
|
|
110
138
|
}
|
|
111
139
|
|
|
112
140
|
if (!config.isResponsiveTicks && !tickRotation) {
|
|
@@ -114,7 +142,7 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
114
142
|
}
|
|
115
143
|
|
|
116
144
|
if (config.isResponsiveTicks && !tickRotation) {
|
|
117
|
-
top = Number(config.dynamicMarginTop ? config.dynamicMarginTop : config.xAxis.labelOffset) + offSet
|
|
145
|
+
top = Number(config.dynamicMarginTop ? config.dynamicMarginTop : config.xAxis.labelOffset) + offSet * 2
|
|
118
146
|
}
|
|
119
147
|
}
|
|
120
148
|
|
|
@@ -123,30 +151,58 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
123
151
|
if (!['Line', 'Bar', 'Area Chart', 'Combo'].includes(config.visualizationType)) {
|
|
124
152
|
return
|
|
125
153
|
}
|
|
154
|
+
|
|
126
155
|
return (
|
|
127
|
-
<
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
<ErrorBoundary component='Brush Chart'>
|
|
157
|
+
<Group
|
|
158
|
+
onMouseMove={() => {
|
|
159
|
+
// show tooltip only once before brush started
|
|
160
|
+
if (textProps.startPosition === 0 && (textProps.endPosition === 0 || textProps.endPosition === props.xMax)) {
|
|
161
|
+
setShowTooltip(true)
|
|
162
|
+
}
|
|
163
|
+
}}
|
|
164
|
+
onMouseLeave={() => setShowTooltip(false)}
|
|
165
|
+
display={config.brush?.active ? 'block' : 'none'}
|
|
166
|
+
top={Number(props.yMax) + calculateTop()}
|
|
167
|
+
left={Number(config.runtime.yAxis.size)}
|
|
168
|
+
pointerEvents='fill'
|
|
169
|
+
>
|
|
170
|
+
<rect fill='#949494' width={props.xMax} height={config.brush.height} rx={radius} />
|
|
171
|
+
<Brush
|
|
172
|
+
key={brushKey}
|
|
173
|
+
disableDraggingOverlay={true}
|
|
174
|
+
renderBrushHandle={props => (
|
|
175
|
+
<BrushHandle
|
|
176
|
+
left={Number(config.runtime.yAxis.size)}
|
|
177
|
+
showTooltip={showTooltip}
|
|
178
|
+
getTextWidth={getTextWidth}
|
|
179
|
+
pixelDistance={textProps.endPosition - textProps.startPosition}
|
|
180
|
+
textProps={textProps}
|
|
181
|
+
fontSize={fontSize[config.fontSize]}
|
|
182
|
+
{...props}
|
|
183
|
+
isBrushing={brushRef.current?.state.isBrushing}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
186
|
+
innerRef={brushRef}
|
|
187
|
+
useWindowMoveEvents={true}
|
|
188
|
+
selectedBoxStyle={style}
|
|
189
|
+
xScale={props.xScaleBrush}
|
|
190
|
+
yScale={props.yScale}
|
|
191
|
+
width={props.xMax}
|
|
192
|
+
resizeTriggerAreas={['left', 'right']}
|
|
193
|
+
height={config.brush.height}
|
|
194
|
+
handleSize={8}
|
|
195
|
+
brushDirection='horizontal'
|
|
196
|
+
initialBrushPosition={initialPosition}
|
|
197
|
+
onChange={onBrushChange}
|
|
198
|
+
/>
|
|
199
|
+
</Group>
|
|
200
|
+
</ErrorBoundary>
|
|
145
201
|
)
|
|
146
202
|
}
|
|
147
203
|
|
|
148
204
|
const BrushHandle = props => {
|
|
149
|
-
const { x, isBrushActive, isBrushing, className, textProps } = props
|
|
205
|
+
const { x, isBrushActive, isBrushing, className, textProps, fontSize, showTooltip, left, getTextWidth } = props
|
|
150
206
|
const pathWidth = 8
|
|
151
207
|
if (!isBrushActive) {
|
|
152
208
|
return null
|
|
@@ -155,14 +211,23 @@ const BrushHandle = props => {
|
|
|
155
211
|
const isLeft = className.includes('left')
|
|
156
212
|
const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
|
|
157
213
|
const textAnchor = isLeft ? 'end' : 'start'
|
|
214
|
+
const tooltipText = isLeft ? ` Drag edges to focus on a specific segment ` : ''
|
|
215
|
+
const textWidth = getTextWidth(tooltipText, `normal ${fontSize / 1.1}px sans-serif`)
|
|
158
216
|
|
|
159
217
|
return (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
218
|
+
<>
|
|
219
|
+
{showTooltip && (
|
|
220
|
+
<Text x={(Number(textProps.xMax) - textWidth) / 2} dy={-12} pointerEvents='visiblePainted' fontSize={fontSize / 1.1}>
|
|
221
|
+
{tooltipText}
|
|
222
|
+
</Text>
|
|
223
|
+
)}
|
|
224
|
+
<Group left={x + pathWidth / 2} top={-2}>
|
|
225
|
+
<Text pointerEvents='visiblePainted' dominantBaseline='hanging' x={isLeft ? 55 : -50} y={25} verticalAnchor='start' textAnchor={textAnchor} fontSize={fontSize / 1.4}>
|
|
226
|
+
{isLeft ? textProps.startValue : textProps.endValue}
|
|
227
|
+
</Text>
|
|
228
|
+
<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={'#297EF1'} strokeWidth='1' transform={transform}></path>
|
|
229
|
+
</Group>
|
|
230
|
+
</>
|
|
166
231
|
)
|
|
167
232
|
}
|
|
168
233
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
annotations: [],
|
|
3
|
+
allowLineToBarGraph: undefined,
|
|
2
4
|
type: 'chart',
|
|
3
5
|
debugSvg: false,
|
|
4
6
|
chartMessage: {
|
|
@@ -22,15 +24,17 @@ export default {
|
|
|
22
24
|
tipRounding: 'top',
|
|
23
25
|
isResponsiveTicks: false,
|
|
24
26
|
general: {
|
|
25
|
-
|
|
27
|
+
annotationDropdownText: 'Annotations',
|
|
28
|
+
showDownloadButton: false,
|
|
29
|
+
showMissingDataLabel: true,
|
|
30
|
+
showSuppressedSymbol: true,
|
|
31
|
+
showZeroValueDataLabel: true
|
|
26
32
|
},
|
|
27
33
|
padding: {
|
|
28
34
|
left: 5,
|
|
29
35
|
right: 5
|
|
30
36
|
},
|
|
31
|
-
suppressedData: [],
|
|
32
37
|
preliminaryData: [],
|
|
33
|
-
|
|
34
38
|
yAxis: {
|
|
35
39
|
hideAxis: false,
|
|
36
40
|
displayNumbersOnBar: false,
|
|
@@ -55,7 +59,9 @@ export default {
|
|
|
55
59
|
axisPadding: 0,
|
|
56
60
|
scalePadding: 10,
|
|
57
61
|
tickRotation: 0,
|
|
58
|
-
anchors: []
|
|
62
|
+
anchors: [],
|
|
63
|
+
shoMissingDataLabel: true,
|
|
64
|
+
showMissingDataLine: true
|
|
59
65
|
},
|
|
60
66
|
boxplot: {
|
|
61
67
|
plots: [],
|
|
@@ -131,7 +137,9 @@ export default {
|
|
|
131
137
|
indexLabel: '',
|
|
132
138
|
download: false,
|
|
133
139
|
showVertical: true,
|
|
134
|
-
dateDisplayFormat: ''
|
|
140
|
+
dateDisplayFormat: '',
|
|
141
|
+
showMissingDataLabel: true,
|
|
142
|
+
showSuppressedSymbol: true
|
|
135
143
|
},
|
|
136
144
|
orientation: 'vertical',
|
|
137
145
|
color: 'pinkpurple',
|
|
@@ -154,11 +162,11 @@ export default {
|
|
|
154
162
|
lineMode: false,
|
|
155
163
|
verticalSorted: false,
|
|
156
164
|
highlightOnHover: false,
|
|
165
|
+
hideSuppressedLabels: false,
|
|
157
166
|
seriesHighlight: []
|
|
158
167
|
},
|
|
159
168
|
brush: {
|
|
160
169
|
height: 25,
|
|
161
|
-
data: [],
|
|
162
170
|
active: false
|
|
163
171
|
},
|
|
164
172
|
exclusions: {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ChartConfig } from './../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export const handleChartTabbing = (config: ChartConfig, legendId: string) => {
|
|
4
|
+
if (!config) return `dataTableSection`
|
|
5
|
+
if (!config.legend?.hide) return legendId
|
|
6
|
+
if (config?.title) return `dataTableSection__${config.title.replace(/\s/g, '')}`
|
|
7
|
+
return `dataTableSection`
|
|
8
|
+
}
|
|
@@ -29,7 +29,7 @@ export const useBarChart = () => {
|
|
|
29
29
|
updateConfig({
|
|
30
30
|
...config,
|
|
31
31
|
yAxis: {
|
|
32
|
-
...config,
|
|
32
|
+
...config.yAxis,
|
|
33
33
|
labelPlacement: 'Below Bar'
|
|
34
34
|
}
|
|
35
35
|
})
|
|
@@ -164,26 +164,13 @@ export const useBarChart = () => {
|
|
|
164
164
|
if (!match?.color) return false
|
|
165
165
|
return match
|
|
166
166
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
if (barWidth < 10) {
|
|
175
|
-
return 6
|
|
176
|
-
}
|
|
177
|
-
if (barWidth < 15) {
|
|
178
|
-
return 7
|
|
179
|
-
}
|
|
180
|
-
if (barWidth < 20) {
|
|
181
|
-
return 8
|
|
182
|
-
}
|
|
183
|
-
if (barWidth < 90) {
|
|
184
|
-
return 8
|
|
185
|
-
}
|
|
186
|
-
return 0
|
|
167
|
+
|
|
168
|
+
const shouldSuppress = bar => {
|
|
169
|
+
return config.preliminaryData?.some(pd => {
|
|
170
|
+
const matchesColumn = pd.column ? pd.column === bar.key : true
|
|
171
|
+
const matchesValue = String(bar.value) === String(pd.value) && pd.value !== ''
|
|
172
|
+
return matchesColumn && matchesValue && pd.symbol && pd.type === 'suppression'
|
|
173
|
+
})
|
|
187
174
|
}
|
|
188
175
|
|
|
189
176
|
const getAdditionalColumn = (series, xAxisDataValue) => {
|
|
@@ -224,13 +211,13 @@ export const useBarChart = () => {
|
|
|
224
211
|
}
|
|
225
212
|
|
|
226
213
|
return {
|
|
227
|
-
generateIconSize,
|
|
228
214
|
isHorizontal,
|
|
229
215
|
barBorderWidth,
|
|
230
216
|
lollipopBarWidth,
|
|
231
217
|
lollipopShapeSize,
|
|
232
218
|
isLabelBelowBar,
|
|
233
219
|
displayNumbersOnBar,
|
|
220
|
+
shouldSuppress,
|
|
234
221
|
section,
|
|
235
222
|
isRounded,
|
|
236
223
|
isStacked,
|
|
@@ -30,7 +30,7 @@ const useColorScale = () => {
|
|
|
30
30
|
})
|
|
31
31
|
}
|
|
32
32
|
if (visualizationType === 'Bar' && visualizationSubType === 'regular' && series?.length === 1 && legend?.colorCode) {
|
|
33
|
-
const set = new Set(data
|
|
33
|
+
const set = new Set(data?.map(d => d[legend.colorCode]))
|
|
34
34
|
colorScale = scaleOrdinal({
|
|
35
35
|
domain: [...set],
|
|
36
36
|
range: generatePalette([...set].length)
|