@cdc/chart 4.24.5 → 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 +39128 -35959
- package/examples/feature/annotations/index.json +542 -0
- package/examples/xaxis.json +493 -0
- package/index.html +5 -4
- package/package.json +5 -4
- package/src/CdcChart.tsx +104 -64
- package/src/_stories/Chart.stories.tsx +18 -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/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 +1 -1
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +44 -42
- 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 +50 -22
- package/src/components/BarChart/helpers/index.ts +102 -0
- package/src/components/EditorPanel/EditorPanel.tsx +232 -98
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +306 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +117 -6
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +2 -3
- 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 +19 -2
- package/src/components/Legend/Legend.Component.tsx +7 -8
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +5 -0
- package/src/components/LineChart/LineChartProps.ts +3 -0
- package/src/components/LineChart/helpers.ts +21 -7
- package/src/components/LineChart/index.tsx +7 -7
- package/src/components/LinearChart.jsx +179 -136
- 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 +90 -44
- 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} +2 -40
- package/src/hooks/useColorScale.ts +1 -1
- package/src/hooks/useMinMax.ts +8 -3
- package/src/hooks/useScales.ts +25 -3
- package/src/hooks/useTooltip.tsx +58 -11
- package/src/scss/main.scss +21 -14
- package/src/types/ChartConfig.ts +50 -3
- package/src/types/ChartContext.ts +9 -0
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- 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,8 +15,11 @@ interface Props {
|
|
|
14
15
|
yMax: number
|
|
15
16
|
}
|
|
16
17
|
const ZoomBrush: FC<Props> = props => {
|
|
17
|
-
const { tableData, config, parseDate, formatDate, setBrushConfig } = useContext(ConfigContext)
|
|
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()
|
|
22
|
+
const [showTooltip, setShowTooltip] = useState(false)
|
|
19
23
|
const [brushKey, setBrushKey] = useState(0)
|
|
20
24
|
const brushRef = useRef(null)
|
|
21
25
|
const radius = 15
|
|
@@ -24,16 +28,16 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
24
28
|
startPosition: 0,
|
|
25
29
|
endPosition: 0,
|
|
26
30
|
startValue: '',
|
|
27
|
-
endValue: ''
|
|
31
|
+
endValue: '',
|
|
32
|
+
xMax: props.xMax
|
|
28
33
|
})
|
|
29
34
|
|
|
30
35
|
const initialPosition = {
|
|
31
36
|
start: { x: 0 },
|
|
32
37
|
end: { x: props.xMax }
|
|
33
38
|
}
|
|
34
|
-
|
|
35
39
|
const style = {
|
|
36
|
-
fill: '#
|
|
40
|
+
fill: '#474747',
|
|
37
41
|
stroke: 'blue',
|
|
38
42
|
fillOpacity: 0.8,
|
|
39
43
|
strokeOpacity: 0,
|
|
@@ -41,18 +45,19 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
const onBrushChange = event => {
|
|
44
|
-
|
|
45
|
-
const
|
|
48
|
+
setShowTooltip(false)
|
|
49
|
+
const filteredValues = event?.xValues?.filter(val => val !== undefined)
|
|
50
|
+
if (filteredValues?.length === 0) return
|
|
46
51
|
|
|
47
52
|
const dataKey = config.xAxis?.dataKey
|
|
48
53
|
|
|
49
|
-
const brushedData = tableData.filter(item =>
|
|
54
|
+
const brushedData = tableData.filter(item => filteredValues.includes(item[dataKey]))
|
|
50
55
|
|
|
51
|
-
const endValue =
|
|
56
|
+
const endValue = filteredValues
|
|
52
57
|
.slice()
|
|
53
58
|
.reverse()
|
|
54
59
|
.find(item => item !== undefined)
|
|
55
|
-
const startValue =
|
|
60
|
+
const startValue = filteredValues.find(item => item !== undefined)
|
|
56
61
|
|
|
57
62
|
const formatIfDate = value => (isDateScale(config.runtime.xAxis) ? formatDate(parseDate(value)) : value)
|
|
58
63
|
|
|
@@ -61,7 +66,8 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
61
66
|
startPosition: brushRef.current?.state.start.x,
|
|
62
67
|
endPosition: brushRef.current?.state.end.x,
|
|
63
68
|
endValue: formatIfDate(endValue),
|
|
64
|
-
startValue: formatIfDate(startValue)
|
|
69
|
+
startValue: formatIfDate(startValue),
|
|
70
|
+
xMax: props.xMax
|
|
65
71
|
}))
|
|
66
72
|
|
|
67
73
|
setBrushConfig(prev => {
|
|
@@ -72,9 +78,9 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
72
78
|
}
|
|
73
79
|
})
|
|
74
80
|
}
|
|
75
|
-
|
|
81
|
+
// reset brush if brush is off.
|
|
76
82
|
useEffect(() => {
|
|
77
|
-
if (!config.brush
|
|
83
|
+
if (!config.brush?.active) {
|
|
78
84
|
setBrushKey(prevKey => prevKey + 1)
|
|
79
85
|
setBrushConfig({
|
|
80
86
|
data: [],
|
|
@@ -82,10 +88,15 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
82
88
|
isBrushing: false
|
|
83
89
|
})
|
|
84
90
|
}
|
|
85
|
-
}, [config.brush
|
|
91
|
+
}, [config.brush?.active])
|
|
92
|
+
|
|
93
|
+
// reset brush if filters or exclusions are ON each time
|
|
86
94
|
|
|
87
95
|
useEffect(() => {
|
|
88
|
-
|
|
96
|
+
const isFiltersActive = config.filters?.some(filter => filter.active)
|
|
97
|
+
const isExclusionsActive = config.exclusions?.active
|
|
98
|
+
|
|
99
|
+
if ((isFiltersActive || isExclusionsActive || isDashboardFilters) && config.brush?.active) {
|
|
89
100
|
setBrushKey(prevKey => prevKey + 1)
|
|
90
101
|
setBrushConfig(prev => {
|
|
91
102
|
return {
|
|
@@ -101,12 +112,12 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
101
112
|
data: []
|
|
102
113
|
}
|
|
103
114
|
})
|
|
104
|
-
}, [config.filters])
|
|
115
|
+
}, [config.filters, config.exclusions, config.brush?.active, isDashboardFilters])
|
|
105
116
|
|
|
106
117
|
const calculateTop = (): number => {
|
|
107
118
|
const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
|
|
108
119
|
let top = 0
|
|
109
|
-
const offSet =
|
|
120
|
+
const offSet = 30
|
|
110
121
|
if (!config.xAxis.label) {
|
|
111
122
|
if (!config.isResponsiveTicks && tickRotation) {
|
|
112
123
|
top = Number(tickRotation + config.xAxis.tickWidthMax) / 1.6
|
|
@@ -123,7 +134,7 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
123
134
|
}
|
|
124
135
|
if (config.xAxis.label) {
|
|
125
136
|
if (!config.isResponsiveTicks && tickRotation) {
|
|
126
|
-
top = Number(config.xAxis.tickWidthMax + tickRotation)
|
|
137
|
+
top = Number(config.xAxis.tickWidthMax + tickRotation) + offSet
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
if (!config.isResponsiveTicks && !tickRotation) {
|
|
@@ -131,7 +142,7 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
if (config.isResponsiveTicks && !tickRotation) {
|
|
134
|
-
top = Number(config.dynamicMarginTop ? config.dynamicMarginTop : config.xAxis.labelOffset) + offSet
|
|
145
|
+
top = Number(config.dynamicMarginTop ? config.dynamicMarginTop : config.xAxis.labelOffset) + offSet * 2
|
|
135
146
|
}
|
|
136
147
|
}
|
|
137
148
|
|
|
@@ -142,30 +153,56 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
142
153
|
}
|
|
143
154
|
|
|
144
155
|
return (
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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>
|
|
164
201
|
)
|
|
165
202
|
}
|
|
166
203
|
|
|
167
204
|
const BrushHandle = props => {
|
|
168
|
-
const { x, isBrushActive, isBrushing, className, textProps } = props
|
|
205
|
+
const { x, isBrushActive, isBrushing, className, textProps, fontSize, showTooltip, left, getTextWidth } = props
|
|
169
206
|
const pathWidth = 8
|
|
170
207
|
if (!isBrushActive) {
|
|
171
208
|
return null
|
|
@@ -174,14 +211,23 @@ const BrushHandle = props => {
|
|
|
174
211
|
const isLeft = className.includes('left')
|
|
175
212
|
const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
|
|
176
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`)
|
|
177
216
|
|
|
178
217
|
return (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
{
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
</>
|
|
185
231
|
)
|
|
186
232
|
}
|
|
187
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,7 +24,11 @@ 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,
|
|
@@ -53,7 +59,9 @@ export default {
|
|
|
53
59
|
axisPadding: 0,
|
|
54
60
|
scalePadding: 10,
|
|
55
61
|
tickRotation: 0,
|
|
56
|
-
anchors: []
|
|
62
|
+
anchors: [],
|
|
63
|
+
shoMissingDataLabel: true,
|
|
64
|
+
showMissingDataLine: true
|
|
57
65
|
},
|
|
58
66
|
boxplot: {
|
|
59
67
|
plots: [],
|
|
@@ -116,9 +124,7 @@ export default {
|
|
|
116
124
|
labelOffset: 65,
|
|
117
125
|
axisPadding: 200,
|
|
118
126
|
target: 0,
|
|
119
|
-
maxTickRotation: 0
|
|
120
|
-
showSuppressedSymbol: true,
|
|
121
|
-
showSuppressedLine: true
|
|
127
|
+
maxTickRotation: 0
|
|
122
128
|
},
|
|
123
129
|
table: {
|
|
124
130
|
label: 'Data Table',
|
|
@@ -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',
|
|
@@ -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
|
})
|
|
@@ -210,43 +210,6 @@ export const useBarChart = () => {
|
|
|
210
210
|
if (config.legend.highlightOnHover && config.legend.behavior === 'highlight') setSeriesHighlight([])
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
const composeSuppressionBars = ({ bar }) => {
|
|
214
|
-
const suppresedBarHeight = config.xAxis.showSuppressedLine ? 3 : 0
|
|
215
|
-
const ASTERISK = 'Asterisk'
|
|
216
|
-
const getIconPadding = symbol => (String(symbol).includes(ASTERISK) ? -5 : -suppresedBarHeight * 3)
|
|
217
|
-
const getVerticalAnchor = symbol => {
|
|
218
|
-
return String(symbol).includes(ASTERISK) ? 'middle' : 'end'
|
|
219
|
-
}
|
|
220
|
-
const getIconSize = (symbol, barWidth) => {
|
|
221
|
-
switch (symbol) {
|
|
222
|
-
case ASTERISK:
|
|
223
|
-
return barWidth * 1.2
|
|
224
|
-
case 'Double ' + ASTERISK:
|
|
225
|
-
return barWidth
|
|
226
|
-
default:
|
|
227
|
-
return barWidth / 1.5
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function shouldSuppressBar() {
|
|
232
|
-
const isSuppressed = config.preliminaryData.some(pd => {
|
|
233
|
-
const selectedSuppressionColumn = !pd.column || pd.column === bar.key
|
|
234
|
-
const isValueMatch = String(pd.value) === String(bar.value) && pd.value !== '' && pd.type === 'suppression'
|
|
235
|
-
|
|
236
|
-
return isValueMatch && selectedSuppressionColumn
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
return isSuppressed && config.xAxis.showSuppressedSymbol
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
suppresedBarHeight,
|
|
244
|
-
getIconSize,
|
|
245
|
-
getIconPadding,
|
|
246
|
-
getVerticalAnchor,
|
|
247
|
-
isSuppressed: shouldSuppressBar()
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
213
|
return {
|
|
251
214
|
isHorizontal,
|
|
252
215
|
barBorderWidth,
|
|
@@ -273,7 +236,6 @@ export const useBarChart = () => {
|
|
|
273
236
|
hoveredBar,
|
|
274
237
|
setHoveredBar,
|
|
275
238
|
onMouseOverBar,
|
|
276
|
-
onMouseLeaveBar
|
|
277
|
-
composeSuppressionBars
|
|
239
|
+
onMouseLeaveBar
|
|
278
240
|
}
|
|
279
241
|
}
|
|
@@ -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)
|
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChartConfig } from '../types/ChartConfig'
|
|
2
2
|
import _ from 'lodash'
|
|
3
|
+
import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
|
|
3
4
|
|
|
4
5
|
type UseMinMaxProps = {
|
|
5
6
|
/** config - standard chart config */
|
|
@@ -30,6 +31,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
30
31
|
return { min, max }
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
const checkLineToBarGraph = () => {
|
|
35
|
+
return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
const { visualizationType, series } = config
|
|
34
39
|
const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
|
|
35
40
|
const minRequiredCIPadding = 1.15 // regardless of Editor if CI data, there must be 10% padding added
|
|
@@ -126,10 +131,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
// this should not apply to bar charts if there is negative CI data
|
|
129
|
-
if ((visualizationType === 'Bar' || (visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
134
|
+
if ((visualizationType === 'Bar' || checkLineToBarGraph() || (visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
130
135
|
min = 0
|
|
131
136
|
}
|
|
132
|
-
if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min < 0) {
|
|
137
|
+
if ((config.visualizationType === 'Bar' || checkLineToBarGraph() || (config.visualizationType === 'Combo' && !isAllLine)) && min < 0) {
|
|
133
138
|
min = min * 1.1
|
|
134
139
|
}
|
|
135
140
|
|
|
@@ -148,7 +153,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
148
153
|
min = enteredMinValue && isMinValid ? enteredMinValue : 0
|
|
149
154
|
}
|
|
150
155
|
|
|
151
|
-
if (config.visualizationType === 'Line') {
|
|
156
|
+
if (config.visualizationType === 'Line' && !checkLineToBarGraph()) {
|
|
152
157
|
const isMinValid = config.useLogScale ? enteredMinValue >= 0 && enteredMinValue < minValue : enteredMinValue < minValue
|
|
153
158
|
// update minValue for (0) Suppression points
|
|
154
159
|
const suppressedMinValue = tableData?.some((dataItem, index) => {
|