@cdc/chart 4.25.3 → 4.25.5-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +36051 -36995
- package/index.html +2 -1
- package/package.json +22 -27
- package/src/CdcChartComponent.tsx +10 -10
- package/src/_stories/Chart.CI.stories.tsx +10 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
- package/src/_stories/Chart.stories.tsx +7 -0
- package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
- package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
- package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
- package/src/components/AreaChart/components/AreaChart.jsx +33 -5
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +50 -40
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +19 -8
- package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -30
- package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
- package/src/components/BarChart/components/context.tsx +20 -2
- package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
- package/src/components/BarChart/helpers/index.ts +5 -2
- package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
- package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
- package/src/components/BoxPlot/BoxPlot.tsx +2 -1
- package/src/components/DeviationBar.jsx +2 -1
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +34 -34
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +51 -25
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +10 -3
- package/src/components/EditorPanel/useEditorPermissions.ts +1 -4
- package/src/components/ForestPlot/ForestPlot.tsx +2 -2
- package/src/components/Legend/Legend.Component.tsx +1 -1
- package/src/components/Legend/Legend.Suppression.tsx +12 -22
- package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
- package/src/components/LineChart/LineChartProps.ts +3 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
- package/src/components/LineChart/helpers.ts +133 -56
- package/src/components/LineChart/index.tsx +107 -53
- package/src/components/LinearChart.tsx +40 -89
- package/src/components/PairedBarChart.jsx +3 -2
- package/src/components/PieChart/PieChart.tsx +71 -91
- package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
- package/src/components/Sparkline/components/SparkLine.tsx +80 -18
- package/src/components/ZoomBrush.tsx +4 -4
- package/src/data/initial-state.js +3 -1
- package/src/helpers/countNumOfTicks.ts +1 -1
- package/src/helpers/dataHelpers.ts +23 -2
- package/src/helpers/sizeHelpers.ts +1 -1
- package/src/hooks/useMinMax.ts +21 -28
- package/src/hooks/useRightAxis.ts +4 -2
- package/src/hooks/useScales.ts +10 -6
- package/src/hooks/useTooltip.tsx +204 -203
- package/src/index.jsx +2 -2
- package/src/scss/main.scss +13 -6
- package/src/types/ChartConfig.ts +5 -0
- package/LICENSE +0 -201
- package/examples/private/DEV-8850-2.json +0 -493
- package/examples/private/DEV-9822.json +0 -574
- package/examples/private/DEV-9840.json +0 -553
- package/examples/private/DEV-9850-3.json +0 -461
- package/examples/private/chart.json +0 -1084
- package/examples/private/ci_formatted.json +0 -202
- package/examples/private/ci_issue.json +0 -3016
- package/examples/private/completed.json +0 -634
- package/examples/private/dem-data-long.csv +0 -20
- package/examples/private/dem-data-long.json +0 -36
- package/examples/private/demographic_data.csv +0 -157
- package/examples/private/demographic_data.json +0 -2654
- package/examples/private/demographic_dynamic.json +0 -443
- package/examples/private/demographic_standard.json +0 -560
- package/examples/private/ehdi.json +0 -29939
- package/examples/private/not-loading.json +0 -360
- package/examples/private/test.json +0 -493
|
@@ -9,6 +9,7 @@ interface BarConfigProps {
|
|
|
9
9
|
config: { [key: string]: any }
|
|
10
10
|
barWidth: number
|
|
11
11
|
isVertical: boolean
|
|
12
|
+
yAxisValue: number
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
// Function to create bar width based on suppression status and missing data label
|
|
@@ -18,7 +19,8 @@ export const getBarConfig = ({
|
|
|
18
19
|
defaultBarWidth,
|
|
19
20
|
config,
|
|
20
21
|
barWidth,
|
|
21
|
-
isVertical
|
|
22
|
+
isVertical,
|
|
23
|
+
yAxisValue
|
|
22
24
|
}: BarConfigProps) => {
|
|
23
25
|
const heightMini = 3 /// height of small bars aka suppressed/NA/Zero valued
|
|
24
26
|
let barHeight = defaultBarHeight
|
|
@@ -97,7 +99,8 @@ export const getBarConfig = ({
|
|
|
97
99
|
return labelFits && isVertical ? label : !isVertical ? label : ''
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
|
-
|
|
102
|
+
const absentDataLabel = getAbsentDataLabel(yAxisValue)
|
|
103
|
+
return { barWidthHorizontal, barHeight, isSuppressed, showMissingDataLabel, getBarY, absentDataLabel }
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
export const testZeroValue = value => {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getHorizontalBarHeights } from '../getBarHeights'
|
|
2
|
+
|
|
3
|
+
describe('getHorizontalBarHeights', () => {
|
|
4
|
+
it('should calculate bar heights for non-stacked horizontal bars', () => {
|
|
5
|
+
const config = {
|
|
6
|
+
orientation: 'horizontal',
|
|
7
|
+
visualizationSubType: 'grouped',
|
|
8
|
+
runtime: { seriesKeys: ['A', 'B'] },
|
|
9
|
+
barHeight: 10,
|
|
10
|
+
barSpace: 5,
|
|
11
|
+
yAxis: { labelPlacement: 'Above Bar' }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const bars = [{ index: 0 }, { index: 1 }]
|
|
15
|
+
|
|
16
|
+
const result = getHorizontalBarHeights(config, bars)
|
|
17
|
+
|
|
18
|
+
expect(result).toEqual([
|
|
19
|
+
{ index: 0, y: 0, height: 20 },
|
|
20
|
+
{ index: 1, y: 25, height: 20 }
|
|
21
|
+
])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should calculate bar heights for stacked horizontal bars', () => {
|
|
25
|
+
const config = {
|
|
26
|
+
orientation: 'horizontal',
|
|
27
|
+
visualizationSubType: 'stacked',
|
|
28
|
+
barHeight: 15,
|
|
29
|
+
barSpace: 5,
|
|
30
|
+
yAxis: { labelPlacement: 'Above Bar' }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const bars = [{ index: 0 }, { index: 1 }]
|
|
34
|
+
|
|
35
|
+
const result = getHorizontalBarHeights(config, bars)
|
|
36
|
+
|
|
37
|
+
expect(result).toEqual([
|
|
38
|
+
{ index: 0, y: 0, height: 15 },
|
|
39
|
+
{ index: 1, y: 20, height: 15 }
|
|
40
|
+
])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should include label space when labelPlacement is Below Bar', () => {
|
|
44
|
+
const config = {
|
|
45
|
+
orientation: 'horizontal',
|
|
46
|
+
visualizationSubType: 'grouped',
|
|
47
|
+
runtime: { seriesKeys: ['A'] },
|
|
48
|
+
barHeight: 10,
|
|
49
|
+
barSpace: 5,
|
|
50
|
+
yAxis: { labelPlacement: 'Below Bar' }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const bars = [{ index: 0 }, { index: 1 }]
|
|
54
|
+
|
|
55
|
+
const result = getHorizontalBarHeights(config, bars)
|
|
56
|
+
|
|
57
|
+
expect(result).toEqual([
|
|
58
|
+
{ index: 0, y: 0, height: 10 },
|
|
59
|
+
{ index: 1, y: 37, height: 10 }
|
|
60
|
+
])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should handle lollipop chart bar heights', () => {
|
|
64
|
+
const config = {
|
|
65
|
+
orientation: 'horizontal',
|
|
66
|
+
visualizationSubType: 'grouped',
|
|
67
|
+
runtime: { seriesKeys: ['A', 'B', 'C'] },
|
|
68
|
+
isLollipopChart: true,
|
|
69
|
+
lollipopSize: 'medium',
|
|
70
|
+
barSpace: 5,
|
|
71
|
+
yAxis: { labelPlacement: 'Above Bar' }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const bars = [{ index: 0 }, { index: 1 }]
|
|
75
|
+
|
|
76
|
+
const result = getHorizontalBarHeights(config, bars)
|
|
77
|
+
|
|
78
|
+
expect(result).toEqual([
|
|
79
|
+
{ index: 0, y: 0, height: 18 },
|
|
80
|
+
{ index: 1, y: 23, height: 18 }
|
|
81
|
+
])
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useState } from 'react'
|
|
2
|
-
import
|
|
2
|
+
import { ChartDispatchContext } from '../../../ConfigContext'
|
|
3
3
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const dispatch = useContext(ChartDispatchContext)
|
|
4
|
+
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
5
|
+
|
|
6
|
+
export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, configContext) => {
|
|
7
|
+
const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, seriesHighlight } = configContext
|
|
9
8
|
const { orientation } = config
|
|
9
|
+
const dispatch = useContext(ChartDispatchContext)
|
|
10
10
|
const [hoveredBar, setHoveredBar] = useState(null)
|
|
11
11
|
|
|
12
12
|
const isHorizontal = orientation === 'horizontal'
|
|
@@ -123,43 +123,6 @@ export const useBarChart = () => {
|
|
|
123
123
|
const barColor = palette[barIndex]
|
|
124
124
|
return barColor
|
|
125
125
|
}
|
|
126
|
-
const updateBars = defaultBars => {
|
|
127
|
-
// function updates stacked && regular && lollipop horizontal bars
|
|
128
|
-
if (config.visualizationType !== 'Bar' && !isHorizontal) return defaultBars
|
|
129
|
-
|
|
130
|
-
const barsArr = [...defaultBars]
|
|
131
|
-
let barHeight
|
|
132
|
-
|
|
133
|
-
const heights = {
|
|
134
|
-
stacked: config.barHeight,
|
|
135
|
-
lollipop: lollipopBarWidth
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (!isStacked) {
|
|
139
|
-
barHeight = heights[config.isLollipopChart ? 'lollipop' : 'stacked'] * stackCount
|
|
140
|
-
} else {
|
|
141
|
-
barHeight = heights.stacked
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const labelHeight = isLabelBelowBar ? appFontSize * 1.2 : 0
|
|
145
|
-
let barSpace = Number(config.barSpace)
|
|
146
|
-
|
|
147
|
-
// calculate height of container based height, space and fontSize of labels
|
|
148
|
-
let totalHeight = barsArr.length * (barHeight + labelHeight + barSpace)
|
|
149
|
-
|
|
150
|
-
if (isHorizontal) {
|
|
151
|
-
config.heights.horizontal = totalHeight
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// return new updated bars/groupes
|
|
155
|
-
return barsArr.map((bar, i) => {
|
|
156
|
-
// set bars Y dynamically to handle space between bars
|
|
157
|
-
let y = 0
|
|
158
|
-
bar.index !== 0 && (y = (barHeight + barSpace + labelHeight) * i)
|
|
159
|
-
|
|
160
|
-
return { ...bar, y: y, height: barHeight }
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
126
|
|
|
164
127
|
const getHighlightedBarColorByValue = value => {
|
|
165
128
|
const match = config?.highlightedBarValues.find(item => {
|
|
@@ -226,17 +189,18 @@ export const useBarChart = () => {
|
|
|
226
189
|
return additionalTooltipItems
|
|
227
190
|
}
|
|
228
191
|
|
|
229
|
-
const onMouseOverBar = (categoryValue, barKey) => {
|
|
192
|
+
const onMouseOverBar = (categoryValue, barKey, event, data) => {
|
|
230
193
|
if (config.legend.highlightOnHover && config.legend.behavior === 'highlight' && barKey) {
|
|
231
194
|
dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: [barKey] })
|
|
232
195
|
}
|
|
233
|
-
|
|
196
|
+
handleTooltipMouseOver(event, data)
|
|
234
197
|
setHoveredBar(categoryValue)
|
|
235
198
|
}
|
|
236
199
|
const onMouseLeaveBar = () => {
|
|
237
200
|
if (config.legend.highlightOnHover && config.legend.behavior === 'highlight') {
|
|
238
201
|
dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: [] })
|
|
239
202
|
}
|
|
203
|
+
handleTooltipMouseOff()
|
|
240
204
|
}
|
|
241
205
|
|
|
242
206
|
return {
|
|
@@ -256,7 +220,6 @@ export const useBarChart = () => {
|
|
|
256
220
|
barStackedSeriesKeys,
|
|
257
221
|
hasMultipleSeries,
|
|
258
222
|
applyRadius,
|
|
259
|
-
updateBars,
|
|
260
223
|
assignColorsToValues,
|
|
261
224
|
getHighlightedBarColorByValue,
|
|
262
225
|
getHighlightedBarByValue,
|
|
@@ -5,6 +5,7 @@ import ConfigContext from '../../ConfigContext'
|
|
|
5
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
6
|
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
7
7
|
import { handleTooltip, createPlots } from './helpers/index'
|
|
8
|
+
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
8
9
|
import _ from 'lodash'
|
|
9
10
|
|
|
10
11
|
const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
|
|
@@ -15,7 +16,7 @@ const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
|
|
|
15
16
|
const boxWidth = xScale.bandwidth()
|
|
16
17
|
|
|
17
18
|
const bodyStyles = getComputedStyle(document.body)
|
|
18
|
-
const defaultColor =
|
|
19
|
+
const defaultColor = APP_FONT_COLOR
|
|
19
20
|
const constrainedWidth = Math.min(40, boxWidth)
|
|
20
21
|
const color_0 = _.get(colorPalettesChart, [config.palette, 0], '#000')
|
|
21
22
|
const plots = createPlots(data, config)
|
|
@@ -6,6 +6,7 @@ import { Text } from '@visx/text'
|
|
|
6
6
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
7
|
import useIntersectionObserver from '../hooks/useIntersectionObserver'
|
|
8
8
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
9
|
+
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
9
10
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
10
11
|
|
|
11
12
|
export default function DeviationBar({ height, xScale }) {
|
|
@@ -178,7 +179,7 @@ export default function DeviationBar({ height, xScale }) {
|
|
|
178
179
|
// colors
|
|
179
180
|
const [leftColor, rightColor] = twoColorPalette[twoColor.palette]
|
|
180
181
|
const barColor = { left: leftColor, right: rightColor }
|
|
181
|
-
const fill = getContrastColor(
|
|
182
|
+
const fill = getContrastColor(APP_FONT_COLOR, barColor[barPosition])
|
|
182
183
|
|
|
183
184
|
let textProps = getTextProps(config.isLollipopChart, textFits, lollipopShapeSize, fill)
|
|
184
185
|
// tooltips
|
|
@@ -113,7 +113,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
113
113
|
)}
|
|
114
114
|
</div>
|
|
115
115
|
)}
|
|
116
|
-
{(visualizationType === 'Bar' || visualizationType === 'Combo'
|
|
116
|
+
{(visualizationType === 'Bar' || visualizationType === 'Combo') && (
|
|
117
117
|
<Select
|
|
118
118
|
value={visualizationSubType || 'Regular'}
|
|
119
119
|
fieldName='visualizationSubType'
|
|
@@ -209,10 +209,8 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
209
209
|
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
210
210
|
</Tooltip.Target>
|
|
211
211
|
<Tooltip.Content>
|
|
212
|
-
<p>
|
|
213
|
-
|
|
214
|
-
</p>
|
|
215
|
-
<hr/>
|
|
212
|
+
<p>Recommended set to display for Section 508 compliance.</p>
|
|
213
|
+
<hr />
|
|
216
214
|
<p>
|
|
217
215
|
Selecting this option will <i> not </i> hide the display of "zero value", "suppressed data", or
|
|
218
216
|
"missing data" indicators on the chart (if applicable).
|
|
@@ -245,35 +243,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
245
243
|
label='Display "Zero Data" Label'
|
|
246
244
|
updateField={updateField}
|
|
247
245
|
/>
|
|
248
|
-
|
|
249
|
-
tooltip={
|
|
250
|
-
<Tooltip style={{ textTransform: 'none' }}>
|
|
251
|
-
<Tooltip.Target>
|
|
252
|
-
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
253
|
-
</Tooltip.Target>
|
|
254
|
-
<Tooltip.Content>
|
|
255
|
-
{config.visualizationSubType === 'stacked' && (
|
|
256
|
-
<p>
|
|
257
|
-
We do not recommend using stacked vertical/horizontal bar charts for missing data. If you choose
|
|
258
|
-
to proceed, selecting this option will display 'N/A' in the tooltip hover and data table (e.g.
|
|
259
|
-
nothing will display in chart).
|
|
260
|
-
</p>
|
|
261
|
-
)}
|
|
262
|
-
{config.visualizationSubType !== 'stacked' && (
|
|
263
|
-
<p>
|
|
264
|
-
Selecting this option will display 'N/A' on the Date/Category Axis, in the tooltip hover, and in
|
|
265
|
-
the data table to indicate missing or undefined data values.
|
|
266
|
-
</p>
|
|
267
|
-
)}
|
|
268
|
-
</Tooltip.Content>
|
|
269
|
-
</Tooltip>
|
|
270
|
-
}
|
|
271
|
-
value={config.general.showMissingDataLabel}
|
|
272
|
-
section='general'
|
|
273
|
-
fieldName='showMissingDataLabel'
|
|
274
|
-
label='Display "Missing Data" Label'
|
|
275
|
-
updateField={updateField}
|
|
276
|
-
/>
|
|
246
|
+
|
|
277
247
|
<CheckBox
|
|
278
248
|
display={config.visualizationType === 'Bar' || config.visualizationType === 'Combo'}
|
|
279
249
|
tooltip={
|
|
@@ -329,6 +299,36 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
329
299
|
</>
|
|
330
300
|
)}
|
|
331
301
|
|
|
302
|
+
<CheckBox
|
|
303
|
+
tooltip={
|
|
304
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
305
|
+
<Tooltip.Target>
|
|
306
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
307
|
+
</Tooltip.Target>
|
|
308
|
+
<Tooltip.Content>
|
|
309
|
+
{config.visualizationSubType === 'stacked' && (
|
|
310
|
+
<p>
|
|
311
|
+
We do not recommend using stacked vertical/horizontal bar charts for missing data. If you choose to
|
|
312
|
+
proceed, selecting this option will display 'N/A' in the tooltip hover and data table (e.g. nothing
|
|
313
|
+
will display in chart).
|
|
314
|
+
</p>
|
|
315
|
+
)}
|
|
316
|
+
{config.visualizationSubType !== 'stacked' && (
|
|
317
|
+
<p>
|
|
318
|
+
Selecting this option will display 'N/A' on the Date/Category Axis, in the tooltip hover, and in the
|
|
319
|
+
data table to indicate missing or undefined data values.
|
|
320
|
+
</p>
|
|
321
|
+
)}
|
|
322
|
+
</Tooltip.Content>
|
|
323
|
+
</Tooltip>
|
|
324
|
+
}
|
|
325
|
+
value={config.general.showMissingDataLabel}
|
|
326
|
+
section='general'
|
|
327
|
+
fieldName='showMissingDataLabel'
|
|
328
|
+
label='Display "Missing Data" Label'
|
|
329
|
+
updateField={updateField}
|
|
330
|
+
/>
|
|
331
|
+
|
|
332
332
|
{visualizationType === 'Pie' && (
|
|
333
333
|
<Select fieldName='pieType' label='Pie Chart Type' updateField={updateField} options={['Regular', 'Donut']} />
|
|
334
334
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../../../ConfigContext'
|
|
3
3
|
|
|
4
4
|
// Core
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
AccordionItemPanel,
|
|
18
18
|
AccordionItemButton
|
|
19
19
|
} from 'react-accessible-accordion'
|
|
20
|
-
import { Draggable } from '@hello-pangea/dnd'
|
|
20
|
+
import { Draggable, DragDropContext, Droppable } from '@hello-pangea/dnd'
|
|
21
21
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
22
22
|
|
|
23
23
|
const SeriesContext = React.createContext({})
|
|
@@ -609,9 +609,20 @@ const SeriesButtonRemove = props => {
|
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
const SeriesItem = props => {
|
|
612
|
-
const { config } = useContext(ConfigContext)
|
|
612
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
613
613
|
const { updateSeries, getColumns } = useContext(SeriesContext)
|
|
614
614
|
const { series, getItemStyle, sortableItemStyles, chartsWithOptions, index: i } = props
|
|
615
|
+
const legendOrderOptions: { label: string; value: string }[] = [
|
|
616
|
+
{ label: 'Order By Data Column', value: 'dataColumn' },
|
|
617
|
+
{
|
|
618
|
+
label: 'Ascending Alphanumeric',
|
|
619
|
+
value: 'asc'
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
label: 'Descending Alphanumeric',
|
|
623
|
+
value: 'desc'
|
|
624
|
+
}
|
|
625
|
+
]
|
|
615
626
|
const showDynamicCategory =
|
|
616
627
|
['Bar', 'Line'].includes(config.visualizationType) &&
|
|
617
628
|
config.visualizationSubType !== 'Stacked' &&
|
|
@@ -646,28 +657,43 @@ const SeriesItem = props => {
|
|
|
646
657
|
<AccordionItemPanel>
|
|
647
658
|
<Series.Input.Name series={series} index={i} />
|
|
648
659
|
{showDynamicCategory && (
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
<Tooltip
|
|
660
|
-
<
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
<
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
660
|
+
<>
|
|
661
|
+
<Select
|
|
662
|
+
label='Dynamic Category'
|
|
663
|
+
value={series.dynamicCategory}
|
|
664
|
+
options={['- Select - ', ...getColumns().filter(col => series.dataKey !== col)]}
|
|
665
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
666
|
+
if (value === '- Select -') value = ''
|
|
667
|
+
updateSeries(i, value, 'dynamicCategory')
|
|
668
|
+
}}
|
|
669
|
+
tooltip={
|
|
670
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
671
|
+
<Tooltip.Target>
|
|
672
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
673
|
+
</Tooltip.Target>
|
|
674
|
+
<Tooltip.Content>
|
|
675
|
+
<p>
|
|
676
|
+
This field is Optional. If you have a dynamic data series you can select the category
|
|
677
|
+
field here. You can only add one dynamic category per visualization.
|
|
678
|
+
</p>
|
|
679
|
+
</Tooltip.Content>
|
|
680
|
+
</Tooltip>
|
|
681
|
+
}
|
|
682
|
+
/>
|
|
683
|
+
|
|
684
|
+
<>
|
|
685
|
+
<Select
|
|
686
|
+
value={config.legend.order}
|
|
687
|
+
options={legendOrderOptions}
|
|
688
|
+
section='legend'
|
|
689
|
+
fieldName='order'
|
|
690
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
691
|
+
updateConfig({ ...config, legend: { ...config.legend, order: value } })
|
|
692
|
+
}}
|
|
693
|
+
label='Dynamic Series Order'
|
|
694
|
+
/>
|
|
695
|
+
</>
|
|
696
|
+
</>
|
|
671
697
|
)}
|
|
672
698
|
<Series.Input.Weight series={series} index={i} />
|
|
673
699
|
<Series.Dropdown.SeriesType series={series} index={i} />
|
|
@@ -22,6 +22,7 @@ import { useEditorPermissions } from '../../useEditorPermissions.js'
|
|
|
22
22
|
import { useEditorPanelContext } from '../../EditorPanelContext.js'
|
|
23
23
|
import ConfigContext from '../../../../ConfigContext.js'
|
|
24
24
|
import { PanelProps } from '../PanelProps'
|
|
25
|
+
import { LineChartConfig } from '../../../../types/ChartConfig'
|
|
25
26
|
|
|
26
27
|
const PanelVisual: FC<PanelProps> = props => {
|
|
27
28
|
const { config, updateConfig, colorPalettes, twoColorPalette } = useContext<ChartContext>(ConfigContext)
|
|
@@ -147,9 +148,7 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
147
148
|
/>
|
|
148
149
|
</Tooltip.Target>
|
|
149
150
|
<Tooltip.Content>
|
|
150
|
-
<p>
|
|
151
|
-
Recommended set to display for Section 508 compliance.
|
|
152
|
-
</p>
|
|
151
|
+
<p>Recommended set to display for Section 508 compliance.</p>
|
|
153
152
|
</Tooltip.Content>
|
|
154
153
|
</Tooltip>
|
|
155
154
|
}
|
|
@@ -222,6 +221,14 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
222
221
|
updateField={updateField}
|
|
223
222
|
options={['Same as Line', 'Lighter than Line']}
|
|
224
223
|
/>
|
|
224
|
+
<CheckBox
|
|
225
|
+
value={!(config as LineChartConfig).isolatedDotsSameSize}
|
|
226
|
+
fieldName='isolatedDotsSameSize'
|
|
227
|
+
label='Accentuate isolated data points'
|
|
228
|
+
updateField={(section, subsection, fieldname, value) =>
|
|
229
|
+
updateField(section, subsection, fieldname, !value)
|
|
230
|
+
}
|
|
231
|
+
/>
|
|
225
232
|
</>
|
|
226
233
|
)}
|
|
227
234
|
{/* eslint-disable */}
|
|
@@ -358,10 +358,7 @@ export const useEditorPermissions = () => {
|
|
|
358
358
|
|
|
359
359
|
const visSupportsReactTooltip = () => {
|
|
360
360
|
if (config.yAxis.type === 'categorical') return true
|
|
361
|
-
if (
|
|
362
|
-
['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar'].includes(visualizationType) ||
|
|
363
|
-
(visualizationType === 'Bar' && config.tooltips.singleSeries)
|
|
364
|
-
) {
|
|
361
|
+
if (['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar'].includes(visualizationType)) {
|
|
365
362
|
return true
|
|
366
363
|
}
|
|
367
364
|
}
|
|
@@ -14,7 +14,7 @@ import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
|
|
|
14
14
|
|
|
15
15
|
// cdc
|
|
16
16
|
import ConfigContext from '../../ConfigContext'
|
|
17
|
-
import {
|
|
17
|
+
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
18
18
|
|
|
19
19
|
const ForestPlot = ({
|
|
20
20
|
xScale,
|
|
@@ -254,7 +254,7 @@ const ForestPlot = ({
|
|
|
254
254
|
<LinePath
|
|
255
255
|
data={regressionPoints}
|
|
256
256
|
x={d => d.x}
|
|
257
|
-
y={d => d.y -
|
|
257
|
+
y={d => d.y - APP_FONT_SIZE / 2}
|
|
258
258
|
stroke='black'
|
|
259
259
|
strokeWidth={2}
|
|
260
260
|
fill={'black'}
|
|
@@ -56,7 +56,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
56
56
|
const { series } = runtime
|
|
57
57
|
|
|
58
58
|
const seriesWithData = getSeriesWithData(config)
|
|
59
|
-
const dontFilterLegendItems = !series.length || legend.unified
|
|
59
|
+
const dontFilterLegendItems = !series.length || legend.unified || !seriesWithData.length
|
|
60
60
|
|
|
61
61
|
const isLegendBottom =
|
|
62
62
|
legend?.position === 'bottom' ||
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { ChartConfig } from '../../types/ChartConfig'
|
|
3
|
-
import
|
|
4
|
-
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
3
|
+
import RichTooltip from '@cdc/core/components/RichTooltip/RichTooltip'
|
|
5
4
|
interface LegendProps {
|
|
6
5
|
config: ChartConfig
|
|
7
6
|
isLegendBottom: boolean
|
|
@@ -35,10 +34,6 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
|
|
|
35
34
|
</div>
|
|
36
35
|
)
|
|
37
36
|
)
|
|
38
|
-
const handleLinkClick = event => {
|
|
39
|
-
// prevent defintion link to change URl
|
|
40
|
-
event.preventDefault()
|
|
41
|
-
}
|
|
42
37
|
|
|
43
38
|
const renderSuppressedItems = () => {
|
|
44
39
|
const getStyle = displayGray => {
|
|
@@ -121,27 +116,22 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
|
|
|
121
116
|
</React.Fragment>
|
|
122
117
|
)}
|
|
123
118
|
{shouldShowSuppressedInfo() && (
|
|
124
|
-
<div className='legend-container__outer
|
|
125
|
-
<Icon alt='info-icon' display='info' />
|
|
119
|
+
<div className='legend-container__outer link-container'>
|
|
126
120
|
<p>
|
|
127
121
|
This chart contains
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
suppressed data
|
|
135
|
-
|
|
122
|
+
<RichTooltip
|
|
123
|
+
tooltipContent={`Data is
|
|
124
|
+
suppressed to maintain statistical reliability.
|
|
125
|
+
This occurs when the number of respondents or
|
|
126
|
+
reported values does not meet the minimum
|
|
127
|
+
reporting threshold.`}
|
|
128
|
+
linkText='suppressed data'
|
|
129
|
+
href={null}
|
|
130
|
+
tooltipOpacity={config.tooltips.opacity}
|
|
131
|
+
/>
|
|
136
132
|
</p>
|
|
137
133
|
</div>
|
|
138
134
|
)}
|
|
139
|
-
|
|
140
|
-
<ReactTooltip // prettier-ignore
|
|
141
|
-
id='my-tooltip'
|
|
142
|
-
variant='light'
|
|
143
|
-
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black', maxWidth: '100%' }}
|
|
144
|
-
/>
|
|
145
135
|
</React.Fragment>
|
|
146
136
|
)
|
|
147
137
|
}
|
|
@@ -17,8 +17,36 @@ export const createFormatLabels =
|
|
|
17
17
|
})
|
|
18
18
|
: labels
|
|
19
19
|
const reverseLabels = labels => {
|
|
20
|
+
if (config.series.some(series => series.dynamicCategory)) {
|
|
21
|
+
return orderDynamicLabels(labels)
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
return config.legend.reverseLabelOrder ? sortVertical(labels).reverse() : sortVertical(labels)
|
|
21
25
|
}
|
|
26
|
+
|
|
27
|
+
const orderDynamicLabels = labels => {
|
|
28
|
+
// Handle different ordering configurations
|
|
29
|
+
switch (config.legend.order) {
|
|
30
|
+
case 'dataColumn':
|
|
31
|
+
return labels
|
|
32
|
+
case 'asc':
|
|
33
|
+
case 'desc':
|
|
34
|
+
return labels.sort((a, b) => {
|
|
35
|
+
const valA = a.datum || a.text
|
|
36
|
+
const valB = b.datum || b.text
|
|
37
|
+
const numA = parseFloat(valA)
|
|
38
|
+
const numB = parseFloat(valB)
|
|
39
|
+
if (!isNaN(numA) && !isNaN(numB)) {
|
|
40
|
+
return config.legend.order === 'asc' ? numA - numB : numB - numA
|
|
41
|
+
} else {
|
|
42
|
+
return config.legend.order === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
default:
|
|
47
|
+
return labels // Default case to handle any unexpected config.legend.order values
|
|
48
|
+
}
|
|
49
|
+
}
|
|
22
50
|
const colorCode = config.legend?.colorCode
|
|
23
51
|
if (visualizationType === 'Deviation Bar') {
|
|
24
52
|
const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
|
|
@@ -29,9 +29,11 @@ export interface StyleProps {
|
|
|
29
29
|
handleLineType: Function
|
|
30
30
|
lineType: string
|
|
31
31
|
preliminaryData: PreliminaryDataItem[]
|
|
32
|
-
seriesKey:
|
|
32
|
+
seriesKey: string
|
|
33
33
|
stroke: string
|
|
34
34
|
strokeWidth: number
|
|
35
|
+
dynamicCategory: string
|
|
36
|
+
originalSeriesKey: string
|
|
35
37
|
}
|
|
36
38
|
export interface Style {
|
|
37
39
|
stroke: string
|