@cdc/chart 4.23.4 → 4.23.5

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 (31) hide show
  1. package/dist/cdcchart.js +52384 -50875
  2. package/examples/feature/__data__/planet-example-data.json +2 -19
  3. package/examples/feature/__data__/planet-logaritmic-data.json +56 -0
  4. package/examples/feature/area/area-chart-category.json +240 -0
  5. package/examples/feature/bar/example-bar-chart.json +544 -22
  6. package/examples/feature/bar/planet-chart-logaritmic-config.json +170 -0
  7. package/examples/feature/boxplot/valid-boxplot.csv +17 -0
  8. package/examples/feature/filters/filter-testing.json +37 -3
  9. package/examples/feature/forecasting/random_data.csv +366 -0
  10. package/examples/feature/line/line-chart.json +2 -2
  11. package/examples/feature/test-highlight/test-highlight-2.json +789 -0
  12. package/examples/feature/test-highlight/test-highlight-vertical.json +561 -0
  13. package/examples/feature/test-highlight/test-highlight.json +100 -0
  14. package/examples/feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json +1 -2
  15. package/examples/gallery/bar-chart-horizontal/horizontal-highlight.json +345 -0
  16. package/index.html +8 -8
  17. package/package.json +2 -2
  18. package/src/CdcChart.jsx +294 -14
  19. package/src/components/AreaChart.jsx +27 -20
  20. package/src/components/BarChart.jsx +85 -25
  21. package/src/components/DeviationBar.jsx +32 -32
  22. package/src/components/EditorPanel.jsx +1105 -184
  23. package/src/components/Legend.jsx +39 -3
  24. package/src/components/LineChart.jsx +1 -8
  25. package/src/components/LinearChart.jsx +121 -270
  26. package/src/data/initial-state.js +18 -3
  27. package/src/hooks/useHighlightedBars.js +154 -0
  28. package/src/hooks/useMinMax.js +92 -0
  29. package/src/hooks/useReduceData.js +31 -57
  30. package/src/hooks/useScales.js +202 -0
  31. /package/examples/feature/area/{area-chart.json → area-chart-date.json} +0 -0
@@ -5,9 +5,10 @@ import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
5
5
  import LegendCircle from '@cdc/core/components/LegendCircle'
6
6
 
7
7
  import useLegendClasses from './../hooks/useLegendClasses'
8
+ import { useHighlightedBars } from '../hooks/useHighlightedBars'
8
9
 
