@cdc/chart 4.24.5 → 4.24.9

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 (87) hide show
  1. package/dist/cdcchart.js +44197 -38258
  2. package/examples/cases-year.json +13379 -0
  3. package/examples/feature/annotations/index.json +542 -0
  4. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
  5. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
  6. package/examples/xaxis.json +493 -0
  7. package/index.html +20 -10
  8. package/package.json +5 -4
  9. package/src/CdcChart.tsx +462 -172
  10. package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
  11. package/src/_stories/Chart.stories.tsx +18 -171
  12. package/src/_stories/ChartAnnotation.stories.tsx +32 -0
  13. package/src/_stories/_mock/annotation_category_mock.json +473 -0
  14. package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
  15. package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
  16. package/src/_stories/_mock/legend.gradient_mock.json +236 -0
  17. package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
  18. package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
  19. package/src/_stories/_mock/lollipop.json +171 -0
  20. package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
  21. package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
  22. package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
  23. package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
  24. package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
  25. package/src/components/Annotations/components/AnnotationList.tsx +42 -0
  26. package/src/components/Annotations/components/findNearestDatum.ts +138 -0
  27. package/src/components/Annotations/components/helpers/index.tsx +46 -0
  28. package/src/components/Annotations/index.tsx +13 -0
  29. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  30. package/src/components/AreaChart/components/AreaChart.jsx +1 -1
  31. package/src/components/Axis/Categorical.Axis.tsx +145 -0
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
  33. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
  34. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
  35. package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
  36. package/src/components/BarChart/helpers/index.ts +91 -0
  37. package/src/components/BrushChart.tsx +205 -0
  38. package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
  39. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
  40. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
  41. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
  42. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
  43. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
  44. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  45. package/src/components/EditorPanel/components/panels.scss +4 -0
  46. package/src/components/EditorPanel/editor-panel.scss +35 -3
  47. package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
  48. package/src/components/Legend/Legend.Component.tsx +185 -194
  49. package/src/components/Legend/Legend.Suppression.tsx +146 -0
  50. package/src/components/Legend/Legend.tsx +21 -5
  51. package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
  52. package/src/components/Legend/helpers/index.ts +35 -0
  53. package/src/components/LegendWrapper.tsx +26 -0
  54. package/src/components/LineChart/LineChartProps.ts +1 -15
  55. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
  56. package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
  57. package/src/components/LineChart/helpers.ts +72 -14
  58. package/src/components/LineChart/index.tsx +117 -42
  59. package/src/components/LinearChart.jsx +179 -136
  60. package/src/components/LinearChart.tsx +1366 -0
  61. package/src/components/PairedBarChart.jsx +9 -9
  62. package/src/components/PieChart/PieChart.tsx +75 -18
  63. package/src/components/Sankey/index.tsx +89 -30
  64. package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
  65. package/src/components/Sparkline/components/SparkLine.tsx +2 -2
  66. package/src/components/ZoomBrush.tsx +90 -44
  67. package/src/data/initial-state.js +25 -7
  68. package/src/helpers/handleChartTabbing.ts +8 -0
  69. package/src/helpers/isConvertLineToBarGraph.ts +4 -0
  70. package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
  71. package/src/hooks/useColorScale.ts +1 -1
  72. package/src/hooks/useLegendClasses.ts +68 -0
  73. package/src/hooks/useMinMax.ts +12 -7
  74. package/src/hooks/useScales.ts +58 -26
  75. package/src/hooks/useTooltip.tsx +135 -25
  76. package/src/scss/DataTable.scss +2 -1
  77. package/src/scss/main.scss +128 -28
  78. package/src/types/ChartConfig.ts +83 -10
  79. package/src/types/ChartContext.ts +14 -4
  80. package/tests-examples/helpers/testZeroValue.test.ts +30 -0
  81. package/LICENSE +0 -201
  82. package/src/components/BrushHandle.jsx +0 -17
  83. package/src/components/LineChart/index.scss +0 -1
  84. package/src/helpers/filterData.ts +0 -18
  85. package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
  86. package/src/hooks/useLegendClasses.js +0 -31
  87. /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
