@cdc/chart 4.24.5 → 4.24.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +44197 -38258
- package/examples/cases-year.json +13379 -0
- package/examples/feature/annotations/index.json +542 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
- package/examples/xaxis.json +493 -0
- package/index.html +20 -10
- package/package.json +5 -4
- package/src/CdcChart.tsx +462 -172
- package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
- package/src/_stories/Chart.stories.tsx +18 -171
- package/src/_stories/ChartAnnotation.stories.tsx +32 -0
- package/src/_stories/_mock/annotation_category_mock.json +473 -0
- package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
- package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
- package/src/_stories/_mock/legend.gradient_mock.json +236 -0
- package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
- package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
- package/src/_stories/_mock/lollipop.json +171 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
- package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
- package/src/components/Annotations/components/AnnotationList.tsx +42 -0
- package/src/components/Annotations/components/findNearestDatum.ts +138 -0
- package/src/components/Annotations/components/helpers/index.tsx +46 -0
- package/src/components/Annotations/index.tsx +13 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +145 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
- package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
- package/src/components/BarChart/helpers/index.ts +91 -0
- package/src/components/BrushChart.tsx +205 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/components/panels.scss +4 -0
- package/src/components/EditorPanel/editor-panel.scss +35 -3
- package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
- package/src/components/Legend/Legend.Component.tsx +185 -194
- package/src/components/Legend/Legend.Suppression.tsx +146 -0
- package/src/components/Legend/Legend.tsx +21 -5
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +35 -0
- package/src/components/LegendWrapper.tsx +26 -0
- package/src/components/LineChart/LineChartProps.ts +1 -15
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
- package/src/components/LineChart/helpers.ts +72 -14
- package/src/components/LineChart/index.tsx +117 -42
- package/src/components/LinearChart.jsx +179 -136
- package/src/components/LinearChart.tsx +1366 -0
- package/src/components/PairedBarChart.jsx +9 -9
- package/src/components/PieChart/PieChart.tsx +75 -18
- package/src/components/Sankey/index.tsx +89 -30
- package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
- package/src/components/Sparkline/components/SparkLine.tsx +2 -2
- package/src/components/ZoomBrush.tsx +90 -44
- package/src/data/initial-state.js +25 -7
- package/src/helpers/handleChartTabbing.ts +8 -0
- package/src/helpers/isConvertLineToBarGraph.ts +4 -0
- package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
- package/src/hooks/useColorScale.ts +1 -1
- package/src/hooks/useLegendClasses.ts +68 -0
- package/src/hooks/useMinMax.ts +12 -7
- package/src/hooks/useScales.ts +58 -26
- package/src/hooks/useTooltip.tsx +135 -25
- package/src/scss/DataTable.scss +2 -1
- package/src/scss/main.scss +128 -28
- package/src/types/ChartConfig.ts +83 -10
- package/src/types/ChartContext.ts +14 -4
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- package/src/components/BrushHandle.jsx +0 -17
- package/src/components/LineChart/index.scss +0 -1
- package/src/helpers/filterData.ts +0 -18
- package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
- package/src/hooks/useLegendClasses.js +0 -31
- /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../ConfigContext'
|
|
3
|
+
|
|
4
|
+
type LegendWrapperProps = {
|
|
5
|
+
children: React.ReactNode
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const LegendWrapper: React.FC<LegendWrapperProps> = props => {
|
|
9
|
+
const { children } = props
|
|
10
|
+
|
|
11
|
+
const { config, currentViewport } = useContext(ConfigContext)
|
|
12
|
+
|
|
13
|
+
const getLegendWrappingClasses = () => {
|
|
14
|
+
let classes = ['legend-wrapper', 'd-flex', 'flex-nowrap', 'w-100']
|
|
15
|
+
const { legend } = config
|
|
16
|
+
if (legend.position === 'bottom' || legend.position === 'top' || ['xxs', 'xs', 'sm'].includes(currentViewport)) {
|
|
17
|
+
classes = classes.filter(item => item !== 'flex-nowrap')
|
|
18
|
+
classes.push('flex-wrap')
|
|
19
|
+
}
|
|
20
|
+
return classes.join(' ')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return <div className={getLegendWrappingClasses()}>{...children}</div>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default LegendWrapper
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// todo: review tooltipData type
|
|
2
2
|
// todo: review svgRef type
|
|
3
|
+
import { type PreliminaryDataItem } from '../../types/ChartConfig'
|
|
3
4
|
export type LineChartProps = {
|
|
4
5
|
xScale: Function
|
|
5
6
|
yScale: Function
|
|
@@ -16,21 +17,6 @@ export type LineChartProps = {
|
|
|
16
17
|
tooltipData: any
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
export interface PreliminaryDataItem {
|
|
20
|
-
column: string
|
|
21
|
-
displayLegend: boolean
|
|
22
|
-
displayTable: boolean
|
|
23
|
-
displayTooltip: boolean
|
|
24
|
-
iconCode: string
|
|
25
|
-
label: string
|
|
26
|
-
lineCode: string
|
|
27
|
-
seriesKey: string
|
|
28
|
-
style: string
|
|
29
|
-
symbol: string
|
|
30
|
-
type: 'effect' | 'suppression'
|
|
31
|
-
value: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
20
|
export interface DataItem {
|
|
35
21
|
[key: string]: any
|
|
36
22
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Group } from '@visx/group'
|
|
3
|
+
import { type Column } from '@cdc/core/types/Column'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { type ChartConfig } from '../../../types/ChartConfig'
|
|
6
|
+
|
|
7
|
+
type LineChartBumpCircleProp = {
|
|
8
|
+
config: ChartConfig,
|
|
9
|
+
xScale: any,
|
|
10
|
+
yScale: any,
|
|
11
|
+
parseDate: any
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const LineChartBumpCircle = (props: LineChartBumpCircleProp) => {
|
|
15
|
+
const { config, xScale, yScale, parseDate } = props
|
|
16
|
+
|
|
17
|
+
// get xScale and yScale...
|
|
18
|
+
if (!config?.runtime?.series) return
|
|
19
|
+
|
|
20
|
+
const handleX = xValue => {
|
|
21
|
+
if (config.xAxis.type === 'date') {
|
|
22
|
+
return parseDate(xValue).getTime()
|
|
23
|
+
}
|
|
24
|
+
if (config.xAxis.type === 'date-time') {
|
|
25
|
+
return new Date(xValue)
|
|
26
|
+
}
|
|
27
|
+
if (config.xAxis.type === 'categorical') {
|
|
28
|
+
return xValue
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const checkBandScale = xValue => {
|
|
33
|
+
return xScale.bandwidth ? xScale.bandwidth() / 2 + Number(xValue) : Number(xValue)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
const getListItems = dataRow => {
|
|
38
|
+
return Object.values(config.columns)
|
|
39
|
+
?.filter(column => column.tooltips).map(column => {
|
|
40
|
+
const label = column.label || column.name;
|
|
41
|
+
return `
|
|
42
|
+
<li className='tooltip-body'>
|
|
43
|
+
<strong>${label}</strong>: ${dataRow[column.name]}
|
|
44
|
+
</li>`;
|
|
45
|
+
})
|
|
46
|
+
.join(' ');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const getTooltip = dataRow => `<ul> ${getListItems(dataRow)} </ul>`
|
|
50
|
+
|
|
51
|
+
const circles = config.runtime?.series.map((series) => {
|
|
52
|
+
return config.data.map((d, dataIndex) => {
|
|
53
|
+
let series_dataKey = d[series.dataKey]
|
|
54
|
+
let axis_dataKey = d[config.xAxis.dataKey]
|
|
55
|
+
return (
|
|
56
|
+
<React.Fragment key={`bump-circle-${series_dataKey}-${dataIndex}`}>
|
|
57
|
+
<Group left={Number(config.runtime.yAxis.size)}>
|
|
58
|
+
{series_dataKey && (
|
|
59
|
+
<>
|
|
60
|
+
<circle
|
|
61
|
+
key={`bump-circle-${series_dataKey}-${dataIndex}`}
|
|
62
|
+
data-tooltip-html={getTooltip(d)}
|
|
63
|
+
data-tooltip-id={`bump-chart`}
|
|
64
|
+
r={10}
|
|
65
|
+
cx={Number(checkBandScale(xScale(handleX(axis_dataKey))))}
|
|
66
|
+
cy={Number(yScale(series_dataKey))}
|
|
67
|
+
stroke='#CACACA'
|
|
68
|
+
strokeWidth={1}
|
|
69
|
+
fill='#E5E4E2'
|
|
70
|
+
/>
|
|
71
|
+
{series_dataKey.toString().length === 2 ? (
|
|
72
|
+
// prettier-ignore
|
|
73
|
+
<text
|
|
74
|
+
x={Number(checkBandScale(xScale(handleX(axis_dataKey)))) - 7}
|
|
75
|
+
y={Number(yScale(series_dataKey)) + 4}
|
|
76
|
+
fill='#000000'
|
|
77
|
+
fontSize={11.5}
|
|
78
|
+
>
|
|
79
|
+
{series_dataKey}
|
|
80
|
+
</text>
|
|
81
|
+
) : (
|
|
82
|
+
// prettier-ignore
|
|
83
|
+
<text
|
|
84
|
+
x={Number(checkBandScale(xScale(handleX(axis_dataKey)))) - 4}
|
|
85
|
+
y={Number(yScale(series_dataKey)) + 4}
|
|
86
|
+
fill='#000000'
|
|
87
|
+
fontSize={11.5}
|
|
88
|
+
>
|
|
89
|
+
{series_dataKey}
|
|
90
|
+
</text>
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
)}
|
|
94
|
+
</Group>
|
|
95
|
+
</React.Fragment>
|
|
96
|
+
)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return <>{circles}</>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default LineChartBumpCircle
|
|
@@ -27,11 +27,33 @@ type LineChartCircleProps = {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const LineChartCircle = (props: LineChartCircleProps) => {
|
|
30
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
config,
|
|
32
|
+
d,
|
|
33
|
+
tableData,
|
|
34
|
+
displayArea,
|
|
35
|
+
seriesKey,
|
|
36
|
+
tooltipData,
|
|
37
|
+
xScale,
|
|
38
|
+
yScale,
|
|
39
|
+
colorScale,
|
|
40
|
+
parseDate,
|
|
41
|
+
yScaleRight,
|
|
42
|
+
data,
|
|
43
|
+
circleData,
|
|
44
|
+
dataIndex,
|
|
45
|
+
mode
|
|
46
|
+
} = props
|
|
31
47
|
const { lineDatapointStyle } = config
|
|
32
|
-
const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
|
|
48
|
+
const filtered = config?.runtime?.series.filter(s => s.dataKey === seriesKey)?.[0]
|
|
33
49
|
// If we're not showing the circle, simply return
|
|
34
|
-
const getColor = (
|
|
50
|
+
const getColor = (
|
|
51
|
+
displayArea: boolean,
|
|
52
|
+
colorScale: Function,
|
|
53
|
+
config: ChartConfig,
|
|
54
|
+
hoveredKey: string,
|
|
55
|
+
seriesKey: string
|
|
56
|
+
) => {
|
|
35
57
|
const seriesLabels = config.runtime.seriesLabels || []
|
|
36
58
|
let color
|
|
37
59
|
|
|
@@ -47,14 +69,19 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
47
69
|
return color
|
|
48
70
|
}
|
|
49
71
|
const getXPos = hoveredXValue => {
|
|
50
|
-
return (
|
|
72
|
+
return (
|
|
73
|
+
(config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))) +
|
|
74
|
+
(xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
75
|
+
)
|
|
51
76
|
}
|
|
52
77
|
if (mode === 'ALWAYS_SHOW_POINTS') {
|
|
53
78
|
if (lineDatapointStyle === 'hidden') return <></>
|
|
54
79
|
const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
|
|
55
80
|
|
|
56
81
|
if (lineDatapointStyle === 'always show') {
|
|
57
|
-
const isMatch = circleData?.some(
|
|
82
|
+
const isMatch = circleData?.some(
|
|
83
|
+
cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey]
|
|
84
|
+
)
|
|
58
85
|
if (isMatch) {
|
|
59
86
|
return <></>
|
|
60
87
|
}
|
|
@@ -98,7 +125,7 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
98
125
|
if (isNaN(hoveredSeriesValue)) return <></>
|
|
99
126
|
const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
|
|
100
127
|
|
|
101
|
-
if (isMatch) {
|
|
128
|
+
if (isMatch || !hoveredSeriesValue) {
|
|
102
129
|
return <></>
|
|
103
130
|
}
|
|
104
131
|
|
|
@@ -135,7 +162,12 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
135
162
|
}
|
|
136
163
|
// Handle points in the middle
|
|
137
164
|
if (currentIndex > 0 && currentIndex < data.length - 1) {
|
|
138
|
-
if (
|
|
165
|
+
if (
|
|
166
|
+
currentPoint &&
|
|
167
|
+
currentPoint[seriesKey] &&
|
|
168
|
+
(!previousPoint || !previousPoint[seriesKey]) &&
|
|
169
|
+
(!nextPoint || !nextPoint[seriesKey])
|
|
170
|
+
) {
|
|
139
171
|
res = true
|
|
140
172
|
}
|
|
141
173
|
}
|
|
@@ -146,7 +178,14 @@ const LineChartCircle = (props: LineChartCircleProps) => {
|
|
|
146
178
|
if (mode) {
|
|
147
179
|
if (drawIsolatedPoints(dataIndex, seriesKey)) {
|
|
148
180
|
return (
|
|
149
|
-
<circle
|
|
181
|
+
<circle
|
|
182
|
+
cx={getXPos(d[config.xAxis?.dataKey])}
|
|
183
|
+
cy={filtered?.axis === 'Right' ? yScaleRight(d[filtered?.dataKey]) : yScale(d[filtered?.dataKey])}
|
|
184
|
+
r={5.3}
|
|
185
|
+
strokeWidth={2}
|
|
186
|
+
stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
|
|
187
|
+
fill={colorScale(config.runtime?.seriesLabels[seriesKey])}
|
|
188
|
+
/>
|
|
150
189
|
)
|
|
151
190
|
}
|
|
152
191
|
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DataItem, StyleProps, Style } from './LineChartProps'
|
|
2
|
+
import { PreliminaryDataItem } from '../../types/ChartConfig'
|
|
2
3
|
import _ from 'lodash'
|
|
3
4
|
export const createStyles = (props: StyleProps): Style[] => {
|
|
4
5
|
const { preliminaryData, data, stroke, strokeWidth, handleLineType, lineType, seriesKey } = props
|
|
5
6
|
|
|
6
|
-
const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
|
|
7
|
-
|
|
7
|
+
const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
|
|
8
|
+
pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
|
|
9
|
+
)
|
|
10
|
+
const getMatchingPd = (point: DataItem): PreliminaryDataItem =>
|
|
11
|
+
validPreliminaryData.find(
|
|
12
|
+
pd =>
|
|
13
|
+
pd.seriesKey === seriesKey &&
|
|
14
|
+
point[pd.column] === pd.value &&
|
|
15
|
+
pd.type === 'effect' &&
|
|
16
|
+
pd.style !== 'Open Circles'
|
|
17
|
+
)
|
|
8
18
|
|
|
9
19
|
let styles: Style[] = []
|
|
10
20
|
const createStyle = (lineStyle): Style => ({
|
|
@@ -15,7 +25,9 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
15
25
|
|
|
16
26
|
data.forEach((d, index) => {
|
|
17
27
|
let matchingPd: PreliminaryDataItem = getMatchingPd(d)
|
|
18
|
-
let style: Style = matchingPd
|
|
28
|
+
let style: Style = matchingPd
|
|
29
|
+
? createStyle(handleLineType(matchingPd.style))
|
|
30
|
+
: createStyle(handleLineType(lineType))
|
|
19
31
|
|
|
20
32
|
styles.push(style)
|
|
21
33
|
|
|
@@ -27,18 +39,54 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
27
39
|
return styles as Style[]
|
|
28
40
|
}
|
|
29
41
|
|
|
30
|
-
export const filterCircles = (
|
|
42
|
+
export const filterCircles = (
|
|
43
|
+
preliminaryData: PreliminaryDataItem[],
|
|
44
|
+
data: DataItem[],
|
|
45
|
+
seriesKey: string
|
|
46
|
+
): DataItem[] => {
|
|
31
47
|
// Filter and map preliminaryData to get circlesFiltered
|
|
32
|
-
const circlesFiltered = preliminaryData
|
|
33
|
-
|
|
48
|
+
const circlesFiltered = preliminaryData
|
|
49
|
+
?.filter(item => item.style.includes('Circles') && item.type === 'effect')
|
|
50
|
+
.map(item => ({
|
|
51
|
+
column: item.column,
|
|
52
|
+
value: item.value,
|
|
53
|
+
seriesKey: item.seriesKey,
|
|
54
|
+
circleSize: item.circleSize,
|
|
55
|
+
style: item.style
|
|
56
|
+
}))
|
|
57
|
+
const filteredData = []
|
|
34
58
|
// Process data to find matching items
|
|
35
59
|
data.forEach(item => {
|
|
36
60
|
circlesFiltered.forEach(fc => {
|
|
37
|
-
if (
|
|
38
|
-
|
|
61
|
+
if (
|
|
62
|
+
item[fc.column] === fc.value &&
|
|
63
|
+
fc.seriesKey === seriesKey &&
|
|
64
|
+
item[seriesKey] &&
|
|
65
|
+
fc.style === 'Open Circles'
|
|
66
|
+
) {
|
|
67
|
+
const result = {
|
|
68
|
+
data: item,
|
|
69
|
+
size: fc.circleSize,
|
|
70
|
+
isFilled: false
|
|
71
|
+
}
|
|
72
|
+
filteredData.push(result)
|
|
73
|
+
}
|
|
74
|
+
if (
|
|
75
|
+
(!fc.value || item[fc.column] === fc.value) &&
|
|
76
|
+
fc.seriesKey === seriesKey &&
|
|
77
|
+
item[seriesKey] &&
|
|
78
|
+
fc.style === 'Filled Circles'
|
|
79
|
+
) {
|
|
80
|
+
const result = {
|
|
81
|
+
data: item,
|
|
82
|
+
size: fc.circleSize,
|
|
83
|
+
isFilled: true
|
|
84
|
+
}
|
|
85
|
+
filteredData.push(result)
|
|
39
86
|
}
|
|
40
87
|
})
|
|
41
88
|
})
|
|
89
|
+
|
|
42
90
|
return filteredData
|
|
43
91
|
}
|
|
44
92
|
|
|
@@ -56,8 +104,10 @@ const handleFirstIndex = (data, seriesKey, preliminaryData) => {
|
|
|
56
104
|
|
|
57
105
|
// Function to check if a data item matches the suppression criteria
|
|
58
106
|
const isSuppressed = pd => {
|
|
59
|
-
if (pd.type === 'effect') return
|
|
60
|
-
return
|
|
107
|
+
if (pd.type === 'effect' || pd.hideLineStyle) return
|
|
108
|
+
return (
|
|
109
|
+
pd.type == 'suppression' && pd.value === firstIndexDataItem[seriesKey] && (!pd.column || pd.column === seriesKey)
|
|
110
|
+
)
|
|
61
111
|
}
|
|
62
112
|
|
|
63
113
|
// Find applicable suppression data for the first item
|
|
@@ -93,7 +143,13 @@ const handleLastIndex = (data, seriesKey, preliminaryData) => {
|
|
|
93
143
|
let lastAddedIndex = -1 // Tracks the last index added to the result
|
|
94
144
|
preliminaryData?.forEach(pd => {
|
|
95
145
|
if (pd.type === 'effect') return
|
|
96
|
-
if (
|
|
146
|
+
if (
|
|
147
|
+
data[data.length - 1][seriesKey] === pd.value &&
|
|
148
|
+
pd.style &&
|
|
149
|
+
(!pd.column || pd.column === seriesKey) &&
|
|
150
|
+
pd.type == 'suppression' &&
|
|
151
|
+
!pd.hideLineStyle
|
|
152
|
+
) {
|
|
97
153
|
const lastIndex = data.length - 1
|
|
98
154
|
const modifiedItem = { ...data[lastIndex], [seriesKey]: 0 }
|
|
99
155
|
result.data.push(modifiedItem)
|
|
@@ -123,7 +179,7 @@ function handleMiddleIndices(data, seriesKey, dataKey, preliminaryData) {
|
|
|
123
179
|
const isValidMiddleIndex = index => index > 0 && index < data.length - 1
|
|
124
180
|
|
|
125
181
|
preliminaryData?.forEach(pd => {
|
|
126
|
-
if (pd.type === 'effect') return
|
|
182
|
+
if (pd.type === 'effect' || pd.hideLineStyle) return
|
|
127
183
|
const targetValue = pd.value
|
|
128
184
|
|
|
129
185
|
// Find all indices
|
|
@@ -143,7 +199,9 @@ function handleMiddleIndices(data, seriesKey, dataKey, preliminaryData) {
|
|
|
143
199
|
}
|
|
144
200
|
|
|
145
201
|
// Find and add the next calculable object
|
|
146
|
-
const nextIndex = data
|
|
202
|
+
const nextIndex = data
|
|
203
|
+
.slice(i + 1)
|
|
204
|
+
.findIndex(item => item[seriesKey] !== targetValue && isCalculable(item[seriesKey]))
|
|
147
205
|
if (nextIndex !== -1) {
|
|
148
206
|
result.data.push(data[i + 1 + nextIndex])
|
|
149
207
|
}
|
|
@@ -16,6 +16,7 @@ import useRightAxis from '../../hooks/useRightAxis'
|
|
|
16
16
|
// Local helpers and components
|
|
17
17
|
import { filterCircles, createStyles, createDataSegments } from './helpers'
|
|
18
18
|
import LineChartCircle from './components/LineChart.Circle'
|
|
19
|
+
import LineChartBumpCircle from './components/LineChart.BumpCircle'
|
|
19
20
|
|
|
20
21
|
// Types
|
|
21
22
|
import { type ChartContext } from '../../types/ChartContext'
|
|
@@ -46,7 +47,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
46
47
|
let data = transformedData
|
|
47
48
|
let tableD = tableData
|
|
48
49
|
// if brush on use brush data and clean
|
|
49
|
-
if (brushConfig.data.length) {
|
|
50
|
+
if (brushConfig.data.length > 0 && config.brush?.active) {
|
|
50
51
|
data = clean(brushConfig.data)
|
|
51
52
|
tableD = clean(brushConfig.data)
|
|
52
53
|
}
|
|
@@ -56,15 +57,29 @@ const LineChart = (props: LineChartProps) => {
|
|
|
56
57
|
{' '}
|
|
57
58
|
{/* left - expects a number not a string */}
|
|
58
59
|
{(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => {
|
|
59
|
-
let lineType = config.series.filter(item => item.dataKey === seriesKey)[0].type
|
|
60
|
-
const seriesData = config.series.filter(item => item.dataKey === seriesKey)
|
|
60
|
+
let lineType = config.runtime.series.filter(item => item.dataKey === seriesKey)[0].type
|
|
61
|
+
const seriesData = config.runtime.series.filter(item => item.dataKey === seriesKey)
|
|
61
62
|
const seriesAxis = seriesData[0].axis ? seriesData[0].axis : 'left'
|
|
62
|
-
let displayArea =
|
|
63
|
+
let displayArea =
|
|
64
|
+
legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
|
|
63
65
|
const circleData = filterCircles(config?.preliminaryData, tableD, seriesKey)
|
|
64
66
|
// styles for preliminary Data items
|
|
65
|
-
let styles = createStyles({
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
let styles = createStyles({
|
|
68
|
+
preliminaryData: config.preliminaryData,
|
|
69
|
+
data: tableD,
|
|
70
|
+
stroke: colorScale(config.runtime.seriesLabels[seriesKey]),
|
|
71
|
+
strokeWidth: seriesData[0].weight || 2,
|
|
72
|
+
handleLineType,
|
|
73
|
+
lineType,
|
|
74
|
+
seriesKey
|
|
75
|
+
})
|
|
76
|
+
const suppressedSegments = createDataSegments(
|
|
77
|
+
tableData,
|
|
78
|
+
seriesKey,
|
|
79
|
+
config.preliminaryData,
|
|
80
|
+
config.xAxis.dataKey
|
|
81
|
+
)
|
|
82
|
+
const splittedData = config?.preliminaryData?.filter(pd => pd.style && !pd.style.includes('Circles'))
|
|
68
83
|
let xPos = d => {
|
|
69
84
|
return xScale(getXAxisData(d)) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
70
85
|
}
|
|
@@ -72,36 +87,56 @@ const LineChart = (props: LineChartProps) => {
|
|
|
72
87
|
return (
|
|
73
88
|
<Group
|
|
74
89
|
key={`series-${seriesKey}`}
|
|
75
|
-
opacity={
|
|
76
|
-
|
|
90
|
+
opacity={
|
|
91
|
+
legend.behavior === 'highlight' &&
|
|
92
|
+
seriesHighlight.length > 0 &&
|
|
93
|
+
seriesHighlight.indexOf(seriesKey) === -1
|
|
94
|
+
? 0.5
|
|
95
|
+
: 1
|
|
96
|
+
}
|
|
97
|
+
display={
|
|
98
|
+
legend.behavior === 'highlight' ||
|
|
99
|
+
(seriesHighlight.length === 0 && !legend.dynamicLegend) ||
|
|
100
|
+
seriesHighlight.indexOf(seriesKey) !== -1
|
|
101
|
+
? 'block'
|
|
102
|
+
: 'none'
|
|
103
|
+
}
|
|
77
104
|
>
|
|
105
|
+
{/* tooltips */}
|
|
106
|
+
<Bar
|
|
107
|
+
key={'bars'}
|
|
108
|
+
width={Number(xMax)}
|
|
109
|
+
height={Number(yMax)}
|
|
110
|
+
fill={DEBUG ? 'red' : 'transparent'}
|
|
111
|
+
fillOpacity={0.05}
|
|
112
|
+
onMouseMove={e => handleTooltipMouseOver(e, tableData)}
|
|
113
|
+
onMouseOut={handleTooltipMouseOff}
|
|
114
|
+
onClick={e => handleTooltipClick(e, data)}
|
|
115
|
+
/>
|
|
78
116
|
{data.map((d, dataIndex) => {
|
|
79
|
-
// Find the series object from the config.series array that has a dataKey matching the seriesKey variable.
|
|
80
|
-
const series = config.series.find(({ dataKey }) => dataKey === seriesKey)
|
|
81
|
-
const { axis } = series
|
|
82
|
-
|
|
83
|
-
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
84
|
-
const labeltype = axis === 'Right' ? 'rightLabel' : 'label'
|
|
85
|
-
let label = config.runtime.yAxis[labeltype]
|
|
86
|
-
|
|
87
|
-
// if has muiltiple series dont show legend value on tooltip
|
|
88
|
-
if (!hasMultipleSeries) label = config.isLegendValue ? config.runtime.seriesLabels[seriesKey] : label
|
|
89
|
-
|
|
90
117
|
return (
|
|
91
118
|
d[seriesKey] !== undefined &&
|
|
92
119
|
d[seriesKey] !== '' &&
|
|
93
120
|
d[seriesKey] !== null &&
|
|
94
121
|
isNumber(d[seriesKey]) && (
|
|
95
|
-
<
|
|
96
|
-
{/*
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
122
|
+
<React.Fragment key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
123
|
+
{/* Render label */}
|
|
124
|
+
{config.labels && (
|
|
125
|
+
<Text
|
|
126
|
+
x={xPos(d)}
|
|
127
|
+
y={
|
|
128
|
+
seriesAxis === 'Right'
|
|
129
|
+
? yScaleRight(getYAxisData(d, seriesKey))
|
|
130
|
+
: yScale(getYAxisData(d, seriesKey))
|
|
131
|
+
}
|
|
132
|
+
fill={'#000'}
|
|
133
|
+
textAnchor='middle'
|
|
134
|
+
>
|
|
135
|
+
{formatNumber(d[seriesKey], 'left')}
|
|
136
|
+
</Text>
|
|
137
|
+
)}
|
|
103
138
|
|
|
104
|
-
{
|
|
139
|
+
{lineDatapointStyle === 'always show' && (
|
|
105
140
|
<LineChartCircle
|
|
106
141
|
mode='ALWAYS_SHOW_POINTS'
|
|
107
142
|
dataIndex={dataIndex}
|
|
@@ -142,7 +177,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
142
177
|
seriesAxis={seriesAxis}
|
|
143
178
|
key={`isolated-circle-${dataIndex}`}
|
|
144
179
|
/>
|
|
145
|
-
</
|
|
180
|
+
</React.Fragment>
|
|
146
181
|
)
|
|
147
182
|
)
|
|
148
183
|
})}
|
|
@@ -168,14 +203,18 @@ const LineChart = (props: LineChartProps) => {
|
|
|
168
203
|
)}
|
|
169
204
|
</>
|
|
170
205
|
{/* SPLIT LINE */}
|
|
171
|
-
{
|
|
206
|
+
{splittedData.length > 0 ? (
|
|
172
207
|
<>
|
|
173
208
|
<SplitLinePath
|
|
174
209
|
curve={allCurves[seriesData[0].lineType]}
|
|
175
210
|
segments={data.map(d => [d])}
|
|
176
211
|
segmentation='x'
|
|
177
212
|
x={d => xPos(d)}
|
|
178
|
-
y={d =>
|
|
213
|
+
y={d =>
|
|
214
|
+
seriesAxis === 'Right'
|
|
215
|
+
? yScaleRight(getYAxisData(d, seriesKey))
|
|
216
|
+
: yScale(Number(getYAxisData(d, seriesKey)))
|
|
217
|
+
}
|
|
179
218
|
styles={styles}
|
|
180
219
|
defined={(item, i) => {
|
|
181
220
|
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
@@ -188,7 +227,11 @@ const LineChart = (props: LineChartProps) => {
|
|
|
188
227
|
key={index}
|
|
189
228
|
data={segment.data}
|
|
190
229
|
x={d => xPos(d)}
|
|
191
|
-
y={d =>
|
|
230
|
+
y={d =>
|
|
231
|
+
seriesAxis === 'Right'
|
|
232
|
+
? yScaleRight(getYAxisData(d, seriesKey))
|
|
233
|
+
: yScale(Number(getYAxisData(d, seriesKey)))
|
|
234
|
+
}
|
|
192
235
|
stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
|
|
193
236
|
strokeWidth={seriesData[0].weight || 2}
|
|
194
237
|
strokeOpacity={1}
|
|
@@ -207,7 +250,9 @@ const LineChart = (props: LineChartProps) => {
|
|
|
207
250
|
<LinePath
|
|
208
251
|
curve={allCurves[seriesData[0].lineType]}
|
|
209
252
|
data={
|
|
210
|
-
config.
|
|
253
|
+
config.visualizationType == 'Bump Chart'
|
|
254
|
+
? data
|
|
255
|
+
: config.xAxis.type === 'date-time' || config.xAxis.type === 'date'
|
|
211
256
|
? data.sort((d1, d2) => {
|
|
212
257
|
let x1 = getXAxisData(d1)
|
|
213
258
|
let x2 = getXAxisData(d2)
|
|
@@ -218,7 +263,11 @@ const LineChart = (props: LineChartProps) => {
|
|
|
218
263
|
: data
|
|
219
264
|
}
|
|
220
265
|
x={d => xPos(d)}
|
|
221
|
-
y={d =>
|
|
266
|
+
y={d =>
|
|
267
|
+
seriesAxis === 'Right'
|
|
268
|
+
? yScaleRight(getYAxisData(d, seriesKey))
|
|
269
|
+
: yScale(Number(getYAxisData(d, seriesKey)))
|
|
270
|
+
}
|
|
222
271
|
stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
|
|
223
272
|
strokeWidth={seriesData[0].weight || 2}
|
|
224
273
|
strokeOpacity={1}
|
|
@@ -232,16 +281,26 @@ const LineChart = (props: LineChartProps) => {
|
|
|
232
281
|
)}
|
|
233
282
|
|
|
234
283
|
{/* circles for preliminaryData data */}
|
|
235
|
-
{circleData.map((
|
|
284
|
+
{circleData.map((item, i) => {
|
|
236
285
|
return (
|
|
237
286
|
<circle
|
|
238
287
|
key={i}
|
|
239
|
-
cx={xPos(
|
|
240
|
-
cy={
|
|
241
|
-
|
|
288
|
+
cx={xPos(item.data)}
|
|
289
|
+
cy={
|
|
290
|
+
seriesAxis === 'Right'
|
|
291
|
+
? yScaleRight(getYAxisData(item.data, seriesKey))
|
|
292
|
+
: yScale(Number(getYAxisData(item.data, seriesKey)))
|
|
293
|
+
}
|
|
294
|
+
r={item.size}
|
|
242
295
|
strokeWidth={seriesData[0].weight || 2}
|
|
243
296
|
stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'}
|
|
244
|
-
fill=
|
|
297
|
+
fill={
|
|
298
|
+
item.isFilled
|
|
299
|
+
? colorScale
|
|
300
|
+
? colorScale(config.runtime.seriesLabels[seriesKey])
|
|
301
|
+
: '#000'
|
|
302
|
+
: '#fff'
|
|
303
|
+
}
|
|
245
304
|
/>
|
|
246
305
|
)
|
|
247
306
|
})}
|
|
@@ -253,7 +312,11 @@ const LineChart = (props: LineChartProps) => {
|
|
|
253
312
|
curve={allCurves[seriesData[0].lineType]}
|
|
254
313
|
data={data}
|
|
255
314
|
x={d => xPos(d)}
|
|
256
|
-
y={d =>
|
|
315
|
+
y={d =>
|
|
316
|
+
seriesAxis === 'Right'
|
|
317
|
+
? yScaleRight(getYAxisData(d, seriesKey))
|
|
318
|
+
: yScale(Number(getYAxisData(d, seriesKey)))
|
|
319
|
+
}
|
|
257
320
|
stroke='#fff'
|
|
258
321
|
strokeWidth={3}
|
|
259
322
|
strokeOpacity={1}
|
|
@@ -278,7 +341,16 @@ const LineChart = (props: LineChartProps) => {
|
|
|
278
341
|
return <></>
|
|
279
342
|
}
|
|
280
343
|
return (
|
|
281
|
-
<text
|
|
344
|
+
<text
|
|
345
|
+
x={xPos(lastDatum) + 5}
|
|
346
|
+
y={yScale(getYAxisData(lastDatum, seriesKey))}
|
|
347
|
+
alignmentBaseline='middle'
|
|
348
|
+
fill={
|
|
349
|
+
config.colorMatchLineSeriesLabels && colorScale
|
|
350
|
+
? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey)
|
|
351
|
+
: 'black'
|
|
352
|
+
}
|
|
353
|
+
>
|
|
282
354
|
{config.runtime.seriesLabels[seriesKey] || seriesKey}
|
|
283
355
|
</text>
|
|
284
356
|
)
|
|
@@ -293,6 +365,9 @@ const LineChart = (props: LineChartProps) => {
|
|
|
293
365
|
</Text>
|
|
294
366
|
)}
|
|
295
367
|
</Group>
|
|
368
|
+
{config.visualizationType === 'Bump Chart' && (
|
|
369
|
+
<LineChartBumpCircle config={config} xScale={xScale} yScale={yScale} />
|
|
370
|
+
)}
|
|
296
371
|
</ErrorBoundary>
|
|
297
372
|
)
|
|
298
373
|
}
|