9
10
  const Legend = () => {
10
- const { config, legend, colorScale, seriesHighlight, highlight, twoColorPalette, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, colorPalettes, rawData, setConfig, currentViewport } = useContext(ConfigContext)
11
+ const { config, legend, colorScale, seriesHighlight, highlight, twoColorPalette, tableData, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, colorPalettes, rawData, setConfig, currentViewport } = useContext(ConfigContext)
11
12
 
12
13
  const { innerClasses, containerClasses } = useLegendClasses(config)
13
14
 
@@ -103,14 +104,14 @@ const Legend = () => {
103
104
  if (config.visualizationType === 'Bar' && config.visualizationSubType === 'regular' && colorCode && config.series?.length === 1) {
104
105
  let palette = colorPalettes[config.palette]
105
106
 
106
- while (data.length > palette.length) {
107
+ while (tableData.length > palette.length) {
107
108
  palette = palette.concat(palette)
108
109
  }
109
110
  palette = palette.slice(0, data.length)
110
111
  //store uniq values to Set by colorCode
111
112
  const set = new Set()
112
113
 
113
- data.forEach(d => set.add(d[colorCode]))
114
+ tableData.forEach(d => set.add(d[colorCode]))
114
115
 
115
116
  // create labels with uniq values
116
117
  const uniqeLabels = Array.from(set).map((val, i) => {
@@ -133,6 +134,10 @@ const Legend = () => {
133
134
  const marginTop = isBottomOrSmallViewport && isHorizontal ? `${config.runtime.xAxis.size}px` : '0px'
134
135
  const marginBottom = isBottomOrSmallViewport ? '15px' : '0px'
135
136
 
137
+ const { HighLightedBarUtils } = useHighlightedBars(config)
138
+
139
+ let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
140
+
136
141
  if (!legend) return null
137
142
 
138
143
  if (!legend.dynamicLegend)
@@ -182,6 +187,37 @@ const Legend = () => {
182
187
  </LegendItem>
183
188
  )
184
189
  })}
190
+
191
+ {highLightedLegendItems.map((bar, i) => {
192
+ // if duplicates only return first item
193
+ let className = 'legend-item'
194
+ let itemName = bar.legendLabel
195
+
196
+ if (!itemName) return
197
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
198
+ className += ' inactive'
199
+ }
200
+ return (
201
+ <LegendItem
202
+ className={className}
203
+ tabIndex={0}
204
+ key={`legend-quantile-${i}`}
205
+ onKeyPress={e => {
206
+ if (e.key === 'Enter') {
207
+ highlight(bar.legendLabel)
208
+ }
209
+ }}
210
+ onClick={() => {
211
+ highlight(bar.legendLabel)
212
+ }}
213
+ >
214
+ <LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
215
+ <LegendLabel align='left' margin='0 0 0 4px'>
216
+ {bar.legendLabel ? bar.legendLabel : bar.value}
217
+ </LegendLabel>
218
+ </LegendItem>
219
+ )
220
+ })}
185
221
  {seriesHighlight.length > 0 && (
186
222
  <button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
187
223
  Reset
@@ -8,7 +8,6 @@ import { Text } from '@visx/text'
8
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
9
  import ConfigContext from '../ConfigContext'
10
10
  import useRightAxis from '../hooks/useRightAxis'
11
- import isNumber from '@cdc/core/helpers/isNumber'
12
11
 
13
12
  export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, seriesStyle = 'Line' }) {
14
13
  const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, updateConfig, handleLineType } = useContext(ConfigContext)
@@ -72,13 +71,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
72
71
  isNumber(d[seriesKey]) && (
73
72
  <Group key={`series-${seriesKey}-point-${dataIndex}`}>
74
73
  {/* Render legend */}
75
- <Text
76
- display={config.labels ? 'block' : 'none'}
77
- x={xScale(getXAxisData(d))}
78
- y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))}
79
- fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
80
- textAnchor='middle'
81
- >
74
+ <Text display={config.labels ? 'block' : 'none'} x={xScale(getXAxisData(d))} y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))} fill={'#000'} textAnchor='middle'>
82
75
  {formatNumber(d[seriesKey], 'left')}
83
76
  </Text>
84
77
 
@@ -4,302 +4,78 @@ import { Tooltip as ReactTooltip } from 'react-tooltip'
4
4
  import { Group } from '@visx/group'
5
5
  import { Line } from '@visx/shape'
6
6
  import { Text } from '@visx/text'
7
- import { scaleLinear, scalePoint, scaleBand, scaleTime } from '@visx/scale'
8
7
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
9
8
 
10
- import CoveScatterPlot from './ScatterPlot'
11
9
  import BarChart from './BarChart'
12
- import LineChart from './LineChart'
13
10
  import ConfigContext from '../ConfigContext'
11
+ import CoveAreaChart from './AreaChart'
12
+ import CoveBoxPlot from './BoxPlot'
13
+ import CoveScatterPlot from './ScatterPlot'
14
+ import DeviationBar from './DeviationBar'
15
+ import LineChart from './LineChart'
14
16
  import PairedBarChart from './PairedBarChart'
15
17
  import useIntersectionObserver from './useIntersectionObserver'
16
- import CoveBoxPlot from './BoxPlot'
17
- import CoveAreaChart from './AreaChart'
18
18
 
19
19
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
20
20
  import '../scss/LinearChart.scss'
21
21
  import useReduceData from '../hooks/useReduceData'
22
+ import useScales from '../hooks/useScales'
23
+ import useMinMax from '../hooks/useMinMax'
22
24
  import useRightAxis from '../hooks/useRightAxis'
23
25
  import useTopAxis from '../hooks/useTopAxis'
24
- import { DeviationBar } from './DeviationBar'
25
26
 
26
- // TODO: Move scaling functions into hooks to manage complexity
27
27
  export default function LinearChart() {
28
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig } = useContext(ConfigContext)
29
-
30
- let [width] = dimensions
31
- const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
32
- const [animatedChart, setAnimatedChart] = useState(false)
33
-
34
- const triggerRef = useRef()
35
- const dataRef = useIntersectionObserver(triggerRef, {
36
- freezeOnceVisible: false
37
- })
38
-
39
- // Make sure the chart is visible if in the editor
40
- /* eslint-disable react-hooks/exhaustive-deps */
41
- useEffect(() => {
42
- const element = document.querySelector('.isEditor')
43
- if (element) {
44
- // parent element is visible
45
- setAnimatedChart(prevState => true)
46
- }
47
- }) /* eslint-disable-line */
28
+ const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType } = useContext(ConfigContext)
48
29
 
