@cdc/chart 1.3.4 → 4.22.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 (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +5 -5
  3. package/dist/cdcchart.js +6 -6
  4. package/examples/age-adjusted-rates.json +1486 -1218
  5. package/examples/case-rate-example-config.json +1 -1
  6. package/examples/covid-confidence-example-config.json +33 -33
  7. package/examples/covid-example-config.json +34 -34
  8. package/examples/covid-example-data-confidence.json +30 -30
  9. package/examples/covid-example-data.json +20 -20
  10. package/examples/cutoff-example-config.json +36 -34
  11. package/examples/cutoff-example-data.json +36 -36
  12. package/examples/date-exclusions-config.json +1 -1
  13. package/examples/dynamic-legends.json +125 -0
  14. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart-with-numbers-on-bar.json +192 -0
  15. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +231 -0
  16. package/examples/gallery/bar-chart-horizontal/horizontal-stacked.json +240 -0
  17. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +137 -0
  18. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +80 -0
  19. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +81 -0
  20. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +68 -0
  21. package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +111 -0
  22. package/examples/gallery/lollipop/lollipop-style-horizontal.json +216 -0
  23. package/examples/gallery/paired-bar/paired-bar-chart.json +196 -0
  24. package/examples/horizontal-chart.json +36 -33
  25. package/examples/horizontal-stacked-bar-chart.json +34 -34
  26. package/examples/line-chart.json +75 -75
  27. package/examples/paired-bar-data.json +16 -14
  28. package/examples/paired-bar-example.json +48 -46
  29. package/examples/paired-bar-formatted.json +36 -36
  30. package/examples/planet-chart-horizontal-example-config.json +33 -33
  31. package/examples/planet-combo-example-config.json +34 -29
  32. package/examples/planet-example-config.json +35 -33
  33. package/examples/planet-example-data.json +56 -56
  34. package/examples/planet-pie-example-config.json +28 -26
  35. package/examples/private/filters.json +170 -0
  36. package/examples/private/line-test-data.json +22 -0
  37. package/examples/private/line-test-two.json +210 -0
  38. package/examples/private/line-test.json +102 -0
  39. package/examples/private/new.json +48800 -0
  40. package/examples/private/shawn.json +1106 -0
  41. package/examples/private/test.json +10123 -10123
  42. package/examples/private/yaxis-test.json +133 -0
  43. package/examples/private/yaxis-testing.csv +27 -0
  44. package/examples/private/yaxis.json +28 -0
  45. package/examples/stacked-vertical-bar-example.json +228 -0
  46. package/examples/temp-example-config.json +61 -54
  47. package/examples/temp-example-data.json +1 -1
  48. package/package.json +2 -2
  49. package/src/CdcChart.tsx +370 -458
  50. package/src/components/BarChart.tsx +449 -441
  51. package/src/components/DataTable.tsx +164 -180
  52. package/src/components/EditorPanel.js +1066 -663
  53. package/src/components/Legend.js +284 -0
  54. package/src/components/LineChart.tsx +114 -63
  55. package/src/components/LinearChart.tsx +394 -358
  56. package/src/components/PairedBarChart.tsx +216 -135
  57. package/src/components/PieChart.tsx +106 -135
  58. package/src/components/SparkLine.js +184 -205
  59. package/src/components/useIntersectionObserver.tsx +27 -0
  60. package/src/context.tsx +3 -3
  61. package/src/data/initial-state.js +44 -7
  62. package/src/hooks/useActiveElement.js +13 -13
  63. package/src/hooks/useChartClasses.js +41 -0
  64. package/src/hooks/useColorPalette.ts +56 -63
  65. package/src/hooks/useLegendClasses.js +28 -0
  66. package/src/hooks/useReduceData.ts +69 -37
  67. package/src/hooks/useRightAxis.js +25 -0
  68. package/src/hooks/useTopAxis.js +6 -0
  69. package/src/index.html +54 -55
  70. package/src/index.tsx +13 -16
  71. package/src/scss/DataTable.scss +5 -4
  72. package/src/scss/editor-panel.scss +103 -71
  73. package/src/scss/main.scss +277 -38
  74. package/src/scss/variables.scss +1 -1
  75. package/src/components/BarStackVertical.js +0 -0
@@ -1,459 +1,495 @@
1
- import React, { Fragment, useContext, useEffect,useState } from 'react';
2
- import ReactTooltip from 'react-tooltip';
3
-
4
- import { Group } from '@visx/group';
5
- import { Line } from '@visx/shape';
6
- import { Text } from '@visx/text';
7
- import { scaleLinear, scalePoint } from '@visx/scale';
8
- import { AxisLeft, AxisBottom } from '@visx/axis';
9
-
10
- import BarChart from './BarChart';
11
- import LineChart from './LineChart';
12
- import Context from '../context';
13
- import PairedBarChart from './PairedBarChart';
14
- import SparkLine from './SparkLine';
15
-
16
- import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
17
-
18
- import '../scss/LinearChart.scss';
19
- import useReduceData from '../hooks/useReduceData';
20
-
1
+ import React, { Fragment, useContext, useEffect, useRef, useState } from 'react'
2
+ import ReactTooltip from 'react-tooltip'
3
+
4
+ import { Group } from '@visx/group'
5
+ import { Line } from '@visx/shape'
6
+ import { Text } from '@visx/text'
7
+ import { scaleLinear, scalePoint } from '@visx/scale'
8
+ import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
9
+
10
+ import BarChart from './BarChart'
11
+ import LineChart from './LineChart'
12
+ import Context from '../context'
13
+ import PairedBarChart from './PairedBarChart'
14
+ import useIntersectionObserver from './useIntersectionObserver'
15
+ import SparkLine from './SparkLine'
16
+
17
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
18
+ import numberFromString from '@cdc/core/helpers/numberFromString'
19
+ import '../scss/LinearChart.scss'
20
+ import useReduceData from '../hooks/useReduceData'
21
+ import useRightAxis from '../hooks/useRightAxis'
22
+ import useTopAxis from '../hooks/useTopAxis'
23
+
24
+ // TODO: Move scaling functions into hooks to manage complexity
25
+
26
+ // TODO: remove unused imports/variables
27
+ // TODO: consider moving logic into hooks
28
+ // TODO: formatting
21
29
  export default function LinearChart() {
22
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport,formatNumber } = useContext<any>(Context);
23
- let [ width ] = dimensions;
24
- const {minValue,maxValue} = useReduceData(config,data)
25
- if(config && config.legend && !config.legend.hide && (currentViewport === 'lg' || currentViewport === 'md')) {
26
- width = width * 0.73;
30
+ const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig } = useContext<any>(Context)
31
+ let [width] = dimensions
32
+ const { minValue, maxValue, existPositiveValue } = useReduceData(config, data)
33
+ const [animatedChart, setAnimatedChart] = useState<boolean>(false)
34
+ const [animatedChartPlayed, setAnimatedChartPlayed] = useState<boolean>(false)
35
+
36
+ const triggerRef = useRef()
37
+ const dataRef = useIntersectionObserver(triggerRef, {
38
+ freezeOnceVisible: false
39
+ })
40
+ // If the chart is in view and set to animate and it has not already played
41
+ useEffect(() => {
42
+ if (dataRef?.isIntersecting === true && config.animate) {
43
+ setTimeout(() => {
44
+ setAnimatedChart(prevState => true)
45
+ }, 500)
46
+ }
47
+ }, [dataRef?.isIntersecting, config.animate])
48
+
49
+ if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && (currentViewport === 'lg' || currentViewport === 'md')) {
50
+ width = width * 0.73
27
51
  }
