@cdc/chart 4.24.10 → 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 +34651 -33978
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +126 -14
- 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 +493 -0
- package/index.html +10 -7
- package/package.json +2 -2
- package/src/CdcChart.tsx +132 -152
- package/src/_stories/Chart.Anchors.stories.tsx +31 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +37 -6
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
- package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
- package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/short_dates.json +288 -0
- package/src/_stories/_mock/suppression_mock.json +1549 -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.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
- package/src/components/BarChart/helpers/getBarData.ts +28 -0
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
- package/src/components/BoxPlot/BoxPlot.tsx +131 -0
- package/src/components/BoxPlot/helpers/index.ts +54 -0
- package/src/components/BrushChart.tsx +23 -26
- package/src/components/EditorPanel/EditorPanel.tsx +117 -139
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +122 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
- package/src/components/Legend/Legend.Component.tsx +11 -12
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
- 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 +49 -43
- package/src/components/LineChart/index.tsx +135 -83
- package/src/components/LinearChart.tsx +187 -181
- 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 -492
- package/src/components/Sankey/sankey.scss +22 -21
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +7 -12
- package/src/helpers/countNumOfTicks.ts +57 -0
- package/src/helpers/getQuartiles.ts +15 -18
- package/src/hooks/useMinMax.ts +44 -16
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +90 -35
- package/src/hooks/useTooltip.tsx +59 -50
- package/src/scss/DataTable.scss +5 -0
- package/src/scss/main.scss +6 -20
- package/src/types/ChartConfig.ts +6 -19
- package/src/types/ChartContext.ts +4 -1
- package/src/types/ForestPlot.ts +8 -0
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
- package/src/hooks/useLegendClasses.ts +0 -72
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import { BoxPlot } from '@visx/stats'
|
|
3
|
+
import { Group } from '@visx/group'
|
|
4
|
+
import ConfigContext from '../../ConfigContext'
|
|
5
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
|
+
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
7
|
+
import { handleTooltip, calculateBoxPlotStats, createPlots } from './helpers/index'
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
|
|
10
|
+
const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
|
|
11
|
+
const { config, colorScale, seriesHighlight, transformedData: data } = useContext(ConfigContext)
|
|
12
|
+
const { boxplot } = config
|
|
13
|
+
|
|
14
|
+
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
15
|
+
const boxWidth = xScale.bandwidth()
|
|
16
|
+
const constrainedWidth = Math.min(40, boxWidth)
|
|
17
|
+
const color_0 = _.get(colorPalettesChart, [config.palette, 0], '#000')
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<ErrorBoundary component='BoxPlot'>
|
|
21
|
+
<Group left={Number(config.yAxis.size)} className='boxplot' key={`boxplot-group`}>
|
|
22
|
+
{createPlots(data, config).map((d, i) => {
|
|
23
|
+
const offset = boxWidth - constrainedWidth
|
|
24
|
+
const radius = 4
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Group
|
|
28
|
+
key={`boxplotplot-${d.columnCategory}`}
|
|
29
|
+
left={xScale(d.columnCategory) + (xScale.bandwidth() - seriesScale.bandwidth()) / 2}
|
|
30
|
+
>
|
|
31
|
+
{config.series.map((item, index) => {
|
|
32
|
+
const valuesByKey = d.keyValues[item.dataKey]
|
|
33
|
+
const { min, max, median, firstQuartile, thirdQuartile } = calculateBoxPlotStats(valuesByKey)
|
|
34
|
+
let iqr = Number(thirdQuartile - firstQuartile).toFixed(config.dataFormat.roundTo)
|
|
35
|
+
|
|
36
|
+
const isTransparent =
|
|
37
|
+
config.legend.behavior === 'highlight' &&
|
|
38
|
+
seriesHighlight.length > 0 &&
|
|
39
|
+
seriesHighlight.indexOf(item.dataKey) === -1
|
|
40
|
+
const displayPlot =
|
|
41
|
+
config.legend.behavior === 'highlight' ||
|
|
42
|
+
seriesHighlight.length === 0 ||
|
|
43
|
+
seriesHighlight.indexOf(item.dataKey) !== -1
|
|
44
|
+
const fillOpacity = isTransparent ? 0.3 : 0.5
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Group key={`boxplotplot-${item.dataKey}-${index}`}>
|
|
48
|
+
{boxplot.plotNonOutlierValues &&
|
|
49
|
+
valuesByKey.map((value, index) => {
|
|
50
|
+
return (
|
|
51
|
+
<circle
|
|
52
|
+
display={displayPlot ? 'block' : 'none'}
|
|
53
|
+
cx={seriesScale(item.dataKey) + seriesScale.bandwidth() / 2}
|
|
54
|
+
cy={yScale(value)}
|
|
55
|
+
r={radius}
|
|
56
|
+
fill={'#ccc'}
|
|
57
|
+
style={{ opacity: fillOpacity, fillOpacity: 1, stroke: 'black' }}
|
|
58
|
+
key={`boxplot-${i}--circle-${index}`}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
})}
|
|
62
|
+
{displayPlot && (
|
|
63
|
+
<BoxPlot
|
|
64
|
+
display={displayPlot ? 'block' : 'none'}
|
|
65
|
+
data-left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
|
|
66
|
+
key={`box-plot-${i}-${item}`}
|
|
67
|
+
min={Number(min)}
|
|
68
|
+
max={Number(max)}
|
|
69
|
+
left={seriesScale(item.dataKey)}
|
|
70
|
+
firstQuartile={firstQuartile}
|
|
71
|
+
thirdQuartile={thirdQuartile}
|
|
72
|
+
median={median}
|
|
73
|
+
boxWidth={seriesScale.bandwidth()}
|
|
74
|
+
fill={colorScale(item.dataKey)}
|
|
75
|
+
fillOpacity={fillOpacity}
|
|
76
|
+
stroke={fillOpacity ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.1)'}
|
|
77
|
+
valueScale={yScale}
|
|
78
|
+
outliers={boxplot.plotOutlierValues ? d.columnOutliers : []}
|
|
79
|
+
outlierProps={{
|
|
80
|
+
style: {
|
|
81
|
+
fill: `${color_0}`,
|
|
82
|
+
opacity: fillOpacity
|
|
83
|
+
}
|
|
84
|
+
}}
|
|
85
|
+
medianProps={{
|
|
86
|
+
style: {
|
|
87
|
+
stroke: 'black',
|
|
88
|
+
opacity: fillOpacity
|
|
89
|
+
}
|
|
90
|
+
}}
|
|
91
|
+
boxProps={{
|
|
92
|
+
style: {
|
|
93
|
+
stroke: 'black',
|
|
94
|
+
strokeWidth: boxplot.borders === 'true' ? 1 : 0,
|
|
95
|
+
opacity: fillOpacity
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
98
|
+
maxProps={{
|
|
99
|
+
style: {
|
|
100
|
+
stroke: 'black',
|
|
101
|
+
opacity: fillOpacity
|
|
102
|
+
}
|
|
103
|
+
}}
|
|
104
|
+
container
|
|
105
|
+
containerProps={{
|
|
106
|
+
'data-tooltip-html': handleTooltip(
|
|
107
|
+
boxplot,
|
|
108
|
+
d,
|
|
109
|
+
item.dataKey,
|
|
110
|
+
firstQuartile,
|
|
111
|
+
thirdQuartile,
|
|
112
|
+
median,
|
|
113
|
+
iqr
|
|
114
|
+
),
|
|
115
|
+
'data-tooltip-id': tooltip_id,
|
|
116
|
+
tabIndex: -1
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
</Group>
|
|
121
|
+
)
|
|
122
|
+
})}
|
|
123
|
+
</Group>
|
|
124
|
+
)
|
|
125
|
+
})}
|
|
126
|
+
</Group>
|
|
127
|
+
</ErrorBoundary>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default CoveBoxPlot
|
|
@@ -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,
|