49
- // If the chart is in view, set to animate if it has not already played
50
- useEffect(() => {
51
- if (dataRef?.isIntersecting === true && config.animate) {
52
- setTimeout(() => {
53
- setAnimatedChart(prevState => true)
54
- }, 500)
55
- }
56
- }, [dataRef?.isIntersecting, config.animate])
30
+ // getters & functions
31
+ const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
32
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
33
+ const xAxisDataMapped = data.map(d => getXAxisData(d))
57
34
 
58
- if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && (currentViewport === 'lg' || currentViewport === 'md')) {
35
+ // configure width
36
+ let [width] = dimensions
37
+ if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
59
38
  width = width * 0.73
60
39
  }
40
+ // configure height , yMax, xMAx
61
41
  const { horizontal: heightHorizontal } = config.heights
42
+ const isHorizontal = config.orientation === 'horizontal'
43
+ const shouldAbbreviate = true
62
44
  const height = config.aspectRatio ? width * config.aspectRatio : config.heights[config.orientation]
63
45
  const xMax = width - config.runtime.yAxis.size - (config.visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
64
46
  const yMax = height - (config.orientation === 'horizontal' ? 0 : config.runtime.xAxis.size)
65
47
 
48
+ // hooks % states
49
+ const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
66
50
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
67
51
  const { hasTopAxis } = useTopAxis(config)
52
+ const [animatedChart, setAnimatedChart] = useState(false)
53
+ const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
54
+ const { min, max } = useMinMax(properties)
55
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale } = useScales({ ...properties, min, max })
68
56
 