28
52
 
29
- const height = config.aspectRatio ? (width * config.aspectRatio) : config.height;
53
+ const height = config.aspectRatio ? width * config.aspectRatio : config.height
54
+ const xMax = width - config.runtime.yAxis.size - config.yAxis.rightAxisSize
55
+ const yMax = height - config.runtime.xAxis.size
56
+
57
+ const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
58
+ const { hasTopAxis } = useTopAxis(config)
30
59
 
31
- const xMax = width - config.runtime.yAxis.size;
32
- const yMax = height - config.runtime.xAxis.size;
60
+ const getXAxisData = (d: any) => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
61
+ const getYAxisData = (d: any, seriesKey: string) => d[seriesKey]
33
62
 
34
- const getXAxisData = (d: any) => config.runtime.xAxis.type === 'date' ? (parseDate(d[config.runtime.originalXAxis.dataKey])).getTime() : d[config.runtime.originalXAxis.dataKey];
35
- const getYAxisData = (d: any, seriesKey: string) => d[seriesKey];
63
+ let xScale
64
+ let yScale
65
+ let seriesScale
36
66
 
37
- let xScale;
38
- let yScale;
39
- let seriesScale;
67
+ const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
68
+ const isMaxValid = existPositiveValue ? numberFromString(enteredMaxValue) >= numberFromString(maxValue) : numberFromString(enteredMaxValue) >= 0
69
+ const isMinValid = (numberFromString(enteredMinValue) <= 0 && numberFromString(minValue) >= 0) || (numberFromString(enteredMinValue) <= minValue && minValue < 0)
40
70
 
