@cdc/chart 4.22.11 → 4.23.2

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 (65) hide show
  1. package/dist/cdcchart.js +54569 -16
  2. package/examples/Barchart_with_negative.json +34 -0
  3. package/examples/box-plot-data.json +71 -0
  4. package/examples/box-plot.csv +5 -0
  5. package/examples/box-plot.json +124 -0
  6. package/examples/dynamic-legends.json +1 -1
  7. package/examples/example-bar-chart-nonnumeric.json +36 -0
  8. package/examples/example-bar-chart.json +33 -0
  9. package/examples/example-combo-bar-nonnumeric.json +105 -0
  10. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +3 -1
  11. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
  12. package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +86 -17
  13. package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
  14. package/examples/line-chart-nonnumeric.json +32 -0
  15. package/examples/line-chart.json +21 -63
  16. package/examples/new-data.csv +17 -0
  17. package/examples/newdata.json +90 -0
  18. package/examples/planet-combo-example-config.json +143 -20
  19. package/examples/planet-example-data-nonnumeric.json +56 -0
  20. package/examples/planet-example-data.json +2 -2
  21. package/examples/planet-pie-example-config-nonnumeric.json +30 -0
  22. package/examples/scatterplot-continuous.csv +17 -0
  23. package/examples/{private/yaxis-test.json → scatterplot.json} +53 -50
  24. package/examples/sparkline-chart-nonnumeric.json +76 -0
  25. package/examples/stacked-vertical-bar-example-negative.json +154 -0
  26. package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
  27. package/{src/index.html → index.html} +18 -11
  28. package/package.json +29 -22
  29. package/src/{CdcChart.tsx → CdcChart.jsx} +193 -119
  30. package/src/components/BarChart.jsx +517 -0
  31. package/src/components/BoxPlot.jsx +88 -0
  32. package/src/components/{DataTable.tsx → DataTable.jsx} +125 -32
  33. package/src/components/{EditorPanel.js → EditorPanel.jsx} +376 -115
  34. package/src/components/Filters.jsx +125 -0
  35. package/src/components/Legend.jsx +303 -0
  36. package/src/components/{LineChart.tsx → LineChart.jsx} +87 -22
  37. package/src/components/{LinearChart.tsx → LinearChart.jsx} +172 -113
  38. package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +46 -79
  39. package/src/components/{PieChart.tsx → PieChart.jsx} +29 -34
  40. package/src/components/ScatterPlot.jsx +48 -0
  41. package/src/components/{SparkLine.js → SparkLine.jsx} +49 -18
  42. package/src/components/useIntersectionObserver.jsx +29 -0
  43. package/src/data/initial-state.js +44 -8
  44. package/src/hooks/{useColorPalette.ts → useColorPalette.js} +10 -28
  45. package/src/hooks/{useReduceData.ts → useReduceData.js} +27 -13
  46. package/src/hooks/useRightAxis.js +3 -1
  47. package/src/index.jsx +16 -0
  48. package/src/scss/DataTable.scss +23 -1
  49. package/src/scss/main.scss +83 -32
  50. package/vite.config.js +4 -0
  51. package/examples/private/filters.json +0 -170
  52. package/examples/private/line-test-data.json +0 -22
  53. package/examples/private/line-test-two.json +0 -210
  54. package/examples/private/line-test.json +0 -102
  55. package/examples/private/new.json +0 -48800
  56. package/examples/private/newtest.csv +0 -101
  57. package/examples/private/shawn.json +0 -1106
  58. package/examples/private/test.json +0 -10124
  59. package/examples/private/yaxis-testing.csv +0 -27
  60. package/examples/private/yaxis.json +0 -28
  61. package/src/components/BarChart.tsx +0 -579
  62. package/src/components/Legend.js +0 -284
  63. package/src/components/useIntersectionObserver.tsx +0 -27
  64. package/src/index.tsx +0 -18
  65. /package/src/{context.tsx → ConfigContext.jsx} +0 -0
