@cdc/chart 4.24.1 → 4.24.3
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 +48948 -37923
- package/examples/{private/combo.json → chart-regression-1.json} +40 -31
- package/examples/chart-regression-2.json +2360 -0
- package/examples/feature/filters/url-filter.json +1076 -0
- package/examples/feature/line/line-chart-preliminary.json +84 -37
- package/examples/feature/line/line-chart.json +2 -1
- package/examples/feature/regions/index.json +55 -5
- package/examples/feature/sankey/sankey-example-data.json +1364 -0
- package/examples/feature/sankey/sankey_chart_data.csv +20 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +306 -19
- package/examples/sparkline.json +868 -0
- package/index.html +128 -121
- package/package.json +4 -2
- package/src/CdcChart.tsx +73 -38
- package/src/_stories/ChartEditor.stories.tsx +15 -4
- package/src/_stories/_mock/pie_config.json +4 -3
- package/src/_stories/_mock/url_filter.json +1076 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +2 -1
- package/src/components/AreaChart/components/AreaChart.jsx +2 -25
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +39 -49
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +36 -56
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +36 -41
- package/src/components/BarChart/components/BarChart.Vertical.tsx +48 -64
- package/src/components/BoxPlot/BoxPlot.jsx +11 -9
- package/src/components/DeviationBar.jsx +3 -3
- package/src/components/EditorPanel/EditorPanel.tsx +1717 -1961
- package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
- package/src/components/EditorPanel/components/{Panel.ForestPlotSettings.tsx → Panels/Panel.ForestPlotSettings.tsx} +16 -7
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
- package/src/components/EditorPanel/components/{Panel.Regions.tsx → Panels/Panel.Regions.tsx} +6 -6
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +108 -0
- package/src/components/EditorPanel/components/{Panel.Series.tsx → Panels/Panel.Series.tsx} +50 -6
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +338 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +19 -0
- package/src/components/EditorPanel/components/panels.scss +11 -0
- package/src/components/EditorPanel/editor-panel.scss +1 -13
- package/src/components/EditorPanel/useEditorPermissions.js +44 -13
- package/src/components/Legend/Legend.Component.tsx +207 -0
- package/src/components/Legend/Legend.tsx +8 -327
- package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
- package/src/components/LineChart/LineChartProps.ts +2 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +85 -52
- package/src/components/LineChart/helpers.ts +3 -3
- package/src/components/LineChart/index.tsx +99 -23
- package/src/components/LinearChart.jsx +12 -33
- package/src/components/PairedBarChart.jsx +10 -12
- package/src/components/PieChart/PieChart.tsx +80 -27
- package/src/components/Regions/components/Regions.tsx +120 -69
- package/src/components/Sankey/index.tsx +434 -0
- package/src/components/Sankey/sankey.scss +153 -0
- package/src/components/Sankey/types/index.ts +16 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +1 -0
- package/src/components/Sparkline/{SparkLine.jsx → components/SparkLine.tsx} +14 -30
- package/src/components/Sparkline/index.scss +3 -0
- package/src/components/Sparkline/index.tsx +1 -1
- package/src/components/ZoomBrush.tsx +2 -1
- package/src/data/initial-state.js +51 -4
- package/src/helpers/computeMarginBottom.ts +4 -3
- package/src/helpers/tests/computeMarginBottom.test.ts +2 -1
- package/src/hooks/useBarChart.js +5 -2
- package/src/hooks/useHighlightedBars.js +1 -1
- package/src/hooks/useMinMax.ts +3 -3
- package/src/hooks/useScales.ts +28 -18
- package/src/hooks/useTooltip.tsx +19 -14
- package/src/scss/main.scss +8 -96
- package/src/types/ChartConfig.ts +47 -20
- package/src/types/ChartContext.ts +17 -4
- package/src/types/Label.ts +7 -0
- package/examples/private/chart-t.json +0 -3740
- package/examples/private/epi-data.csv +0 -13
- package/examples/private/epi-data.json +0 -62
- package/examples/private/epi.json +0 -403
- package/examples/private/occupancy.json +0 -109283
- package/examples/private/prod-line-config.json +0 -401
- package/examples/private/region-data.json +0 -822
- package/examples/private/region-testing.json +0 -312
- package/examples/private/scaling.json +0 -45325
- package/examples/private/testing-data.json +0 -1739
- package/examples/private/testing.json +0 -816
- package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +0 -109
- package/src/components/EditorPanel/components/Panels.tsx +0 -13
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { colorPalettesChart as colorPalettes, sequentialPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
|
|
2
|
+
import { FaStar } from 'react-icons/fa'
|
|
3
|
+
import { Label } from '../../../types/Label'
|
|
4
|
+
import { ColorScale, TransformedData } from '../../../types/ChartContext'
|
|
5
|
+
import { ChartConfig } from '../../../types/ChartConfig'
|
|
6
|
+
|
|
7
|
+
export const createFormatLabels =
|
|
8
|
+
(config: ChartConfig, tableData: Object[], data: TransformedData[], colorScale: ColorScale) =>
|
|
9
|
+
(defaultLabels: Label[]): Label[] => {
|
|
10
|
+
const { visualizationType, visualizationSubType, series, runtime } = config
|
|
11
|
+
|
|
12
|
+
const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
|
|
13
|
+
const colorCode = config.legend?.colorCode
|
|
14
|
+
if (visualizationType === 'Deviation Bar') {
|
|
15
|
+
const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
|
|
16
|
+
const labelBelow = {
|
|
17
|
+
datum: 'X',
|
|
18
|
+
index: 0,
|
|
19
|
+
text: `Below ${config.xAxis.targetLabel}`,
|
|
20
|
+
value: belowColor
|
|
21
|
+
}
|
|
22
|
+
const labelAbove = {
|
|
23
|
+
datum: 'X',
|
|
24
|
+
index: 1,
|
|
25
|
+
text: `Above ${config.xAxis.targetLabel}`,
|
|
26
|
+
value: aboveColor
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return reverseLabels([labelBelow, labelAbove])
|
|
30
|
+
}
|
|
31
|
+
if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
|
|
32
|
+
let palette = colorPalettes[config.palette]
|
|
33
|
+
|
|
34
|
+
while (tableData.length > palette.length) {
|
|
35
|
+
palette = palette.concat(palette)
|
|
36
|
+
}
|
|
37
|
+
palette = palette.slice(0, data.length)
|
|
38
|
+
//store unique values to Set by colorCode
|
|
39
|
+
const set = new Set()
|
|
40
|
+
|
|
41
|
+
tableData.forEach(d => set.add(d[colorCode]))
|
|
42
|
+
|
|
43
|
+
// create labels with unique values
|
|
44
|
+
const uniqueLabels = Array.from(set).map((val, i) => {
|
|
45
|
+
const newLabel = {
|
|
46
|
+
datum: val,
|
|
47
|
+
index: i,
|
|
48
|
+
text: val,
|
|
49
|
+
value: palette[i]
|
|
50
|
+
}
|
|
51
|
+
return newLabel
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return reverseLabels(uniqueLabels)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// get forecasting items inside of combo
|
|
58
|
+
if (runtime?.forecastingSeriesKeys?.length > 0) {
|
|
59
|
+
let seriesLabels = []
|
|
60
|
+
|
|
61
|
+
//store unique values to Set by colorCode
|
|
62
|
+
// loop through each stage/group/area on the chart and create a label
|
|
63
|
+
config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
|
|
64
|
+
return outerGroup?.stages?.map((stage, index) => {
|
|
65
|
+
let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
|
|
66
|
+
|
|
67
|
+
const newLabel = {
|
|
68
|
+
datum: stage.key,
|
|
69
|
+
index: index,
|
|
70
|
+
text: stage.key,
|
|
71
|
+
value: colorValue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
seriesLabels.push(newLabel)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// loop through bars for now to meet requirements.
|
|
79
|
+
config.runtime.barSeriesKeys &&
|
|
80
|
+
config.runtime.barSeriesKeys.forEach((bar, index) => {
|
|
81
|
+
let colorValue = colorPalettes[config.palette][index] ? colorPalettes[config.palette][index] : '#ccc'
|
|
82
|
+
|
|
83
|
+
const newLabel = {
|
|
84
|
+
datum: bar,
|
|
85
|
+
index: index,
|
|
86
|
+
text: bar,
|
|
87
|
+
value: colorValue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
seriesLabels.push(newLabel)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return reverseLabels(seriesLabels)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// DEV-4161: replaceable series name in the legend
|
|
97
|
+
const hasNewSeriesName = config.series.filter(item => !!item.name).length > 0
|
|
98
|
+
if (hasNewSeriesName) {
|
|
99
|
+
//store unique values to Set by colorCode
|
|
100
|
+
const set = new Set()
|
|
101
|
+
|
|
102
|
+
config.series.forEach(d => {
|
|
103
|
+
set.add(d.name || d.dataKey)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// create labels with unique values
|
|
107
|
+
const uniqueLabels = Array.from(set).map((val, i) => {
|
|
108
|
+
const newLabel = {
|
|
109
|
+
datum: val,
|
|
110
|
+
index: i,
|
|
111
|
+
text: val,
|
|
112
|
+
value: colorScale(val)
|
|
113
|
+
}
|
|
114
|
+
return newLabel
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return reverseLabels(uniqueLabels)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if ((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.visualizationSubType === 'regular' && config.suppressedData) {
|
|
121
|
+
const lastIndex = defaultLabels.length - 1
|
|
122
|
+
let newLabels = []
|
|
123
|
+
|
|
124
|
+
config.suppressedData?.forEach(({ label, icon }, index) => {
|
|
125
|
+
if (label && icon) {
|
|
126
|
+
const newLabel = {
|
|
127
|
+
datum: label,
|
|
128
|
+
index: lastIndex + index,
|
|
129
|
+
text: label,
|
|
130
|
+
icon: <FaStar color='#000' size={15} />
|
|
131
|
+
}
|
|
132
|
+
newLabels.push(newLabel)
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
return [...defaultLabels, ...newLabels]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return reverseLabels(defaultLabels)
|
|
140
|
+
}
|
|
@@ -22,6 +22,7 @@ export interface PreliminaryDataItem {
|
|
|
22
22
|
column: string
|
|
23
23
|
value: string
|
|
24
24
|
seriesKey: string
|
|
25
|
+
label: string
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export interface DataItem {
|
|
@@ -33,7 +34,7 @@ export interface Config {
|
|
|
33
34
|
}
|
|
34
35
|
export interface StyleProps {
|
|
35
36
|
preliminaryData: PreliminaryDataItem[]
|
|
36
|
-
|
|
37
|
+
data: DataItem[]
|
|
37
38
|
stroke: string
|
|
38
39
|
handleLineType: Function
|
|
39
40
|
lineType: string
|
|
@@ -21,17 +21,15 @@ type LineChartCircleProps = {
|
|
|
21
21
|
colorScale: any
|
|
22
22
|
parseDate: any
|
|
23
23
|
seriesAxis: string
|
|
24
|
+
dataIndex: number
|
|
25
|
+
mode: 'ISOLATED_POINTS' | 'HOVER_POINTS' | 'ALWAYS_SHOW_POINTS'
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const LineChartCircle = (props: LineChartCircleProps) => {
|
|
27
|
-
const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData } = props
|
|
29
|
+
const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData, dataIndex, mode } = props
|
|
28
30
|
const { lineDatapointStyle } = config
|
|
29
31
|
const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
|
|
30
32
|
// If we're not showing the circle, simply return
|
|
31
|
-
if (lineDatapointStyle === 'hidden') return <></>
|
|
32
|
-
|
|
33
|
-
const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
|
|
34
|
-
|
|
35
33
|
const getColor = (displayArea: boolean, colorScale: Function, config: ChartConfig, hoveredKey: string, seriesKey: string) => {
|
|
36
34
|
const seriesLabels = config.runtime.seriesLabels || []
|
|
37
35
|
let color
|
|
@@ -47,65 +45,100 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
47
45
|
}
|
|
48
46
|
return color
|
|
49
47
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (isMatch) {
|
|
53
|
-
return <></>
|
|
54
|
-
}
|
|
55
|
-
return (
|
|
56
|
-
<circle
|
|
57
|
-
cx={config.xAxis.type === 'categorical' ? xScale(d[config.xAxis.dataKey]) : xScale(parseDate(d[config.xAxis.dataKey]))}
|
|
58
|
-
cy={filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered.dataKey])}
|
|
59
|
-
r={4.5}
|
|
60
|
-
opacity={d[seriesKey] ? 1 : 0}
|
|
61
|
-
fillOpacity={1}
|
|
62
|
-
fill={getColor(displayArea, colorScale, config, seriesKey, seriesKey)}
|
|
63
|
-
style={{ filter: 'unset', opacity: 1 }}
|
|
64
|
-
/>
|
|
65
|
-
)
|
|
48
|
+
const getXPos = hoveredXValue => {
|
|
49
|
+
return (config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
66
50
|
}
|
|
51
|
+
if (mode === 'ALWAYS_SHOW_POINTS') {
|
|
52
|
+
if (lineDatapointStyle === 'hidden') return <></>
|
|
53
|
+
const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
|
|
67
54
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!seriesKey) return
|
|
71
|
-
if (!tooltipData.data) return
|
|
72
|
-
let hoveredXValue = tooltipData?.data?.[0]?.[1]
|
|
73
|
-
if (!hoveredXValue) return
|
|
74
|
-
|
|
75
|
-
let hoveredSeriesValue
|
|
76
|
-
let hoveredSeriesIndex
|
|
77
|
-
let hoveredSeriesData = tooltipData.data.filter(d => d[0] === seriesKey)
|
|
78
|
-
let hoveredSeriesKey = hoveredSeriesData?.[0]?.[0]
|
|
79
|
-
let hoveredSeriesAxis = hoveredSeriesData?.[0]?.[2]
|
|
80
|
-
if (!hoveredSeriesKey) return
|
|
81
|
-
hoveredSeriesIndex = tooltipData?.data.indexOf(hoveredSeriesKey)
|
|
82
|
-
hoveredSeriesValue = data?.find(d => {
|
|
83
|
-
return d[config?.xAxis.dataKey] === hoveredXValue
|
|
84
|
-
})?.[seriesKey]
|
|
85
|
-
|
|
86
|
-
// hoveredSeriesValue = extractNumber(hoveredSeriesValue)
|
|
87
|
-
return tooltipData?.data.map((tooltipItem, index) => {
|
|
88
|
-
let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
|
|
89
|
-
|
|
90
|
-
if (isNaN(hoveredSeriesValue)) return <></>
|
|
91
|
-
const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
|
|
92
|
-
|
|
55
|
+
if (lineDatapointStyle === 'always show') {
|
|
56
|
+
const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey])
|
|
93
57
|
if (isMatch) {
|
|
94
58
|
return <></>
|
|
95
59
|
}
|
|
96
60
|
return (
|
|
97
61
|
<circle
|
|
98
|
-
cx={config.xAxis.
|
|
99
|
-
cy={
|
|
62
|
+
cx={getXPos(d[config.xAxis.dataKey])}
|
|
63
|
+
cy={filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered.dataKey])}
|
|
100
64
|
r={4.5}
|
|
101
|
-
opacity={1}
|
|
65
|
+
opacity={d[seriesKey] ? 1 : 0}
|
|
102
66
|
fillOpacity={1}
|
|
103
|
-
fill={getColor(displayArea, colorScale, config,
|
|
67
|
+
fill={getColor(displayArea, colorScale, config, seriesKey, seriesKey)}
|
|
104
68
|
style={{ filter: 'unset', opacity: 1 }}
|
|
105
|
-
key={`line-chart-circle--${JSON.stringify(tooltipItem)}--${index}`}
|
|
106
69
|
/>
|
|
107
70
|
)
|
|
108
|
-
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (mode === 'HOVER_POINTS') {
|
|
75
|
+
if (lineDatapointStyle === 'hover') {
|
|
76
|
+
if (!tooltipData) return
|
|
77
|
+
if (!seriesKey) return
|
|
78
|
+
if (!tooltipData.data) return
|
|
79
|
+
let hoveredXValue = tooltipData?.data?.[0]?.[1]
|
|
80
|
+
if (!hoveredXValue) return
|
|
81
|
+
|
|
82
|
+
let hoveredSeriesValue
|
|
83
|
+
let hoveredSeriesIndex
|
|
84
|
+
let hoveredSeriesData = tooltipData.data.filter(d => d[0] === seriesKey)
|
|
85
|
+
let hoveredSeriesKey = hoveredSeriesData?.[0]?.[0]
|
|
86
|
+
let hoveredSeriesAxis = hoveredSeriesData?.[0]?.[2]
|
|
87
|
+
if (!hoveredSeriesKey) return
|
|
88
|
+
hoveredSeriesIndex = tooltipData?.data.indexOf(hoveredSeriesKey)
|
|
89
|
+
hoveredSeriesValue = data?.find(d => {
|
|
90
|
+
return d[config?.xAxis.dataKey] === hoveredXValue
|
|
91
|
+
})?.[seriesKey]
|
|
92
|
+
|
|
93
|
+
// hoveredSeriesValue = extractNumber(hoveredSeriesValue)
|
|
94
|
+
return tooltipData?.data.map((tooltipItem, index) => {
|
|
95
|
+
let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
|
|
96
|
+
|
|
97
|
+
if (isNaN(hoveredSeriesValue)) return <></>
|
|
98
|
+
const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
|
|
99
|
+
|
|
100
|
+
if (isMatch) {
|
|
101
|
+
return <></>
|
|
102
|
+
}
|
|
103
|
+
return (
|
|
104
|
+
<circle
|
|
105
|
+
cx={getXPos(hoveredXValue)}
|
|
106
|
+
cy={hoveredSeriesAxis === 'right' ? yScaleRight(hoveredSeriesValue) : yScale(hoveredSeriesValue)}
|
|
107
|
+
r={4.5}
|
|
108
|
+
opacity={1}
|
|
109
|
+
fillOpacity={1}
|
|
110
|
+
fill={getColor(displayArea, colorScale, config, hoveredSeriesKey, seriesKey)}
|
|
111
|
+
style={{ filter: 'unset', opacity: 1 }}
|
|
112
|
+
key={`line-chart-circle--${JSON.stringify(tooltipItem)}--${index}`}
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (mode === 'ISOLATED_POINTS') {
|
|
120
|
+
const drawIsolatedPoints = (currentIndex, seriesKey) => {
|
|
121
|
+
const currentPoint = data[currentIndex]
|
|
122
|
+
const previousPoint = data[currentIndex - 1]
|
|
123
|
+
const nextPoint = data[currentIndex + 1]
|
|
124
|
+
if (currentIndex === 0 && !nextPoint[seriesKey]) {
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
if (currentIndex === data.length - 1 && !previousPoint[seriesKey]) {
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
if (currentIndex !== 0 && currentPoint[seriesKey] && !previousPoint[seriesKey] && !nextPoint[seriesKey]) {
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (mode) {
|
|
136
|
+
if (drawIsolatedPoints(dataIndex, seriesKey)) {
|
|
137
|
+
return (
|
|
138
|
+
<circle cx={getXPos(d[config.xAxis.dataKey])} cy={filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered.dataKey])} r={5.3} strokeWidth={2} stroke={colorScale(config.runtime.seriesLabels[seriesKey])} fill={colorScale(config.runtime.seriesLabels[seriesKey])} />
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
109
142
|
}
|
|
110
143
|
|
|
111
144
|
return null
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PreliminaryDataItem, DataItem, StyleProps, Style } from './LineChartProps'
|
|
2
2
|
|
|
3
3
|
export const createStyles = (props: StyleProps): Style[] => {
|
|
4
|
-
const { preliminaryData,
|
|
4
|
+
const { preliminaryData, data, stroke, strokeWidth, handleLineType, lineType, seriesKey } = props
|
|
5
5
|
|
|
6
6
|
const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style)
|
|
7
7
|
const getMatchingPd = (point: DataItem): PreliminaryDataItem => validPreliminaryData.find(pd => pd.seriesKey === seriesKey && point[pd.column] === pd.value && pd.type === 'effect' && pd.style !== 'Open Circles')
|
|
@@ -9,11 +9,11 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
9
9
|
let styles: Style[] = []
|
|
10
10
|
const createStyle = (lineStyle): Style => ({
|
|
11
11
|
stroke: stroke,
|
|
12
|
-
strokeWidth:
|
|
12
|
+
strokeWidth: strokeWidth,
|
|
13
13
|
strokeDasharray: lineStyle
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
data.forEach((d, index) => {
|
|
17
17
|
let matchingPd: PreliminaryDataItem = getMatchingPd(d)
|
|
18
18
|
let style: Style = matchingPd ? createStyle(handleLineType(matchingPd.style)) : createStyle(handleLineType(lineType))
|
|
19
19
|
|
|
@@ -52,7 +52,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
54
|
<ErrorBoundary component='LineChart'>
|
|
55
|
-
<Group left={config.runtime.yAxis.size
|
|
55
|
+
<Group left={config.runtime.yAxis.size}>
|
|
56
56
|
{' '}
|
|
57
57
|
{/* left - expects a number not a string */}
|
|
58
58
|
{(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => {
|
|
@@ -62,7 +62,11 @@ const LineChart = (props: LineChartProps) => {
|
|
|
62
62
|
let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
|
|
63
63
|
const circleData = filterCircles(config.preliminaryData, rawData, seriesKey)
|
|
64
64
|
// styles for preliminary Data items
|
|
65
|
-
let styles = createStyles({ preliminaryData: config.preliminaryData,
|
|
65
|
+
let styles = createStyles({ preliminaryData: config.preliminaryData, data: tableData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), strokeWidth: seriesData[0].weight || 2, handleLineType, lineType, seriesKey })
|
|
66
|
+
|
|
67
|
+
let xPos = d => {
|
|
68
|
+
return xScale(getXAxisData(d)) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
69
|
+
}
|
|
66
70
|
|
|
67
71
|
return (
|
|
68
72
|
<Group
|
|
@@ -91,12 +95,14 @@ const LineChart = (props: LineChartProps) => {
|
|
|
91
95
|
<Bar key={'bars'} width={Number(xMax)} height={Number(yMax)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, tableData)} onMouseOut={handleTooltipMouseOff} onClick={e => handleTooltipClick(e, data)} />
|
|
92
96
|
|
|
93
97
|
{/* Render legend */}
|
|
94
|
-
<Text display={config.labels ? 'block' : 'none'} x={
|
|
98
|
+
<Text display={config.labels ? 'block' : 'none'} x={xPos(d)} y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))} fill={'#000'} textAnchor='middle'>
|
|
95
99
|
{formatNumber(d[seriesKey], 'left')}
|
|
96
100
|
</Text>
|
|
97
101
|
|
|
98
102
|
{(lineDatapointStyle === 'hidden' || lineDatapointStyle === 'always show') && (
|
|
99
103
|
<LineChartCircle
|
|
104
|
+
mode='ALWAYS_SHOW_POINTS'
|
|
105
|
+
dataIndex={dataIndex}
|
|
100
106
|
circleData={circleData}
|
|
101
107
|
data={data}
|
|
102
108
|
d={d}
|
|
@@ -113,43 +119,113 @@ const LineChart = (props: LineChartProps) => {
|
|
|
113
119
|
key={`line-circle--${dataIndex}`}
|
|
114
120
|
/>
|
|
115
121
|
)}
|
|
122
|
+
|
|
123
|
+
<LineChartCircle
|
|
124
|
+
mode='ISOLATED_POINTS'
|
|
125
|
+
dataIndex={dataIndex}
|
|
126
|
+
circleData={circleData}
|
|
127
|
+
data={data}
|
|
128
|
+
d={d}
|
|
129
|
+
config={config}
|
|
130
|
+
seriesKey={seriesKey}
|
|
131
|
+
displayArea={displayArea}
|
|
132
|
+
tooltipData={tooltipData}
|
|
133
|
+
xScale={xScale}
|
|
134
|
+
yScale={yScale}
|
|
135
|
+
colorScale={colorScale}
|
|
136
|
+
parseDate={parseDate}
|
|
137
|
+
yScaleRight={yScaleRight}
|
|
138
|
+
seriesAxis={seriesAxis}
|
|
139
|
+
key={`isolated-circle-${dataIndex}`}
|
|
140
|
+
/>
|
|
116
141
|
</Group>
|
|
117
142
|
)
|
|
118
143
|
)
|
|
119
144
|
})}
|
|
120
145
|
<>
|
|
121
146
|
{lineDatapointStyle === 'hover' && (
|
|
122
|
-
<LineChartCircle
|
|
147
|
+
<LineChartCircle
|
|
148
|
+
dataIndex={0}
|
|
149
|
+
mode='HOVER_POINTS'
|
|
150
|
+
circleData={circleData}
|
|
151
|
+
data={data}
|
|
152
|
+
config={config}
|
|
153
|
+
seriesKey={seriesKey}
|
|
154
|
+
displayArea={displayArea}
|
|
155
|
+
tooltipData={tooltipData}
|
|
156
|
+
xScale={xScale}
|
|
157
|
+
yScale={yScale}
|
|
158
|
+
colorScale={colorScale}
|
|
159
|
+
parseDate={parseDate}
|
|
160
|
+
yScaleRight={yScaleRight}
|
|
161
|
+
seriesAxis={seriesAxis}
|
|
162
|
+
/>
|
|
123
163
|
)}
|
|
124
164
|
</>
|
|
125
|
-
{/*
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
165
|
+
{/* SPLIT LINE */}
|
|
166
|
+
{config?.preliminaryData?.some(d => d.value && d.column) ? (
|
|
167
|
+
<SplitLinePath
|
|
168
|
+
curve={allCurves[seriesData[0].lineType]}
|
|
169
|
+
segments={(config.xAxis.type === 'date-time'
|
|
170
|
+
? data.sort((d1, d2) => {
|
|
171
|
+
let x1 = getXAxisData(d1)
|
|
172
|
+
let x2 = getXAxisData(d2)
|
|
173
|
+
if (x1 < x2) return -1
|
|
174
|
+
if (x2 < x1) return 1
|
|
175
|
+
return 0
|
|
176
|
+
})
|
|
177
|
+
: data
|
|
178
|
+
).map(d => [d])}
|
|
179
|
+
segmentation='x'
|
|
180
|
+
x={d => xPos(d)}
|
|
181
|
+
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
182
|
+
styles={styles}
|
|
183
|
+
defined={(item, i) => {
|
|
184
|
+
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
) : (
|
|
188
|
+
<>
|
|
189
|
+
{/* STANDARD LINE */}
|
|
190
|
+
<LinePath
|
|
191
|
+
curve={allCurves[seriesData[0].lineType]}
|
|
192
|
+
data={
|
|
193
|
+
config.xAxis.type === 'date-time'
|
|
194
|
+
? data.sort((d1, d2) => {
|
|
195
|
+
let x1 = getXAxisData(d1)
|
|
196
|
+
let x2 = getXAxisData(d2)
|
|
197
|
+
if (x1 < x2) return -1
|
|
198
|
+
if (x2 < x1) return 1
|
|
199
|
+
return 0
|
|
200
|
+
})
|
|
201
|
+
: data
|
|
202
|
+
}
|
|
203
|
+
x={d => xPos(d)}
|
|
204
|
+
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
205
|
+
stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
|
|
206
|
+
strokeWidth={seriesData[0].weight || 2}
|
|
207
|
+
strokeOpacity={1}
|
|
208
|
+
shapeRendering='geometricPrecision'
|
|
209
|
+
strokeDasharray={lineType ? handleLineType(lineType) : 0}
|
|
210
|
+
defined={(item, i) => {
|
|
211
|
+
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
212
|
+
}}
|
|
213
|
+
/>
|
|
214
|
+
</>
|
|
215
|
+
)}
|
|
140
216
|
|
|
141
217
|
{/* circles for preliminaryData data */}
|
|
142
218
|
{circleData.map((d, i) => {
|
|
143
|
-
return <circle key={i} cx={
|
|
219
|
+
return <circle key={i} cx={xPos(d)} cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={seriesData[0].weight || 2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
|
|
144
220
|
})}
|
|
145
221
|
|
|
146
222
|
{/* ANIMATED LINE */}
|
|
147
223
|
{config.animate && (
|
|
148
224
|
<LinePath
|
|
149
225
|
className='animation'
|
|
150
|
-
curve={seriesData.lineType}
|
|
226
|
+
curve={allCurves[seriesData[0].lineType]}
|
|
151
227
|
data={data}
|
|
152
|
-
x={d =>
|
|
228
|
+
x={d => xPos(d)}
|
|
153
229
|
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
154
230
|
stroke='#fff'
|
|
155
231
|
strokeWidth={3}
|
|
@@ -175,7 +251,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
175
251
|
return <></>
|
|
176
252
|
}
|
|
177
253
|
return (
|
|
178
|
-
<text x={
|
|
254
|
+
<text x={xPos(lastDatum) + 5} y={yScale(getYAxisData(lastDatum, seriesKey))} alignmentBaseline='middle' fill={config.colorMatchLineSeriesLabels && colorScale ? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey) : 'black'}>
|
|
179
255
|
{config.runtime.seriesLabels[seriesKey] || seriesKey}
|
|
180
256
|
</text>
|
|
181
257
|
)
|
|
@@ -7,6 +7,7 @@ import { Line, Bar } from '@visx/shape'
|
|
|
7
7
|
import { Text } from '@visx/text'
|
|
8
8
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
9
9
|
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
10
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
10
11
|
|
|
11
12
|
// CDC Components
|
|
12
13
|
import { AreaChart, AreaChartStacked } from './AreaChart'
|
|
@@ -36,32 +37,10 @@ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
|
36
37
|
import ZoomBrush from './ZoomBrush'
|
|
37
38
|
|
|
38
39
|
const LinearChart = props => {
|
|
39
|
-
const {
|
|
40
|
-
isEditor,
|
|
41
|
-
isDashboard,
|
|
42
|
-
computeMarginBottom,
|
|
43
|
-
transformedData: data,
|
|
44
|
-
dimensions,
|
|
45
|
-
config,
|
|
46
|
-
parseDate,
|
|
47
|
-
formatDate,
|
|
48
|
-
currentViewport,
|
|
49
|
-
formatNumber,
|
|
50
|
-
handleChartAriaLabels,
|
|
51
|
-
updateConfig,
|
|
52
|
-
handleLineType,
|
|
53
|
-
rawData,
|
|
54
|
-
capitalize,
|
|
55
|
-
setSharedFilter,
|
|
56
|
-
setSharedFilterValue,
|
|
57
|
-
getTextWidth,
|
|
58
|
-
isDebug
|
|
59
|
-
} = useContext(ConfigContext)
|
|
40
|
+
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, getTextWidth } = useContext(ConfigContext)
|
|
60
41
|
// todo: start destructuring this file for conciseness
|
|
61
42
|
const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
|
|
62
43
|
|
|
63
|
-
const getDate = d => new Date(d[config.xAxis.dataKey])
|
|
64
|
-
|
|
65
44
|
// configure width
|
|
66
45
|
let [width] = dimensions
|
|
67
46
|
if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
|
|
@@ -80,8 +59,8 @@ const LinearChart = props => {
|
|
|
80
59
|
yMax = yMax + config.data.length * config.forestPlot.rowHeight
|
|
81
60
|
width = dimensions[0]
|
|
82
61
|
}
|
|
83
|
-
if (config.brush
|
|
84
|
-
height = height + config.brush
|
|
62
|
+
if (config.brush?.active) {
|
|
63
|
+
height = height + config.brush?.height
|
|
85
64
|
}
|
|
86
65
|
|
|
87
66
|
// hooks % states
|
|
@@ -99,7 +78,7 @@ const LinearChart = props => {
|
|
|
99
78
|
})
|
|
100
79
|
|
|
101
80
|
// getters & functions
|
|
102
|
-
const getXAxisData = d => (config.runtime.xAxis
|
|
81
|
+
const getXAxisData = d => (isDateScale(config.runtime.xAxis) ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
|
|
103
82
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
104
83
|
const xAxisDataMapped = config.brush.active && config.brush.data?.length ? config.brush.data.map(d => getXAxisData(d)) : data.map(d => getXAxisData(d))
|
|
105
84
|
const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
|
|
@@ -122,7 +101,7 @@ const LinearChart = props => {
|
|
|
122
101
|
|
|
123
102
|
if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
|
|
124
103
|
if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
|
|
125
|
-
if (runtime.yAxis
|
|
104
|
+
if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
|
|
126
105
|
if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
127
106
|
return tick
|
|
128
107
|
}
|
|
@@ -133,7 +112,7 @@ const LinearChart = props => {
|
|
|
133
112
|
tick = 0
|
|
134
113
|
}
|
|
135
114
|
|
|
136
|
-
if (runtime.xAxis
|
|
115
|
+
if (isDateScale(runtime.xAxis) && config.visualizationType !== 'Forest Plot') return formatDate(tick)
|
|
137
116
|
if (orientation === 'horizontal' && config.visualizationType !== 'Forest Plot') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
138
117
|
if (config.xAxis.type === 'continuous' && config.visualizationType !== 'Forest Plot') return formatNumber(tick, 'bottom', shouldAbbreviate)
|
|
139
118
|
if (config.visualizationType === 'Forest Plot') return formatNumber(tick, 'left', config.dataFormat.abbreviated, config.runtime.xAxis.prefix, config.runtime.xAxis.suffix, Number(config.dataFormat.roundTo))
|
|
@@ -267,7 +246,7 @@ const LinearChart = props => {
|
|
|
267
246
|
<Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
|
|
268
247
|
{/* Y axis */}
|
|
269
248
|
{!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
|
|
270
|
-
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
|
|
249
|
+
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.yAxis?.label || runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
|
|
271
250
|
{props => {
|
|
272
251
|
const axisCenter = config.orientation === 'horizontal' ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
273
252
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
@@ -388,7 +367,7 @@ const LinearChart = props => {
|
|
|
388
367
|
{/* X axis */}
|
|
389
368
|
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
390
369
|
<AxisBottom
|
|
391
|
-
top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax
|
|
370
|
+
top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax}
|
|
392
371
|
left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
|
|
393
372
|
label={runtime.xAxis.label}
|
|
394
373
|
tickFormat={handleBottomTickFormatting}
|
|
@@ -502,7 +481,7 @@ const LinearChart = props => {
|
|
|
502
481
|
)}
|
|
503
482
|
{visualizationType === 'Paired Bar' && (
|
|
504
483
|
<>
|
|
505
|
-
<AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={runtime.xAxis
|
|
484
|
+
<AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={isDateScale(runtime.xAxis) ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={runtime.xAxis.numTicks || undefined}>
|
|
506
485
|
{props => {
|
|
507
486
|
return (
|
|
508
487
|
<Group className='bottom-axis'>
|
|
@@ -529,7 +508,7 @@ const LinearChart = props => {
|
|
|
529
508
|
top={yMax}
|
|
530
509
|
left={Number(runtime.yAxis.size)}
|
|
531
510
|
label={runtime.xAxis.label}
|
|
532
|
-
tickFormat={runtime.xAxis
|
|
511
|
+
tickFormat={isDateScale(runtime.xAxis) ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
|
|
533
512
|
scale={g2xScale}
|
|
534
513
|
stroke='#333'
|
|
535
514
|
tickStroke='#333'
|
|
@@ -708,7 +687,7 @@ const LinearChart = props => {
|
|
|
708
687
|
newX = yAxis
|
|
709
688
|
}
|
|
710
689
|
|
|
711
|
-
let anchorPosition = newX
|
|
690
|
+
let anchorPosition = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
|
|
712
691
|
|
|
713
692
|
// have to move up
|
|
714
693
|
// const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|