@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.
Files changed (76) hide show
  1. package/dist/cdcchart.js +39611 -36038
  2. package/examples/feature/annotations/index.json +542 -0
  3. package/examples/xaxis.json +493 -0
  4. package/index.html +9 -8
  5. package/package.json +5 -4
  6. package/src/CdcChart.tsx +115 -71
  7. package/src/_stories/Chart.stories.tsx +26 -171
  8. package/src/_stories/ChartAnnotation.stories.tsx +32 -0
  9. package/src/_stories/_mock/annotation_category_mock.json +473 -0
  10. package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
  11. package/src/_stories/_mock/annotation_date-time_mock.json +530 -0
  12. package/src/_stories/_mock/bar-chart-suppressed.json +474 -0
  13. package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
  14. package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
  15. package/src/_stories/_mock/lollipop.json +171 -0
  16. package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
  17. package/src/components/Annotations/components/AnnotationDraggable.tsx +154 -0
  18. package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
  19. package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
  20. package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
  21. package/src/components/Annotations/components/AnnotationList.tsx +42 -0
  22. package/src/components/Annotations/components/findNearestDatum.ts +138 -0
  23. package/src/components/Annotations/components/helpers/index.tsx +46 -0
  24. package/src/components/Annotations/index.tsx +13 -0
  25. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  26. package/src/components/AreaChart/components/AreaChart.jsx +2 -2
  27. package/src/components/BarChart/components/BarChart.Horizontal.tsx +78 -71
  28. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -2
  29. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -11
  30. package/src/components/BarChart/components/BarChart.Vertical.tsx +100 -87
  31. package/src/components/BarChart/helpers/index.ts +102 -0
  32. package/src/components/DeviationBar.jsx +4 -2
  33. package/src/components/EditorPanel/EditorPanel.tsx +435 -613
  34. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +306 -0
  35. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +135 -7
  36. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +2 -3
  37. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
  38. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -2
  39. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  40. package/src/components/EditorPanel/components/panels.scss +4 -0
  41. package/src/components/EditorPanel/editor-panel.scss +19 -0
  42. package/src/components/EditorPanel/useEditorPermissions.js +23 -3
  43. package/src/components/Legend/Legend.Component.tsx +66 -15
  44. package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
  45. package/src/components/Legend/helpers/index.ts +5 -0
  46. package/src/components/LineChart/LineChartProps.ts +16 -6
  47. package/src/components/LineChart/components/LineChart.Circle.tsx +22 -11
  48. package/src/components/LineChart/helpers.ts +148 -10
  49. package/src/components/LineChart/index.tsx +71 -44
  50. package/src/components/LinearChart.jsx +184 -125
  51. package/src/components/PairedBarChart.jsx +9 -9
  52. package/src/components/PieChart/PieChart.tsx +4 -4
  53. package/src/components/Sankey/index.tsx +73 -20
  54. package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
  55. package/src/components/ZoomBrush.tsx +120 -55
  56. package/src/data/initial-state.js +14 -6
  57. package/src/helpers/handleChartTabbing.ts +8 -0
  58. package/src/helpers/isConvertLineToBarGraph.ts +4 -0
  59. package/src/hooks/{useBarChart.js → useBarChart.ts} +9 -22
  60. package/src/hooks/useColorScale.ts +1 -1
  61. package/src/hooks/useMinMax.ts +29 -5
  62. package/src/hooks/useScales.ts +48 -26
  63. package/src/hooks/useTooltip.tsx +62 -15
  64. package/src/scss/main.scss +69 -12
  65. package/src/types/ChartConfig.ts +53 -16
  66. package/src/types/ChartContext.ts +13 -0
  67. package/tests-examples/helpers/testZeroValue.test.ts +30 -0
  68. package/LICENSE +0 -201
  69. package/src/_stories/ChartLine.preliminary.tsx +0 -19
  70. package/src/_stories/ChartSuppress.stories.tsx +0 -19
  71. package/src/_stories/_mock/suppress_mock.json +0 -911
  72. package/src/helpers/computeMarginBottom.ts +0 -56
  73. package/src/helpers/filterData.ts +0 -18
  74. package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
  75. /package/src/hooks/{useLegendClasses.js → useLegendClasses.ts} +0 -0
  76. /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