@@ -0,0 +1,125 @@
1
+ import React, { useState, useContext } from 'react'
2
+ import ConfigContext from './../ConfigContext'
3
+ import Button from '@cdc/core/components/elements/Button'
4
+
5
+ const useFilters = () => {
6
+ const { config, setConfig, filteredData, setFilteredData, excludedData, filterData, runtimeFilters } = useContext(ConfigContext)
7
+ const [showApplyButton, setShowApplyButton] = useState(false)
8
+
9
+ const sortAsc = (a, b) => {
10
+ return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
11
+ }
12
+
13
+ const sortDesc = (a, b) => {
14
+ return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
15
+ }
16
+
17
+ const announceChange = text => { }
18
+
19
+ const changeFilterActive = (index, value) => {
20
+ let newFilters = config.filters
21
+ newFilters[index].active = value
22
+ setConfig({
23
+ ...config,
24
+ filters: newFilters
25
+ })
26
+ setShowApplyButton(true)
27
+ }
28
+
29
+ const handleApplyButton = newFilters => {
30
+ setConfig({ ...config, filters: newFilters })
31
+ setFilteredData(filterData(newFilters, excludedData))
32
+ setShowApplyButton(false)
33
+ }
34
+
35
+ const handleReset = () => {
36
+ let newFilters = config.filters
37
+
38
+ // reset to first item in values array.
39
+ newFilters.map(filter => {
40
+ filter.active = filter.values[0]
41
+ })
42
+
43
+ setFilteredData(filterData(newFilters, excludedData))
44
+ setConfig({ ...config, filters: newFilters })
45
+ }
46
+
47
+ return { handleApplyButton, changeFilterActive, announceChange, sortAsc, sortDesc, showApplyButton, handleReset }
48
+ }
49
+
50
+ const Filters = () => {
51
+ const { config } = useContext(ConfigContext)
52
+ const { handleApplyButton, changeFilterActive, announceChange, sortAsc, sortDesc, showApplyButton, handleReset } = useFilters()
53
+ const { filters } = config
54
+ const buttonText = 'Apply Filters'
55
+ const resetText = 'Reset All'
56
+
57
+ // A List of Dropdowns
58
+ const FilterList = () => {
59
+ if (config.filters) {
60
+ return config.filters.map((singleFilter, index) => {
61
+ const values = []
62
+
63
+ if (!singleFilter.order || singleFilter.order === '') {
64
+ singleFilter.order = 'asc'
65
+ }
66
+
67
+ if (singleFilter.order === 'desc') {
68
+ singleFilter.values = singleFilter.values.sort(sortDesc)
69
+ }
70
+
71
+ if (singleFilter.order === 'asc') {
72
+ singleFilter.values = singleFilter.values.sort(sortAsc)
73
+ }
74
+
75
+ singleFilter.values.forEach((filterOption, index) => {
76
+ values.push(
77
+ <option key={index} value={filterOption}>
78
+ {filterOption}
79
+ </option>
80
+ )
81
+ })
82
+
83
+ return (
84
+ <div className='single-filter' key={index}>
85
+ <label htmlFor={`filter-${index}`}>{singleFilter.label}</label>
86
+ <select
87
+ id={`filter-${index}`}
88
+ className='filter-select'
89
+ data-index='0'
90
+ value={singleFilter.active}
91
+ onChange={e => {
92
+ changeFilterActive(index, e.target.value)
93
+ announceChange(`Filter ${singleFilter.label} value has been changed to ${e.target.value}, please reference the data table to see updated values.`)
94
+ }}
95
+ >
96
+ {values}
97
+ </select>
98
+ </div>
99
+ )
100
+ })
101
+ } else {
102
+ return null
103
+ }
104
+ }
105
+
106
+ return (
107
+ <section className={`filters-section`} style={{ display: 'block', width: '100%' }}>
108
+ <div className='filters-section__wrapper' style={{ flexWrap: 'wrap', display: 'flex', gap: '7px 15px', marginTop: '15px' }}>
109
+ <FilterList />
110
+ {config.filters.length > 0 && (
111
+ <div className='filter-section__buttons' style={{ width: '100%' }}>
112
+ <Button onClick={() => handleApplyButton(filters)} disabled={!showApplyButton} style={{ marginRight: '10px' }}>
113
+ {buttonText}
114
+ </Button>
115
+ <a href='#!' role='button' onClick={handleReset}>
116
+ {resetText}
117
+ </a>
118
+ </div>
119
+ )}
120
+ </div>
121
+ </section>
122
+ )
123
+ }
124
+
125
+ export default Filters
@@ -0,0 +1,303 @@
1
+ import React, { useContext, useEffect } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+ import parse from 'html-react-parser'
4
+ import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
5
+ import LegendCircle from '@cdc/core/components/LegendCircle'
6
+
7
+ import useLegendClasses from './../hooks/useLegendClasses'
8
+
9
+ const Legend = () => {
10
+ const { config, legend, colorScale, seriesHighlight, highlight, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, colorPalettes, rawData, setConfig, currentViewport } = useContext(ConfigContext)
11
+
12
+ const { innerClasses, containerClasses } = useLegendClasses(config)
13
+
14
+ useEffect(() => {
15
+ if (dynamicLegendItems.length === 0) return
16
+
17
+ let itemsToHighlight = dynamicLegendItems.map(item => item.text)
18
+
19
+ setSeriesHighlight(itemsToHighlight)
20
+
21
+ let colsToKeep = [...itemsToHighlight]
22
+ let tmpLabels = []
23
+
24
+ rawData.map(dataItem => {
25
+ let tmp = {}
26
+ colsToKeep.map(col => {
27
+ tmp[col] = isNaN(dataItem[col]) ? dataItem[col] : dataItem[col]
28
+ })
29
+ return tmp
30
+ })
31
+
32
+ colsToKeep.map(col => {
33
+ tmpLabels[col] = col
34
+ })
35
+
36
+ if (dynamicLegendItems.length > 0) {
37
+ setConfig({
38
+ ...config,
39
+ runtime: {
40
+ ...config.runtime,
41
+ seriesKeys: colsToKeep,
42
+ seriesLabels: tmpLabels
43
+ }
44
+ })
45
+ }
46
+ }, [dynamicLegendItems])
47
+
48
+ useEffect(() => {
49
+ if (dynamicLegendItems.length === 0) {
50
+ // loop through all labels and add keys
51
+ let resetSeriesNames = [...config.runtime.seriesLabelsAll]
52
+ let tmpLabels = []
53
+ config.runtime.seriesLabelsAll.map(item => {
54
+ resetSeriesNames.map(col => {
55
+ tmpLabels[col] = col
56
+ })
57
+ })
58
+
59
+ setConfig({
60
+ ...config,
61
+ runtime: {
62
+ ...config.runtime,
63
+ seriesKeys: config.runtime.seriesLabelsAll,
64
+ seriesLabels: tmpLabels
65
+ }
66
+ })
67
+ }
68
+ }, [dynamicLegendItems])
69
+
70
+ const removeDynamicLegendItem = label => {
71
+ let newLegendItems = dynamicLegendItems.filter(item => item.text !== label.text)
72
+ let newLegendItemsText = newLegendItems.map(item => item.text)
73
+ setDynamicLegendItems(newLegendItems)
74
+ setSeriesHighlight(newLegendItemsText)
75
+ }
76
+ const handleDynamicLegendChange = e => {
77
+ setDynamicLegendItems([...dynamicLegendItems, JSON.parse(e.target.value)])
78
+ }
79
+
80
+ const createLegendLabels = (data, defaultLabels) => {
81
+ const colorCode = config.legend?.colorCode
82
+ if (config.visualizationType !== 'Bar' || config.visualizationSubType !== 'regular' || !colorCode || config.series?.length > 1) {
83
+ return defaultLabels
84
+ }
85
+ let palette = colorPalettes[config.palette]
86
+
87
+ while (data.length > palette.length) {
88
+ palette = palette.concat(palette)
89
+ }
90
+ palette = palette.slice(0, data.length)
91
+ //store uniq values to Set by colorCode
92
+ const set = new Set()
93
+
94
+ data.forEach(d => set.add(d[colorCode]))
95
+
96
+ // create labels with uniq values
97
+ const uniqeLabels = Array.from(set).map((val, i) => {
98
+ const newLabel = {
99
+ datum: val,
100
+ index: i,
101
+ text: val,
102
+ value: palette[i]
103
+ }
104
+ return newLabel
105
+ })
106
+
107
+ return uniqeLabels
108
+ }
109
+ // in small screens update config legend position.
110
+ useEffect(() => {
111
+ if (currentViewport === 'sm' || currentViewport === 'xs' || config.legend.position === 'left') {
112
+ setConfig({ ...config, legend: { ...config.legend, position: 'bottom' } })
113
+ }
114
+ setConfig({ ...config, legend: { ...config.legend, position: 'right' } })
115
+ }, [currentViewport])
116
+
117
+ if (!legend) return
118
+
119
+ if (!legend.dynamicLegend)
120
+ return config.visualizationType !== 'Box Plot' ? (
121
+ <aside
122
+ style={{ marginTop: config.legend.position === 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px', marginBottom: config.legend.position === 'bottom' ? '15px' : '0px' }}
123
+ id='legend'
124
+ className={containerClasses.join(' ')}
125
+ role='region'
126
+ aria-label='legend'
127
+ tabIndex={0}
128
+ >
129
+ {legend.label && <h2>{parse(legend.label)}</h2>}
130
+ {legend.description && <p>{parse(legend.description)}</p>}
131
+ <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
132
+ {labels => (
133
+ <div className={innerClasses.join(' ')}>
134
+ {createLegendLabels(data, labels).map((label, i) => {
135
+ let className = 'legend-item'
136
+ let itemName = label.datum
137
+
138
+ // Filter excluded data keys from legend
139
+ if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
140
+ return
141
+ }
142
+
143
+ if (config.runtime.seriesLabels) {
144
+ let index = config.runtime.seriesLabelsAll.indexOf(itemName)
145
+ itemName = config.runtime.seriesKeys[index]
146
+ }
147
+
148
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
149
+ className += ' inactive'
150
+ }
151
+
152
+ return (
153
+ <LegendItem
154
+ className={className}
155
+ tabIndex={0}
156
+ key={`legend-quantile-${i}`}
157
+ onKeyPress={e => {
158
+ if (e.key === 'Enter') {
159
+ highlight(label)
160
+ }
161
+ }}
162
+ onClick={() => {
163
+ highlight(label)
164
+ }}
165
+ >
166
+ <LegendCircle fill={label.value} />
167
+ <LegendLabel align='left' margin='0 0 0 4px'>
168
+ {label.text}
169
+ </LegendLabel>
170
+ </LegendItem>
171
+ )
172
+ })}
173
+ {seriesHighlight.length > 0 && (
174
+ <button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
175
+ Reset
176
+ </button>
177
+ )}
178
+ </div>
179
+ )}
180
+ </LegendOrdinal>
181
+ </aside>
182
+ ) : (
183
+ <aside id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
184
+ {config.boxplot.legend.displayHowToReadText && <h3>{config.boxplot.legend.howToReadText}</h3>}
185
+ </aside>
186
+ )
187
+ return (
188
+ config.visualizationType !== 'Box Plot' && (
189
+ <aside id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
190
+ {legend.label && <h2>{parse(legend.label)}</h2>}
191
+ {legend.description && <p>{parse(legend.description)}</p>}
192
+
193
+ <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
194
+ {labels => {
195
+ if (
196
+ Number(config.legend.dynamicLegendItemLimit) > dynamicLegendItems.length && // legend items are less than limit
197
+ dynamicLegendItems.length !== config.runtime.seriesLabelsAll.length
198
+ ) {
199
+ // legend items are equal to series length
200
+ return (
201
+ <select className='dynamic-legend-dropdown' onChange={e => handleDynamicLegendChange(e)}>
202
+ <option className={'all'} tabIndex={0} value={JSON.stringify({ text: config.legend.dynamicLegendDefaultText })}>
203
+ {config.legend.dynamicLegendDefaultText}
204
+ </option>
205
+ {labels.map((label, i) => {
206
+ let className = 'legend-item'
207
+ let itemName = label.datum
208
+ let inDynamicList = false
209
+
210
+ // Filter excluded data keys from legend
211
+ if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
212
+ return
213
+ }
214
+
215
+ if (config.runtime.seriesLabels) {
216
+ let index = config.runtime.seriesLabelsAll.indexOf(itemName)
217
+ itemName = config.runtime.seriesKeys[index]
218
+ }
219
+
220
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
221
+ className += ' inactive'
222
+ }
223
+
224
+ dynamicLegendItems.map(listItem => {
225
+ if (listItem.text === label.text) {
226
+ inDynamicList = true
227
+ }
228
+ })
229
+
230
+ if (inDynamicList) return true
231
+ let palette = colorPalettes[config.palette]
232
+
233
+ label.value = palette[dynamicLegendItems.length]
234
+
235
+ return (
236
+ <option className={className} tabIndex={0} value={JSON.stringify(label)}>
237
+ {label.text}
238
+ </option>
239
+ )
240
+ })}
241
+ </select>
242
+ )
243
+ } else {
244
+ return config.legend.dynamicLegendItemLimitMessage
245
+ }
246
+ }}
247
+ </LegendOrdinal>
248
+
249
+ <div className='dynamic-legend-list'>
250
+ {dynamicLegendItems.map((label, i) => {
251
+ let className = ['legend-item']
252
+ let itemName = label.text
253
+ let palette = colorPalettes[config.palette]
254
+
255
+ // Filter excluded data keys from legend
256
+ if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
257
+ return
258
+ }
259
+
260
+ if (config.runtime.seriesLabels && !config.legend.dynamicLegend) {
261
+ let index = config.runtime.seriesLabelsAll.indexOf(itemName)
262
+ itemName = config.runtime.seriesKeys[index]
263
+ }
264
+
265
+ if (seriesHighlight.length > 0 && !seriesHighlight.includes(itemName)) {
266
+ className.push('inactive')
267
+ }
268
+
269
+ if (seriesHighlight.length === 0 && config.legend.dynamicLegend) {
270
+ className.push('inactive')
271
+ }
272
+
273
+ return (
274
+ <>
275
+ <LegendItem className={className.join(' ')} tabIndex={0} key={`dynamic-legend-item-${i}`} alignItems='center'>
276
+ <button
277
+ className='btn-wrapper'
278
+ onClick={() => {
279
+ highlight(label)
280
+ }}
281
+ >
282
+ <LegendCircle fill={palette[i]} config={config} />
283
+ <LegendLabel align='space-between' margin='4px 0 0 4px'>
284
+ {label.text}
285
+ </LegendLabel>
286
+ </button>
287
+ <button onClick={() => removeDynamicLegendItem(label)}>x</button>
288
+ </LegendItem>
289
+ </>
290
+ )
291
+ })}
292
+ </div>
293
+ {seriesHighlight.length < dynamicLegendItems.length && (
294
+ <button className={`legend-reset legend-reset--dynamic ${config.theme}`} onClick={highlightReset} tabIndex={0}>
295
+ Reset
296
+ </button>
297
+ )}
298
+ </aside>
299
+ )
300
+ )
301
+ }
302
+
303
+ export default Legend
@@ -7,12 +7,15 @@ import { Text } from '@visx/text'
7
7
 