@@ -1,16 +1,20 @@
1
1
  import parse from 'html-react-parser'
2
2
  import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
3
- import LegendCircle from '@cdc/core/components/LegendCircle'
3
+ import LegendShape from '@cdc/core/components/LegendShape'
4
4
  import Button from '@cdc/core/components/elements/Button'
5
5
  import useLegendClasses from '../../hooks/useLegendClasses'
6
6
  import { useHighlightedBars } from '../../hooks/useHighlightedBars'
7
7
  import { handleLineType } from '../../helpers/handleLineType'
8
- import { useBarChart } from '../../hooks/useBarChart'
8
+
9
+ import { getMarginTop, getGradientConfig, getMarginBottom } from './helpers/index'
9
10
  import { Line } from '@visx/shape'
10
11
  import { Label } from '../../types/Label'
11
12
  import { ChartConfig } from '../../types/ChartConfig'
12
13
  import { ColorScale } from '../../types/ChartContext'
13
14
  import { forwardRef } from 'react'
15
+ import LegendSuppression from './Legend.Suppression'
16
+ import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
17
+ import { DimensionsType } from '@cdc/core/types/Dimensions'
14
18
 
15
19
  export interface LegendProps {
16
20
  colorScale: ColorScale
@@ -22,210 +26,197 @@ export interface LegendProps {
22
26
  ref: React.Ref<() => void>
23
27
  seriesHighlight: string[]
24
28
  skipId: string
29
+ dimensions: DimensionsType // for responsive width legend
30
+ getTextWidth: (text: string, font: string) => string
25
31
  }
26
32
 
27
33
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
28
- const Legend: React.FC<LegendProps> = forwardRef(({ config, colorScale, seriesHighlight, highlight, highlightReset, currentViewport, formatLabels, skipId = 'legend' }, ref) => {
29
- const { innerClasses, containerClasses } = useLegendClasses(config)
30
- const { runtime, orientation, legend } = config
31
-
32
- if (!legend) return null
33
- const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
34
-
35
- const legendClasses = {
36
- marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
37
- marginTop: isBottomOrSmallViewport ? '15px' : '0px'
38
- }
39
-
40
- const { HighLightedBarUtils } = useHighlightedBars(config)
41
-
42
- let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
43
- const fontSize = ['sm', 'xs', 'xxs'].includes(currentViewport) ? { fontSize: '11px' } : null
44
-
45
- return (
46
- <aside ref={ref} style={legendClasses} id={skipId || 'legend'} className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
47
- {legend.label && <h3>{parse(legend.label)}</h3>}
48
- {legend.description && <p style={fontSize}>{parse(legend.description)}</p>}
34
+ const Legend: React.FC<LegendProps> = forwardRef(
35
+ (
36
+ {
37
+ config,
38
+ colorScale,
39
+ seriesHighlight,
40
+ highlight,
41
+ highlightReset,
42
+ currentViewport,
43
+ formatLabels,
44
+ skipId = 'legend',
45
+ dimensions,
46
+ getTextWidth
47
+ },
48
+ ref
49
+ ) => {
50
+ const { innerClasses, containerClasses } = useLegendClasses(config)
51
+ const { runtime, legend } = config
52
+
53
+ const isBottomOrSmallViewport =
54
+ legend?.position === 'bottom' || (['sm', 'xs', 'xxs'].includes(currentViewport) && !legend.hide)
55
+
56
+ const legendClasses = {
57
+ marginBottom: getMarginBottom(isBottomOrSmallViewport, config),
58
+
59
+ marginTop:
60
+ isBottomOrSmallViewport && config.orientation === 'horizontal'
61
+ ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px`
62
+ : getMarginTop(isBottomOrSmallViewport, config.brush.active, legend)
63
+ }
64
+
65
+ const { HighLightedBarUtils } = useHighlightedBars(config)
66
+ let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
67
+ if (!legend) return null
68
+ return (
69
+ <aside
70
+ ref={ref}
71
+ style={legendClasses}
72
+ id={skipId || 'legend'}
73
+ className={containerClasses.join(' ')}
74
+ role='region'
75
+ aria-label='legend'
76
+ tabIndex={0}
77
+ >
78
+ {legend.label && <h3>{parse(legend.label)}</h3>}
79
+ {legend.description && <p>{parse(legend.description)}</p>}
80
+ <LegendGradient
81
+ getTextWidth={getTextWidth}
82
+ config={config}
83
+ {...getGradientConfig(config, formatLabels, colorScale)}
84
+ dimensions={dimensions}
85
+ currentViewport={currentViewport}
86
+ />
87
+
88
+ <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
89
+ {labels => {
90
+ return (
91
+ <>
92
+ <div className={innerClasses.join(' ')}>
93
+ {formatLabels(labels as Label[]).map((label, i) => {
94
+ let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
95
+ let itemName = label.datum
96
+
97
+ // Filter excluded data keys from legend
98
+ if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
99
+ return null
100
+ }
49
101
 
50
- <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
51
- {labels => {
52
- return (
53
- <>
54
- <div className={innerClasses.join(' ')}>
55
- {formatLabels(labels as Label[]).map((label, i) => {
56
- let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
57
- let itemName = label.datum
102
+ if (runtime.seriesLabels) {
103
+ let index = config.runtime.seriesLabelsAll.indexOf(itemName)
104
+ itemName = config.runtime.seriesKeys[index]
58
105
 
59
- // Filter excluded data keys from legend
60
- if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
61
- return null
62
- }
106
+ if (runtime?.forecastingSeriesKeys?.length > 0) {
107
+ itemName = label.text
108
+ }
109
+ }
63
110
 
64
- if (runtime.seriesLabels) {
65
- let index = config.runtime.seriesLabelsAll.indexOf(itemName)
66
- itemName = config.runtime.seriesKeys[index]
111
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
112
+ className.push('inactive')
113
+ }
67
114
 
68
- if (runtime?.forecastingSeriesKeys?.length > 0) {
69
- itemName = label.text
115
+ if (config.legend.style === 'gradient') {
116
+ return <></>
70
117
  }
71
- }
72
-
73
- if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
74
- className.push('inactive')
75
- }
76
-
77
- return (
78
- <LegendItem
79
- className={className.join(' ')}
80
- tabIndex={0}
81
- key={`legend-quantile-${i}`}
82
- onKeyDown={e => {
83
- if (e.key === 'Enter') {
118
+
119
+ return (
120
+ <LegendItem
121
+ className={className.join(' ')}
122
+ tabIndex={0}
123
+ key={`legend-quantile-${i}`}
124
+ onKeyDown={e => {
125
+ if (e.key === 'Enter') {
126
+ e.preventDefault()
127
+ highlight(label)
128
+ }
129
+ }}
130
+ onClick={e => {
84
131
  e.preventDefault()
85
132
  highlight(label)
86
- }
87
- }}
88
- onClick={e => {
89
- e.preventDefault()
90
- highlight(label)
91
- }}
92
- role='button'
93
- >
94
- <div>
95
- {config.visualizationType === 'Line' && config.legend.lineMode ? (
96
- <svg width={40} height={20}>
97
- <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
98
- </svg>
99
- ) : (
100
- <div style={{ display: 'flex', flexDirection: 'column' }}>
101
- <LegendCircle viewport={currentViewport} margin='0' fill={label.value} display={true} />
102
- </div>
103
- )}
104
- </div>
105
-
106
- <LegendLabel style={fontSize} align='left' margin='0 0 0 4px'>
107
- {label.text}
108
- </LegendLabel>
109
- </LegendItem>
110
- )
111
- })}
112
-
113
- {highLightedLegendItems.map((bar, i) => {
114
- // if duplicates only return first item
115
- let className = 'legend-item'
116
- let itemName = bar.legendLabel
117
-
118
- if (!itemName) return false
119
- if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
120
- className += ' inactive'
121
- }
122
- return (
123
- <LegendItem
124
- className={className}
125
- tabIndex={0}
126
- key={`legend-quantile-${i}`}
127
- onKeyDown={e => {
128
- if (e.key === 'Enter') {
133
+ }}
134
+ role='button'
135
+ >
136
+ <div>
137
+ {config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
138
+ <svg width={40} height={20}>
139
+ <Line
140
+ from={{ x: 10, y: 10 }}
141
+ to={{ x: 40, y: 10 }}
142
+ stroke={label.value}
143
+ strokeWidth={2}
144
+ strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')}
145
+ />
146
+ </svg>
147
+ ) : (
148
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
149
+ <LegendShape
150
+ shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
151
+ viewport={currentViewport}
152
+ margin='0'
153
+ fill={label.value}
154
+ display={true}
155
+ />
156
+ </div>
157
+ )}
158
+ </div>
159
+
160
+ <LegendLabel align='left' margin='0 0 0 4px'>
161
+ {label.text}
162
+ </LegendLabel>
163
+ </LegendItem>
164
+ )
165
+ })}
166
+
167
+ {highLightedLegendItems.map((bar, i) => {
168
+ // if duplicates only return first item
169
+ let className = 'legend-item'
170
+ let itemName = bar.legendLabel
171
+
172
+ if (!itemName) return false
173
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
174
+ className += ' inactive'
175
+ }
176
+
177
+ return (
178
+ <LegendItem
179
+ className={className}
180
+ tabIndex={0}
181
+ key={`legend-quantile-${i}`}
182
+ onKeyDown={e => {
183
+ if (e.key === 'Enter') {
184
+ e.preventDefault()
185
+ highlight(bar.legendLabel)
186
+ }
187
+ }}
188
+ onClick={e => {
129
189
  e.preventDefault()
130
190
  highlight(bar.legendLabel)
131
- }
132
- }}
133
- onClick={e => {
134
- e.preventDefault()
135
- highlight(bar.legendLabel)
136
- }}
137
- >
138
- <LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
139
- <LegendLabel align='left' margin='0 0 0 4px'>
140
- {bar.legendLabel ? bar.legendLabel : bar.value}
141
- </LegendLabel>
142
- </LegendItem>
143
- )
144
- })}
145
- </div>
146
-
147
- <>
148
- {config?.preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style) && ['Line', 'Combo'].includes(config.visualizationType) && (
149
- <>
150
- <hr></hr>
151
- <div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''}>
152
- {config?.preliminaryData?.map((pd, index) => {
153
- return (
154
- <>
155
- {pd.label && pd.type === 'effect' && pd.style && (
156
- <div key={index} className='legend-preliminary'>
157
- <span className={pd.symbol}>{pd.lineCode}</span>
158
- <p> {pd.label}</p>
159
- </div>
160
- )}
161
- </>
162
- )
163
- })}
164
- </div>
165
- </>
166
- )}
167
- {!config.legend.hideSuppressedLabels &&
168
- config?.preliminaryData?.some(pd => pd.label && pd.displayLegend && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)) &&
169
- ((config.visualizationType === 'Bar' && config.visualizationSubType === 'regular') || config.visualizationType === 'Line' || config.visualizationType === 'Combo') && (
170
- <>
171
- <hr></hr>
172
- <div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''}>
173
- {config?.preliminaryData?.map(
174
- (pd, index) =>
175
- pd.displayLegend &&
176
- pd.type === 'suppression' && (
177
- <>
178
- {config.visualizationType === 'Bar' && (
179
- <>
180
- <div key={index + 'Bar'} className={`legend-preliminary ${pd.symbol}`}>
181
- <span className={pd.symbol}>{pd.iconCode}</span>
182
- <p className={pd.type}>{pd.label}</p>
183
- </div>
184
- </>
185
- )}
186
- {config.visualizationType === 'Line' && (
187
- <>
188
- <div key={index + 'Line'} className={`legend-preliminary `}>
189
- <span>{pd.lineCode}</span>
190
- <p className={pd.type}>{pd.label}</p>
191
- </div>
192
- </>
193
- )}
194
- {config.visualizationType === 'Combo' && (
195
- <>
196
- {pd.symbol && pd.iconCode && (
197
- <div key={index + 'Combo'} className={`legend-preliminary ${pd.symbol}`}>
198
- <span className={pd.symbol}>{pd.iconCode}</span>
199
- <p className={pd.type}>{pd.label}</p>
200
- </div>
201
- )}
202
-
203
- {pd.style && pd.lineCode && (
204
- <div key={index + 'Combo'} className='legend-preliminary'>
205
- <span>{pd.lineCode}</span>
206
- <p>{pd.label}</p>
207
- </div>
208
- )}
209
- </>
210
- )}
211
- </>
212
- )
213
- )}
214
- </div>
215
- </>
216
- )}
191
+ }}
192
+ >
193
+ <LegendShape
194
+ shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
195
+ style={{ borderRadius: '0px' }}
196
+ fill='transparent'
197
+ borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`}
198
+ />{' '}
199
+ <LegendLabel align='left' margin='0 0 0 4px'>
200
+ {bar.legendLabel ? bar.legendLabel : bar.value}
201
+ </LegendLabel>
202
+ </LegendItem>
203
+ )
204
+ })}
205
+ </div>
206
+
207
+ <LegendSuppression config={config} isBottomOrSmallViewport={isBottomOrSmallViewport} />
217
208
  </>
218
- </>
219
- )
220
- }}
221
- </LegendOrdinal>
222
- {seriesHighlight.length > 0 && (
223
- <Button onClick={labels => highlightReset(labels)} style={{ marginTop: '1rem' }}>
224
- Reset
225
- </Button>
226
- )}
227
- </aside>
228
- )
229
- })
209
+ )
210
+ }}
211
+ </LegendOrdinal>
212
+ {seriesHighlight.length > 0 && (
213
+ <Button onClick={labels => highlightReset(labels)} style={{ marginTop: '1rem' }}>
214
+ Reset
215
+ </Button>
216
+ )}
217
+ </aside>
218
+ )
219
+ }
220
+ )
230
221
 