- // !info - changed config.sankey.data here to work with our current upload pattern saved on config.data
22
- const data = config?.data[0]
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.find(item => item.node === tooltipID) || {}).value}`
141
- const tooltipSummary = `${(data?.tooltips.find(item => item.node === tooltipID) || {}).summary}`
142
- const tooltipColumn1Label = (data?.tooltips.find(item => item.node === tooltipID) || {}).column1Label
143
- const tooltipColumn2Label = (data?.tooltips.find(item => item.node === tooltipID) || {}).column2Label
144
- const tooltipColumn1 = (data?.tooltips.find(item => item.node === tooltipID) || {}).column1
145
- const tooltipColumn2 = (data?.tooltips.find(item => item.node === tooltipID) || {}).column2
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 verticalAnchor='end' className={classStyle} x={node.x0! + textPositionHorizontal} y={(node.y1! + node.y0! + 25) / 2} fill={sankeyConfig.storyNodeFontColor || sankeyConfig.nodeFontColor} fontWeight='bold' textAnchor='start' style={{ pointerEvents: 'none' }}>
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
- <text x={node.x0! + textPositionHorizontal} y={(node.y1! + node.y0!) / 2 + textPositionVertical} dominantBaseline='text-before-edge' fill={sankeyConfig.nodeFontColor} fontWeight='bold' textAnchor='start' style={{ pointerEvents: 'none' }}>
257
- <tspan id={node.id} className='node-id'>
258
- {node.id}
259
- </tspan>
260
- </text>
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: 'none' }}
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 <path key={i} d={path!} stroke={strokeColor} fill='none' strokeOpacity={opacityValue} strokeWidth={link.width! + 2} />
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 className={classStyle}>{sankeyConfig.nodeValueStyle.textBefore + (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) + sankeyConfig.nodeValueStyle.textAfter}</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, getXAxisData, getYAxisData }) => {
6
- const { colorScale, transformedData: data, config, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
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
- let circleRadii = 4.5
10
+ const circleRadii = 4.5
10
11
  const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
11
-
12
- const handleTooltip = (item, s) => `<div>
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 { transformedData: data, config, parseDate, formatDate, updateConfig } = 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()
19
-
20
- const [filteredData, setFilteredData] = useState([...data])
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: '#ddd',
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
- if (!event) return
46
-
47
- const { xValues } = event
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 filteredData = data.filter(item => xValues.includes(item[dataKey]))
54
+ const brushedData = tableData.filter(item => filteredValues.includes(item[dataKey]))
52
55
 
53
- const endValue = xValues
56
+ const endValue = filteredValues
54
57
  .slice()
55
58
  .reverse()
56
59
  .find(item => item !== undefined)
57
- const startValue = xValues.find(item => item !== undefined)
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
- setFilteredData(filteredData)
70
- }
71
-
72
- useEffect(() => {
73
- updateConfig({
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
- }, [filteredData])
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
- if (!config.brush.active) {
85
- setFilteredData(data)
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
- }, [config.brush.active])
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 = 20
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
- <Group display={config.brush.active ? 'block' : 'none'} top={Number(props.yMax) + calculateTop()} left={Number(config.runtime.yAxis.size)} pointerEvents='fill'>
128
- <rect fill='#eee' width={props.xMax} height={config.brush.height} rx={radius} />
129
- <Brush
130
- renderBrushHandle={props => <BrushHandle textProps={textProps} fontSize={fontSize[config.fontSize]} {...props} isBrushing={brushRef.current?.state.isBrushing} />}
131
- innerRef={brushRef}
132
- useWindowMoveEvents={true}
133
- selectedBoxStyle={style}
134
- xScale={props.xScaleBrush}
135
- yScale={props.yScale}
136
- width={props.xMax}
137
- resizeTriggerAreas={['left', 'right']}
138
- height={config.brush.height}
139
- handleSize={8}
140
- brushDirection='horizontal'
141
- initialBrushPosition={initialPosition}
142
- onChange={onBrushChange}
143
- />
144
- </Group>
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
- <Group left={x + pathWidth / 2} top={-2}>
161
- <Text pointerEvents='visiblePainted' dominantBaseline='hanging' x={0} verticalAnchor='start' textAnchor={textAnchor} fontSize={props.fontSize / 1.4} dy={10} y={15}>
162
- {isLeft ? textProps.startValue : textProps.endValue}
163
- </Text>
164
- <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>
165
- </Group>
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
- showDownloadButton: false
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
+ }
@@ -0,0 +1,4 @@
1
+ export const isConvertLineToBarGraph = (visualizationType, filteredData, allowLineToBarGraph) => {
2
+ const convertLineToBarGraph = visualizationType === 'Line' && filteredData?.length < 3 && allowLineToBarGraph ? true : false
3
+ return convertLineToBarGraph
4
+ }
@@ -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
- const generateIconSize = barWidth => {
168
- if (barWidth < 4) {
169
- return 1
170
- }
171
- if (barWidth < 5) {
172
- return 4
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.map(d => d[legend.colorCode]))
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)