8
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
9
 
10
- import Context from '../context'
10
+ import ConfigContext from '../ConfigContext'
11
11
 
12
12
  import useRightAxis from '../hooks/useRightAxis'
13
13
 
14
14
  export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, seriesStyle = 'Line' }) {
15
- const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, updateConfig } = useContext<any>(Context)
15
+ const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, cleanData, updateConfig } = useContext(ConfigContext)
16
+ // Just do this once up front otherwise we end up
17
+ // calling clean several times on same set of data (TT)
18
+ const cleanedData = cleanData(data, config.xAxis.dataKey)
16
19
  const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
17
20
 
18
21
  const handleLineType = lineType => {
@@ -28,9 +31,22 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
28
31
  }
29
32
  }
30
33
 
34
+ const handleAxisFormating = (axis = 'left', label, value) => {
35
+ // if this is an x axis category/date value return without doing any formatting.
36
+ if (label === config.runtime.xAxis.label) return value
37
+
38
+ axis = String(axis).toLocaleLowerCase()
39
+ if (label) {
40
+ return `${label}: ${formatNumber(value, axis)}`
41
+ }
42
+ return `${formatNumber(value, axis)}`
43
+ }
44
+
31
45
  return (
32
46
  <ErrorBoundary component='LineChart'>
33
- <Group left={config.runtime.yAxis.size}>
47
+ <Group left={config.runtime.yAxis.size ? parseInt(config.runtime.yAxis.size) : 66}>
48
+ {' '}
49
+ {/* left - expects a number not a string */}
34
50
  {(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => {
35
51
  let lineType = config.series.filter(item => item.dataKey === seriesKey)[0].type
36
52
  const seriesData = config.series.filter(item => item.dataKey === seriesKey)
@@ -42,28 +58,41 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
42
58
  opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
43
59
  display={config.legend.behavior === 'highlight' || (seriesHighlight.length === 0 && !config.legend.dynamicLegend) || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
44
60
  >
45
- {data.map((d, dataIndex) => {
46
- let seriesAxis = config.series.filter(s => s.dataKey === seriesKey)[0].axis
61
+ {cleanedData.map((d, dataIndex) => {
62
+ // Find the series object from the config.series array that has a dataKey matching the seriesKey variable.
63
+ const series = config.series.find(({ dataKey }) => dataKey === seriesKey)
64
+ const { axis } = series
65
+
47
66
  const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(d[config.runtime.xAxis.dataKey])) : d[config.runtime.xAxis.dataKey]
48
- const yAxisValue = formatNumber(getYAxisData(d, seriesKey));
49
- let yAxisTooltip = config.runtime.yAxis.isLegendValue ? `${seriesKey}: ${yAxisValue} ` : config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue;
50
- let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
51
- if (seriesAxis === 'Left') {
52
- yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(getYAxisData(d, seriesKey))}` : formatNumber(getYAxisData(d, seriesKey))
53
- } else {
54
- yAxisTooltip = config.runtime.yAxis.rightLabel ? `${config.runtime.yAxis.rightLabel}: ${formatNumber(getYAxisData(d, seriesKey))}` : formatNumber(getYAxisData(d, seriesKey))
55
- }
67
+ const yAxisValue = getYAxisData(d, seriesKey)
68
+
69
+ const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
70
+ const labeltype = axis === 'Right' ? 'rightLabel' : 'label'
71
+ let label = config.runtime.yAxis[labeltype]
72
+ // if has muiltiple series dont show legend value on tooltip
73
+ if (!hasMultipleSeries) label = config.isLegendValue ? config.runtime.seriesLabels[seriesKey] : label
74
+
75
+ let yAxisTooltip = handleAxisFormating(axis, label, yAxisValue)
76
+ let xAxisTooltip = handleAxisFormating(axis, config.runtime.xAxis.label, xAxisValue)
77
+
56
78
  const tooltip = `<div>
79
+ ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && Object.keys(config.runtime.seriesLabels).length > 1 ? `${config.runtime.seriesLabels[seriesKey] || ''}<br/>` : ''}
57
80
  ${yAxisTooltip}<br />
58
- ${xAxisTooltip}<br />
59
- ${config.seriesLabel ? `${config.seriesLabel}: ${seriesKey}` : ''}
81
+ ${xAxisTooltip}
60
82
  </div>`
61
83
  let circleRadii = 4.5
84
+
62
85
  return (
63
86
  d[seriesKey] !== undefined &&
64
87
  d[seriesKey] !== '' &&
65
88
  d[seriesKey] !== null && (
89
+ // isNumber(d[seriesKey]) &&
90
+ // isNumber(getYAxisData(d, seriesKey)) &&
91
+ // isNumber(getXAxisData(d)) &&
92
+ // isNumber(yScaleRight(getXAxisData(d))) &&
93
+ // isNumber(yScale(getXAxisData(d))) &&
66
94
  <Group key={`series-${seriesKey}-point-${dataIndex}`}>
95
+ {/* Render legend */}
67
96
  <Text
68
97
  display={config.labels ? 'block' : 'none'}
69
98
  x={xScale(getXAxisData(d))}
@@ -77,12 +106,12 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
77
106
  <circle
78
107
  key={`${seriesKey}-${dataIndex}`}
79
108
  r={circleRadii}
80
- cx={xScale(getXAxisData(d))}
109
+ cx={Number(xScale(getXAxisData(d)))}
81
110
  cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))}
82
111
  fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
83
112
  style={{ fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000' }}
84
- data-tip={tooltip}
85
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
113
+ data-tooltip-html={tooltip}
114
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
86
115
  />
87
116
  </Group>
88
117
  )
@@ -91,7 +120,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
91
120
 
92
121
  <LinePath
93
122
  curve={allCurves.curveLinear}
94
- data={data}
123
+ data={cleanedData}
95
124
  x={d => xScale(getXAxisData(d))}
96
125
  y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
97
126
  stroke={
@@ -107,14 +136,50 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
107
136
  strokeOpacity={1}
108
137
  shapeRendering='geometricPrecision'
109
138
  strokeDasharray={lineType ? handleLineType(lineType) : 0}
110
- defined={(item,i) => {
111
- return item[config.runtime.seriesLabels[seriesKey]] !== "" && item[config.runtime.seriesLabels[seriesKey]] !== null;
139
+ defined={(item, i) => {
140
+ return item[config.runtime.seriesLabels[seriesKey]] !== '' && item[config.runtime.seriesLabels[seriesKey]] !== null && item[config.runtime.seriesLabels[seriesKey]] !== undefined
112
141
  }}
113
142
  />
143
+ {config.animate && (
144
+ <LinePath
145
+ className='animation'
146
+ curve={allCurves.curveLinear}
147
+ data={cleanedData}
148
+ x={d => xScale(getXAxisData(d))}
149
+ y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
150
+ stroke='#fff'
151
+ strokeWidth={3}
152
+ strokeOpacity={1}
153
+ shapeRendering='geometricPrecision'
154
+ strokeDasharray={lineType ? handleLineType(lineType) : 0}
155
+ defined={(item, i) => {
156
+ return isNumber(item[config.runtime.seriesLabels[seriesKey]])
157
+ }}
158
+ />
159
+ )}
160
+
161
+ {/* Render series labels at end if each line if selected in the editor */}
162
+ {config.showLineSeriesLabels &&
163
+ (config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map(seriesKey => {
164
+ let lastDatum
165
+ for (let i = data.length - 1; i >= 0; i--) {
166
+ if (data[i][seriesKey]) {
167
+ lastDatum = data[i]
168
+ break
169
+ }
170
+ }
171
+ if (!lastDatum) {
172
+ return <></>
173
+ }
174
+ return (
175
+ <text x={xScale(getXAxisData(lastDatum)) + 5} y={yScale(getYAxisData(lastDatum, seriesKey))} alignmentBaseline='middle' fill={config.colorMatchLineSeriesLabels && colorScale ? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey) : 'black'}>
176
+ {config.runtime.seriesLabels[seriesKey] || seriesKey}
177
+ </text>
178
+ )
179
+ })}
114
180
  </Group>
115
181
  )
116
182
  })}
117
-
118
183
  {/* Message when dynamic legend and nothing has been picked */}
119
184
  {config.legend.dynamicLegend && seriesHighlight.length === 0 && (
120
185
  <Text x={xMax / 2} y={yMax / 2} fill='black' textAnchor='middle' color='black'>