231
222
  export default Legend
@@ -0,0 +1,146 @@
1
+ import React from 'react'
2
+ import { ChartConfig } from '../../types/ChartConfig'
3
+ import Icon from '@cdc/core/components/ui/Icon'
4
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
5
+ interface LegendProps {
6
+ config: ChartConfig
7
+ isBottomOrSmallViewport: boolean
8
+ }
9
+
10
+ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewport }) => {
11
+ const { preliminaryData, visualizationType, visualizationSubType, legend } = config
12
+
13
+ const hasOpenCircleEffects = () =>
14
+ preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style === 'Open Circles') &&
15
+ ['Line', 'Combo'].includes(visualizationType)
16
+
17
+ const shouldShowSuppressedLabels = () =>
18
+ !legend.hideSuppressedLabels &&
19
+ preliminaryData?.some(
20
+ pd => pd.label && pd.displayLegend && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)
21
+ ) &&
22
+ ((visualizationType === 'Bar' && visualizationSubType === 'regular') ||
23
+ visualizationType === 'Line' ||
24
+ visualizationType === 'Combo')
25
+
26
+ const renderEffectItems = () =>
27
+ preliminaryData?.map(
28
+ (pd, index) =>
29
+ pd.label &&
30
+ pd.type === 'effect' &&
31
+ pd.style && (
32
+ <div key={index} className='legend-preliminary'>
33
+ <span className={pd.symbol}>{pd.lineCode}</span>
34
+ <p>{pd.label}</p>
35
+ </div>
36
+ )
37
+ )
38
+ const handleLinkClick = event => {
39
+ // prevent defintion link to change URl
40
+ event.preventDefault()
41
+ }
42
+
43
+ const renderSuppressedItems = () => {
44
+ const getStyle = displayGray => {
45
+ if (displayGray) {
46
+ return {
47
+ color: '#777772'
48
+ }
49
+ }
50
+ return null
51
+ }
52
+ return preliminaryData?.map((pd, index) => {
53
+ if (!pd.displayLegend || pd.type !== 'suppression') return null
54
+
55
+ const baseClass = 'legend-preliminary'
56
+ const itemKey = index + visualizationType
57
+
58
+ if (visualizationType === 'Bar') {
59
+ return (
60
+ <div style={getStyle(pd.displayGray)} key={itemKey} className={`${baseClass} ${pd.symbol}`}>
61
+ <span className={pd.symbol}>{pd.iconCode}</span>
62
+ <p className={pd.type}>{pd.label}</p>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ if (visualizationType === 'Line') {
68
+ return (
69
+ <div style={getStyle(pd.displayGray)} key={itemKey} className={baseClass}>
70
+ <span>{pd.lineCode}</span>
71
+ <p className={pd.type}>{pd.label}</p>
72
+ </div>
73
+ )
74
+ }
75
+
76
+ if (visualizationType === 'Combo') {
77
+ return (
78
+ <React.Fragment>
79
+ {pd.symbol && pd.iconCode && (
80
+ <div style={getStyle(pd.displayGray)} key={itemKey} className={`${baseClass} ${pd.symbol}`}>
81
+ <span className={pd.symbol}>{pd.iconCode}</span>
82
+ <p className={pd.type}>{pd.label}</p>
83
+ </div>
84
+ )}
85
+
86
+ {pd.style && pd.lineCode && (
87
+ <div style={getStyle(pd.displayGray)} key={itemKey} className={baseClass}>
88
+ <span>{pd.lineCode}</span>
89
+ <p>{pd.label}</p>
90
+ </div>
91
+ )}
92
+ </React.Fragment>
93
+ )
94
+ }
95
+
96
+ return null
97
+ })
98
+ }
99
+
100
+ const getLegendContainerClass = () =>
101
+ legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''
102
+
103
+ return (
104
+ <React.Fragment>
105
+ {hasOpenCircleEffects() && (
106
+ <React.Fragment>
107
+ <hr />
108
+ <div className={getLegendContainerClass()}>{renderEffectItems()}</div>
109
+ </React.Fragment>
110
+ )}
111
+
112
+ {shouldShowSuppressedLabels() && (
113
+ <React.Fragment>
114
+ <hr />
115
+ <div className={getLegendContainerClass()}>{renderSuppressedItems()}</div>
116
+ </React.Fragment>
117
+ )}
118
+ {!config.legend.hideSuppressionLink &&
119
+ config.visualizationSubType !== 'stacked' &&
120
+ preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)) && (
121
+ <div className='legend-container__outer definition-link'>
122
+ <Icon alt='info-icon' display='info' />
123
+ <p>
124
+ This chart contains
125
+ <a // prettier-ignore
126
+ onClick={handleLinkClick}
127
+ data-tooltip-content='Data is suppressed to maintain statistical reliability. This occurs when the number of respondents or reported values does not meet the minimum reporting threshold.'
128
+ data-tooltip-id='my-tooltip'
129
+ href='no-router-link'
130
+ >
131
+ suppressed data
132
+ </a>
133
+ </p>
134
+ </div>
135
+ )}
136
+
137
+ <ReactTooltip // prettier-ignore
138
+ id='my-tooltip'
139
+ variant='light'
140
+ style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black', maxWidth: '100%' }}
141
+ />
142
+ </React.Fragment>
143
+ )
144
+ }
145
+
146
+ export default LegendSuppression
@@ -1,4 +1,4 @@
1
- import { useContext, forwardRef } from 'react'
1
+ import { useContext, forwardRef, Fragment } from 'react'
2
2
  import ConfigContext from '../../ConfigContext'