69
- const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
70
- const getYAxisData = (d, seriesKey) => d[seriesKey]
71
-
72
- let xScale
73
- let yScale
74
- let seriesScale
75
-
76
- const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
77
- const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
78
- const isMinValid = (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
79
-
80
- let max = 0 // need outside the if statement
81
- let min = 0
82
- if (data) {
83
- min = enteredMinValue && isMinValid ? enteredMinValue : minValue
84
- max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
85
-
86
- // If Confidence Intervals in data, then need to account for increased height in max for YScale
87
- if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Deviation Bar') {
88
- let ciYMax = 0
89
- if (config.hasOwnProperty('confidenceKeys')) {
90
- let upperCIValues = data.map(function (d) {
91
- return d[config.confidenceKeys.upper]
92
- })
93
- ciYMax = Math.max.apply(Math, upperCIValues)
94
- if (ciYMax > max) max = ciYMax // bump up the max
95
- }
96
- }
97
-
98
- if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
99
- min = 0
100
- }
101
- if (config.visualizationType === 'Combo' && isAllLine) {
102
- if ((enteredMinValue === undefined || enteredMinValue === null || enteredMinValue === '') && min > 0) {
103
- min = 0
104
- }
105
- if (enteredMinValue) {
106
- const isMinValid = +enteredMinValue < minValue
107
- min = +enteredMinValue && isMinValid ? enteredMinValue : minValue
108
- }
109
- }
110
-
111
- if (config.visualizationType === 'Deviation Bar' && min > 0) {
112
- const isMinValid = Number(enteredMinValue) < Math.min(minValue, Number(config.xAxis.target))
113
- min = enteredMinValue && isMinValid ? enteredMinValue : 0
114
- }
115
-
116
- if (config.visualizationType === 'Line') {
117
- const isMinValid = enteredMinValue < minValue
118
- min = enteredMinValue && isMinValid ? enteredMinValue : minValue
119
- }
120
- //If data value max wasn't provided, calculate it
121
- if (max === Number.MIN_VALUE) {
122
- // if all values in data are negative set max = 0
123
- max = existPositiveValue ? maxValue : 0
124
- }
125
-
126
- //Adds Y Axis data padding if applicable
127
- if (config.runtime.yAxis.paddingPercent) {
128
- let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent
129
- min -= paddingValue
130
- max += paddingValue
131
- }
132
-
133
- let xAxisDataMapped = data.map(d => getXAxisData(d))
134
-
135
- if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
136
- const dataKey = data.map(item => item[config.series[0].dataKey])
137
- const maxDataVal = Math.max(...dataKey).toString().length
138
-
139
- switch (true) {
140
- case maxDataVal > 8 && maxDataVal <= 12:
141
- max = max * 1.3
142
- break
143
- case maxDataVal > 4 && maxDataVal <= 7:
144
- max = max * 1.1
145
- break
146
- default:
147
- break
148
- }
149
- }
150
-
151
- // DEV-3219 - bc some values are going above YScale - adding 10% or 20% factor onto Max
152
- // - put the statement up here and it works for both vert and horiz charts of all types
153
- if (config.yAxis.enablePadding) {
154
- if (min < 0) {
155
- // sets with negative data need more padding on the max
156
- max *= 1.2
157
- min *= 1.2
158
- } else {
159
- max *= 1.1
160
- }
161
- }
162
-
163
- if (config.runtime.horizontal) {
164
- xScale = scaleLinear({
165
- domain: [min * 1.03, max],
166
- range: [0, xMax]
167
- })
168
-
169
- yScale =
170
- config.runtime.xAxis.type === 'date'
171
- ? scaleLinear({
172
- domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]
173
- })
174
- : scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
175
-
176
- seriesScale = scalePoint({
177
- domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
178
- range: [0, yMax]
179
- })
180
-
181
- yScale.rangeRound([0, yMax])
182
- } else {
183
- min = min < 0 ? min * 1.11 : min
184
-
185
- yScale = scaleLinear({
186
- domain: [min, max],
187
- range: [yMax, 0]
188
- })
189
-
190
- xScale = scalePoint({
191
- domain: xAxisDataMapped,
192
- range: [0, xMax],
193
- padding: 0.5
194
- })
195
-
196
- seriesScale = scalePoint({
197
- domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
198
- range: [0, xMax]
199
- })
200
- }
201
-
202
- if (config.visualizationType === 'Area Chart' && config.xAxis.type === 'date') {
203
- xScale = scaleTime({
204
- domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)],
205
- range: [0, xMax]
206
- })
207
- }
208
-
209
- if (config.visualizationType === 'Paired Bar') {
210
- const offset = 1.02 // Offset of the ticks/values from the Axis
211
- let groupOneMax = Math.max.apply(
212
- Math,
213
- data.map(d => d[config.series[0].dataKey])
214
- )
215
- let groupTwoMax = Math.max.apply(
216
- Math,
217
- data.map(d => d[config.series[1].dataKey])
218
- )
219
-
220
- // group one
221
- var g1xScale = scaleLinear({
222
- domain: [0, Math.max(groupOneMax, groupTwoMax) * offset],
223
- range: [xMax / 2, 0]
224
- })
225
-
226
- // group 2
227
- var g2xScale = scaleLinear({
228
- domain: g1xScale.domain(),
229
- range: [xMax / 2, xMax],
230
- nice: true
231
- })
232
- }
233
-
234
- if (config.visualizationType === 'Scatter Plot') {
235
- if (config.xAxis.type === 'continuous') {
236
- xScale = scaleLinear({
237
- domain: [0, Math.max.apply(null, xScale.domain())],
238
- range: [0, xMax]
239
- })
240
- }
241
- }
242
-
243
- if (config.visualizationType === 'Deviation Bar') {
244
- const leftOffset = config.isLollipopChart ? 1.05 : 1.03
245
- yScale = scaleBand({
246
- domain: xAxisDataMapped,
247
- range: [0, yMax]
248
- })
249
- xScale = scaleLinear({
250
- domain: [min * leftOffset, Math.max(Number(config.xAxis.target), max)],
251
- range: [0, xMax],
252
- round: true,
253
- nice: true
254
- })
255
- }
256
- // Handle Box Plots
257
- if (config.visualizationType === 'Box Plot') {
258
- const allOutliers = []
259
- const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
260
-
261
- // check if outliers are lower
262
- if (hasOutliers) {
263
- let outlierMin = Math.min(...allOutliers)
264
- let outlierMax = Math.max(...allOutliers)
265
-
266
- // check if outliers exceed standard bounds
267
- if (outlierMin < min) min = outlierMin
268
- if (outlierMax > max) max = outlierMax
269
- }
270
-
271
- // check fences for max/min
272
- let lowestFence = Math.min(...config.boxplot.plots.map(item => item.columnLowerBounds))
273
- let highestFence = Math.max(...config.boxplot.plots.map(item => item.columnUpperBounds))
274
-
275
- if (lowestFence < min) min = lowestFence
276
- if (highestFence > max) max = highestFence
277
-
278
- // Set Scales
279
- yScale = scaleLinear({
280
- range: [yMax, 0],
281
- round: true,
282
- domain: [min, max]
283
- })
284
-
285
- xScale = scaleBand({
286
- range: [0, xMax],
287
- round: true,
288
- domain: config.boxplot.categories,
289
- padding: 0.4
290
- })
291
- }
292
- }
293
-
294
- const shouldAbbreviate = true
57
+ // refs
58
+ const triggerRef = useRef()
59
+ const svgRef = useRef()
60
+ const dataRef = useIntersectionObserver(triggerRef, {
61
+ freezeOnceVisible: false
62
+ })
295
63
 