41
71
  if (data) {
42
- let min = config.runtime.yAxis.min !== undefined ? config.runtime.yAxis.min : minValue
43
- let max = config.runtime.yAxis.max !== undefined ? config.runtime.yAxis.max : Number.MIN_VALUE;
72
+ let min = enteredMinValue && isMinValid ? enteredMinValue : minValue
73
+ let max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
44
74
 
45
- if((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && min > 0) {
46
- min = 0;
75
+ if ((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && min > 0) {
76
+ min = 0
77
+ }
78
+ if (config.visualizationType === 'Line') {
79
+ const isMinValid = Number(enteredMinValue) < Number(minValue)
80
+ min = enteredMinValue && isMinValid ? Number(enteredMinValue) : minValue
47
81
  }
48
82
  //If data value max wasn't provided, calculate it
49
- if(max === Number.MIN_VALUE){
50
- max = maxValue
83
+ if (max === Number.MIN_VALUE) {
84
+ // if all values in data are negative set max = 0
85
+ max = existPositiveValue ? maxValue : 0
51
86
  }
52
-
87
+
53
88
  //Adds Y Axis data padding if applicable
54
- if(config.runtime.yAxis.paddingPercent) {
55
- let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent;
56
- min -= paddingValue;
57
- max += paddingValue;
89
+ if (config.runtime.yAxis.paddingPercent) {
90
+ let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent
91
+ min -= paddingValue
92
+ max += paddingValue
58
93
  }
59
94
 
60
- let xAxisDataMapped = data.map(d => getXAxisData(d));
95
+ let xAxisDataMapped = data.map(d => getXAxisData(d))
96
+
97
+ if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
98
+ const dataKey = data.map(item => item[config.series[0].dataKey])
99
+ const maxDataVal = Math.max(...dataKey).toString().length
100
+
101
+ switch (true) {
102
+ case maxDataVal > 8 && maxDataVal <= 12:
103
+ max = max * 1.3
104
+ break
105
+ case maxDataVal > 4 && maxDataVal <= 7:
106
+ max = max * 1.1
107
+ break
108
+ }
109
+ }
61
110
 
62
- if(config.runtime.horizontal){
111
+ if (config.runtime.horizontal) {
63
112
  xScale = scaleLinear<number>({
64
113
  domain: [min, max],
65
114
  range: [0, xMax]
66
- });
115
+ })
67
116
 
68
- yScale = config.runtime.xAxis.type === 'date' ?
69
- scaleLinear<number>({domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]}) :
70
- scalePoint<string>({domain: xAxisDataMapped, padding: 0.5});
117
+ yScale =
118
+ config.runtime.xAxis.type === 'date'
119
+ ? scaleLinear<number>({
120
+ domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]
121
+ })
122
+ : scalePoint<string>({ domain: xAxisDataMapped, padding: 0.5 })
71
123
 
72
124
  seriesScale = scalePoint<string>({
73
- domain: (config.runtime.barSeriesKeys || config.runtime.seriesKeys),
125
+ domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
74
126
  range: [0, yMax]
75
- });
76
-
77
- yScale.rangeRound([0, yMax]);
127
+ })
128
+
129
+ yScale.rangeRound([0, yMax])
78
130
  } else {
79
- min = min < 0 ? min * 1.11 : min
131
+ min = min < 0 ? min * 1.11 : min
80
132
 
81
- yScale = scaleLinear<number>({
133
+ yScale = scaleLinear<number>({
82
134
  domain: [min, max],
83
135
  range: [yMax, 0]
84
- });
136
+ })
85
137
 
86
- xScale = scalePoint<string>({domain: xAxisDataMapped, range: [0, xMax], padding: 0.5});
138
+ xScale = scalePoint<string>({
139
+ domain: xAxisDataMapped,
140
+ range: [0, xMax],
141
+ padding: 0.5
142
+ })
87
143
 
88
144
  seriesScale = scalePoint<string>({
89
- domain: (config.runtime.barSeriesKeys || config.runtime.seriesKeys),
145
+ domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
90
146
  range: [0, xMax]
91
- });
147
+ })
92
148
  }
93
149
 
