@cdc/chart 4.23.10 → 4.24.1

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 (125) hide show
  1. package/dist/cdcchart.js +34606 -32218
  2. package/examples/feature/bar/additional-column-tooltip.json +446 -0
  3. package/examples/feature/bar/example-bar-chart.json +1 -46
  4. package/examples/feature/bar/lollipop.json +156 -0
  5. package/examples/feature/bar/tall-data.json +98 -0
  6. package/examples/feature/combo/planet-combo-example-config.json +99 -9
  7. package/examples/feature/dev-4261.json +399 -0
  8. package/examples/feature/forest-plot/forest-plot.json +63 -19
  9. package/examples/feature/forest-plot/{broken.json → linear.json} +77 -23
  10. package/examples/feature/forest-plot/log.json +26 -0
  11. package/examples/feature/forest-plot/logarithmic.json +271 -0
  12. package/examples/feature/line/line-chart-preliminary.json +346 -0
  13. package/examples/feature/line/line-points.json +340 -0
  14. package/examples/feature/regions/index.json +462 -0
  15. package/examples/feature/scatterplot/scatterplot.json +272 -33
  16. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
  17. package/examples/private/chart-t.json +3740 -0
  18. package/examples/private/combo.json +369 -0
  19. package/examples/private/epi-data.csv +13 -0
  20. package/examples/private/epi-data.json +62 -0
  21. package/examples/private/epi.json +403 -0
  22. package/examples/private/occupancy.json +109283 -0
  23. package/examples/private/prod-line-config.json +401 -0
  24. package/examples/private/region-data.json +822 -0
  25. package/examples/private/region-testing.json +312 -0
  26. package/examples/private/scaling.json +45325 -0
  27. package/examples/private/testing-data.json +1739 -0
  28. package/examples/private/testing.json +816 -0
  29. package/examples/sparkline-multilple.json +846 -0
  30. package/index.html +12 -8
  31. package/package.json +3 -3
  32. package/src/CdcChart.tsx +42 -211
  33. package/src/ConfigContext.tsx +6 -0
  34. package/src/_stories/Chart.stories.tsx +188 -0
  35. package/src/_stories/Chart.tooltip.stories.tsx +305 -0
  36. package/src/_stories/ChartBrush.stories.tsx +19 -0
  37. package/src/_stories/ChartEditor.stories.tsx +22 -0
  38. package/src/_stories/ChartLine.preliminary.tsx +19 -0
  39. package/src/_stories/ChartSuppress.stories.tsx +19 -0
  40. package/src/_stories/_mock/brush_mock.json +393 -0
  41. package/src/_stories/_mock/pie_config.json +191 -0
  42. package/src/_stories/_mock/pie_data.json +218 -0
  43. package/src/_stories/_mock/preliminary_mock.json +346 -0
  44. package/src/_stories/_mock/suppress_mock.json +911 -0
  45. package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +6 -7
  46. package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +7 -36
  47. package/src/components/AreaChart/index.tsx +4 -0
  48. package/src/components/{BarChart.Horizontal.jsx → BarChart/components/BarChart.Horizontal.tsx} +111 -34
  49. package/src/components/{BarChart.StackedHorizontal.jsx → BarChart/components/BarChart.StackedHorizontal.tsx} +55 -20
  50. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +106 -0
  51. package/src/components/{BarChart.Vertical.jsx → BarChart/components/BarChart.Vertical.tsx} +162 -34
  52. package/src/components/BarChart/components/BarChart.jsx +39 -0
  53. package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
  54. package/src/components/BarChart/components/context.tsx +13 -0
  55. package/src/components/BarChart/index.tsx +3 -0
  56. package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +1 -1
  57. package/src/components/BoxPlot/index.tsx +3 -0
  58. package/src/components/DeviationBar.jsx +4 -3
  59. package/src/components/{EditorPanel.jsx → EditorPanel/EditorPanel.tsx} +807 -865
  60. package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +109 -0
  61. package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panel.ForestPlotSettings.tsx} +190 -220
  62. package/src/components/EditorPanel/components/Panel.Regions.tsx +168 -0
  63. package/src/components/{Series.jsx → EditorPanel/components/Panel.Series.tsx} +23 -4
  64. package/src/components/EditorPanel/components/PanelProps.ts +3 -0
  65. package/src/components/EditorPanel/components/Panels.tsx +13 -0
  66. package/src/components/EditorPanel/components/panels.scss +72 -0
  67. package/src/components/EditorPanel/editor-panel.scss +751 -0
  68. package/src/components/EditorPanel/index.tsx +3 -0
  69. package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +50 -5
  70. package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
  71. package/src/components/Forecasting/index.tsx +3 -0
  72. package/src/components/ForestPlot/ForestPlot.tsx +254 -0
  73. package/src/components/ForestPlot/ForestPlotProps.ts +18 -0
  74. package/src/components/ForestPlot/index.scss +1 -0
  75. package/src/components/ForestPlot/index.tsx +3 -0
  76. package/src/components/Legend/Legend.tsx +347 -0
  77. package/src/components/Legend/index.tsx +3 -0
  78. package/src/components/LineChart/LineChartProps.ts +46 -0
  79. package/src/components/{LineChart.Circle.tsx → LineChart/components/LineChart.Circle.tsx} +36 -30
  80. package/src/components/LineChart/helpers.ts +45 -0
  81. package/src/components/LineChart/index.scss +1 -0
  82. package/src/components/{LineChart.tsx → LineChart/index.tsx} +83 -42
  83. package/src/components/LinearChart.jsx +125 -82
  84. package/src/components/PairedBarChart.jsx +2 -2
  85. package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +16 -7
  86. package/src/components/PieChart/index.tsx +3 -0
  87. package/src/components/Regions/components/Regions.tsx +135 -0
  88. package/src/components/Regions/index.tsx +3 -0
  89. package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
  90. package/src/components/ScatterPlot/index.tsx +3 -0
  91. package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
  92. package/src/components/Sparkline/index.tsx +3 -0
  93. package/src/components/ZoomBrush.tsx +168 -0
  94. package/src/data/initial-state.js +30 -16
  95. package/src/helpers/abbreviateNumber.ts +17 -0
  96. package/src/helpers/computeMarginBottom.ts +55 -0
  97. package/src/helpers/filterData.ts +18 -0
  98. package/src/helpers/generateColorsArray.ts +8 -0
  99. package/src/helpers/getQuartiles.ts +30 -0
  100. package/src/helpers/handleChartAriaLabels.ts +19 -0
  101. package/src/helpers/handleLineType.ts +18 -0
  102. package/src/helpers/lineOptions.ts +18 -0
  103. package/src/helpers/sort.ts +7 -0
  104. package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
  105. package/src/hooks/useBarChart.js +72 -7
  106. package/src/hooks/useColorScale.ts +50 -0
  107. package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
  108. package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
  109. package/src/hooks/{useScales.js → useScales.ts} +64 -17
  110. package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +84 -55
  111. package/src/scss/main.scss +70 -38
  112. package/src/types/ChartConfig.ts +178 -0
  113. package/src/types/ChartContext.ts +54 -0
  114. package/src/types/ForestPlot.ts +53 -0
  115. package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
  116. package/src/ConfigContext.jsx +0 -5
  117. package/src/components/BarChart.StackedVertical.jsx +0 -95
  118. package/src/components/BarChart.jsx +0 -30
  119. package/src/components/ForestPlot.jsx +0 -191
  120. package/src/components/Legend.jsx +0 -277
  121. package/src/scss/LinearChart.scss +0 -0
  122. package/src/scss/editor-panel.scss +0 -745
  123. package/src/scss/legend.scss +0 -206
  124. package/src/scss/mixins.scss +0 -0
  125. package/src/scss/variables.scss +0 -1
