@cdc/chart 4.23.6 → 4.23.8

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 (48) hide show
  1. package/dist/cdcchart.js +29981 -29995
  2. package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
  3. package/examples/feature/__data__/city-temperature.json +2198 -0
  4. package/examples/feature/__data__/planet-example-data.json +1 -1
  5. package/examples/feature/area/area-chart-category.json +45 -45
  6. package/examples/feature/area/area-chart-date-apple.json +10376 -0
  7. package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
  8. package/examples/feature/area/area-chart-date.json +111 -3
  9. package/examples/feature/combo/right-issues.json +1 -1
  10. package/examples/feature/forecasting/combo-forecasting.json +72 -46
  11. package/examples/feature/forecasting/effective_reproduction.json +57 -8
  12. package/examples/feature/forecasting/forecasting.json +12 -3
  13. package/examples/feature/forest-plot/broken.json +700 -0
  14. package/examples/feature/forest-plot/data.csv +24 -0
  15. package/examples/feature/forest-plot/forest-plot.json +717 -0
  16. package/examples/feature/line/line-chart.json +11 -11
  17. package/examples/feature/pie/planet-pie-example-config.json +1 -1
  18. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
  19. package/examples/private/confidence_interval_test.json +248 -0
  20. package/examples/private/tooltip-issue.json +45275 -0
  21. package/index.html +13 -11
  22. package/package.json +4 -3
  23. package/src/CdcChart.jsx +78 -27
  24. package/src/components/AreaChart.jsx +65 -151
  25. package/src/components/BarChart.Horizontal.jsx +251 -0
  26. package/src/components/BarChart.StackedHorizontal.jsx +118 -0
  27. package/src/components/BarChart.StackedVertical.jsx +93 -0
  28. package/src/components/BarChart.Vertical.jsx +204 -0
  29. package/src/components/BarChart.jsx +17 -667
  30. package/src/components/BarChartType.jsx +15 -0
  31. package/src/components/BrushHandle.jsx +17 -0
  32. package/src/components/DataTable.jsx +67 -22
  33. package/src/components/EditorPanel.jsx +426 -358
  34. package/src/components/Forecasting.jsx +23 -86
  35. package/src/components/ForestPlot.jsx +191 -0
  36. package/src/components/ForestPlotSettings.jsx +508 -0
  37. package/src/components/Legend.jsx +10 -8
  38. package/src/components/LineChart.jsx +31 -6
  39. package/src/components/LinearChart.jsx +317 -230
  40. package/src/components/Series.jsx +40 -4
  41. package/src/data/initial-state.js +50 -3
  42. package/src/hooks/useBarChart.js +186 -0
  43. package/src/hooks/useEditorPermissions.js +218 -0
  44. package/src/hooks/useMinMax.js +18 -5
  45. package/src/hooks/useRightAxis.js +2 -1
  46. package/src/hooks/useScales.js +45 -2
  47. package/src/hooks/useTooltip.jsx +407 -0
  48. package/src/scss/main.scss +11 -17