94
-
95
- if(config.visualizationType === 'Paired Bar') {
96
-
97
-
98
- let groupOneMax = Math.max.apply(Math, data.map(d => d[config.series[0].dataKey]))
99
- let groupTwoMax = Math.max.apply(Math, data.map(d => d[config.series[1].dataKey]))
150
+ if (config.visualizationType === 'Paired Bar') {
151
+ let groupOneMax = Math.max.apply(
152
+ Math,
153
+ data.map(d => d[config.series[0].dataKey])
154
+ )
155
+ let groupTwoMax = Math.max.apply(
156
+ Math,
157
+ data.map(d => d[config.series[1].dataKey])
158
+ )
100
159
 
101
160
  // group one
102
161
  var g1xScale = scaleLinear<number>({
103
- domain: [0, Math.max(groupOneMax,groupTwoMax) ],
104
- range: [xMax/2, 0]
162
+ domain: [0, Math.max(groupOneMax, groupTwoMax)],
163
+ range: [xMax / 2, 0]
105
164
  })
106
-
165
+
107
166
  // group 2
108
167
  var g2xScale = scaleLinear<number>({
109
168
  domain: g1xScale.domain(),
110
169
  range: [xMax / 2, xMax]
111
170
  })
112
-
113
171
  }
114
172
  }
115
173
 
116
-
174
+
175
+ const countNumOfTicks = (axis)=>{
176
+ // function get number of ticks based on bar type & users value
177
+ const isHorizontal = config.orientation ==='horizontal';
178
+ const {numTicks} = config.runtime[axis];
179
+ let tickCount = undefined;
180
+
181
+ if(axis === 'yAxis'){
182
+ tickCount = (
183
+ (isHorizontal && !numTicks) ? data.length
184
+ : (isHorizontal && numTicks) ? numTicks
185
+ :(!isHorizontal && !numTicks) ? undefined
186
+ :(!isHorizontal && numTicks) && numTicks
187
+ );
188
+ };
189
+
190
+ if(axis === 'xAxis'){
191
+ tickCount = (
192
+ (isHorizontal && !numTicks) ? undefined
193
+ : (isHorizontal && numTicks) ? numTicks
194
+ :(!isHorizontal && !numTicks) ? undefined
195
+ :(!isHorizontal && numTicks) && numTicks
196
+ );
197
+ };
198
+ return tickCount;
199
+ };
117
200
 
