@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.
Files changed (87) hide show
  1. package/dist/cdcchart.js +44197 -38258
  2. package/examples/cases-year.json +13379 -0
  3. package/examples/feature/annotations/index.json +542 -0
  4. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
  5. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
  6. package/examples/xaxis.json +493 -0
  7. package/index.html +20 -10
  8. package/package.json +5 -4
  9. package/src/CdcChart.tsx +462 -172
  10. package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
  11. package/src/_stories/Chart.stories.tsx +18 -171
  12. package/src/_stories/ChartAnnotation.stories.tsx +32 -0
  13. package/src/_stories/_mock/annotation_category_mock.json +473 -0
  14. package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
  15. package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
  16. package/src/_stories/_mock/legend.gradient_mock.json +236 -0
  17. package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
  18. package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
  19. package/src/_stories/_mock/lollipop.json +171 -0
  20. package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
  21. package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
  22. package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
  23. package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
  24. package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
  25. package/src/components/Annotations/components/AnnotationList.tsx +42 -0
  26. package/src/components/Annotations/components/findNearestDatum.ts +138 -0
  27. package/src/components/Annotations/components/helpers/index.tsx +46 -0
  28. package/src/components/Annotations/index.tsx +13 -0
  29. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  30. package/src/components/AreaChart/components/AreaChart.jsx +1 -1
  31. package/src/components/Axis/Categorical.Axis.tsx +145 -0
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
  33. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
  34. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
  35. package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
  36. package/src/components/BarChart/helpers/index.ts +91 -0
  37. package/src/components/BrushChart.tsx +205 -0
  38. package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
  39. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
  40. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
  41. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
  42. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
  43. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
  44. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  45. package/src/components/EditorPanel/components/panels.scss +4 -0
  46. package/src/components/EditorPanel/editor-panel.scss +35 -3
  47. package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
  48. package/src/components/Legend/Legend.Component.tsx +185 -194
  49. package/src/components/Legend/Legend.Suppression.tsx +146 -0
  50. package/src/components/Legend/Legend.tsx +21 -5
  51. package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
  52. package/src/components/Legend/helpers/index.ts +35 -0
  53. package/src/components/LegendWrapper.tsx +26 -0
  54. package/src/components/LineChart/LineChartProps.ts +1 -15
  55. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
  56. package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
  57. package/src/components/LineChart/helpers.ts +72 -14
  58. package/src/components/LineChart/index.tsx +117 -42
  59. package/src/components/LinearChart.jsx +179 -136
  60. package/src/components/LinearChart.tsx +1366 -0
  61. package/src/components/PairedBarChart.jsx +9 -9
  62. package/src/components/PieChart/PieChart.tsx +75 -18
  63. package/src/components/Sankey/index.tsx +89 -30
  64. package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
  65. package/src/components/Sparkline/components/SparkLine.tsx +2 -2
  66. package/src/components/ZoomBrush.tsx +90 -44
  67. package/src/data/initial-state.js +25 -7
  68. package/src/helpers/handleChartTabbing.ts +8 -0
  69. package/src/helpers/isConvertLineToBarGraph.ts +4 -0
  70. package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
  71. package/src/hooks/useColorScale.ts +1 -1
  72. package/src/hooks/useLegendClasses.ts +68 -0
  73. package/src/hooks/useMinMax.ts +12 -7
  74. package/src/hooks/useScales.ts +58 -26
  75. package/src/hooks/useTooltip.tsx +135 -25
  76. package/src/scss/DataTable.scss +2 -1
  77. package/src/scss/main.scss +128 -28
  78. package/src/types/ChartConfig.ts +83 -10
  79. package/src/types/ChartContext.ts +14 -4
  80. package/tests-examples/helpers/testZeroValue.test.ts +30 -0
  81. package/LICENSE +0 -201
  82. package/src/components/BrushHandle.jsx +0 -17
  83. package/src/components/LineChart/index.scss +0 -1
  84. package/src/helpers/filterData.ts +0 -18
  85. package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
  86. package/src/hooks/useLegendClasses.js +0 -31
  87. /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 { config, d, tableData, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData, dataIndex, mode } = props
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 = (displayArea: boolean, colorScale: Function, config: ChartConfig, hoveredKey: string, seriesKey: string) => {
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 (config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
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(cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey])
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 (currentPoint && currentPoint[seriesKey] && (!previousPoint || !previousPoint[seriesKey]) && (!nextPoint || !nextPoint[seriesKey])) {
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 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])} />
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 { type PreliminaryDataItem, DataItem, StyleProps, Style } from './LineChartProps'
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(pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect')
7
- const getMatchingPd = (point: DataItem): PreliminaryDataItem => validPreliminaryData.find(pd => pd.seriesKey === seriesKey && point[pd.column] === pd.value && pd.type === 'effect' && pd.style !== 'Open Circles')
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 ? createStyle(handleLineType(matchingPd.style)) : createStyle(handleLineType(lineType))
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 = (preliminaryData: PreliminaryDataItem[], data: DataItem[], seriesKey: string): DataItem[] => {
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?.filter(item => item.style === 'Open Circles' && item.type === 'effect').map(item => ({ column: item.column, value: item.value, seriesKey: item.seriesKey }))
33
- const filteredData: DataItem[] = []
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 (item[fc.column] === fc.value && fc.seriesKey === seriesKey) {
38
- filteredData.push(item)
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 pd.type == 'suppression' && pd.value === firstIndexDataItem[seriesKey] && (!pd.column || pd.column === seriesKey)
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 (data[data.length - 1][seriesKey] === pd.value && pd.style && (!pd.column || pd.column === seriesKey) && pd.type == 'suppression') {
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.slice(i + 1).findIndex(item => item[seriesKey] !== targetValue && isCalculable(item[seriesKey]))
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 = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
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({ preliminaryData: config.preliminaryData, data: tableD, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), strokeWidth: seriesData[0].weight || 2, handleLineType, lineType, seriesKey })
66
- const suppressedSegments = createDataSegments(tableData, seriesKey, config.preliminaryData, config.xAxis.dataKey)
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={legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
76
- display={legend.behavior === 'highlight' || (seriesHighlight.length === 0 && !legend.dynamicLegend) || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
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
- <Group key={`series-${seriesKey}-point-${dataIndex}`} className='checkwidth'>
96
- {/* tooltips */}
97
- <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)} />
98
-
99
- {/* Render legend */}
100
- <Text display={config.labels ? 'block' : 'none'} x={xPos(d)} y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))} fill={'#000'} textAnchor='middle'>
101
- {formatNumber(d[seriesKey], 'left')}
102
- </Text>
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
- {(lineDatapointStyle === 'hidden' || lineDatapointStyle === 'always show') && (
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
- </Group>
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
- {config?.preliminaryData?.some(pd => pd.value && pd.type) ? (
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 => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
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 => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
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.xAxis.type === 'date-time'
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 => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
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((d, i) => {
284
+ {circleData.map((item, i) => {
236
285
  return (
237
286
  <circle
238
287
  key={i}
239
- cx={xPos(d)}
240
- cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey)))}
241
- r={6}
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='#fff'
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 => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
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 x={xPos(lastDatum) + 5} y={yScale(getYAxisData(lastDatum, seriesKey))} alignmentBaseline='middle' fill={config.colorMatchLineSeriesLabels && colorScale ? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey) : 'black'}>
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
  }