3
3
  import LegendComponent from './Legend.Component'
4
4
  import { createFormatLabels } from './helpers/createFormatLabels'
@@ -7,6 +7,7 @@ import { createFormatLabels } from './helpers/createFormatLabels'
7
7
  const Legend = forwardRef((props, ref) => {
8
8
  // prettier-ignore
9
9
  const {
10
+ // prettier-ignore
10
11
  config,
11
12
  colorScale,
12
13
  seriesHighlight,
@@ -14,17 +15,32 @@ const Legend = forwardRef((props, ref) => {
14
15
  tableData,
15
16
  highlightReset,
16
17
  transformedData: data,
17
- currentViewport
18
+ currentViewport,
19
+ dimensions,
20
+ getTextWidth,
18
21
  } = useContext(ConfigContext)
19
-
20
22
  if (!config.legend) return null
21
23
  // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
22
24
 
23
25
  const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
24
26
 
25
27
  return (
26
- !['Box Plot', 'Pie'].includes(config.visualizationType) && (
27
- <LegendComponent ref={ref} skipId={props.skipId || 'legend'} config={config} colorScale={colorScale} seriesHighlight={seriesHighlight} highlight={highlight} highlightReset={highlightReset} currentViewport={currentViewport} formatLabels={createLegendLabels} />
28
+ !['Box Plot'].includes(config.visualizationType) && (
29
+ <Fragment>
30
+ <LegendComponent
31
+ getTextWidth={getTextWidth}
32
+ dimensions={dimensions}
33
+ ref={ref}
34
+ skipId={props.skipId || 'legend'}
35
+ config={config}
36
+ colorScale={colorScale}
37
+ seriesHighlight={seriesHighlight}
38
+ highlight={highlight}
39
+ highlightReset={highlightReset}
40
+ currentViewport={currentViewport}
41
+ formatLabels={createLegendLabels}
42
+ />
43
+ </Fragment>
28
44
  )
29
45
  )
30
46
  })
@@ -9,7 +9,7 @@ export const createFormatLabels =
9
9
  (defaultLabels: Label[]): Label[] => {
10
10
  const { visualizationType, visualizationSubType, series, runtime } = config
11
11
 
12
- const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
12
+ const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend?.position === 'bottom' ? labels.reverse() : labels)
13
13
  const colorCode = config.legend?.colorCode
14
14
  if (visualizationType === 'Deviation Bar') {
15
15
  const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
@@ -0,0 +1,35 @@
1
+ export const getMarginTop = (isBottomOrSmallViewport, isBrushActive, legend) => {
2
+ if (!isBottomOrSmallViewport) return '0px'
3
+ if (isBrushActive && legend.position === 'bottom') return '35px'
4
+ }
5
+
6
+ export const getGradientConfig = (config, formatLabels, colorScale) => {
7
+ const defaultValue = [{ datum: '', index: 0, text: '', value: '' }]
8
+
9
+ const formatted = formatLabels(defaultValue)
10
+ const colors = config.legend.colorCode ? formatted.map(label => label?.value) : colorScale?.range() ?? []
11
+ const labels = config.legend.colorCode
12
+ ? formatted.map(label => label?.text || label?.datum)
13
+ : colorScale?.domain() ?? []
14
+
15
+ return { colors, labels }
16
+ }
17
+
18
+ export const getMarginBottom = (isBottomOrSmallViewport, config) => {
19
+ const isSuppressedActive = config.preliminaryData.some(pd => pd.label) && !config.legend.hideSuppressionLink
20
+
21
+ const isLegendTop = config.legend?.position === 'top' && !config.legend.hide
22
+
23
+ let marginBottom = '0px'
24
+ if (isLegendTop && !isSuppressedActive) {
25
+ marginBottom = config.legend.hideBorder.topBottom ? '15px' : '25px'
26
+ }
27
+ if (isLegendTop && isSuppressedActive) {
28
+ marginBottom = '75px'
29
+ }
30
+ if (isBottomOrSmallViewport && isSuppressedActive) {
31
+ marginBottom = '45px'
32
+ }
33
+
34
+ return marginBottom
35
+ }