@@ -0,0 +1,251 @@
1
+ import React, { useContext } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+ import { useBarChart } from '../hooks/useBarChart'
4
+ import { Group } from '@visx/group'
5
+ import { Text } from '@visx/text'
6
+ import { BarGroup } from '@visx/shape'
7
+ import { useHighlightedBars } from '../hooks/useHighlightedBars'
8
+
9
+ // third party
10
+ import chroma from 'chroma-js'
11
+
12
+ export const BarChartHorizontal = props => {
13
+ const { xScale, yScale, yMax, seriesScale } = props
14
+ const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter, isNumber, getTextWidth, getYAxisData, getXAxisData } = useContext(ConfigContext)
15
+ const { isHorizontal, barBorderWidth, hasMultipleSeries, applyRadius, updateBars, assignColorsToValues, section, fontSize, isLabelBelowBar, displayNumbersOnBar, lollipopBarWidth, lollipopShapeSize, getHighlightedBarColorByValue, getHighlightedBarByValue } = useBarChart()
16
+
17
+ const { HighLightedBarUtils } = useHighlightedBars(config)
18
+
19
+ return (
20
+ config.visualizationSubType !== 'stacked' &&
21
+ config.visualizationType === 'Bar' &&
22
+ config.orientation === 'horizontal' && (
23
+ <Group>
24
+ <BarGroup
25
+ data={data}
26
+ keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
27
+ height={yMax}
28
+ x0={d => d[config.runtime.originalXAxis.dataKey]}
29
+ x0Scale={yScale}
30
+ x1Scale={seriesScale}
31
+ yScale={xScale}
32
+ color={() => {
33
+ return ''
34
+ }}
35
+ >
36
+ {barGroups => {
37
+ return updateBars(barGroups).map((barGroup, index) => (
38
+ <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}`} top={barGroup.y}>
39
+ {barGroup.bars.map((bar, index) => {
40
+ const scaleVal = config.useLogScale ? 0.1 : 0
41
+
42
+ let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
43
+
44
+ highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
45
+
46
+ let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
47
+ let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
48
+ let barHeight = config.barHeight
49
+ let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
50
+ const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(0)
51
+ const barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(scaleVal))
52
+ const barWidth = config.barHeight
53
+
54
+ const yAxisValue = formatNumber(bar.value, 'left')
55
+ const xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
56
+
57
+ const barPosition = bar.value < 0 ? 'below' : 'above'
58
+
59
+ // check if bar text/value string fits into each bars.
60
+ let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
61
+ let textFits = textWidth < barWidthHorizontal - 5 // minus padding 5
62
+
63
+ // control text position
64
+ let textAnchor = textFits ? 'end' : 'start'
65
+ let textAnchorLollipop = 'start'
66
+ let textPadding = textFits ? -5 : 5
67
+ let textPaddingLollipop = 10
68
+ // if bars are negative we change positions of text
69
+ if (barPosition === 'below') {
70
+ textAnchor = textFits ? 'start' : 'end'
71
+ textPadding = textFits ? 5 : -5
72
+ if (config.isLollipopChart) {
73
+ textAnchorLollipop = 'end'
74
+ textPaddingLollipop = -10
75
+ }
76
+ }
77
+
78
+ // create new Index for bars with negative values
79
+ const newIndex = bar.value < 0 ? -1 : index
80
+ const borderRadius = applyRadius(newIndex)
81
+
82
+ let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
83
+ let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
84
+ if (!hasMultipleSeries) {
85
+ xAxisTooltip = config.isLegendValue ? `<p className="tooltip-heading">${bar.key}: ${xAxisValue}</p>` : config.runtime.xAxis.label ? `<p className="tooltip-heading">${config.runtime.xAxis.label}: ${xAxisValue}</p>` : xAxisValue
86
+ }
87
+
88
+ const tooltip = `<ul>
89
+ ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[bar.key] || ''}<br/>` : ''}
90
+ <li class="tooltip-heading">${yAxisTooltip}</li>
91
+ <li class="tooltip-body">${xAxisTooltip}</li>
92
+ </li></ul>`
93
+
94
+ // configure colors
95
+ let labelColor = '#000000'
96
+ labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
97
+ let barColor = config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key] ? colorScale(config.runtime.seriesLabels[bar.key]) : colorScale(bar.key)
98
+ barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor) // Color code by category
99
+ const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
100
+ const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
101
+ const isHighlightedBar = highlightedBarValues?.includes(yAxisValue)
102
+ const highlightedBarColor = getHighlightedBarColorByValue(yAxisValue)
103
+ const highlightedBar = getHighlightedBarByValue(yAxisValue)
104
+ const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
105
+ const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
106
+ // update label color
107
+ if (barColor && labelColor) {
108
+ if (chroma.contrast(labelColor, barColor) < 4.9) {
109
+ labelColor = textFits ? '#FFFFFF' : '#000000'
110
+ }
111
+ }
112
+ const background = () => {
113
+ if (isRegularLollipopColor) return barColor
114
+ if (isTwoToneLollipopColor) return chroma(barColor).brighten(1)
115
+ if (isHighlightedBar) return 'transparent'
116
+ return barColor
117
+ }
118
+ const finalStyle = {
119
+ background: background(),
120
+ borderColor,
121
+ borderStyle: 'solid',
122
+ borderWidth,
123
+ ...borderRadius
124
+ }
125
+
126
+ return (
127
+ <Group key={`${barGroup.index}--${index}`}>
128
+ {/* This feels gross but inline transition was not working well*/}
129
+ <style>
130
+ {`
131
+ .linear #barGroup${barGroup.index},
132
+ .Combo #barGroup${barGroup.index} {
133
+ transform-origin: 0 ${barY + barHeight}px;
134
+ }
135
+ `}
136
+ </style>
137
+ <Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
138
+ <foreignObject
139
+ id={`barGroup${barGroup.index}`}
140
+ key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
141
+ x={barX}
142
+ y={barWidth * bar.index}
143
+ width={barWidthHorizontal}
144
+ height={!config.isLollipopChart ? barWidth : lollipopBarWidth}
145
+ style={finalStyle}
146
+ opacity={transparentBar ? 0.5 : 1}
147
+ display={displayBar ? 'block' : 'none'}
148
+ data-tooltip-html={tooltip}
149
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
150
+ onClick={e => {
151
+ e.preventDefault()
152
+ if (setSharedFilter) {
153
+ bar[config.xAxis.dataKey] = yAxisValue
154
+ setSharedFilter(config.uid, bar)
155
+ }
156
+ }}
157
+ ></foreignObject>
158
+ {!config.isLollipopChart && displayNumbersOnBar && (
159
+ <Text // prettier-ignore
160
+ display={displayBar ? 'block' : 'none'}
161
+ x={bar.y}
162
+ y={config.barHeight / 2 + config.barHeight * bar.index}
163
+ fill={labelColor}
164
+ dx={textPadding}
165
+ verticalAnchor='middle'
166
+ textAnchor={textAnchor}
167
+ >
168
+ {yAxisValue}
169
+ </Text>
170
+ )}
171
+ {config.isLollipopChart && displayNumbersOnBar && (
172
+ <Text // prettier-ignore
173
+ display={displayBar ? 'block' : 'none'}
174
+ x={bar.y}
175
+ y={0}
176
+ fill={'#000000'}
177
+ dx={textPaddingLollipop}
178
+ textAnchor={textAnchorLollipop}
179
+ verticalAnchor='middle'
180
+ fontWeight={'normal'}
181
+ >
182
+ {yAxisValue}
183
+ </Text>
184
+ )}
185
+ {isLabelBelowBar && !config.yAxis.hideLabel && (
186
+ <Text x={config.yAxis.hideAxis ? 0 : 5} y={barGroup.height} dy={4} verticalAnchor={'start'} textAnchor={'start'}>
187
+ {config.runtime.yAxis.type === 'date'
188
+ ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
189
+ : isHorizontal
190
+ ? data[barGroup.index][config.runtime.originalXAxis.dataKey]
191
+ : formatNumber(data[barGroup.index][config.runtime.originalXAxis.dataKey])}
192
+ </Text>
193
+ )}
194
+
195
+ {config.isLollipopChart && config.lollipopShape === 'circle' && (
196
+ <circle cx={bar.y} cy={0 + lollipopBarWidth / 2} r={lollipopShapeSize / 2} fill={barColor} key={`circle--${bar.index}`} data-tooltip-html={tooltip} data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} style={{ filter: 'unset', opacity: 1 }} />
197
+ )}
198
+ {config.isLollipopChart && config.lollipopShape === 'square' && (
199
+ <rect
200
+ x={bar.y > 10 ? bar.y - lollipopShapeSize / 2 : 0}
201
+ y={0 - lollipopBarWidth / 2}
202
+ width={lollipopShapeSize}
203
+ height={lollipopShapeSize}
204
+ fill={barColor}
205
+ key={`circle--${bar.index}`}
206
+ data-tooltip-html={tooltip}
207
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
208
+ style={{ opacity: 1, filter: 'unset' }}
209
+ >
210
+ <animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
211
+ </rect>
212
+ )}
213
+ </Group>
214
+ </Group>
215
+ )
216
+ })}
217
+ </Group>
218
+ ))
219
+ }}
220
+ </BarGroup>
221
+
222
+ {Object.keys(config.confidenceKeys).length > 0
223
+ ? data.map(d => {
224
+ let xPos, yPos
225
+ let upperPos
226
+ let lowerPos
227
+ let tickWidth = 5
228
+ yPos = yScale(getXAxisData(d)) - 0.75 * config.barHeight
229
+ upperPos = xScale(getYAxisData(d, config.confidenceKeys.upper))
230
+ lowerPos = xScale(getYAxisData(d, config.confidenceKeys.lower))
231
+ return (
232
+ <path
233
+ key={`confidence-interval-h-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
234
+ stroke='#333'
235
+ strokeWidth='px'
236
+ d={`
237
+ M${lowerPos} ${yPos - tickWidth}
238
+ L${lowerPos} ${yPos + tickWidth}
239
+ M${lowerPos} ${yPos}
240
+ L${upperPos} ${yPos}
241
+ M${upperPos} ${yPos - tickWidth}
242
+ L${upperPos} ${yPos + tickWidth} `}
243
+ />
244
+ )
245
+ })
246
+ : ''}
247
+ </Group>
248
+ )
249
+ )
250
+ }
251
+ export default BarChartHorizontal
@@ -0,0 +1,118 @@
1
+ import React, { useContext } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+ import { useBarChart } from '../hooks/useBarChart'
4
+ import { BarStackHorizontal } from '@visx/shape'
5
+ import { Group } from '@visx/group'
6
+ import { Text } from '@visx/text'
7
+
8
+ // third party
9
+ import chroma from 'chroma-js'
10
+
11
+ const BarChartStackedHorizontal = props => {
12
+ const { xScale, yScale, xMax, yMax } = props
13
+ const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter, animatedChart, getTextWidth } = useContext(ConfigContext)
14
+ const { isHorizontal, barBorderWidth, hasMultipleSeries, applyRadius, updateBars, isLabelBelowBar, displayNumbersOnBar, fontSize } = useBarChart()
15
+ const { orientation, visualizationSubType } = config
16
+
17
+ return (
18
+ config.visualizationSubType === 'stacked' &&
19
+ isHorizontal && (
20
+ <>
21
+ <BarStackHorizontal data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} height={yMax} y={d => d[config.runtime.yAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale} offset='none'>
22
+ {barStacks =>
23
+ barStacks.map(barStack =>
24
+ updateBars(barStack.bars).map((bar, index) => {
25
+ let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
26
+ let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
27
+ config.barHeight = Number(config.barHeight)
28
+ let labelColor = '#000000'
29
+ // tooltips
30
+ const xAxisValue = formatNumber(data[bar.index][bar.key], 'left')
31
+ const yAxisValue = config.runtime.yAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.originalXAxis.dataKey])) : data[bar.index][config.runtime.originalXAxis.dataKey]
32
+ const style = applyRadius(barStack.index)
33
+ let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
34
+ let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
35
+ let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
36
+ if (!hasMultipleSeries) {
37
+ xAxisTooltip = config.isLegendValue ? `${bar.key}: ${xAxisValue}` : config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisTooltip
38
+ }
39
+ const tooltip = `<div>
40
+ ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[bar.key] || ''}<br/>` : ''}
41
+ ${yAxisTooltip}<br />
42
+ ${xAxisTooltip}
43
+ </div>`
44
+
45
+ if (chroma.contrast(labelColor, bar.color) < 4.9) {
46
+ labelColor = '#FFFFFF'
47
+ }
48
+
49
+ return (
50
+ <>
51
+ <style>
52
+ {`
53
+ #barStack${barStack.index}-${bar.index} rect,
54
+ #barStack${barStack.index}-${bar.index} foreignObject{
55
+ animation-delay: ${barStack.index * 0.5}s;
56
+ transform-origin: ${bar.x}px
57
+ }
58
+ `}
59
+ </style>
60
+ <Group key={index} id={`barStack${barStack.index}-${bar.index}`} className='stack horizontal'>
61
+ <foreignObject
62
+ key={`barstack-horizontal-${barStack.index}-${bar.index}-${index}`}
63
+ className={`animated-chart group ${animatedChart ? 'animated' : ''}`}
64
+ x={bar.x}
65
+ y={bar.y}
66
+ width={bar.width}
67
+ height={bar.height}
68
+ style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
69
+ opacity={transparentBar ? 0.5 : 1}
70
+ display={displayBar ? 'block' : 'none'}
71
+ data-tooltip-html={tooltip}
72
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
73
+ onClick={e => {
74
+ e.preventDefault()
75
+ if (setSharedFilter) {
76
+ bar[config.xAxis.dataKey] = xAxisValue
77
+ setSharedFilter(config.uid, bar)
78
+ }
79
+ }}
80
+ ></foreignObject>
81
+
82
+ {orientation === 'horizontal' && visualizationSubType === 'stacked' && isLabelBelowBar && barStack.index === 0 && !config.yAxis.hideLabel && (
83
+ <Text
84
+ x={`${bar.x + (config.isLollipopChart ? 15 : 5)}`} // padding
85
+ y={bar.y + bar.height * 1.2}
86
+ fill={'#000000'}
87
+ textAnchor='start'
88
+ verticalAnchor='start'
89
+ >
90
+ {yAxisValue}
91
+ </Text>
92
+ )}
93
+
94
+ {displayNumbersOnBar && textWidth < bar.width && (
95
+ <Text
96
+ display={displayBar ? 'block' : 'none'}
97
+ x={bar.x + barStack.bars[bar.index].width / 2} // padding
98
+ y={bar.y + bar.height / 2}
99
+ fill={labelColor}
100
+ textAnchor='middle'
101
+ verticalAnchor='middle'
102
+ >
103
+ {xAxisValue}
104
+ </Text>
105
+ )}
106
+ </Group>
107
+ </>
108
+ )
109
+ })
110
+ )
111
+ }
112
+ </BarStackHorizontal>
113
+ </>
114
+ )
115
+ )
116
+ }
117
+
118
+ export default BarChartStackedHorizontal
@@ -0,0 +1,93 @@
1
+ import React, { useContext } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+ import { useBarChart } from '../hooks/useBarChart'
4
+ import { BarStack } from '@visx/shape'
5
+ import { Group } from '@visx/group'
6
+ import { Text } from '@visx/text'
7
+
8
+ const BarChartStackedVertical = props => {
9
+ const { xScale, yScale, xMax, yMax } = props
10
+ const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter } = useContext(ConfigContext)
11
+ const { isHorizontal, barBorderWidth, hasMultipleSeries, applyRadius } = useBarChart()
12
+ const { orientation } = config
13
+
14
+ return (
15
+ config.visualizationSubType === 'stacked' &&
16
+ !isHorizontal && (
17
+ <BarStack data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} x={d => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
18
+ {barStacks =>
19
+ barStacks.reverse().map(barStack =>
20
+ barStack.bars.map(bar => {
21
+ let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
22
+ let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
23
+ let barThickness = xMax / barStack.bars.length
24
+ let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
25
+ let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
26
+ // tooltips
27
+ const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.xAxis.dataKey])) : data[bar.index][config.runtime.xAxis.dataKey]
28
+ const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
29
+
30
+ const style = applyRadius(barStack.index)
31
+ let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
32
+ const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
33
+ if (!hasMultipleSeries) {
34
+ yAxisTooltip = config.isLegendValue ? `${bar.key}: ${yAxisValue}` : config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
35
+ }
36
+
37
+ const {
38
+ legend: { showLegendValuesTooltip },
39
+ runtime: { seriesLabels }
40
+ } = config
41
+
42
+ const barStackTooltip = `<div>
43
+ <p class="tooltip-heading"><strong>${xAxisTooltip}</strong></p>
44
+ ${showLegendValuesTooltip && seriesLabels && hasMultipleSeries ? `${seriesLabels[bar.key] || ''}<br/>` : ''}
45
+ ${yAxisTooltip}<br />
46
+ </div>`
47
+
48
+ return (
49
+ <Group key={`${barStack.index}--${bar.index}--${orientation}`}>
50
+ <style>
51
+ {`
52
+ #barStack${barStack.index}-${bar.index} rect,
53
+ #barStack${barStack.index}-${bar.index} foreignObject{
54
+ animation-delay: ${barStack.index * 0.5}s;
55
+ transform-origin: ${barThicknessAdjusted / 2}px ${bar.y + bar.height}px
56
+ }
57
+ `}
58
+ </style>
59
+ <Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
60
+ <Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barThickness * bar.index + offset} y={bar.y - 5} fill={'#000'} textAnchor='middle'>
61
+ {yAxisValue}
62
+ </Text>
63
+ <foreignObject
64
+ key={`bar-stack-${barStack.index}-${bar.index}`}
65
+ x={barThickness * bar.index + offset}
66
+ y={bar.y}
67
+ width={barThicknessAdjusted}
68
+ height={bar.height}
69
+ style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
70
+ opacity={transparentBar ? 0.5 : 1}
71
+ display={displayBar ? 'block' : 'none'}
72
+ data-tooltip-html={barStackTooltip}
73
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
74
+ onClick={e => {
75
+ e.preventDefault()
76
+ if (setSharedFilter) {
77
+ bar[config.xAxis.dataKey] = xAxisValue
78
+ setSharedFilter(config.uid, bar)
79
+ }
80
+ }}
81
+ ></foreignObject>
82
+ </Group>
83
+ </Group>
84
+ )
85
+ })
86
+ )
87
+ }
88
+ </BarStack>
89
+ )
90
+ )
91
+ }
92
+
93
+ export default BarChartStackedVertical
@@ -0,0 +1,204 @@
1
+ import React, { useContext } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+ import { useBarChart } from '../hooks/useBarChart'
4
+ import { Group } from '@visx/group'
5
+ import { Text } from '@visx/text'
6
+ import { BarGroup } from '@visx/shape'
7
+ import { useHighlightedBars } from '../hooks/useHighlightedBars'
8
+
9
+ // third party
10
+ import chroma from 'chroma-js'
11
+
12
+ export const BarChartVertical = props => {
13
+ 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()
16
+ const { HighLightedBarUtils } = useHighlightedBars(config)
17
+
18
+ return (
19
+ config.visualizationSubType !== 'stacked' &&
20
+ (config.visualizationType === 'Bar' || config.visualizationType === 'Combo') &&
21
+ config.orientation === 'vertical' && (
22
+ <Group>
23
+ <BarGroup
24
+ data={data}
25
+ keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
26
+ height={yMax}
27
+ x0={d => d[config.runtime.originalXAxis.dataKey]}
28
+ x0Scale={xScale}
29
+ x1Scale={seriesScale}
30
+ yScale={yScale}
31
+ color={() => {
32
+ return ''
33
+ }}
34
+ >
35
+ {barGroups => {
36
+ return updateBars(barGroups).map((barGroup, index) => (
37
+ <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
+ {barGroup.bars.map((bar, index) => {
39
+ const scaleVal = config.useLogScale ? 0.1 : 0
40
+ let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
41
+ highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
42
+ let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
43
+ 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)
46
+ let barGroupWidth = (xMax / barGroups.length) * (config.barThickness || 0.8)
47
+ let offset = ((xMax / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
48
+ // configure left side offset of lollipop bars
49
+ if (config.isLollipopChart) {
50
+ offset = xMax / barGroups.length / 2 - lollipopBarWidth / 2
51
+ }
52
+
53
+ let barWidth = config.isLollipopChart ? lollipopBarWidth : barGroupWidth / barGroup.bars.length
54
+
55
+ let yAxisValue = formatNumber(bar.value, 'left')
56
+ let xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
57
+
58
+ // create new Index for bars with negative values
59
+ const newIndex = bar.value < 0 ? -1 : index
60
+ const borderRadius = applyRadius(newIndex)
61
+
62
+ let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
63
+ 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
+ }
68
+
69
+ 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>
73
+ </li></ul>`
74
+
75
+ // configure colors
76
+ let labelColor = '#000000'
77
+ labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
78
+ let barColor = config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key] ? colorScale(config.runtime.seriesLabels[bar.key]) : colorScale(bar.key)
79
+ barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor) // Color code by category
80
+ const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
81
+ const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
82
+ const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
83
+ const highlightedBarColor = getHighlightedBarColorByValue(xAxisValue)
84
+ const highlightedBar = getHighlightedBarByValue(xAxisValue)
85
+ const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
86
+ const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
87
+
88
+ const background = () => {
89
+ if (isRegularLollipopColor) return barColor
90
+ if (isTwoToneLollipopColor) return chroma(barColor).brighten(1)
91
+ if (isHighlightedBar) return 'transparent'
92
+ return barColor
93
+ }
94
+ const finalStyle = {
95
+ background: background(),
96
+ borderColor,
97
+ borderStyle: 'solid',
98
+ borderWidth,
99
+ ...borderRadius
100
+ }
101
+
102
+ return (
103
+ <Group key={`${barGroup.index}--${index}`}>
104
+ {/* This feels gross but inline transition was not working well*/}
105
+ <style>
106
+ {`
107
+ .linear #barGroup${barGroup.index},
108
+ .Combo #barGroup${barGroup.index} {
109
+ transform-origin: 0 ${barY + barHeight}px;
110
+ }
111
+ `}
112
+ </style>
113
+ <Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
114
+ <foreignObject
115
+ id={`barGroup${barGroup.index}`}
116
+ key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
117
+ x={barWidth * bar.index + offset}
118
+ y={barY}
119
+ width={barWidth}
120
+ height={barHeight}
121
+ style={finalStyle}
122
+ opacity={transparentBar ? 0.5 : 1}
123
+ display={displayBar ? 'block' : 'none'}
124
+ data-tooltip-html={tooltip}
125
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
126
+ onClick={e => {
127
+ e.preventDefault()
128
+ if (setSharedFilter) {
129
+ bar[config.xAxis.dataKey] = xAxisValue
130
+ setSharedFilter(config.uid, bar)
131
+ }
132
+ }}
133
+ ></foreignObject>
134
+
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}
137
+ </Text>
138
+
139
+ {config.isLollipopChart && config.lollipopShape === 'circle' && (
140
+ <circle
141
+ cx={barWidth * (barGroup.bars.length - bar.index - 1) + offset + lollipopShapeSize / 3.5}
142
+ cy={bar.y}
143
+ r={lollipopShapeSize / 2}
144
+ fill={barColor}
145
+ key={`circle--${bar.index}`}
146
+ data-tooltip-html={tooltip}
147
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
148
+ style={{ filter: 'unset', opacity: 1 }}
149
+ />
150
+ )}
151
+ {config.isLollipopChart && config.lollipopShape === 'square' && (
152
+ <rect
153
+ x={offset - lollipopBarWidth / 2}
154
+ y={barY}
155
+ width={lollipopShapeSize}
156
+ height={lollipopShapeSize}
157
+ fill={barColor}
158
+ key={`circle--${bar.index}`}
159
+ data-tooltip-html={tooltip}
160
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
161
+ style={{ opacity: 1, filter: 'unset' }}
162
+ >
163
+ <animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
164
+ </rect>
165
+ )}
166
+ </Group>
167
+ </Group>
168
+ )
169
+ })}
170
+ </Group>
171
+ ))
172
+ }}
173
+ </BarGroup>
174
+
175
+ {Object.keys(config.confidenceKeys).length > 0
176
+ ? data.map(d => {
177
+ let xPos, yPos
178
+ let upperPos
179
+ let lowerPos
180
+ let tickWidth = 5
181
+ xPos = xScale(getXAxisData(d))
182
+ upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
183
+ lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
184
+ return (
185
+ <path
186
+ key={`confidence-interval-v-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
187
+ stroke='#333'
188
+ strokeWidth='px'
189
+ d={`
190
+ M${xPos - tickWidth} ${upperPos}
191
+ L${xPos + tickWidth} ${upperPos}
192
+ M${xPos} ${upperPos}
193
+ L${xPos} ${lowerPos}
194
+ M${xPos - tickWidth} ${lowerPos}
195
+ L${xPos + tickWidth} ${lowerPos}`}
196
+ />
197
+ )
198
+ })
199
+ : ''}
200
+ </Group>
201
+ )
202
+ )
203
+ }
204
+ export default BarChartVertical