@cdc/chart 4.23.10 → 4.23.11

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 (56) hide show
  1. package/dist/cdcchart.js +30989 -29057
  2. package/examples/feature/bar/example-bar-chart.json +1 -46
  3. package/examples/feature/bar/lollipop.json +156 -0
  4. package/examples/feature/combo/planet-combo-example-config.json +99 -9
  5. package/examples/feature/dev-4261.json +399 -0
  6. package/examples/feature/forest-plot/{broken.json → linear.json} +55 -50
  7. package/examples/feature/forest-plot/logarithmic.json +306 -0
  8. package/examples/feature/line/line-points.json +340 -0
  9. package/examples/feature/regions/index.json +462 -0
  10. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
  11. package/examples/sparkline-multilple.json +846 -0
  12. package/index.html +10 -6
  13. package/package.json +3 -3
  14. package/src/CdcChart.tsx +75 -63
  15. package/src/_stories/Chart.stories.tsx +188 -0
  16. package/src/_stories/Chart.tooltip.stories.tsx +305 -0
  17. package/src/_stories/ChartBrush.stories.tsx +19 -0
  18. package/src/_stories/ChartSuppress.stories.tsx +19 -0
  19. package/src/_stories/_mock/brush_mock.json +393 -0
  20. package/src/_stories/_mock/suppress_mock.json +911 -0
  21. package/src/components/AreaChart.Stacked.jsx +4 -5
  22. package/src/components/AreaChart.jsx +6 -35
  23. package/src/components/{BarChart.Horizontal.jsx → BarChart.Horizontal.tsx} +106 -29
  24. package/src/components/{BarChart.StackedHorizontal.jsx → BarChart.StackedHorizontal.tsx} +22 -17
  25. package/src/components/{BarChart.StackedVertical.jsx → BarChart.StackedVertical.tsx} +22 -26
  26. package/src/components/{BarChart.Vertical.jsx → BarChart.Vertical.tsx} +175 -31
  27. package/src/components/BarChart.jsx +1 -1
  28. package/src/components/DeviationBar.jsx +4 -3
  29. package/src/components/EditorPanel.jsx +176 -50
  30. package/src/components/ForestPlot/ForestPlotProps.ts +11 -0
  31. package/src/components/ForestPlot/Readme.md +0 -0
  32. package/src/components/ForestPlot/index.scss +1 -0
  33. package/src/components/{ForestPlot.jsx → ForestPlot/index.tsx} +51 -31
  34. package/src/components/ForestPlotSettings.jsx +162 -113
  35. package/src/components/Legend.jsx +36 -3
  36. package/src/components/{LineChart.Circle.tsx → LineChart/LineChart.Circle.tsx} +26 -29
  37. package/src/components/LineChart/LineChartProps.ts +17 -0
  38. package/src/components/LineChart/index.scss +1 -0
  39. package/src/components/{LineChart.tsx → LineChart/index.tsx} +64 -35
  40. package/src/components/LinearChart.jsx +120 -60
  41. package/src/components/PairedBarChart.jsx +2 -2
  42. package/src/components/Series.jsx +22 -3
  43. package/src/components/ZoomBrush.tsx +168 -0
  44. package/src/data/initial-state.js +27 -12
  45. package/src/hooks/useBarChart.js +71 -7
  46. package/src/hooks/useColorScale.ts +50 -0
  47. package/src/hooks/useEditorPermissions.js +22 -4
  48. package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
  49. package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
  50. package/src/hooks/{useScales.js → useScales.ts} +64 -17
  51. package/src/hooks/useTooltip.jsx +68 -41
  52. package/src/scss/main.scss +3 -35
  53. package/src/types/ChartConfig.ts +43 -0
  54. package/src/types/ChartContext.ts +38 -0
  55. package/src/types/ChartProps.ts +7 -0
  56. package/src/types/ForestPlot.ts +60 -0
@@ -1,20 +1,44 @@
1
- import React, { useContext } from 'react'
1
+ import React, { useContext, useState } from 'react'
2
2
  import ConfigContext from '../ConfigContext'