118
201
  useEffect(() => {
119
- ReactTooltip.rebuild();
120
- });
121
-
122
-
123
- return (
124
- <ErrorBoundary component="LinearChart">
125
- <svg width={width} height={height} className="linear">
126
- {/* Higlighted regions */}
127
- { config.regions ? config.regions.map((region) => {
128
- if(!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
129
-
130
- const from = xScale((parseDate(region.from)).getTime());
131
- const to = xScale((parseDate(region.to)).getTime());
132
- const width = to - from;
202
+ ReactTooltip.rebuild()
203
+ })
204
+
205
+ return isNaN(width) ? (
206
+ <></>
207
+ ) : (
208
+ <ErrorBoundary component='LinearChart'>
209
+ <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0}>
210
+ {/* Higlighted regions */}
211
+ {config.regions
212
+ ? config.regions.map(region => {
213
+ if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
214
+
215
+ const from = xScale(parseDate(region.from).getTime())
216
+ const to = xScale(parseDate(region.to).getTime())
217
+ const width = to - from
133
218
 
134
- return (
135
- <Group className="regions" left={config.runtime.yAxis.size} key={region.label}>
136
- <path stroke="#333" d={`M${from} -5
219
+ return (
220
+ <Group className='regions' left={config.runtime.yAxis.size} key={region.label}>
221
+ <path
222
+ stroke='#333'
223
+ d={`M${from} -5
137
224
  L${from} 5
138
225
  M${from} 0
139
226
  L${to} 0
140
227
  M${to} -5
141
- L${to} 5`} />
142
- <rect
143
- x={from}
144
- y={0}
145
- width={width}
146
- height={yMax}
147
- fill={region.background}
148
- opacity={0.3} />
149
- <Text
150
- x={from + (width / 2)}
151
- y={5}
152
- fill={region.color}
153
- verticalAnchor="start"
154
- textAnchor="middle">
228
+ L${to} 5`}
229
+ />
230
+ <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
231
+ <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
155
232
  {region.label}
156
- </Text>
157
- </Group>
158
- )
159
- }) : '' }
160
-
161
- {/* Y axis */}
162
- {config.visualizationType !== "Spark Line" &&
163
- <AxisLeft
233
+ </Text>
234
+ </Group>
235
+ )
236
+ })
237
+ : ''}
238
+
239
+ {/* Y axis */}
240
+ {config.visualizationType !== 'Spark Line' && (
241
+ <AxisLeft
164
242
  scale={yScale}
165
243
  left={config.runtime.yAxis.size}
166
244
  label={config.runtime.yAxis.label}
167
- stroke="#333"
168
- tickFormat={(tick)=> config.runtime.yAxis.type ==='date' ? formatDate(parseDate(tick)) : config.orientation==='vertical' ? formatNumber(tick) : tick }
169
- numTicks={config.runtime.yAxis.numTicks || undefined}
245
+ stroke='#333'
246
+ tickFormat={tick => (config.runtime.yAxis.type === 'date' ? formatDate(parseDate(tick)) : config.orientation === 'vertical' ? formatNumber(tick) : tick)}
247
+ numTicks={countNumOfTicks('yAxis')}
170
248
  >
171
249
  {props => {
172
- const lollipopShapeSize = config.lollipopSize === 'large' ? 14 : config.lollipopSize === 'medium' ? 12 : 10;
173
- const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2;
174
- const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length * (1 - config.barThickness)) + 5;
175
- const belowBarPaddingFromTop = 9;
176
- return (
177
- <Group className="left-axis">
250
+ const lollipopShapeSize = config.lollipopSize === 'large' ? 14 : config.lollipopSize === 'medium' ? 12 : 10
251
+ const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
252
+ const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
253
+ const belowBarPaddingFromTop = 9
254
+ return (
255
+ <Group className='left-axis'>
178
256
  {props.ticks.map((tick, i) => {
179
257
  return (
180
- <Group
181
- key={`vx-tick-${tick.value}-${i}`}
182
- className={'vx-axis-tick'}
183
- >
184
- {!config.runtime.yAxis.hideTicks && (
185
- <Line
186
- from={tick.from}
187
- to={tick.to}
188
- stroke="#333"
189
- display={config.runtime.horizontal ? 'none' : 'block'}
190
- />
191
- )}
192
- { config.runtime.yAxis.gridLines ? (
193
- <Line
194
- from={{x: tick.from.x + xMax, y: tick.from.y}}
195
- to={tick.from}
196
- stroke="rgba(0,0,0,0.3)"
197
- />
198
- ) : ''
199
- }
200
-
201
- {( config.orientation === "horizontal" && config.visualizationSubType !== 'stacked') && (config.yAxis.labelPlacement === 'On Date/Category Axis' ) && !config.yAxis.hideLabel &&
202
- // 17 is a magic number from the offset in barchart.
203
- <Fragment>
204
- <Text
205
- transform={`translate(${tick.to.x - 5}, ${ config.isLollipopChart ? tick.from.y : tick.from.y - 17 }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
206
- verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
207
- textAnchor={"end"}
208
- >{formatNumber(tick.formattedValue)}</Text>
209
- </Fragment>
210
- }
211
-
212
- { (config.orientation === "horizontal" && config.visualizationSubType === 'stacked') && (config.yAxis.labelPlacement === 'On Date/Category Axis' ) && !config.yAxis.hideLabel &&
213
- // 17 is a magic number from the offset in barchart.
214
- <Text
215
- transform={`translate(${tick.to.x - 5}, ${ tick.from.y - config.barHeight / 2 - 3 }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
216
- verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
217
- textAnchor={"end"}
218
- >{formatNumber(tick.formattedValue)}</Text>
219
- }
220
-
221
- { (config.orientation === "horizontal" && config.visualizationType === 'Paired Bar') && !config.yAxis.hideLabel &&
222
- // 17 is a magic number from the offset in barchart.
223
- <Text
224
- transform={`translate(${-15}, ${ tick.from.y }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
225
- verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
226
- textAnchor={"end"}
227
- >{formatNumber(tick.formattedValue)}</Text>
228
- }
229
-
230
-
231
- { config.orientation !== "horizontal" && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel &&
232
- <Text
233
- x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
234
- y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
235
- verticalAnchor={config.runtime.horizontal ? "start" : "middle"}
236
- textAnchor={config.runtime.horizontal ? 'start' : 'end'}
237
- >
238
- {formatNumber(tick.value)}
258
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
259
+ {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke={config.yAxis.tickColor} display={config.runtime.horizontal ? 'none' : 'block'} />}
260
+
261
+ {config.runtime.yAxis.gridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
262
+
263
+ {config.orientation === 'horizontal' && config.visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
264
+ // 17 is a magic number from the offset in barchart.
265
+ <Fragment>
266
+ <Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.from.y : tick.from.y - 17}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} verticalAnchor={config.isLollipopChart ? 'middle' : 'middle'} textAnchor={'end'}>
267
+ {tick.formattedValue}
239
268
  </Text>
240
- }
269
+ </Fragment>
270
+ )}
241
271
 
242
- </Group>
243
- );
244
- })}
245
- {!config.yAxis.hideAxis && (
246
- <Line
247
- from={props.axisFromPoint}
248
- to={props.axisToPoint}
249
- stroke="#333"
250
- />
251
- )}
252
- { yScale.domain()[0] < 0 && (
253
- <Line
254
- from={{x: props.axisFromPoint.x, y: yScale(0)}}
255
- to={{x: xMax, y: yScale(0)}}
256
- stroke="#333"
257
- />
258
- )}
259
- <Text
260
- className="y-label"
261
- textAnchor="middle"
262
- verticalAnchor="start"
263
- transform={`translate(${-1 * config.runtime.yAxis.size}, ${axisCenter}) rotate(-90)`}
264
- fontWeight="bold"
265
- >
266
- {props.label}
267
- </Text>
268
- </Group>
269
- );
270
- }}
271
- </AxisLeft>
272
- }
272
+ {config.orientation === 'horizontal' && config.visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
273
+ // 17 is a magic number from the offset in barchart.
274
+ <Text transform={`translate(${tick.to.x - 5}, ${tick.from.y - config.barHeight / 2 - 3}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} verticalAnchor={config.isLollipopChart ? 'middle' : 'middle'} textAnchor={'end'}>
275
+ {tick.formattedValue}
276
+ </Text>
277
+ )}
273
278
 
274
- {/* X axis */}
275
- {config.visualizationType !== 'Paired Bar' && config.visualizationType !== "Spark Line" && (
276
- <AxisBottom
277
- top={yMax}
278
- left={config.runtime.yAxis.size}
279
- label={config.runtime.xAxis.label}
280
- tickFormat={tick=> config.runtime.xAxis.type === 'date' ? formatDate(tick) : config.orientation ==='horizontal' ? formatNumber(tick) : tick}
281
- scale={xScale}
282
- stroke="#333"
283
- tickStroke="#333"
284
- numTicks={config.runtime.xAxis.numTicks || undefined}
285
- >
286
- {props => {
287
- const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2;
288
- return (
289
- <Group className="bottom-axis">
290
- {props.ticks.map((tick, i) => {
291
- const tickWidth = xMax / props.ticks.length;
292
- return (
293
- <Group
294
- key={`vx-tick-${tick.value}-${i}`}
295
- className={'vx-axis-tick'}
296
- >
297
- {!config.xAxis.hideTicks && (
298
- <Line
299
- from={tick.from}
300
- to={tick.to}
301
- stroke="#333"
302
- />
279
+ {config.orientation === 'horizontal' && config.visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
280
+ // 17 is a magic number from the offset in barchart.
281
+ <Text transform={`translate(${-15}, ${tick.from.y}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} verticalAnchor={config.isLollipopChart ? 'middle' : 'middle'} textAnchor={'end'}>
282
+ {tick.formattedValue}
283
+ </Text>
303
284
  )}
304
- {!config.xAxis.hideLabel && (
305
- <Text
306
- transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
307
- verticalAnchor="start"
308
- textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
309
- width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
310
- >
311
- {tick.formattedValue}
312
- </Text>
285
+
286
+ {config.orientation === 'horizontal' && config.visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
287
+ // 17 is a magic number from the offset in barchart.
288
+ <Text transform={`translate(${-15}, ${tick.from.y}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} verticalAnchor={config.isLollipopChart ? 'middle' : 'middle'} textAnchor={'end'}>
289
+ {formatNumber(tick.formattedValue)}
290
+ </Text>
313
291
  )}
314
292
 
293
+ {config.orientation !== 'horizontal' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
294
+ <Text
295
+ x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
296
+ y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
297
+ verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
298
+ textAnchor={config.runtime.horizontal ? 'start' : 'end'}
299
+ fill={config.yAxis.tickLabelColor}
300
+ >
301
+ {tick.formattedValue}
302
+ </Text>
303
+ )}
315
304
  </Group>
316
- );
305
+ )
317
306
  })}
318
- {!config.xAxis.hideAxis && (
319
- <Line
320
- from={props.axisFromPoint}
321
- to={props.axisToPoint}
322
- stroke="#333"
323
- />
324
- )}
325
- <Text
326
- x={axisCenter}
327
- y={config.runtime.xAxis.size}
328
- textAnchor="middle"
329
- verticalAnchor="end"
330
- fontWeight="bold"
331
- >
307
+ {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
308
+ {yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
309
+ <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * config.runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
332
310
  {props.label}
333
311
  </Text>
334
312
  </Group>
335
- );
313
+ )
336
314
  }}
337
- </AxisBottom>
338
- )}
315
+ </AxisLeft>
316
+ )}
339
317
 
340
- {config.visualizationType === 'Paired Bar' &&
341
- <>
342
- <AxisBottom
343
- top={yMax}
344
- left={config.runtime.yAxis.size}
345
- label={config.runtime.xAxis.label}
346
- tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : (tick) => tick}
347
- scale={g1xScale}
348
- stroke="#333"
349
- tickStroke="#333"
350
- numTicks={config.runtime.xAxis.numTicks || undefined}
351
- >
318
+ {/* Right Axis */}
319
+ {hasRightAxis && (
320
+ <AxisRight scale={yScaleRight} left={width - config.yAxis.rightAxisSize} label={config.yAxis.rightLabel} tickFormat={tick => formatNumber(tick, 'right')} numTicks={config.runtime.yAxis.rightNumTicks || undefined} labelOffset={45}>
352
321
  {props => {
353
- const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2;
322
+ const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
323
+ const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
354
324
  return (
355
- <Group className="bottom-axis">
325
+ <Group className='right-axis'>
356
326
  {props.ticks.map((tick, i) => {
357
- const tickWidth = xMax / props.ticks.length;
358
327
  return (
359
- <Group
360
- key={`vx-tick-${tick.value}-${i}`}
361
- className={'vx-axis-tick'}
362
- >
363
- {!config.runtime.yAxis.hideTicks &&
364
- <Line
365
- from={tick.from}
366
- to={tick.to}
367
- stroke="#333"
368
- />
369
- }
370
- {!config.runtime.yAxis.hideLabel &&
371
- <Text
372
- transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`}
373
- verticalAnchor="start"
374
- textAnchor={'end'}
375
- width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
376
- >
377
- {formatNumber(tick.formattedValue)}
328
+ <Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
329
+ {!config.runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={config.runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
330
+
331
+ {config.runtime.yAxis.rightGridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
332
+
333
+ {!config.yAxis.rightHideLabel && (
334
+ <Text x={tick.to.x} y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)} verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'} textAnchor={'start'} fill={config.yAxis.rightAxisTickLabelColor}>
335
+ {tick.formattedValue}
378
336
  </Text>
379
- }
337
+ )}
380
338
  </Group>
381
- );
339
+ )
382
340
  })}
383
- {!config.runtime.yAxis.hideAxis &&
384
- <Line
385
- from={props.axisFromPoint}
386
- to={props.axisToPoint}
387
- stroke="#333"
388
- />
389
- }
341
+ {!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
342
+ <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0}, ${axisCenter}) rotate(90)`} fontWeight='bold' fill={config.yAxis.rightAxisLabelColor}>
343
+ {props.label}
344
+ </Text>
390
345
  </Group>
391
- );
346
+ )
392
347
  }}
393
- </AxisBottom>
348
+ </AxisRight>
349
+ )}
350
+
351
+ {hasTopAxis && config.topAxis.hasLine && (
352
+ <AxisTop
353
+ stroke='#333'
354
+ left={config.runtime.yAxis.size}
355
+ scale={xScale}
356
+ hideTicks
357
+ hideZero
358
+ tickLabelProps={() => ({
359
+ fill: 'transparent'
360
+ })}
361
+ />
362
+ )}
363
+
364
+ {/* X axis */}
365
+ {config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Spark Line' && (
394
366
  <AxisBottom
395
367
  top={yMax}
396
368
  left={config.runtime.yAxis.size}
397
369
  label={config.runtime.xAxis.label}
398
- tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : (tick) => tick}
399
- scale={g2xScale}
400
- stroke="#333"
401
- tickStroke="#333"
402
- numTicks={config.runtime.xAxis.numTicks || undefined}
370
+ tickFormat={tick => (config.runtime.xAxis.type === 'date' ? formatDate(tick) : config.orientation === 'horizontal' ? formatNumber(tick) : tick)}
371
+ scale={xScale}
372
+ stroke='#333'
373
+ tickStroke='#333'
374
+ numTicks={countNumOfTicks('xAxis')}
403
375
  >
404
376
  {props => {
405
- const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2;
377
+ const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
406
378
  return (
407
- <Group className="bottom-axis">
379
+ <Group className='bottom-axis'>
408
380
  {props.ticks.map((tick, i) => {
409
- const tickWidth = xMax / props.ticks.length;
381
+ const tickWidth = xMax / props.ticks.length
410
382
  return (
411
- <Group
412
- key={`vx-tick-${tick.value}-${i}`}
413
- className={'vx-axis-tick'}
414
- >
415
- <Line
416
- from={tick.from}
417
- to={tick.to}
418
- stroke="#333"
419
- />
420
- <Text
421
- transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`}
422
- verticalAnchor="start"
423
- textAnchor={'end'}
424
- width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
425
- >
426
- {tick.formattedValue}
427
- </Text>
383
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
384
+ {!config.xAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke={config.xAxis.tickColor} />}
385
+ {!config.xAxis.hideLabel && (
386
+ <Text
387
+ transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
388
+ verticalAnchor='start'
389
+ textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
390
+ width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
391
+ fill={config.xAxis.tickLabelColor}
392
+ >
393
+ {tick.formattedValue}
394
+ </Text>
395
+ )}
428
396
  </Group>
429
- );
397
+ )
430
398
  })}
431
- <Line
432
- from={props.axisFromPoint}
433
- to={props.axisToPoint}
434
- stroke="#333"
435
- />
399
+ {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
400
+ <Text x={axisCenter} y={config.runtime.xAxis.size} textAnchor='middle' verticalAnchor='end' fontWeight='bold' fill={config.xAxis.labelColor}>
401
+ {props.label}
402
+ </Text>
436
403
  </Group>
437
- );
404
+ )
438
405
  }}
439
406
  </AxisBottom>
407
+ )}
408
+
409
+ {config.visualizationType === 'Paired Bar' && (
410
+ <>
411
+ <AxisBottom top={yMax} left={config.runtime.yAxis.size} label={config.runtime.xAxis.label} tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={config.runtime.xAxis.numTicks || undefined}>
412
+ {props => {
413
+ const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
414
+ return (
415
+ <Group className='bottom-axis'>
416
+ {props.ticks.map((tick, i) => {
417
+ const tickWidth = xMax / props.ticks.length
418
+ return (
419
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
420
+ {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
421
+ {!config.runtime.yAxis.hideLabel && (
422
+ <Text transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`} verticalAnchor='start' textAnchor={'end'} width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}>
423
+ {formatNumber(tick.formattedValue)}
424
+ </Text>
425
+ )}
426
+ </Group>
427
+ )
428
+ })}
429
+ {!config.runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
430
+ </Group>
431
+ )
432
+ }}
433
+ </AxisBottom>
434
+ <AxisBottom
435
+ top={yMax}
436
+ left={config.runtime.yAxis.size}
437
+ label={config.runtime.xAxis.label}
438
+ tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : config.runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
439
+ scale={g2xScale}
440
+ stroke='#333'
441
+ tickStroke='#333'
442
+ numTicks={config.runtime.xAxis.numTicks || undefined}
443
+ >
444
+ {props => {
445
+ const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
446
+ return (
447
+ <>
448
+ <Group className='bottom-axis'>
449
+ {props.ticks.map((tick, i) => {
450
+ const tickWidth = xMax / props.ticks.length
451
+ return (
452
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
453
+ {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
454
+ {!config.runtime.yAxis.hideLabel && (
455
+ <Text transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`} verticalAnchor='start' textAnchor={'end'} width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}>
456
+ {tick.formattedValue}
457
+ </Text>
458
+ )}
459
+ </Group>
460
+ )
461
+ })}
462
+ {!config.runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
463
+ </Group>
464
+ <Group>
465
+ <Text transform={`translate(${xMax / 2}, ${config.height - yMax + 20}) rotate(-${0})`} verticalAnchor='start' textAnchor={'middle'} stroke='#333'>
466
+ {config.runtime.xAxis.label}
467
+ </Text>
468
+ </Group>
469
+ </>
470
+ )
471
+ }}
472
+ </AxisBottom>
473
+ </>
474
+ )}
475
+ {config.visualizationType === 'Paired Bar' && <PairedBarChart width={xMax} height={yMax} />}
476
+
477
+ {/* Bar chart */}
478
+ {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && (
479
+ <>
480
+ <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
481
+ </>
482
+ )}
483
+
484
+ {/* Line chart */}
485
+ {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && (
486
+ <>
487
+ <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
440
488
  </>
441
- }
442
- { config.visualizationType === 'Paired Bar' && (
443
- <PairedBarChart width={xMax} height={yMax} />
444
- ) }
445
-
446
- {/* Bar chart */}
447
- { (config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar') && (
448
- <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />
449
- )}
450
-
451
- {/* Line chart */}
452
- { (config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar') && (
453
- <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />
454
- )}
489
+ )}
455
490
  </svg>
456
- <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} html={true} type="light" arrowColor="rgba(0,0,0,0)" className="tooltip"/>
491
+ <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} html={true} type='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
492
+ <div className='animation-trigger' ref={triggerRef} />
457
493
  </ErrorBoundary>
458
494
  )
459
495
  }