@@ -0,0 +1,347 @@
1
+ import { useContext } 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
+ import { useHighlightedBars } from '../../hooks/useHighlightedBars'
9
+ import { handleLineType } from '../../helpers/handleLineType'
10
+ import { Line } from '@visx/shape'
11
+ import { colorPalettesChart as colorPalettes, sequentialPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
12
+ import { scaleOrdinal } from '@visx/scale'
13
+ import { FaStar } from 'react-icons/fa'
14
+ import { Text } from '@visx/text'
15
+ import { Group } from '@visx/group'
16
+
17
+ type Label = {
18
+ datum: string
19
+ index: number
20
+ text: string
21
+ value: string
22
+ icon?: any
23
+ }
24
+
25
+ /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
26
+ const Legend = () => {
27
+ // prettier-ignore
28
+ const {
29
+ config,
30
+ colorScale,
31
+ seriesHighlight,
32
+ highlight,
33
+ tableData,
34
+ highlightReset,
35
+ transformedData: data,
36
+ currentViewport
37
+ } = useContext(ConfigContext)
38
+
39
+ const { innerClasses, containerClasses } = useLegendClasses(config)
40
+ const { visualizationType, visualizationSubType, series, runtime, orientation, legend } = config
41
+ // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
42
+ const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
43
+ const displayScale = scaleOrdinal({
44
+ domain: config.suppressedData?.map(d => d.label),
45
+ range: ['none'],
46
+ unknown: 'block'
47
+ })
48
+
49
+ const renderDashes = style => {
50
+ const dashCount = style === 'Dashed Small' ? 3 : 2
51
+ const dashClass = `dashes ${style.toLowerCase().replace(' ', '-')}`
52
+
53
+ return (
54
+ <div className={dashClass}>
55
+ {Array.from({ length: dashCount }, (_, i) => (
56
+ <span key={i}>-</span>
57
+ ))}
58
+ </div>
59
+ )
60
+ }
61
+ const renderDashesOrCircle = style => {
62
+ if (['Dashed Small', 'Dashed Medium', 'Dashed Large'].includes(style)) {
63
+ return renderDashes(style)
64
+ } else if (style === 'Open Circles') {
65
+ return <div className='dashes open-circles'></div>
66
+ }
67
+ }
68
+ const createLegendLabels = (defaultLabels): Label[] => {
69
+ const colorCode = config.legend?.colorCode
70
+ if (visualizationType === 'Deviation Bar') {
71
+ const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
72
+ const labelBelow = {
73
+ datum: 'X',
74
+ index: 0,
75
+ text: `Below ${config.xAxis.targetLabel}`,
76
+ value: belowColor
77
+ }
78
+ const labelAbove = {
79
+ datum: 'X',
80
+ index: 1,
81
+ text: `Above ${config.xAxis.targetLabel}`,
82
+ value: aboveColor
83
+ }
84
+
85
+ return reverseLabels([labelBelow, labelAbove])
86
+ }
87
+ if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
88
+ let palette = colorPalettes[config.palette]
89
+
90
+ while (tableData.length > palette.length) {
91
+ palette = palette.concat(palette)
92
+ }
93
+ palette = palette.slice(0, data.length)
94
+ //store unique values to Set by colorCode
95
+ const set = new Set()
96
+
97
+ tableData.forEach(d => set.add(d[colorCode]))
98
+
99
+ // create labels with unique values
100
+ const uniqueLabels = Array.from(set).map((val, i) => {
101
+ const newLabel = {
102
+ datum: val,
103
+ index: i,
104
+ text: val,
105
+ value: palette[i]
106
+ }
107
+ return newLabel
108
+ })
109
+
110
+ return reverseLabels(uniqueLabels)
111
+ }
112
+
113
+ // get forecasting items inside of combo
114
+ if (runtime?.forecastingSeriesKeys?.length > 0) {
115
+ let seriesLabels = []
116
+
117
+ //store unique values to Set by colorCode
118
+ // loop through each stage/group/area on the chart and create a label
119
+ config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
120
+ return outerGroup?.stages?.map((stage, index) => {
121
+ let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
122
+
123
+ const newLabel = {
124
+ datum: stage.key,
125
+ index: index,
126
+ text: stage.key,
127
+ value: colorValue
128
+ }
129
+
130
+ seriesLabels.push(newLabel)
131
+ })
132
+ })
133
+
134
+ // loop through bars for now to meet requirements.
135
+ config.runtime.barSeriesKeys &&
136
+ config.runtime.barSeriesKeys.forEach((bar, index) => {
137
+ let colorValue = colorPalettes[config.palette][index] ? colorPalettes[config.palette][index] : '#ccc'
138
+
139
+ const newLabel = {
140
+ datum: bar,
141
+ index: index,
142
+ text: bar,
143
+ value: colorValue
144
+ }
145
+
146
+ seriesLabels.push(newLabel)
147
+ })
148
+
149
+ return reverseLabels(seriesLabels)
150
+ }
151
+
152
+ // DEV-4161: replaceable series name in the legend
153
+ const hasNewSeriesName = config.series.map(s => s.name).filter(item => item).length > 0
154
+ if (hasNewSeriesName) {
155
+ let palette = colorPalettes[config.palette]
156
+
157
+ while (tableData.length > palette.length) {
158
+ palette = palette.concat(palette)
159
+ }
160
+
161
+ palette = palette.slice(0, data.length)
162
+ //store unique values to Set by colorCode
163
+ const set = new Set()
164
+
165
+ config.series.forEach(d => {
166
+ set.add(d['name'] ? d['name'] : d['dataKey'])
167
+ })
168
+
169
+ // create labels with unique values
170
+ const uniqueLabels = Array.from(set).map((val, i) => {
171
+ const newLabel = {
172
+ datum: val,
173
+ index: i,
174
+ text: val,
175
+ value: colorScale(val)
176
+ }
177
+ return newLabel
178
+ })
179
+
180
+ return reverseLabels(uniqueLabels)
181
+ }
182
+
183
+ if ((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.visualizationSubType === 'regular' && config.suppressedData) {
184
+ const lastIndex = defaultLabels.length - 1
185
+ let newLabels = []
186
+
187
+ config.suppressedData?.forEach(({ label, icon }, index) => {
188
+ if (label && icon) {
189
+ const newLabel = {
190
+ datum: label,
191
+ index: lastIndex + index,
192
+ text: label,
193
+ icon: <FaStar color='#000' size={15} />
194
+ }
195
+ newLabels.push(newLabel)
196
+ }
197
+ })
198
+
199
+ return [...defaultLabels, ...newLabels]
200
+ }
201
+
202
+ return reverseLabels(defaultLabels)
203
+ }
204
+
205
+ const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
206
+
207
+ const legendClasses = {
208
+ marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
209
+ marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `${isBottomOrSmallViewport ? config.dynamicMarginTop + 15 : 0}px`
210
+ }
211
+
212
+ const { HighLightedBarUtils } = useHighlightedBars(config)
213
+
214
+ let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
215
+ if (!legend) return null
216
+
217
+ return (
218
+ config.visualizationType !== 'Box Plot' && (
219
+ <aside style={legendClasses} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
220
+ {legend.label && <h2>{parse(legend.label)}</h2>}
221
+ {legend.description && <p>{parse(legend.description)}</p>}
222
+ <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
223
+ {labels => {
224
+ return (
225
+ <>
226
+ <div className={innerClasses.join(' ')}>
227
+ {createLegendLabels(labels).map((label, i) => {
228
+ let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
229
+ let itemName = label.datum
230
+
231
+ // Filter excluded data keys from legend
232
+ if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
233
+ return null
234
+ }
235
+
236
+ if (runtime.seriesLabels) {
237
+ let index = config.runtime.seriesLabelsAll.indexOf(itemName)
238
+ itemName = config.runtime.seriesKeys[index]
239
+
240
+ if (runtime?.forecastingSeriesKeys?.length > 0) {
241
+ itemName = label.text
242
+ }
243
+ }
244
+
245
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
246
+ className.push('inactive')
247
+ }
248
+
249
+ return (
250
+ <LegendItem
251
+ className={className.join(' ')}
252
+ tabIndex={0}
253
+ key={`legend-quantile-${i}`}
254
+ onKeyPress={e => {
255
+ if (e.key === 'Enter') {
256
+ highlight(label)
257
+ }
258
+ }}
259
+ onClick={() => {
260
+ highlight(label)
261
+ }}
262
+ >
263
+ {config.visualizationType === 'Line' && config.legend.lineMode ? (
264
+ <svg width={40} height={20}>
265
+ <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
266
+ </svg>
267
+ ) : (
268
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
269
+ <LegendCircle margin='0' fill={label.value} display={displayScale(label.datum)} />
270
+ <div style={{ marginTop: '2px', marginRight: '6px' }}>{label.icon}</div>
271
+ </div>
272
+ )}
273
+
274
+ <LegendLabel align='left' margin='0 0 0 4px'>
275
+ {label.text}
276
+ </LegendLabel>
277
+ </LegendItem>
278
+ )
279
+ })}
280
+
281
+ {highLightedLegendItems.map((bar, i) => {
282
+ // if duplicates only return first item
283
+ let className = 'legend-item'
284
+ let itemName = bar.legendLabel
285
+
286
+ if (!itemName) return false
287
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
288
+ className += ' inactive'
289
+ }
290
+ return (
291
+ <LegendItem
292
+ className={className}
293
+ tabIndex={0}
294
+ key={`legend-quantile-${i}`}
295
+ onKeyPress={e => {
296
+ if (e.key === 'Enter') {
297
+ highlight(bar.legendLabel)
298
+ }
299
+ }}
300
+ onClick={() => {
301
+ highlight(bar.legendLabel)
302
+ }}
303
+ >
304
+ <LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
305
+ <LegendLabel align='left' margin='0 0 0 4px'>
306
+ {bar.legendLabel ? bar.legendLabel : bar.value}
307
+ </LegendLabel>
308
+ </LegendItem>
309
+ )
310
+ })}
311
+ {seriesHighlight.length > 0 && (
312
+ <button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
313
+ Reset
314
+ </button>
315
+ )}
316
+ </div>
317
+ <>
318
+ {config.preliminaryData.some(pd => pd.label) && (
319
+ <>
320
+ <hr></hr>
321
+ <div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : 'dash-left'}>
322
+ {config.preliminaryData.map((pd, index) => {
323
+ return (
324
+ <div className='dash-container' key={index}>
325
+ {pd.label && (
326
+ <>
327
+ <div className='dash-inner'>{renderDashesOrCircle(pd.style)}</div>
328
+ <div style={{ marginLeft: '7px' }}>{pd.label}</div>
329
+ </>
330
+ )}
331
+ </div>
332
+ )
333
+ })}
334
+ </div>
335
+ </>
336
+ )}
337
+ </>
338
+ </>
339
+ )
340
+ }}
341
+ </LegendOrdinal>
342
+ </aside>
343
+ )
344
+ )
345
+ }
346
+
347
+ export default Legend
@@ -0,0 +1,3 @@
1
+ import Legend from './Legend'
2
+
3
+ export default Legend
@@ -0,0 +1,46 @@
1
+ // todo: review tooltipData type
2
+ // todo: review svgRef type
3
+ export type LineChartProps = {
4
+ xScale: Function
5
+ yScale: Function
6
+ getXAxisData: Function
7
+ getYAxisData: Function
8
+ xMax: number
9
+ yMax: number
10
+ handleTooltipMouseOver: Function
11
+ handleTooltipMouseOff: Function
12
+ showTooltip: boolean
13
+ seriesStyle: String
14
+ svgRef: any
15
+ handleTooltipClick: Function
16
+ tooltipData: any
17
+ }
18
+
19
+ export interface PreliminaryDataItem {
20
+ style: string
21
+ type: string
22
+ column: string
23
+ value: string
24
+ seriesKey: string
25
+ }
26
+
27
+ export interface DataItem {
28
+ [key: string]: any
29
+ }
30
+
31
+ export interface Config {
32
+ preliminaryData: PreliminaryDataItem[] | []
33
+ }
34
+ export interface StyleProps {
35
+ preliminaryData: PreliminaryDataItem[]
36
+ rawData: DataItem[]
37
+ stroke: string
38
+ handleLineType: Function
39
+ lineType: string
40
+ seriesKey: 'string'
41
+ }
42
+ export interface Style {
43
+ stroke: string
44
+ strokeWidth: number
45
+ strokeDasharray: string
46
+ }
@@ -1,13 +1,12 @@
1
1
  import React from 'react'