3
3
  import { useBarChart } from '../hooks/useBarChart'
4
4
  import { Group } from '@visx/group'
5
5
  import { Text } from '@visx/text'
6
6
  import { BarGroup } from '@visx/shape'
7
7
  import { useHighlightedBars } from '../hooks/useHighlightedBars'
8
+ import { FaStar } from 'react-icons/fa'
8
9
 
9
10
  // third party
10
11
  import chroma from 'chroma-js'
11
12
 
12
- export const BarChartVertical = props => {
13
+ import { type BarChartProps } from '../types/ChartProps'
14
+
15
+ export const BarChartVertical = (props: BarChartProps) => {
13
16
  const { xScale, yScale, xMax, yMax, seriesScale } = props
14
- const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter, isNumber, getXAxisData, getYAxisData } = useContext(ConfigContext)
15
- const { barBorderWidth, hasMultipleSeries, applyRadius, updateBars, assignColorsToValues, section, lollipopBarWidth, lollipopShapeSize, getHighlightedBarColorByValue, getHighlightedBarByValue } = useBarChart()
17
+
18
+ const [barWidth, setBarWidth] = useState(0)
19
+ const [totalBarsInGroup, setTotalBarsInGroup] = useState(0)
20
+
21
+ const { barBorderWidth, hasMultipleSeries, applyRadius, updateBars, assignColorsToValues, section, lollipopBarWidth, lollipopShapeSize, getHighlightedBarColorByValue, getHighlightedBarByValue, generateIconSize, getAdditionalColumn, hoveredBar, onMouseOverBar, onMouseLeaveBar } = useBarChart()
22
+
23
+ // CONTEXT VALUES
24
+ // prettier-ignore
25
+ const { colorScale, config, formatDate, formatNumber, getXAxisData, getYAxisData, isNumber, parseDate, seriesHighlight, setSharedFilter, transformedData, dashboardConfig, setSeriesHighlight } = useContext(ConfigContext)
26
+
27
+ const { runtime } = config
28
+
16
29
  const { HighLightedBarUtils } = useHighlightedBars(config)
30
+ const data = config.brush.active && config.brush.data?.length ? config.brush.data : transformedData
17
31
 
32
+ const getIcon = (bar, barWidth) => {
33
+ let icon = null
34
+ const iconSize = generateIconSize(barWidth)
35
+ config.suppressedData?.forEach(d => {
36
+ if (bar.key === d.column && String(bar.value) === String(d.value) && d.icon) {
37
+ icon = <FaStar color='#000' size={iconSize} />
38
+ }
39
+ })
40
+ return icon
41
+ }
18
42
  return (
19
43
  config.visualizationSubType !== 'stacked' &&
20
44
  (config.visualizationType === 'Bar' || config.visualizationType === 'Combo') &&
@@ -33,16 +57,20 @@ export const BarChartVertical = props => {
33
57
  }}
34
58
  >
35
59
  {barGroups => {
36
- return updateBars(barGroups).map((barGroup, index) => (
60
+ return barGroups.map((barGroup, index) => (
37
61
  <Group className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`} key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`} id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`} left={(xMax / barGroups.length) * barGroup.index}>
38
62
  {barGroup.bars.map((bar, index) => {
39
63
  const scaleVal = config.useLogScale ? 0.1 : 0
64
+ const suppresedBarHeight = 20
40
65
  let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
41
66
  highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
42
67
  let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
43
68
  let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
44
- let barHeight = Math.abs(yScale(bar.value) - yScale(scaleVal))
45
- let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
69
+ let barHeightBase = Math.abs(yScale(bar.value) - yScale(scaleVal))
70
+ let barYBase = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
71
+ const supprssedBarY = bar.value >= 0 && isNumber(bar.value) ? yScale(scaleVal) - suppresedBarHeight : yScale(0)
72
+ const barY = config.suppressedData.some(d => bar.key === d.column && String(bar.value) === String(d.value)) ? supprssedBarY : barYBase
73
+
46
74
  let barGroupWidth = (xMax / barGroups.length) * (config.barThickness || 0.8)
47
75
  let offset = ((xMax / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
48
76
  // configure left side offset of lollipop bars
@@ -51,6 +79,8 @@ export const BarChartVertical = props => {
51
79
  }
52
80
 
53
81
  let barWidth = config.isLollipopChart ? lollipopBarWidth : barGroupWidth / barGroup.bars.length
82
+ setBarWidth(barWidth)
83
+ setTotalBarsInGroup(barGroup.bars.length)
54
84
 
55
85
  let yAxisValue = formatNumber(bar.value, 'left')
56
86
  let xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
@@ -58,18 +88,15 @@ export const BarChartVertical = props => {
58
88
  // create new Index for bars with negative values
59
89
  const newIndex = bar.value < 0 ? -1 : index
60
90
  const borderRadius = applyRadius(newIndex)
61
-
62
- let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
91
+ // tooltips
92
+ const additionalColTooltip = getAdditionalColumn(hoveredBar)
63
93
  let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
64
-
65
- if (!hasMultipleSeries) {
66
- yAxisTooltip = config.isLegendValue ? `${bar.key}: ${yAxisValue}` : config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
67
- }
94
+ const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
68
95
 
69
96
  const tooltip = `<ul>
70
- ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[bar.key] || ''}<br/>` : ''}
71
- <li class="tooltip-heading">${yAxisTooltip}</li>
72
- <li class="tooltip-body">${xAxisTooltip}</li>
97
+ <li class="tooltip-heading">${xAxisTooltip}</li>
98
+ <li class="tooltip-body ">${tooltipBody}</li>
99
+ <li class="tooltip-body ">${additionalColTooltip}</li>
73
100
  </li></ul>`
74
101
 
75
102
  // configure colors
@@ -84,19 +111,76 @@ export const BarChartVertical = props => {
84
111
  const highlightedBar = getHighlightedBarByValue(xAxisValue)
85
112
  const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
86
113
  const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
114
+ const barValueLabel = config.suppressedData.some(d => bar.key === d.column && bar.value === d.value) ? '' : yAxisValue
115
+ let barHeight = config.suppressedData.some(d => bar.key === d.column && String(bar.value) === String(d.value)) ? suppresedBarHeight : barHeightBase
116
+ const displaylollipopShape = config.suppressedData.some(d => bar.key === d.column && bar.value === d.value) ? 'none' : 'block'
117
+
118
+ const getBarBackgroundColor = (barColor: string, filteredOutColor?: string): string => {
119
+ let _barColor = barColor
120
+ let _filteredOutColor = filteredOutColor || '#f2f2f2'
121
+
122
+ /**
123
+ * If this is a dashboard using a setBy column on the bars
124
+ * color the bar that is using the filter with barColor and
125
+ * color the filteredOut (typically gray) bars with the filteredOutColor
126
+ */
127
+ if (dashboardConfig && dashboardConfig.dashboard.sharedFilters) {
128
+ const { sharedFilters } = dashboardConfig.dashboard
129
+
130
+ _barColor = sharedFilters.map(_sharedFilter => {
131
+ if (_sharedFilter.setBy === config.uid) {
132
+ // If the current filter is the reset filter item.
133
+ if (_sharedFilter.resetLabel === _sharedFilter.active) return barColor
134
+ // If the current filter is the bars
135
+ if (_sharedFilter.active === transformedData[barGroup.index][config.xAxis.dataKey]) return barColor
136
+ return _filteredOutColor
137
+ } else {
138
+ // If the setBy isn't the config.uid return the original barColor
139
+ return barColor
140
+ }
141
+ })[0]
142
+
143
+ if (isRegularLollipopColor) _barColor = barColor
144
+ if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
145
+ if (isHighlightedBar) _barColor = 'transparent'
146
+ return _barColor
147
+ }
148
+
149
+ // if this is a two tone lollipop slightly lighten the bar.
150
+ if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
151
+
152
+ // if we're highlighting a bar make it invisible since it gets a border
153
+ if (isHighlightedBar) _barColor = 'transparent'
154
+ return _barColor
155
+ }
87
156
 
88
- const background = () => {
89
- if (isRegularLollipopColor) return barColor
90
- if (isTwoToneLollipopColor) return chroma(barColor).brighten(1)
91
- if (isHighlightedBar) return 'transparent'
92
- return barColor
157
+ const getLeft = () => {
158
+ if (barWidth < 50 && barWidth > 15) return barWidth / 2.5
159
+ if (barWidth < 15 && barWidth > 5) return barWidth / 6
160
+ if (barWidth < 5) return 0
161
+ return barWidth / 2
93
162
  }
163
+ const iconStyle: { [key: string]: any } = {
164
+ position: 'absolute',
165
+ top: bar.value >= 0 && isNumber(bar.value) ? -suppresedBarHeight : undefined,
166
+ bottom: bar.value >= 0 && isNumber(bar.value) ? undefined : `-${suppresedBarHeight}px`,
167
+ left: getLeft()
168
+ }
169
+
170
+ if (config.isLollipopChart) {
171
+ iconStyle.left = 0
172
+ iconStyle.transform = `translateX(0)`
173
+ }
174
+
94
175
  const finalStyle = {
95
- background: background(),
176
+ background: getBarBackgroundColor(barColor),
96
177
  borderColor,
97
178
  borderStyle: 'solid',
98
- borderWidth,
99
- ...borderRadius
179
+ borderWidth: `${borderWidth}px`,
180
+ width: barWidth,
181
+ height: barHeight,
182
+ ...borderRadius,
183
+ cursor: dashboardConfig ? 'pointer' : 'default'
100
184
  }
101
185
 
102
186
  return (
@@ -104,22 +188,24 @@ export const BarChartVertical = props => {
104
188
  {/* This feels gross but inline transition was not working well*/}
105
189
  <style>
106
190
  {`
107
- .linear #barGroup${barGroup.index},
108
- .Combo #barGroup${barGroup.index} {
191
+ .linear #barGroup${barGroup.index} div,
192
+ .Combo #barGroup${barGroup.index} div {
109
193
  transform-origin: 0 ${barY + barHeight}px;
110
194
  }
111
195
  `}
112
196
  </style>
113
197
  <Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
114
198
  <foreignObject
199
+ onMouseOver={() => onMouseOverBar(xAxisValue, bar.key)}
200
+ onMouseLeave={onMouseLeaveBar}
201
+ style={{ overflow: 'visible', transition: 'all 0.2s linear' }}
115
202
  id={`barGroup${barGroup.index}`}
116
203
  key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
117
204
  x={barWidth * bar.index + offset}
118
205
  y={barY}
119
206
  width={barWidth}
120
207
  height={barHeight}
121
- style={finalStyle}
122
- opacity={transparentBar ? 0.5 : 1}
208
+ opacity={transparentBar ? 0.2 : 1}
123
209
  display={displayBar ? 'block' : 'none'}
124
210
  data-tooltip-html={tooltip}
125
211
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
@@ -130,14 +216,27 @@ export const BarChartVertical = props => {
130
216
  setSharedFilter(config.uid, bar)
131
217
  }
132
218
  }}
133
- ></foreignObject>
219
+ >
220
+ <div style={{ position: 'relative' }}>
221
+ <div style={iconStyle}>{getIcon(bar, barWidth)}</div>
222
+ <div style={{ ...finalStyle }}></div>
223
+ </div>
224
+ </foreignObject>
134
225
 
135
- <Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barWidth * (bar.index + 0.5) + offset} y={barY - 5} fill={labelColor} textAnchor='middle'>
136
- {yAxisValue}
226
+ <Text // prettier-ignore
227
+ display={config.labels && displayBar ? 'block' : 'none'}
228
+ opacity={transparentBar ? 0.5 : 1}
229
+ x={barWidth * (bar.index + 0.5) + offset}
230
+ y={barY - 5}
231
+ fill={labelColor}
232
+ textAnchor='middle'
233
+ >
234
+ {barValueLabel}
137
235
  </Text>
138
236
 
139
237
  {config.isLollipopChart && config.lollipopShape === 'circle' && (
140
238
  <circle
239
+ display={displaylollipopShape}
141
240
  cx={barWidth * (barGroup.bars.length - bar.index - 1) + offset + lollipopShapeSize / 3.5}
142
241
  cy={bar.y}
143
242
  r={lollipopShapeSize / 2}
@@ -150,6 +249,7 @@ export const BarChartVertical = props => {
150
249
  )}
151
250
  {config.isLollipopChart && config.lollipopShape === 'square' && (
152
251
  <rect
252
+ display={displaylollipopShape}
153
253
  x={offset - lollipopBarWidth / 2}
154
254
  y={barY}
155
255
  width={lollipopShapeSize}
@@ -197,6 +297,50 @@ export const BarChartVertical = props => {
197
297
  )
198
298
  })
199
299
  : ''}
300
+
301
+ {config.regions && config.visualizationType !== 'Combo'
302
+ ? config.regions.map(region => {
303
+ if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
304
+
305
+ let from
306
+ let to
307
+ let width
308
+
309
+ if (config.xAxis.type === 'date') {
310
+ from = xScale(parseDate(region.from).getTime()) - (barWidth * totalBarsInGroup) / 2
311
+ to = xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2
312
+
313
+ width = to - from
314
+ }
315
+
316
+ if (config.xAxis.type === 'categorical') {
317
+ from = xScale(region.from)
318
+ to = xScale(region.to)
319
+ width = to - from
320
+ }
321
+
322
+ if (!from) return null
323
+ if (!to) return null
324
+
325
+ return (
326
+ <Group className='regions' left={0} key={region.label}>
327
+ <path
328
+ stroke='#333'
329
+ d={`M${from} -5
330
+ L${from} 5
331
+ M${from} 0
332
+ L${to} 0
333
+ M${to} -5
334
+ L${to} 5`}
335
+ />
336
+ <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
337
+ <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
338
+ {region.label}
339
+ </Text>
340
+ </Group>
341
+ )
342
+ })
343
+ : ''}
200
344
  </Group>
201
345
  )
202
346
  )
@@ -21,7 +21,7 @@ const BarChart = ({ xScale, yScale, seriesScale, xMax, yMax, handleTooltipMouseO
21
21
  <BarChartType.Horizontal xScale={xScale} yScale={yScale} xMax={xMax} yMax={yMax} seriesScale={seriesScale} />
22
22
 
23
23
  {/* tooltips */}
24
- <Bar key={'bars'} width={Number(xMax)} height={Number(yMax)} fill={false ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} onClick={e => handleTooltipClick(e, data)} />
24
+ <Bar key={'bars'} display={config.tooltips.singleSeries ? 'none' : 'block'} width={Number(xMax)} height={Number(yMax)} fill={'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} onClick={e => handleTooltipClick(e, data)} />
25
25
  </Group>
26
26
  </ErrorBoundary>
27
27
  )
@@ -125,7 +125,7 @@ export default function DeviationBar({ height, xScale }) {
125
125
  })
126
126
  }, [config.animate, config, animatedChart])
127
127
 
128
- if (!config || config?.series?.length !== 1) return <></>
128
+ // if (!config || config?.series?.length !== 1) return <></>
129
129
 
130
130
  return (
131
131
  <ErrorBoundary component='Deviation Bar'>
@@ -185,10 +185,11 @@ export default function DeviationBar({ height, xScale }) {
185
185
  y={barY}
186
186
  width={barWidth}
187
187
  height={barHeight}
188
- style={{ border: `${borderWidth}px solid #333`, backgroundColor: barColor[barPosition], ...borderRadius }}
189
188
  data-tooltip-html={tooltip}
190
189
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
191
- />
190
+ >
191
+ <div style={{ width: barWidth, height: barHeight, border: `${borderWidth}px solid #333`, backgroundColor: barColor[barPosition], ...borderRadius }}></div>
192
+ </foreignObject>
192
193
  {config.yAxis.displayNumbersOnBar && (
193
194
  <Text verticalAnchor='middle' x={textX} y={textY} {...textProps[barPosition]}>
194
195
  {formatNumber(d[seriesKey], 'left')}