296
64
  const handleLeftTickFormatting = tick => {
65
+ if (config.useLogScale && tick === 0.1) {
66
+ //when logaritmic scale applyed change value of FIRST tick
67
+ tick = 0
68
+ }
297
69
  if (config.runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
298
70
  if (config.orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
299
71
  return tick
300
72
  }
301
73
 
302
74
  const handleBottomTickFormatting = tick => {
75
+ if (config.useLogScale && tick === 0.1) {
76
+ // when logaritmic scale applyed change value FIRST of tick
77
+ tick = 0
78
+ }
303
79
  if (config.runtime.xAxis.type === 'date') return formatDate(tick)
304
80
  if (config.orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
305
81
  if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
@@ -307,8 +83,6 @@ export default function LinearChart() {
307
83
  }
308
84
 
309
85
  const countNumOfTicks = axis => {
310
- // function get number of ticks based on bar type & users value
311
- const isHorizontal = config.orientation === 'horizontal'
312
86
  const { numTicks } = config.runtime[axis]
313
87
  let tickCount = undefined
314
88
 
@@ -344,7 +118,26 @@ export default function LinearChart() {
344
118
  return tickCount
345
119
  }
346
120
 
347
- const svgRef = useRef()
121
+ // Make sure the chart is visible if in the editor
122
+ /* eslint-disable react-hooks/exhaustive-deps */
123
+ useEffect(() => {
124
+ const element = document.querySelector('.isEditor')
125
+ if (element) {
126
+ // parent element is visible
127
+ setAnimatedChart(prevState => true)
128
+ }
129
+ }) /* eslint-disable-line */
130
+
131
+ // If the chart is in view, set to animate if it has not already played
132
+ useEffect(() => {
133
+ if (dataRef?.isIntersecting === true && config.animate) {
134
+ setTimeout(() => {
135
+ setAnimatedChart(prevState => true)
136
+ }, 500)
137
+ }
138
+ }, [dataRef?.isIntersecting, config.animate])
139
+
140
+ const { orientation, xAxis, yAxis } = config
348
141
 
349
142
  return isNaN(width) ? (
350
143
  <></>
@@ -397,7 +190,7 @@ export default function LinearChart() {
397
190
 
398
191
  {/* Y axis */}
399
192
  {config.visualizationType !== 'Spark Line' && (
400
- <AxisLeft scale={yScale} left={Number(config.runtime.yAxis.size) - config.yAxis.axisPadding} label={config.runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
193
+ <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(config.runtime.yAxis.size) - config.yAxis.axisPadding} label={config.runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
401
194
  {props => {
402
195
  const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
403
196
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
@@ -406,12 +199,15 @@ export default function LinearChart() {
406
199
  {props.ticks.map((tick, i) => {
407
200
  const minY = props.ticks[0].to.y
408
201
  const barMinHeight = 15 // 15 is the min height for bars by default
202
+ const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
203
+ const tickLength = showTicks === 'block' ? 7 : 0
204
+ const to = { x: tick.to.x - tickLength, y: tick.to.y }
409
205
 
410
206
  return (
411
207
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
412
- {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke={config.yAxis.tickColor} display={config.runtime.horizontal ? 'none' : 'block'} />}
208
+ {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={config.runtime.horizontal ? 'none' : 'block'} />}
413
209
 
414
- {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)' /> : ''}
210
+ {config.runtime.yAxis.gridLines ? <Line display={config.useLogScale && showTicks} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
415
211
 
416
212
  {config.orientation === 'horizontal' && config.visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
417
213
  <Text
@@ -440,8 +236,10 @@ export default function LinearChart() {
440
236
  </Text>
441
237
  )}
442
238
 
443
- {config.orientation !== 'horizontal' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
239
+ {config.orientation === 'vertical' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
444
240
  <Text
241
+ display={config.useLogScale ? showTicks : 'block'}
242
+ dx={config.useLogScale ? -6 : 0}
445
243
  x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
446
244
  y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
447
245
  verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
@@ -529,12 +327,19 @@ export default function LinearChart() {
529
327
  return (
530
328
  <Group className='bottom-axis'>
531
329
  {props.ticks.map((tick, i) => {
330
+ // when using LogScale show major ticks values only
331
+ const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
532
332
  const tickWidth = xMax / props.ticks.length
333
+ const tickLength = showTick === 'block' ? 16 : 8
334
+ const to = { x: tick.to.x, y: tickLength }
335
+
533
336
  return (
534
337
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
535
- {!config.xAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke={config.xAxis.tickColor} />}
338
+ {!config.xAxis.hideTicks && <Line from={tick.from} to={config.orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' ? 1.3 : 1} />}
536
339
  {!config.xAxis.hideLabel && (
537
340
  <Text
341
+ dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
342
+ display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
538
343
  transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
539
344
  verticalAnchor='start'
540
345
  textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
@@ -645,6 +450,52 @@ export default function LinearChart() {
645
450
  <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
646
451
  </>
647
452
  )}
453
+
454
+ {/* y anchors */}
455
+ {config.yAxis.anchors &&
456
+ config.yAxis.anchors.map(anchor => {
457
+ let anchorPosition = yScale(anchor.value)
458
+ const padding = config.orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
459
+ const middleOffset = config.orientation === 'horizontal' && config.visualizationType === 'Bar' ? config.barHeight / 4 : 0
460
+
461
+ return (
462
+ // prettier-ignore
463
+ <Line
464
+ key={anchor.value}
465
+ strokeDasharray={handleLineType(anchor.lineStyle)}
466
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
467
+ className='anchor-y'
468
+ from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
469
+ to={{ x: width, y: anchorPosition - middleOffset }}
470
+ />
471
+ )
472
+ })}
473
+
474
+ {/* x anchors */}
475
+ {config.xAxis.anchors &&
476
+ config.xAxis.anchors.map(anchor => {
477
+ let newX = xAxis
478
+ if (orientation === 'horizontal') {
479
+ newX = yAxis
480
+ }
481
+
482
+ let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
483
+
484
+ const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
485
+
486
+ return (
487
+ // prettier-ignore
488
+ <Line
489
+ key={anchor.value}
490
+ strokeDasharray={handleLineType(anchor.lineStyle)}
491
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
492
+ fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
493
+ className='anchor-x'
494
+ from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
495
+ to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
496
+ />
497
+ )
498
+ })}
648
499
  </svg>
649
500
  <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
650
501
  <div className='animation-trigger' ref={triggerRef} />
@@ -2,6 +2,7 @@ export default {
2
2
  type: 'chart',
3
3
  title: '',
4
4
  showTitle: true,
5
+ showDownloadMediaButton: false,
5
6
  theme: 'theme-blue',
6
7
  animate: false,
7
8
  fontSize: 'medium',
@@ -14,6 +15,9 @@ export default {
14
15
  barStyle: '',
15
16
  roundingStyle: 'standard',
16
17
  tipRounding: 'top',
18
+ general: {
19
+ showDownloadButton: false
20
+ },
17
21
  padding: {
18
22
  left: 5,
19
23
  right: 5
@@ -40,7 +44,8 @@ export default {
40
44
  rightAxisTickColor: '#333',
41
45
  numTicks: '',
42
46
  axisPadding: 0,
43
- tickRotation: 0
47
+ tickRotation: 0,
48
+ anchors: []
44
49
  },
45
50
  boxplot: {
46
51
  plots: [],
@@ -84,6 +89,7 @@ export default {
84
89
  horizontal: 750
85
90
  },
86
91
  xAxis: {
92
+ anchors: [],
87
93
  type: 'categorical',
88
94
  showTargetLabel: true,
89
95
  targetLabel: 'Target',
@@ -109,9 +115,15 @@ export default {
109
115
  height: '',
110
116
  caption: '',
111
117
  showDownloadUrl: false,
112
- showDataTableLink: true
118
+ showDataTableLink: true,
119
+ indexLabel: '',
120
+ download: false,
121
+ showVertical: true
113
122
  },
114
123
  orientation: 'vertical',
124
+ color: 'pinkpurple',
125
+ columns: { // start with a blank list
126
+ },
115
127
  legend: {
116
128
  behavior: 'isolate',
117
129
  singleRow: false,
@@ -150,5 +162,8 @@ export default {
150
162
  accent: true,
151
163
  background: true
152
164
  },
153
- filterBehavior: 'Filter Change'
165
+ useLogScale: false,
166
+ filterBehavior: 'Filter Change',
167
+ highlightedBarValues: [],
168
+ series: []
154
169
  }