2
+ import chroma from 'chroma-js'
3
+ import { type ChartConfig } from '../../../types/ChartConfig'
2
4
 
3
5
  // todo: change this config obj to ChartConfig once its created
4
6
  type LineChartCircleProps = {
5
- config: {
6
- xAxis: string
7
- data: Object[]
8
- lineDatapointStyle: string
9
- runtime: Object
10
- }
7
+ circleData: object[]
8
+ config: ChartConfig
9
+ data: object[]
11
10
  d?: Object
12
11
  displayArea: boolean
13
12
  seriesKey: string
@@ -25,34 +24,34 @@ type LineChartCircleProps = {
25
24
  }
26
25
 
27
26
  const LineChartCircle = (props: LineChartCircleProps) => {
28
- const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight } = props
27
+ const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData } = props
29
28
  const { lineDatapointStyle } = config
30
29
  const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
30
+ // If we're not showing the circle, simply return
31
+ if (lineDatapointStyle === 'hidden') return <></>
31
32
 
32
- if (lineDatapointStyle === 'hidden') return null
33
+ const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
33
34
 
34
- const getColor = (displayArea, colorScale, config, seriesIndex, hoveredKey, seriesKey) => {
35
- const customColors = config.customColors || []
35
+ const getColor = (displayArea: boolean, colorScale: Function, config: ChartConfig, hoveredKey: string, seriesKey: string) => {
36
36
  const seriesLabels = config.runtime.seriesLabels || []
37
37
  let color
38
38
 
39
- const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
40
-
41
39
  if (displayArea) {
42
- if (colorScale) {
43
- if (getIndex(hoveredKey) === false) return
44
- color = customColors.length > 0 ? customColors[getIndex(hoveredKey)] : colorScale(seriesLabels[hoveredKey] || seriesKey)
45
- } else if (customColors) {
46
- color = customColors.length > 0 ? customColors[getIndex(hoveredKey)] : 'transparent'
47
- }
40
+ color = colorScale(seriesLabels[hoveredKey] || seriesKey)
48
41
  } else {
49
42
  color = 'transparent'
50
43
  }
51
- console.log('color', color)
44
+
45
+ if (config.lineDatapointColor === 'Lighter than Line' && color !== 'transparent' && color) {
46
+ color = chroma(color).brighten(1)
47
+ }
52
48
  return color
53
49
  }
54
-
55
50
  if (lineDatapointStyle === 'always show') {
51
+ const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey])
52
+ if (isMatch) {
53
+ return <></>
54
+ }
56
55
  return (
57
56
  <circle
58
57
  cx={config.xAxis.type === 'categorical' ? xScale(d[config.xAxis.dataKey]) : xScale(parseDate(d[config.xAxis.dataKey]))}
@@ -60,7 +59,7 @@ const LineChartCircle = (props: LineChartCircleProps) => {
60
59
  r={4.5}
61
60
  opacity={d[seriesKey] ? 1 : 0}
62
61
  fillOpacity={1}
63
- fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000') : 'transparent'}
62
+ fill={getColor(displayArea, colorScale, config, seriesKey, seriesKey)}
64
63
  style={{ filter: 'unset', opacity: 1 }}
65
64
  />
66
65
  )
@@ -68,26 +67,32 @@ const LineChartCircle = (props: LineChartCircleProps) => {
68
67
 
69
68
  if (lineDatapointStyle === 'hover') {
70
69
  if (!tooltipData) return
70
+ if (!seriesKey) return
71
71
  if (!tooltipData.data) return
72
72
  let hoveredXValue = tooltipData?.data?.[0]?.[1]
73
73
  if (!hoveredXValue) return
74
+
74
75
  let hoveredSeriesValue
75
76
  let hoveredSeriesIndex
76
77
  let hoveredSeriesData = tooltipData.data.filter(d => d[0] === seriesKey)
77
78
  let hoveredSeriesKey = hoveredSeriesData?.[0]?.[0]
78
79
  let hoveredSeriesAxis = hoveredSeriesData?.[0]?.[2]
79
- hoveredSeriesIndex = tooltipData.data.indexOf(hoveredSeriesKey)
80
- hoveredSeriesValue = hoveredSeriesData?.[0]?.[1]
81
-
82
- console.log('hoveredSeriesKey', hoveredSeriesKey)
83
- console.log('hoveredSeriesAxis', hoveredSeriesAxis)
84
- console.log('hoveredSeriesValue', hoveredSeriesValue)
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
85
 
86
- console.log('hoveredSeriesData', hoveredSeriesData)
87
- return tooltipData.data.map((tooltipItem, index) => {
86
+ // hoveredSeriesValue = extractNumber(hoveredSeriesValue)
87
+ return tooltipData?.data.map((tooltipItem, index) => {
88
88
  let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
89
89
 
90
90
  if (isNaN(hoveredSeriesValue)) return <></>
91
+ const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
92
+
93
+ if (isMatch) {
94
+ return <></>
95
+ }
91
96
  return (
92
97
  <circle
93
98
  cx={config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))}
@@ -95,8 +100,9 @@ const LineChartCircle = (props: LineChartCircleProps) => {
95
100
  r={4.5}
96
101
  opacity={1}
97
102
  fillOpacity={1}
98
- fill={getColor(displayArea, colorScale, config, seriesIndex, hoveredSeriesKey, seriesKey)}
103
+ fill={getColor(displayArea, colorScale, config, hoveredSeriesKey, seriesKey)}
99
104
  style={{ filter: 'unset', opacity: 1 }}
105
+ key={`line-chart-circle--${JSON.stringify(tooltipItem)}--${index}`}
100
106
  />
101
107
  )
102
108
  })
@@ -0,0 +1,45 @@
1
+ import { type PreliminaryDataItem, DataItem, StyleProps, Style } from './LineChartProps'
2
+
3
+ export const createStyles = (props: StyleProps): Style[] => {
4
+ const { preliminaryData, rawData, stroke, handleLineType, lineType, seriesKey } = props
5
+
6
+ const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style)
7
+ const getMatchingPd = (point: DataItem): PreliminaryDataItem => validPreliminaryData.find(pd => pd.seriesKey === seriesKey && point[pd.column] === pd.value && pd.type === 'effect' && pd.style !== 'Open Circles')
8
+
9
+ let styles: Style[] = []
10
+ const createStyle = (lineStyle): Style => ({
11
+ stroke: stroke,
12
+ strokeWidth: 2,
13
+ strokeDasharray: lineStyle
14
+ })
15
+
16
+ rawData.forEach((d, index) => {
17
+ let matchingPd: PreliminaryDataItem = getMatchingPd(d)
18
+ let style: Style = matchingPd ? createStyle(handleLineType(matchingPd.style)) : createStyle(handleLineType(lineType))
19
+
20
+ styles.push(style)
21
+
22
+ // If matchingPd exists, update the previous style if there is a previous element
23
+ if (matchingPd && index > 0) {
24
+ styles[index - 1] = createStyle(handleLineType(matchingPd.style))
25
+ }
26
+ })
27
+ return styles as Style[]
28
+ }
29
+
30
+ export const filterCircles = (preliminaryData: PreliminaryDataItem[], data: DataItem[], seriesKey: string): DataItem[] => {
31
+ // 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
+
34
+ let filteredData: DataItem[] = []
35
+
36
+ // Process data to find matching items
37
+ data.forEach(item => {
38
+ if (circlesFiltered.some(d => item[d.column] === d.value && d.seriesKey === seriesKey)) {
39
+ // Add current item
40
+ filteredData.push(item)
41
+ }
42
+ })
43
+
44
+ return filteredData
45
+ }
@@ -0,0 +1 @@
1
+ // Line Chart Styles...