@cdc/chart 4.24.11 → 4.24.12
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 +31248 -31198
- package/examples/feature/sankey/sankey-example-data.json +126 -13
- package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
- package/examples/private/DEV-8850-2.json +493 -0
- package/examples/private/DEV-9822.json +574 -0
- package/examples/private/DEV-9840.json +553 -0
- package/examples/private/DEV-9850-3.json +461 -0
- package/examples/private/chart.json +1084 -0
- package/examples/private/ci_formatted.json +202 -0
- package/examples/private/ci_issue.json +3016 -0
- package/examples/private/completed.json +634 -0
- package/examples/private/dem-data-long.csv +20 -0
- package/examples/private/dem-data-long.json +36 -0
- package/examples/private/demographic_data.csv +157 -0
- package/examples/private/demographic_data.json +2654 -0
- package/examples/private/demographic_dynamic.json +443 -0
- package/examples/private/demographic_standard.json +560 -0
- package/examples/private/test.json +448 -20047
- package/index.html +9 -6
- package/package.json +2 -2
- package/src/CdcChart.tsx +62 -82
- package/src/_stories/Chart.Anchors.stories.tsx +31 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +8 -1
- package/src/_stories/Chart.stories.tsx +32 -0
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
- package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
- package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
- package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
- package/src/_stories/_mock/short_dates.json +288 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
- package/src/components/BarChart/components/BarChart.Vertical.tsx +28 -40
- package/src/components/BarChart/helpers/getBarData.ts +28 -0
- package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
- package/src/components/BoxPlot/BoxPlot.tsx +12 -70
- package/src/components/BoxPlot/helpers/index.ts +54 -0
- package/src/components/BrushChart.tsx +23 -26
- package/src/components/EditorPanel/EditorPanel.tsx +55 -79
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +1 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +5 -1
- package/src/components/Legend/Legend.Component.tsx +2 -2
- package/src/{hooks/useLegendClasses.ts → components/Legend/helpers/getLegendClasses.ts} +5 -5
- package/src/components/Legend/helpers/index.ts +2 -1
- package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
- package/src/components/LineChart/helpers.ts +1 -0
- package/src/components/LineChart/index.tsx +47 -1
- package/src/components/LinearChart.tsx +171 -172
- package/src/components/PieChart/PieChart.tsx +7 -1
- package/src/components/Sankey/components/ColumnList.tsx +19 -0
- package/src/components/Sankey/components/Sankey.tsx +479 -0
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
- package/src/components/Sankey/index.tsx +1 -510
- package/src/components/Sankey/sankey.scss +16 -16
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/data/initial-state.js +4 -3
- package/src/helpers/countNumOfTicks.ts +57 -0
- package/src/helpers/getQuartiles.ts +15 -18
- package/src/hooks/useMinMax.ts +18 -4
- package/src/hooks/useScales.ts +38 -4
- package/src/hooks/useTooltip.tsx +5 -1
- package/src/scss/DataTable.scss +5 -0
- package/src/scss/main.scss +6 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { getBarData } from '../getBarData'
|
|
2
|
+
|
|
3
|
+
import { TransformedData } from '../../../../types/ChartContext'
|
|
4
|
+
import { ChartConfig } from '../../../../types/ChartConfig'
|
|
5
|
+
|
|
6
|
+
describe('getBarData', () => {
|
|
7
|
+
it('should return the original data when there is no dynamicSeries', () => {
|
|
8
|
+
const config = {
|
|
9
|
+
series: [{ dataKey: 'value' }],
|
|
10
|
+
runtime: { originalXAxis: { dataKey: 'category' } }
|
|
11
|
+
} as ChartConfig
|
|
12
|
+
const data: TransformedData[] = [
|
|
13
|
+
{ category: 'A', value: 10 },
|
|
14
|
+
{ category: 'B', value: 20 }
|
|
15
|
+
]
|
|
16
|
+
const hasConfidenceInterval = false
|
|
17
|
+
|
|
18
|
+
const result = getBarData(config, data, hasConfidenceInterval)
|
|
19
|
+
|
|
20
|
+
expect(result).toEqual(data)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should return transformed data when there is a dynamicSeries but no confidence interval', () => {
|
|
24
|
+
const config = {
|
|
25
|
+
series: [{ dataKey: 'value', dynamicCategory: 'subCategory' }],
|
|
26
|
+
runtime: { originalXAxis: { dataKey: 'category' } }
|
|
27
|
+
} as ChartConfig
|
|
28
|
+
const data: TransformedData[] = [
|
|
29
|
+
{ category: 'A', subCategory: 'X', value: 10 },
|
|
30
|
+
{ category: 'A', subCategory: 'Y', value: 20 },
|
|
31
|
+
{ category: 'B', subCategory: 'X', value: 30 }
|
|
32
|
+
]
|
|
33
|
+
const hasConfidenceInterval = false
|
|
34
|
+
|
|
35
|
+
const result = getBarData(config, data, hasConfidenceInterval)
|
|
36
|
+
|
|
37
|
+
expect(result).toEqual([
|
|
38
|
+
{ category: 'A', X: 10, Y: 20, dynamicData: true },
|
|
39
|
+
{ category: 'B', X: 30, dynamicData: true }
|
|
40
|
+
])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should return transformed data with confidence intervals when there is a dynamicSeries and confidence interval', () => {
|
|
44
|
+
const config = {
|
|
45
|
+
series: [{ dataKey: 'value', dynamicCategory: 'subCategory' }],
|
|
46
|
+
runtime: { originalXAxis: { dataKey: 'category' } },
|
|
47
|
+
confidenceKeys: { lower: 'lowerCI', upper: 'upperCI' }
|
|
48
|
+
} as ChartConfig
|
|
49
|
+
const data: TransformedData[] = [
|
|
50
|
+
{ category: 'A', subCategory: 'X', value: 10, lowerCI: 5, upperCI: 15 },
|
|
51
|
+
{ category: 'A', subCategory: 'Y', value: 20, lowerCI: 15, upperCI: 25 },
|
|
52
|
+
{ category: 'B', subCategory: 'X', value: 30, lowerCI: 25, upperCI: 35 }
|
|
53
|
+
]
|
|
54
|
+
const hasConfidenceInterval = true
|
|
55
|
+
|
|
56
|
+
const result = getBarData(config, data, hasConfidenceInterval)
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual([
|
|
59
|
+
{
|
|
60
|
+
category: 'A',
|
|
61
|
+
X: 10,
|
|
62
|
+
Y: 20,
|
|
63
|
+
CI: { X: { lower: 5, upper: 15 }, Y: { lower: 15, upper: 25 } },
|
|
64
|
+
dynamicData: true
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
category: 'B',
|
|
68
|
+
X: 30,
|
|
69
|
+
CI: { X: { lower: 25, upper: 35 } },
|
|
70
|
+
dynamicData: true
|
|
71
|
+
}
|
|
72
|
+
])
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -4,81 +4,22 @@ import { Group } from '@visx/group'
|
|
|
4
4
|
import ConfigContext from '../../ConfigContext'
|
|
5
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
6
|
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
7
|
-
import {
|
|
7
|
+
import { handleTooltip, calculateBoxPlotStats, createPlots } from './helpers/index'
|
|
8
8
|
import _ from 'lodash'
|
|
9
|
-
|
|
10
|
-
const CoveBoxPlot = ({ xScale, yScale }) => {
|
|
9
|
+
|
|
10
|
+
const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
|
|
11
11
|
const { config, colorScale, seriesHighlight, transformedData: data } = useContext(ConfigContext)
|
|
12
12
|
const { boxplot } = config
|
|
13
13
|
|
|
14
|
-
// tooltips
|
|
15
14
|
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
16
|
-
|
|
17
|
-
const handleTooltip = (d, key, q1, q3, median, iqr) => {
|
|
18
|
-
return `
|
|
19
|
-
<strong>${d.columnCategory}</strong></br>
|
|
20
|
-
<strong>Key:${key}</strong></br>
|
|
21
|
-
${boxplot.labels.q1}: ${q1}<br/>
|
|
22
|
-
${boxplot.labels.q3}: ${q3}<br/>
|
|
23
|
-
${boxplot.labels.iqr}: ${iqr}<br/>
|
|
24
|
-
${boxplot.labels.median}: ${median}
|
|
25
|
-
`
|
|
26
|
-
}
|
|
27
|
-
|
|
28
15
|
const boxWidth = xScale.bandwidth()
|
|
29
16
|
const constrainedWidth = Math.min(40, boxWidth)
|
|
30
|
-
const color_0 = colorPalettesChart[config
|
|
31
|
-
const seriesScale = scaleBand({
|
|
32
|
-
range: [0, config.barThickness * 100 || 1],
|
|
33
|
-
domain: config.series?.map(item => item?.dataKey)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const calculateBoxPlotStats = values => {
|
|
37
|
-
if (!values || !values.length) return {}
|
|
38
|
-
const sortedValues = values.sort((a, b) => a - b)
|
|
39
|
-
return {
|
|
40
|
-
min: min(values),
|
|
41
|
-
max: max(values),
|
|
42
|
-
median: median(values),
|
|
43
|
-
firstQuartile: quantile(sortedValues, 0.25),
|
|
44
|
-
thirdQuartile: quantile(sortedValues, 0.75)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const getValuesBySeriesKey = (group: string) => {
|
|
49
|
-
const allSeriesKeys = config.series.map(item => item?.dataKey)
|
|
50
|
-
const result = {}
|
|
51
|
-
const filteredData = data.filter(item => item[config.xAxis.dataKey] === group)
|
|
52
|
-
allSeriesKeys.forEach(key => {
|
|
53
|
-
result[key] = filteredData.map(item => item[key])
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
return result
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface Plot {
|
|
60
|
-
columnCategory: string
|
|
61
|
-
keyValues: { [key: string]: number[] }
|
|
62
|
-
}
|
|
63
|
-
const createPlots = data => {
|
|
64
|
-
const dataKeys = data.map(d => d[config.xAxis.dataKey])
|
|
65
|
-
const plots: Plot[] = []
|
|
66
|
-
const groups: string[] = _.uniq(dataKeys)
|
|
67
|
-
if (groups && groups.length > 0) {
|
|
68
|
-
groups.forEach(group => {
|
|
69
|
-
plots.push({
|
|
70
|
-
columnCategory: group,
|
|
71
|
-
keyValues: getValuesBySeriesKey(group)
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
return plots
|
|
76
|
-
}
|
|
17
|
+
const color_0 = _.get(colorPalettesChart, [config.palette, 0], '#000')
|
|
77
18
|
|
|
78
19
|
return (
|
|
79
20
|
<ErrorBoundary component='BoxPlot'>
|
|
80
21
|
<Group left={Number(config.yAxis.size)} className='boxplot' key={`boxplot-group`}>
|
|
81
|
-
{createPlots(data).map((d, i) => {
|
|
22
|
+
{createPlots(data, config).map((d, i) => {
|
|
82
23
|
const offset = boxWidth - constrainedWidth
|
|
83
24
|
const radius = 4
|
|
84
25
|
|
|
@@ -87,12 +28,12 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
|
|
|
87
28
|
key={`boxplotplot-${d.columnCategory}`}
|
|
88
29
|
left={xScale(d.columnCategory) + (xScale.bandwidth() - seriesScale.bandwidth()) / 2}
|
|
89
30
|
>
|
|
90
|
-
{config.series.map(item => {
|
|
31
|
+
{config.series.map((item, index) => {
|
|
91
32
|
const valuesByKey = d.keyValues[item.dataKey]
|
|
92
33
|
const { min, max, median, firstQuartile, thirdQuartile } = calculateBoxPlotStats(valuesByKey)
|
|
93
34
|
let iqr = Number(thirdQuartile - firstQuartile).toFixed(config.dataFormat.roundTo)
|
|
94
35
|
|
|
95
|
-
const
|
|
36
|
+
const isTransparent =
|
|
96
37
|
config.legend.behavior === 'highlight' &&
|
|
97
38
|
seriesHighlight.length > 0 &&
|
|
98
39
|
seriesHighlight.indexOf(item.dataKey) === -1
|
|
@@ -100,10 +41,10 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
|
|
|
100
41
|
config.legend.behavior === 'highlight' ||
|
|
101
42
|
seriesHighlight.length === 0 ||
|
|
102
43
|
seriesHighlight.indexOf(item.dataKey) !== -1
|
|
103
|
-
const fillOpacity =
|
|
44
|
+
const fillOpacity = isTransparent ? 0.3 : 0.5
|
|
104
45
|
|
|
105
46
|
return (
|
|
106
|
-
<Group key={`boxplotplot-${item}`}>
|
|
47
|
+
<Group key={`boxplotplot-${item.dataKey}-${index}`}>
|
|
107
48
|
{boxplot.plotNonOutlierValues &&
|
|
108
49
|
valuesByKey.map((value, index) => {
|
|
109
50
|
return (
|
|
@@ -123,8 +64,8 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
|
|
|
123
64
|
display={displayPlot ? 'block' : 'none'}
|
|
124
65
|
data-left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
|
|
125
66
|
key={`box-plot-${i}-${item}`}
|
|
126
|
-
min={min}
|
|
127
|
-
max={max}
|
|
67
|
+
min={Number(min)}
|
|
68
|
+
max={Number(max)}
|
|
128
69
|
left={seriesScale(item.dataKey)}
|
|
129
70
|
firstQuartile={firstQuartile}
|
|
130
71
|
thirdQuartile={thirdQuartile}
|
|
@@ -163,6 +104,7 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
|
|
|
163
104
|
container
|
|
164
105
|
containerProps={{
|
|
165
106
|
'data-tooltip-html': handleTooltip(
|
|
107
|
+
boxplot,
|
|
166
108
|
d,
|
|
167
109
|
item.dataKey,
|
|
168
110
|
firstQuartile,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { max, min, median, quantile } from 'd3-array'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
|
|
4
|
+
interface Plot {
|
|
5
|
+
columnCategory: string
|
|
6
|
+
keyValues: { [key: string]: number[] }
|
|
7
|
+
}
|
|
8
|
+
export const handleTooltip = (boxplot, d, key, q1, q3, median, iqr) => {
|
|
9
|
+
return `
|
|
10
|
+
<strong>${d.columnCategory}</strong></br>
|
|
11
|
+
<strong>Key:${key}</strong></br>
|
|
12
|
+
${boxplot.labels.q1}: ${q1}<br/>
|
|
13
|
+
${boxplot.labels.q3}: ${q3}<br/>
|
|
14
|
+
${boxplot.labels.iqr}: ${iqr}<br/>
|
|
15
|
+
${boxplot.labels.median}: ${median}
|
|
16
|
+
`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const calculateBoxPlotStats = values => {
|
|
20
|
+
if (!values || !values.length) return {}
|
|
21
|
+
const sortedValues = _.sortBy(values)
|
|
22
|
+
return {
|
|
23
|
+
min: min(values),
|
|
24
|
+
max: max(values),
|
|
25
|
+
median: median(values),
|
|
26
|
+
firstQuartile: quantile(sortedValues, 0.25),
|
|
27
|
+
thirdQuartile: quantile(sortedValues, 0.75)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const getValuesBySeriesKey = (group: string, config, data) => {
|
|
31
|
+
const allSeriesKeys = config.series.map(item => item?.dataKey)
|
|
32
|
+
const result = {}
|
|
33
|
+
const filteredData = data.filter(item => item[config.xAxis.dataKey] === group)
|
|
34
|
+
allSeriesKeys.forEach(key => {
|
|
35
|
+
result[key] = filteredData.map(item => item[key])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return result
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const createPlots = (data, config) => {
|
|
42
|
+
const dataKeys = data.map(d => d[config.xAxis.dataKey])
|
|
43
|
+
const plots: Plot[] = []
|
|
44
|
+
const groups: string[] = _.uniq(dataKeys)
|
|
45
|
+
if (groups && groups.length > 0) {
|
|
46
|
+
groups.forEach(group => {
|
|
47
|
+
plots.push({
|
|
48
|
+
columnCategory: group,
|
|
49
|
+
keyValues: getValuesBySeriesKey(group, config, data)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
return plots
|
|
54
|
+
}
|
|
@@ -4,6 +4,7 @@ import ConfigContext from '../ConfigContext'
|
|
|
4
4
|
import * as d3 from 'd3'
|
|
5
5
|
import { Text } from '@visx/text'
|
|
6
6
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
7
|
+
import _ from 'lodash'
|
|
7
8
|
|
|
8
9
|
interface BrushChartProps {
|
|
9
10
|
xMax: number
|
|
@@ -11,7 +12,7 @@ interface BrushChartProps {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
|
|
14
|
-
const { tableData, config, setBrushConfig, dashboardConfig, formatDate } = useContext(ConfigContext)
|
|
15
|
+
const { tableData, config, setBrushConfig, dashboardConfig, formatDate, parseDate } = useContext(ConfigContext)
|
|
15
16
|
const [brushState, setBrushState] = useState({ isBrushing: false, selection: [] })
|
|
16
17
|
const [brushKey, setBrushKey] = useState(0)
|
|
17
18
|
const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
|
|
@@ -26,9 +27,15 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
|
|
|
26
27
|
|
|
27
28
|
const tooltipText = 'Drag edges to focus on a specific segment '
|
|
28
29
|
const textWidth = getTextWidth(tooltipText, `normal ${16 / 1.1}px sans-serif`)
|
|
30
|
+
const DASHBOARD_MARGIN = 50
|
|
31
|
+
const BRUSH_HEIGHT_MULTIPLIER = 1.5
|
|
29
32
|
|
|
30
33
|
const calculateGroupTop = (): number => {
|
|
31
|
-
|
|
34
|
+
if (dashboardConfig?.type === 'dashboard') {
|
|
35
|
+
return Number(yMax) + config.xAxis.axisBBox + brushheight * BRUSH_HEIGHT_MULTIPLIER + DASHBOARD_MARGIN
|
|
36
|
+
} else {
|
|
37
|
+
return Number(yMax) + config.xAxis.axisBBox + brushheight * BRUSH_HEIGHT_MULTIPLIER
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
const handleMouseOver = () => {
|
|
@@ -85,41 +92,31 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
|
|
|
85
92
|
|
|
86
93
|
const [x0, x1] = selection.map(value => xScale.invert(value))
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (!dateValue) {
|
|
93
|
-
return false
|
|
94
|
-
}
|
|
95
|
+
const newFilteredData = _.filter(tableData, d => {
|
|
96
|
+
const parsedDate = new Date(d[config.xAxis.dataKey])
|
|
97
|
+
return parsedDate && !isNaN(parsedDate.getTime()) && parsedDate >= x0 && parsedDate <= x1
|
|
98
|
+
})
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
const sortByRecentDate = config.xAxis.sortByRecentDate
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
if (isNaN(parsedDate.getTime())) {
|
|
100
|
-
return false
|
|
101
|
-
}
|
|
102
|
+
const sortedData = _.sortBy(newFilteredData, item => new Date(item[config.xAxis.dataKey]))
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return true
|
|
106
|
-
}
|
|
107
|
-
})
|
|
104
|
+
// If ascending is false, reverse the sorted array
|
|
105
|
+
const finalData = !sortByRecentDate ? sortedData : sortedData.reverse()
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
newFilteredData[newFilteredData.length - 1][config?.runtime?.originalXAxis?.dataKey]) ??
|
|
113
|
-
''
|
|
107
|
+
// Retrieve the start and end dates based on the sorted data array
|
|
108
|
+
const startDate = _.get(_.first(finalData), config.xAxis.dataKey, '')
|
|
109
|
+
const endDate = _.get(_.last(finalData), config.xAxis.dataKey, '')
|
|
114
110
|
// add custom blue colored handlers to each corners of brush
|
|
115
111
|
svg.selectAll('.handle--custom').remove()
|
|
116
112
|
// append handler
|
|
117
|
-
|
|
113
|
+
const [formattedStartDate, formattedEndDate] = [startDate, endDate].map(date => formatDate(parseDate(date)))
|
|
114
|
+
svg.call(brushHandle, selection, formattedStartDate, formattedEndDate)
|
|
118
115
|
|
|
119
116
|
setBrushConfig({
|
|
120
117
|
active: config.brush.active,
|
|
121
118
|
isBrushing: isUserBrushing,
|
|
122
|
-
data:
|
|
119
|
+
data: finalData
|
|
123
120
|
})
|
|
124
121
|
setBrushState({
|
|
125
122
|
isBrushing: true,
|
|
@@ -647,7 +647,8 @@ const EditorPanel = () => {
|
|
|
647
647
|
visSupportsValueAxisLine,
|
|
648
648
|
visSupportsValueAxisMax,
|
|
649
649
|
visSupportsValueAxisMin,
|
|
650
|
-
visSupportsValueAxisTicks
|
|
650
|
+
visSupportsValueAxisTicks,
|
|
651
|
+
visSupportsYPadding
|
|
651
652
|
} = useEditorPermissions()
|
|
652
653
|
|
|
653
654
|
// when the visualization type changes we
|
|
@@ -1346,6 +1347,9 @@ const EditorPanel = () => {
|
|
|
1346
1347
|
updateConfig(updatedConfig)
|
|
1347
1348
|
}
|
|
1348
1349
|
|
|
1350
|
+
const hasDynamicCategory = ![undefined, '- Select - '].includes(config.series?.[0]?.dynamicCategory)
|
|
1351
|
+
const hasMultipleSeries = config.series?.length > 1
|
|
1352
|
+
|
|
1349
1353
|
const editorContextValues = {
|
|
1350
1354
|
addNewExclusion,
|
|
1351
1355
|
data,
|
|
@@ -1496,7 +1500,8 @@ const EditorPanel = () => {
|
|
|
1496
1500
|
</Panels.Series.Wrapper>
|
|
1497
1501
|
)}
|
|
1498
1502
|
</>
|
|
1499
|
-
{config.series && config.series.length
|
|
1503
|
+
{((config.series && config.series.length && config.visualizationType === 'Bar') ||
|
|
1504
|
+
(config.series && config.series.length <= 1 && config.visualizationType === 'Line')) && (
|
|
1500
1505
|
<>
|
|
1501
1506
|
<span className='divider-heading'>Confidence Keys</span>
|
|
1502
1507
|
<Select
|
|
@@ -1768,14 +1773,16 @@ const EditorPanel = () => {
|
|
|
1768
1773
|
title={!config.yAxis.gridLines ? 'Show gridlines to enable' : ''}
|
|
1769
1774
|
/>
|
|
1770
1775
|
)}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1776
|
+
{visSupportsYPadding() && (
|
|
1777
|
+
<CheckBox
|
|
1778
|
+
value={config.yAxis.enablePadding}
|
|
1779
|
+
section='yAxis'
|
|
1780
|
+
fieldName='enablePadding'
|
|
1781
|
+
label='Add Padding to Value Axis Scale'
|
|
1782
|
+
updateField={updateField}
|
|
1783
|
+
/>
|
|
1784
|
+
)}
|
|
1785
|
+
{config.yAxis.enablePadding && visSupportsYPadding() && (
|
|
1779
1786
|
<TextField
|
|
1780
1787
|
type='number'
|
|
1781
1788
|
section='yAxis'
|
|
@@ -1839,7 +1846,7 @@ const EditorPanel = () => {
|
|
|
1839
1846
|
className='number-narrow'
|
|
1840
1847
|
updateField={updateField}
|
|
1841
1848
|
min={0}
|
|
1842
|
-
/>
|
|
1849
|
+
/>{' '}
|
|
1843
1850
|
<div className='two-col-inputs'>
|
|
1844
1851
|
<TextField
|
|
1845
1852
|
value={config.dataFormat.prefix}
|
|
@@ -1886,7 +1893,6 @@ const EditorPanel = () => {
|
|
|
1886
1893
|
}
|
|
1887
1894
|
/>
|
|
1888
1895
|
</div>
|
|
1889
|
-
|
|
1890
1896
|
{config.orientation === 'horizontal' ? ( // horizontal - x is vertical y is horizontal
|
|
1891
1897
|
<>
|
|
1892
1898
|
{visSupportsValueAxisLine() && (
|
|
@@ -2015,20 +2021,23 @@ const EditorPanel = () => {
|
|
|
2015
2021
|
updateField={updateField}
|
|
2016
2022
|
/>
|
|
2017
2023
|
<span style={{ color: 'red', display: 'block' }}>{warningMsg.maxMsg}</span>
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2024
|
+
{config.visualizationType !== 'Area Chart' && config.visualizationSubType !== 'stacked' && (
|
|
2025
|
+
<>
|
|
2026
|
+
<TextField
|
|
2027
|
+
value={config.yAxis.min}
|
|
2028
|
+
section='yAxis'
|
|
2029
|
+
fieldName='min'
|
|
2030
|
+
type='number'
|
|
2031
|
+
label='left axis min value'
|
|
2032
|
+
placeholder='Auto'
|
|
2033
|
+
updateField={updateField}
|
|
2034
|
+
/>
|
|
2035
|
+
<span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
|
|
2036
|
+
</>
|
|
2037
|
+
)}
|
|
2028
2038
|
</>
|
|
2029
2039
|
)
|
|
2030
2040
|
)}
|
|
2031
|
-
|
|
2032
2041
|
{/* start: anchors */}
|
|
2033
2042
|
{visHasAnchors() && config.orientation !== 'horizontal' && (
|
|
2034
2043
|
<div className='edit-block'>
|
|
@@ -2155,7 +2164,6 @@ const EditorPanel = () => {
|
|
|
2155
2164
|
</button>
|
|
2156
2165
|
</div>
|
|
2157
2166
|
)}
|
|
2158
|
-
|
|
2159
2167
|
{visHasAnchors() && config.orientation === 'horizontal' && (
|
|
2160
2168
|
<div className='edit-block'>
|
|
2161
2169
|
<span className='edit-label column-heading'>Anchors</span>
|
|
@@ -2764,13 +2772,12 @@ const EditorPanel = () => {
|
|
|
2764
2772
|
/>
|
|
2765
2773
|
</>
|
|
2766
2774
|
)}
|
|
2767
|
-
|
|
2768
2775
|
<CheckBox
|
|
2769
2776
|
value={config.exclusions.active}
|
|
2770
2777
|
section='exclusions'
|
|
2771
2778
|
fieldName='active'
|
|
2772
2779
|
label={
|
|
2773
|
-
config.xAxis.type === 'date'
|
|
2780
|
+
config.xAxis.type === 'date' || config.xAxis.type === 'date-time'
|
|
2774
2781
|
? 'Limit by start and/or end dates'
|
|
2775
2782
|
: 'Exclude one or more values'
|
|
2776
2783
|
}
|
|
@@ -2869,26 +2876,27 @@ const EditorPanel = () => {
|
|
|
2869
2876
|
</>
|
|
2870
2877
|
)}
|
|
2871
2878
|
|
|
2872
|
-
{config.xAxis.type === 'date'
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2879
|
+
{config.xAxis.type === 'date' ||
|
|
2880
|
+
(config.xAxis.type === 'date-time' && (
|
|
2881
|
+
<>
|
|
2882
|
+
<TextField
|
|
2883
|
+
type='date'
|
|
2884
|
+
section='exclusions'
|
|
2885
|
+
fieldName='dateStart'
|
|
2886
|
+
label='Start Date'
|
|
2887
|
+
updateField={updateField}
|
|
2888
|
+
value={config.exclusions.dateStart || ''}
|
|
2889
|
+
/>
|
|
2890
|
+
<TextField
|
|
2891
|
+
type='date'
|
|
2892
|
+
section='exclusions'
|
|
2893
|
+
fieldName='dateEnd'
|
|
2894
|
+
label='End Date'
|
|
2895
|
+
updateField={updateField}
|
|
2896
|
+
value={config.exclusions.dateEnd || ''}
|
|
2897
|
+
/>
|
|
2898
|
+
</>
|
|
2899
|
+
))}
|
|
2892
2900
|
</>
|
|
2893
2901
|
)}
|
|
2894
2902
|
|
|
@@ -3671,38 +3679,6 @@ const EditorPanel = () => {
|
|
|
3671
3679
|
updateField={updateField}
|
|
3672
3680
|
/>
|
|
3673
3681
|
|
|
3674
|
-
{/* <fieldset className="checkbox-group">
|
|
3675
|
-
<CheckBox value={config.legend.dynamicLegend} section="legend" fieldName="dynamicLegend" label="Dynamic Legend" updateField={updateField}/>
|
|
3676
|
-
{config.legend.dynamicLegend && (
|
|
3677
|
-
<>
|
|
3678
|
-
<TextField value={config.legend.dynamicLegendDefaultText} section="legend" fieldName="dynamicLegendDefaultText" label="Dynamic Legend Default Text" updateField={updateField} />
|
|
3679
|
-
<TextField value={config.legend.dynamicLegendItemLimit} type="number" min="0" section="legend" fieldName="dynamicLegendItemLimit" label={'Dynamic Legend Limit'} className="number-narrow" updateField={updateField}/>
|
|
3680
|
-
<TextField value={config.legend.dynamicLegendItemLimitMessage} section="legend" fieldName="dynamicLegendItemLimitMessage" label="Dynamic Legend Item Limit Message" updateField={updateField} />
|
|
3681
|
-
<TextField value={config.legend.dynamicLegendChartMessage} section="legend" fieldName="dynamicLegendChartMessage" label="Dynamic Legend Chart Message" updateField={updateField} />
|
|
3682
|
-
</>
|
|
3683
|
-
)}
|
|
3684
|
-
</fieldset> */}
|
|
3685
|
-
|
|
3686
|
-
<CheckBox
|
|
3687
|
-
value={config.legend.hide ? true : false}
|
|
3688
|
-
section='legend'
|
|
3689
|
-
fieldName='hide'
|
|
3690
|
-
label='Hide Legend'
|
|
3691
|
-
updateField={updateField}
|
|
3692
|
-
tooltip={
|
|
3693
|
-
<Tooltip style={{ textTransform: 'none' }}>
|
|
3694
|
-
<Tooltip.Target>
|
|
3695
|
-
<Icon
|
|
3696
|
-
display='question'
|
|
3697
|
-
style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
|
|
3698
|
-
/>
|
|
3699
|
-
</Tooltip.Target>
|
|
3700
|
-
<Tooltip.Content>
|
|
3701
|
-
<p>With a single-series chart, consider hiding the legend to reduce visual clutter.</p>
|
|
3702
|
-
</Tooltip.Content>
|
|
3703
|
-
</Tooltip>
|
|
3704
|
-
}
|
|
3705
|
-
/>
|
|
3706
3682
|
<CheckBox
|
|
3707
3683
|
display={config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value)}
|
|
3708
3684
|
value={config.legend.hideSuppressedLabels}
|
|
@@ -3755,7 +3731,7 @@ const EditorPanel = () => {
|
|
|
3755
3731
|
</>
|
|
3756
3732
|
} */}
|
|
3757
3733
|
<Select
|
|
3758
|
-
display={
|
|
3734
|
+
display={hasDynamicCategory || hasMultipleSeries}
|
|
3759
3735
|
value={config.legend.behavior}
|
|
3760
3736
|
section='legend'
|
|
3761
3737
|
fieldName='behavior'
|
|
@@ -612,6 +612,7 @@ const SeriesItem = props => {
|
|
|
612
612
|
const { series, getItemStyle, sortableItemStyles, chartsWithOptions, index: i } = props
|
|
613
613
|
const showDynamicCategory =
|
|
614
614
|
['Bar', 'Line'].includes(config.visualizationType) &&
|
|
615
|
+
config.visualizationSubType !== 'Stacked' &&
|
|
615
616
|
!config.series.find(s => s.dynamicCategory && s.dataKey !== series.dataKey)
|
|
616
617
|
return (
|
|
617
618
|
<Draggable key={series.dataKey} draggableId={`draggableFilter-${series.dataKey}`} index={i}>
|
|
@@ -153,7 +153,6 @@ export const useEditorPermissions = () => {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
const visHasBrushChart = () => {
|
|
156
|
-
return false
|
|
157
156
|
if (config.xAxis.type === 'categorical') return false
|
|
158
157
|
return ['Line', 'Bar', 'Area Chart', 'Combo'].includes(visualizationType) && orientation === 'vertical'
|
|
159
158
|
}
|
|
@@ -384,6 +383,10 @@ export const useEditorPermissions = () => {
|
|
|
384
383
|
)
|
|
385
384
|
}
|
|
386
385
|
|
|
386
|
+
const visSupportsYPadding = () => {
|
|
387
|
+
return !config.dataFormat.onlyShowTopPrefixSuffix || !config.dataFormat.suffix?.includes(' ')
|
|
388
|
+
}
|
|
389
|
+
|
|
387
390
|
const visHasSingleSeriesTooltip = () => {
|
|
388
391
|
if (visualizationType === 'Bar' || visualizationType === 'Line') {
|
|
389
392
|
return true
|
|
@@ -457,6 +460,7 @@ export const useEditorPermissions = () => {
|
|
|
457
460
|
visSupportsValueAxisMax,
|
|
458
461
|
visSupportsValueAxisMin,
|
|
459
462
|
visSupportsDynamicSeries,
|
|
463
|
+
visSupportsYPadding,
|
|
460
464
|
visHasSingleSeriesTooltip,
|
|
461
465
|
visHasCategoricalAxis
|
|
462
466
|
}
|
|
@@ -2,7 +2,7 @@ import parse from 'html-react-parser'
|
|
|
2
2
|
import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
|
|
3
3
|
import LegendShape from '@cdc/core/components/LegendShape'
|
|
4
4
|
import Button from '@cdc/core/components/elements/Button'
|
|
5
|
-
import
|
|
5
|
+
import { getLegendClasses } from './helpers/getLegendClasses'
|
|
6
6
|
import { useHighlightedBars } from '../../hooks/useHighlightedBars'
|
|
7
7
|
import { handleLineType } from '../../helpers/handleLineType'
|
|
8
8
|
|
|
@@ -48,7 +48,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
48
48
|
},
|
|
49
49
|
ref
|
|
50
50
|
) => {
|
|
51
|
-
const { innerClasses, containerClasses } =
|
|
51
|
+
const { innerClasses, containerClasses } = getLegendClasses(config)
|
|
52
52
|
const { runtime, legend } = config
|
|
53
53
|
|
|
54
54
|
const [hasSuppression, setHasSuppression] = useState(false)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ChartConfig } from '
|
|
1
|
+
import { ChartConfig } from './../../../types/ChartConfig'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const getLegendClasses = (config: ChartConfig) => {
|
|
4
4
|
const { position, singleRow, reverseLabelOrder, verticalSorted, hideBorder } = config.legend
|
|
5
5
|
const containerClasses = ['legend-container']
|
|
6
6
|
const innerClasses = ['legend-container__inner']
|
|
@@ -40,11 +40,11 @@ const useLegendClasses = (config: ChartConfig) => {
|
|
|
40
40
|
|
|
41
41
|
// Configure border classes
|
|
42
42
|
if (hideBorder.side && (['right', 'left'].includes(position) || !position)) {
|
|
43
|
-
containerClasses.push('
|
|
43
|
+
containerClasses.push('border-0')
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
if (hideBorder.topBottom && ['top', 'bottom'].includes(position)) {
|
|
47
|
-
containerClasses.push('
|
|
47
|
+
containerClasses.push('border-0')
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (hideBorder.topBottom && ['top'].includes(position)) {
|
|
@@ -56,4 +56,4 @@ const useLegendClasses = (config: ChartConfig) => {
|
|
|
56
56
|
innerClasses
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
export default
|
|
59
|
+
export default getLegendClasses
|