@cdc/chart 4.23.2 → 4.23.4

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 (94) hide show
  1. package/dist/cdcchart.js +42292 -40337
  2. package/examples/feature/__data__/area-chart.json +56 -0
  3. package/examples/{planet-example-data.json → feature/__data__/planet-example-data-max-increase.json} +4 -4
  4. package/examples/feature/__data__/planet-example-data.json +68 -0
  5. package/examples/feature/area/area-chart.json +244 -0
  6. package/examples/{example-bar-chart.json → feature/bar/example-bar-chart.json} +4 -1
  7. package/examples/feature/bar/horizontal-chart-max-increase.json +44 -0
  8. package/examples/{horizontal-chart.json → feature/bar/horizontal-chart.json} +10 -4
  9. package/examples/{horizontal-stacked-bar-chart.json → feature/bar/horizontal-stacked-bar-chart.json} +7 -3
  10. package/examples/{planet-chart-horizontal-example-config.json → feature/bar/planet-chart-horizontal-example-config.json} +8 -3
  11. package/examples/feature/bar/planet-example-config.json +156 -0
  12. package/examples/{box-plot.json → feature/boxplot/boxplot.json} +7 -8
  13. package/examples/feature/boxplot/testing.csv +38 -0
  14. package/examples/feature/combo/combochart-categories_are_numbers .json +18 -0
  15. package/examples/{planet-combo-example-config.json → feature/combo/planet-combo-example-config.json} +1 -1
  16. package/examples/feature/deviation/planet-deviation-config.json +168 -0
  17. package/examples/feature/deviation/planet-deviation-data.json +38 -0
  18. package/examples/feature/filters/filter-testing.json +178 -0
  19. package/examples/feature/forecasting/case_date_example.csv +130 -0
  20. package/examples/feature/forecasting/effective_reproduction.json +202 -0
  21. package/examples/feature/forecasting/r_data.csv +130 -0
  22. package/examples/feature/line/line-chart-max-increase.json +32 -0
  23. package/examples/feature/line/line-chart.json +124 -0
  24. package/examples/{paired-bar-example.json → feature/paired-bar/paired-bar-example.json} +10 -4
  25. package/examples/{planet-pie-example-config.json → feature/pie/planet-pie-example-config.json} +2 -2
  26. package/examples/{scatterplot-continuous.csv → feature/scatterplot/scatterplot-continuous.csv} +3 -3
  27. package/examples/{scatterplot.json → feature/scatterplot/scatterplot.json} +3 -3
  28. package/examples/feature/sparkline/example-sparkline.json +76 -0
  29. package/examples/feature/tests-big-small/big-small-test-bar.json +328 -0
  30. package/examples/feature/tests-big-small/big-small-test-line.json +328 -0
  31. package/examples/feature/tests-big-small/big-small-test-negative.json +328 -0
  32. package/examples/{case-rate-example-config.json → feature/tests-case-rate/case-rate-example-config.json} +2 -2
  33. package/examples/{covid-confidence-example-config.json → feature/tests-covid/covid-confidence-example-config.json} +8 -3
  34. package/examples/{covid-example-config.json → feature/tests-covid/covid-example-config.json} +7 -3
  35. package/examples/{cutoff-example-config.json → feature/tests-cutoff/cutoff-example-config.json} +7 -3
  36. package/examples/{date-exclusions-config.json → feature/tests-date-exclusions/date-exclusions-config.json} +2 -2
  37. package/examples/{example-bar-chart-nonnumeric.json → feature/tests-non-numerics/example-bar-chart-nonnumeric.json} +1 -1
  38. package/examples/{line-chart-nonnumeric.json → feature/tests-non-numerics/line-chart-nonnumeric.json} +5 -5
  39. package/examples/{planet-pie-example-config-nonnumeric.json → feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json} +2 -2
  40. package/examples/{sparkline-chart-nonnumeric.json → feature/tests-non-numerics/sparkline-chart-nonnumeric.json} +2 -2
  41. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
  42. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +145 -7
  43. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
  44. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
  45. package/examples/gallery/line/line.json +1 -0
  46. package/examples/gallery/paired-bar/paired-bar-chart.json +1 -0
  47. package/index.html +76 -35
  48. package/package.json +6 -3
  49. package/src/CdcChart.jsx +245 -106
  50. package/src/components/AreaChart.jsx +233 -0
  51. package/src/components/BarChart.jsx +103 -62
  52. package/src/components/BoxPlot.jsx +39 -18
  53. package/src/components/DataTable.jsx +26 -21
  54. package/src/components/DeviationBar.jsx +191 -0
  55. package/src/components/EditorPanel.jsx +662 -298
  56. package/src/components/Legend.jsx +59 -46
  57. package/src/components/LineChart.jsx +12 -36
  58. package/src/components/LinearChart.jsx +163 -64
  59. package/src/components/PairedBarChart.jsx +6 -7
  60. package/src/components/PieChart.jsx +12 -17
  61. package/src/components/ScatterPlot.jsx +19 -16
  62. package/src/components/SparkLine.jsx +84 -118
  63. package/src/components/useIntersectionObserver.jsx +1 -1
  64. package/src/data/initial-state.js +27 -7
  65. package/src/hooks/useColorPalette.js +58 -48
  66. package/src/hooks/useReduceData.js +3 -4
  67. package/src/index.jsx +3 -2
  68. package/src/scss/editor-panel.scss +20 -0
  69. package/src/scss/main.scss +8 -6
  70. package/src/test/CdcChart.test.jsx +6 -0
  71. package/examples/box-plot.csv +0 -5
  72. package/examples/dynamic-legends.json +0 -125
  73. package/examples/line-chart.json +0 -34
  74. package/examples/planet-example-config.json +0 -37
  75. package/examples/temp-example-config.json +0 -64
  76. package/examples/temp-example-data.json +0 -130
  77. package/src/components/Filters.jsx +0 -125
  78. /package/examples/{age-adjusted-rates.json → feature/__data__/age-adjusted-rates.json} +0 -0
  79. /package/examples/{new-data.csv → feature/__data__/new-data.csv} +0 -0
  80. /package/examples/{Barchart_with_negative.json → feature/bar/Barchart_with_negative.json} +0 -0
  81. /package/examples/{stacked-vertical-bar-example-negative.json → feature/bar/stacked-vertical-bar-example-negative.json} +0 -0
  82. /package/examples/{stacked-vertical-bar-example.json → feature/bar/stacked-vertical-bar-example.json} +0 -0
  83. /package/examples/{box-plot-data.json → feature/boxplot/box-plot-data.json} +0 -0
  84. /package/examples/{newdata.json → feature/boxplot/boxplot-data.json} +0 -0
  85. /package/examples/{paired-bar-data.json → feature/paired-bar/paired-bar-data.json} +0 -0
  86. /package/examples/{paired-bar-formatted.json → feature/paired-bar/paired-bar-formatted.json} +0 -0
  87. /package/examples/{case-rate-example-data.json → feature/tests-case-rate/case-rate-example-data.json} +0 -0
  88. /package/examples/{covid-example-data-confidence.json → feature/tests-covid/covid-example-data-confidence.json} +0 -0
  89. /package/examples/{covid-example-data.json → feature/tests-covid/covid-example-data.json} +0 -0
  90. /package/examples/{cutoff-example-data.json → feature/tests-cutoff/cutoff-example-data.json} +0 -0
  91. /package/examples/{date-exclusions-data.json → feature/tests-date-exclusions/date-exclusions-data.json} +0 -0
  92. /package/examples/{example-combo-bar-nonnumeric.json → feature/tests-non-numerics/example-combo-bar-nonnumeric.json} +0 -0
  93. /package/examples/{planet-example-data-nonnumeric.json → feature/tests-non-numerics/planet-example-data-nonnumeric.json} +0 -0
  94. /package/examples/{stacked-vertical-bar-example-nonnumerics.json → feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json} +0 -0
@@ -1,17 +1,24 @@
1
- import React, { useContext } from 'react'
1
+ import React, { useContext, useEffect } from 'react'
2
2
  import { BoxPlot } from '@visx/stats'
3
3
  import { Group } from '@visx/group'
4
- import { scaleBand, scaleLinear } from '@visx/scale'
5
4
  import ConfigContext from '../ConfigContext'
6
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
6
  import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
8
7
 
9
8
  const CoveBoxPlot = ({ xScale, yScale }) => {
10
- const { transformedData: data, config } = useContext(ConfigContext)
9
+ const { config, setConfig } = useContext(ConfigContext)
11
10
 
12
- const boxWidth = xScale.bandwidth()
13
- const constrainedWidth = Math.min(40, boxWidth)
14
- const color_0 = colorPalettesChart[config?.palette][0] ? colorPalettesChart[config?.palette][0] : '#000'
11
+ useEffect(() => {
12
+ if (config.legend.hide === false) {
13
+ setConfig({
14
+ ...config,
15
+ legend: {
16
+ ...config.legend,
17
+ hide: true
18
+ }
19
+ })
20
+ }
21
+ }, []) // eslint-disable-line
15
22
 
16
23
  // tooltips
17
24
  const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
@@ -24,6 +31,18 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
24
31
  ${config.boxplot.labels.median}: ${d.columnMedian}
25
32
  `
26
33
  }
34
+
35
+ // accessors & constants
36
+ const max = d => Number(d.columnMax)
37
+ const min = d => Number(d.columnMin)
38
+ const median = d => Number(d.columnMedian)
39
+ const thirdQuartile = d => Number(d.columnThirdQuartile)
40
+ const firstQuartile = d => Number(d.columnFirstQuartile)
41
+ const fillOpacity = 0.5
42
+ const boxWidth = xScale.bandwidth()
43
+ const constrainedWidth = Math.min(40, boxWidth)
44
+ const color_0 = colorPalettesChart[config?.palette][0] ? colorPalettesChart[config?.palette][0] : '#000'
45
+
27
46
  return (
28
47
  <ErrorBoundary component='BoxPlot'>
29
48
  <Group className='boxplot' key={`boxplot-group`}>
@@ -31,20 +50,23 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
31
50
  const offset = boxWidth - constrainedWidth
32
51
  const radius = 4
33
52
  return (
34
- <>
35
- {config.boxplot.plotNonOutlierValues && d.nonOutlierValues.map(value => <circle cx={xScale(d.columnCategory) + config.yAxis.size + boxWidth / 2} cy={yScale(value)} r={radius} fill={'#ccc'} style={{ opacity: 1, fillOpacity: 1, stroke: 'black' }} />)}
53
+ <Group key={`boxplotplot-${i}`}>
54
+ {config.boxplot.plotNonOutlierValues &&
55
+ d.nonOutlierValues.map((value, index) => {
56
+ return <circle cx={xScale(d.columnCategory) + Number(config.yAxis.size) + boxWidth / 2} cy={yScale(value)} r={radius} fill={'#ccc'} style={{ opacity: 1, fillOpacity: 1, stroke: 'black' }} key={`boxplot-${i}--circle-${index}`} />
57
+ })}
36
58
  <BoxPlot
37
59
  data-left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
38
60
  key={`box-plot-${i}`}
39
- min={d.columnMin}
40
- max={d.columnMax}
41
- left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
42
- firstQuartile={d.columnFirstQuartile}
43
- thirdQuartile={d.columnThirdQuartile}
44
- median={d.columnMedian}
61
+ min={min(d)}
62
+ max={max(d)}
63
+ left={Number(xScale(d.columnCategory)) + Number(config.yAxis.size) + offset / 2 + 0.5}
64
+ firstQuartile={firstQuartile(d)}
65
+ thirdQuartile={thirdQuartile(d)}
66
+ median={median(d)}
45
67
  boxWidth={constrainedWidth}
46
68
  fill={color_0}
47
- fillOpacity={0.5}
69
+ fillOpacity={fillOpacity}
48
70
  stroke='black'
49
71
  valueScale={yScale}
50
72
  outliers={config.boxplot.plotOutlierValues ? d.columnOutliers : []}
@@ -63,8 +85,7 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
63
85
  style: {
64
86
  stroke: 'black',
65
87
  strokeWidth: config.boxplot.borders === 'true' ? 1 : 0
66
- },
67
- 'data-tooltip-html': 'cool'
88
+ }
68
89
  }}
69
90
  maxProps={{
70
91
  style: {
@@ -77,7 +98,7 @@ const CoveBoxPlot = ({ xScale, yScale }) => {
77
98
  'data-tooltip-id': tooltip_id
78
99
  }}
79
100
  />
80
- </>
101
+ </Group>
81
102
  )
82
103
  })}
83
104
  </Group>
@@ -12,10 +12,7 @@ import ConfigContext from '../ConfigContext'
12
12
  import CoveMediaControls from '@cdc/core/components/CoveMediaControls'
13
13
 
14
14
  export default function DataTable() {
15
- const { rawData, transformedData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes, imageId } = useContext(ConfigContext)
16
-
17
- // Debugging.
18
- // if (config.visualizationType === 'Box Plot') return null
15
+ const { rawData, tableData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes } = useContext(ConfigContext)
19
16
 
20
17
  const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
21
18
  const [tableExpanded, setTableExpanded] = useState(config.table.expanded)
@@ -38,7 +35,7 @@ export default function DataTable() {
38
35
  switch (type) {
39
36
  case 'download':
40
37
  return (
41
- <a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`btn btn-download no-border`}>
38
+ <a download={fileName} onClick={saveBlob} href={`data:text/csv;base64,${Base64.encode(csvData)}`} aria-label='Download this data in a CSV file format.' className={`btn btn-download no-border margin-sm`}>
42
39
  Download Data (CSV)
43
40
  </a>
44
41
  )
@@ -57,7 +54,7 @@ export default function DataTable() {
57
54
  config.visualizationType === 'Pie'
58
55
  ? []
59
56
  : config.visualizationType === 'Box Plot'
60
- ? [
57
+ ? [
61
58
  {
62
59
  Header: 'Measures',
63
60
  Cell: props => {
@@ -76,9 +73,11 @@ export default function DataTable() {
76
73
  columnThirdQuartile: labels.q3,
77
74
  columnOutliers: labels.outliers,
78
75
  values: labels.values,
79
- columnCount: labels.count,
76
+ columnTotal: labels.total,
80
77
  columnSd: 'Standard Deviation',
81
- nonOutlierValues: 'Non Outliers'
78
+ nonOutlierValues: 'Non Outliers',
79
+ columnLowerBounds: labels.lowerBounds,
80
+ columnUpperBounds: labels.upperBounds
82
81
  }
83
82
 
84
83
  let resolvedName = columnLookup[props.row.original[0]]
@@ -90,7 +89,7 @@ export default function DataTable() {
90
89
  }
91
90
  }
92
91
  ]
93
- : [
92
+ : [
94
93
  {
95
94
  Header: '',
96
95
  Cell: ({ row }) => {
@@ -105,8 +104,8 @@ export default function DataTable() {
105
104
  ? colorScale(seriesLabel)
106
105
  : // dynamic legend
107
106
  config.legend.dynamicLegend
108
- ? colorPalettes[config.palette][row.index]
109
- : // fallback
107
+ ? colorPalettes[config.palette][row.index]
108
+ : // fallback
110
109
  '#000'
111
110
  }
112
111
  />
@@ -122,12 +121,13 @@ export default function DataTable() {
122
121
  data.forEach((d, index) => {
123
122
  const resolveTableHeader = () => {
124
123
  if (config.runtime[section].type === 'date') return formatDate(parseDate(d[config.runtime.originalXAxis.dataKey]))
124
+ if (config.runtime[section].type === 'continuous') return numberFormatter(d[config.runtime.originalXAxis.dataKey], 'bottom')
125
125
  return d[config.runtime.originalXAxis.dataKey]
126
126
  }
127
127
  const newCol = {
128
128
  Header: resolveTableHeader(),
129
129
  Cell: ({ row }) => {
130
- return <>{numberFormatter(d[row.original])}</>
130
+ return <>{numberFormatter(d[row.original], 'left')}</>
131
131
  },
132
132
  id: `${d[config.runtime.originalXAxis.dataKey]}--${index}`,
133
133
  canSort: true
@@ -149,7 +149,7 @@ export default function DataTable() {
149
149
  if (Number(props.row.id) === 3) return plot.columnMedian
150
150
  if (Number(props.row.id) === 4) return plot.columnFirstQuartile
151
151
  if (Number(props.row.id) === 5) return plot.columnMin
152
- if (Number(props.row.id) === 6) return plot.columnCount
152
+ if (Number(props.row.id) === 6) return plot.columnTotal
153
153
  if (Number(props.row.id) === 7) return plot.columnSd
154
154
  if (Number(props.row.id) === 8) return plot.columnMean
155
155
  if (Number(props.row.id) === 9) return plot.columnOutliers.length > 0 ? plot.columnOutliers.toString() : '-'
@@ -162,12 +162,12 @@ export default function DataTable() {
162
162
  canSort: false
163
163
  }
164
164
 
165
- newTableColumns.push(newCol)
165
+ return newTableColumns.push(newCol)
166
166
  })
167
167
  }
168
168
 
169
169
  return newTableColumns
170
- }, [config, colorScale])
170
+ }, [config, colorScale]) // eslint-disable-line
171
171
 
172
172
  // prettier-ignore
173
173
  const tableData = useMemo(() => (
@@ -176,7 +176,7 @@ export default function DataTable() {
176
176
  : config.visualizationType === 'Box Plot'
177
177
  ? Object.entries(config.boxplot.tableData[0])
178
178
  : config.runtime.seriesKeys),
179
- [config.runtime.seriesKeys])
179
+ [config.runtime.seriesKeys]) // eslint-disable-line
180
180
 
181
181
  // Change accessibility label depending on expanded status
182
182
  useEffect(() => {
@@ -201,8 +201,13 @@ export default function DataTable() {
201
201
  }),
202
202
  []
203
203
  )
204
-
205
204
  const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({ columns: tableColumns, data: tableData, defaultColumn }, useSortBy, useBlockLayout, useResizeColumns)
205
+
206
+ // sort continuous x axis scaling for data tables, ie. xAxis should read 1,2,3,4,5
207
+ if (config.xAxis.type === 'continuous' && headerGroups) {
208
+ data.sort((a, b) => a[config.xAxis.dataKey] - b[config.xAxis.dataKey])
209
+ }
210
+
206
211
  return (
207
212
  <ErrorBoundary component='DataTable'>
208
213
  <CoveMediaControls.Section classes={['download-links']}>
@@ -224,13 +229,12 @@ export default function DataTable() {
224
229
  }
225
230
  }}
226
231
  >
227
- <Icon display={tableExpanded ? 'minus' : 'plus'} base/>
232
+ <Icon display={tableExpanded ? 'minus' : 'plus'} base />
228
233
  {config.table.label}
229
234
  </div>
230
235
  <div className='table-container' hidden={!tableExpanded} style={{ maxHeight: config.table.limitHeight && `${config.table.height}px`, overflowY: 'scroll' }}>
231
236
  <table className={tableExpanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} {...getTableProps()} aria-rowcount={config?.series?.length ? config?.series?.length : '-1'}>
232
- <caption className='cdcdataviz-sr-only'>{config.table.caption ? config.table.caption : ''}</caption>
233
- <caption className='visually-hidden'>{config.table.label}</caption>
237
+ <caption className='cdcdataviz-sr-only visually-hidden'>{config.table.caption ? config.table.caption : config.table.label ? config.table.label : 'Data Table'}</caption>
234
238
  <thead>
235
239
  {headerGroups.map((headerGroup, index) => (
236
240
  <tr {...headerGroup.getHeaderGroupProps()} key={`headerGroups--${index}`}>
@@ -272,7 +276,7 @@ export default function DataTable() {
272
276
  })}
273
277
  </tbody>
274
278
  </table>
275
- {config.regions && config.regions.length > 0 ? (
279
+ {config.regions && config.regions.length > 0 && config.visualizationType !== 'Box Plot' ? (
276
280
  <table className='region-table data-table'>
277
281
  <caption className='visually-hidden'>Table of the highlighted regions in the visualization</caption>
278
282
  <thead>
@@ -284,6 +288,7 @@ export default function DataTable() {
284
288
  </thead>
285
289
  <tbody>
286
290
  {config.regions.map((region, index) => {
291
+ if (config.visualizationType === 'Box Plot') return false
287
292
  if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
288
293
 
289
294
  return (
@@ -0,0 +1,191 @@
1
+ import { Line } from '@visx/shape'
2
+ import { Group } from '@visx/group'
3
+ import { useContext, useEffect } from 'react'
4
+ import ConfigContext from '../ConfigContext'
5
+ import { Text } from '@visx/text'
6
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
+ import chroma from 'chroma-js'
8
+
9
+ // create function to position text based where bar is located left/or right
10
+ function getTextProps(isLollipopChart, textFits, lollipopShapeSize, fill) {
11
+ if (isLollipopChart) {
12
+ return {
13
+ right: {
14
+ textAnchor: 'start',
15
+ dx: lollipopShapeSize + 6,
16
+ fill: '#000000'
17
+ },
18
+ left: {
19
+ textAnchor: 'end',
20
+ dx: -lollipopShapeSize,
21
+ fill: '#000000'
22
+ }
23
+ }
24
+ } else {
25
+ return {
26
+ right: {
27
+ textAnchor: textFits ? 'end' : 'start',
28
+ dx: textFits ? -6 : 6,
29
+ fill: textFits ? fill : '#000000'
30
+ },
31
+ left: {
32
+ textAnchor: textFits ? 'start' : 'end',
33
+ dx: textFits ? 6 : -6,
34
+ fill: textFits ? fill : '#000000'
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ export function DeviationBar({ height, xScale }) {
41
+ const { transformedData: data, config, formatNumber, twoColorPalette, getTextWidth, updateConfig, parseDate, formatDate } = useContext(ConfigContext)
42
+
43
+ if (!config || config?.series?.length !== 1 || config.orientation !== 'horizontal') return
44
+
45
+ const { barStyle, tipRounding, roundingStyle, twoColor } = config
46
+
47
+ const radius = roundingStyle === 'standard' ? '8px' : roundingStyle === 'shallow' ? '5px' : roundingStyle === 'finger' ? '15px' : '0px'
48
+ const fontSize = { small: 16, medium: 18, large: 20 }
49
+ const isRounded = config.barStyle === 'rounded'
50
+ const target = Number(config.xAxis.target)
51
+ const seriesKey = config.series[0].dataKey
52
+ const maxVal = Number(xScale.domain()[1])
53
+ const hasNegativeValues = data.some(d => d[seriesKey] < 0)
54
+ const shouldShowTargetLine = hasNegativeValues || target > 0 || xScale.domain()[0] < 0
55
+ const borderWidth = config.barHasBorder === 'true' ? 1 : 0
56
+ const lollipopBarHeight = config.lollipopSize === 'large' ? 7 : config.lollipopSize === 'medium' ? 6 : 5
57
+ const lollipopShapeSize = config.lollipopSize === 'large' ? 14 : config.lollipopSize === 'medium' ? 12 : 10
58
+
59
+ const targetX = Math.max(xScale(0), Math.min(xScale(target), xScale(maxVal)))
60
+
61
+ const applyRadius = barPosition => {
62
+ if (barPosition === undefined || barPosition === null || barStyle !== 'rounded') return
63
+ let style = {}
64
+ if (barPosition === 'left') {
65
+ style = { borderRadius: `${radius} 0 0 ${radius}` }
66
+ }
67
+
68
+ if (barPosition === 'right') {
69
+ style = { borderRadius: `0 ${radius} ${radius} 0` }
70
+ }
71
+ if (tipRounding === 'full') {
72
+ style = { borderRadius: radius }
73
+ }
74
+
75
+ return style
76
+ }
77
+
78
+ const targetLabel = {
79
+ calculate: function () {
80
+ const firstBarValue = data[0][seriesKey]
81
+ const barPosition = firstBarValue < target ? 'left' : 'right'
82
+ const label = `${config.xAxis.targetLabel} ${formatNumber(config.xAxis.target || 0, 'left')}`
83
+ const labelWidth = getTextWidth(label, `bold ${fontSize[config.fontSize]}px sans-serif`)
84
+ let labelY = config.isLollipopChart ? lollipopBarHeight / 2 : Number(config.barHeight) / 2
85
+ let paddingX = 0
86
+ let labelX = 0
87
+ let showLabel = false
88
+
89
+ if (barPosition === 'right') {
90
+ paddingX = -10
91
+ showLabel = labelWidth - paddingX < targetX
92
+ labelX = targetX - labelWidth
93
+ }
94
+
95
+ if (barPosition === 'left') {
96
+ paddingX = 10
97
+ showLabel = xScale(maxVal) - targetX > labelWidth + paddingX
98
+ labelX = targetX
99
+ }
100
+
101
+ this.text = label
102
+ this.y = labelY
103
+ this.x = labelX
104
+ this.padding = paddingX
105
+ this.showLabel = config.xAxis.showTargetLabel ? showLabel : false
106
+ }
107
+ }
108
+ targetLabel.calculate()
109
+
110
+ useEffect(() => {
111
+ if (config.barStyle === 'lollipop' && !config.isLollipopChart) {
112
+ updateConfig({ ...config, isLollipopChart: true })
113
+ }
114
+ if (isRounded || config.barStyle === 'flat') {
115
+ updateConfig({ ...config, isLollipopChart: false })
116
+ }
117
+ }, [config.barStyle])
118
+
119
+ return (
120
+ <ErrorBoundary component='Deviation Bar'>
121
+ <Group left={Number(config.xAxis.size)}>
122
+ {data.map((d, index) => {
123
+ const barValue = Number(d[seriesKey])
124
+ const barHeight = config.isLollipopChart ? lollipopBarHeight : Number(config.barHeight)
125
+ const barSpace = Number(config.barSpace)
126
+ const barWidth = Math.abs(xScale(barValue) - targetX)
127
+ const barBaseX = xScale(barValue)
128
+ const barX = barValue > target ? targetX : barBaseX
129
+ const barPosition = barValue < target ? 'left' : 'right'
130
+
131
+ // update bar Y to give dynamic Y when user applyes BarSpace
132
+ let barY = 0
133
+ barY = index !== 0 ? (barSpace + barHeight + borderWidth) * index : barY
134
+ const totalheight = (barSpace + barHeight + borderWidth) * data.length
135
+ config.heights.horizontal = totalheight
136
+
137
+ // text,labels postiions
138
+ const textWidth = getTextWidth(formatNumber(barValue, 'left'), `normal ${fontSize[config.fontSize]}px sans-serif`)
139
+ const textFits = textWidth < barWidth - 6
140
+ const textX = barBaseX
141
+ const textY = barY + barHeight / 2
142
+
143
+ // lollipop shapes
144
+ const circleX = barBaseX
145
+ const circleY = barY + barHeight / 2
146
+ const squareX = barBaseX
147
+ const squareY = barY - barHeight / 2
148
+ const borderRadius = applyRadius(barPosition)
149
+ // colors
150
+ const [leftColor, rightColor] = twoColorPalette[twoColor.palette]
151
+ const barColor = { left: leftColor, right: rightColor }
152
+ const isBarColorDark = chroma.contrast('#000000', barColor[barPosition]) < 4.9
153
+ const fill = isBarColorDark ? '#FFFFFF' : '#000000'
154
+
155
+ let textProps = getTextProps(config.isLollipopChart, textFits, lollipopShapeSize, fill)
156
+
157
+ // tooltips
158
+ const xAxisValue = formatNumber(barValue, 'left')
159
+ const yAxisValue = config.runtime.yAxis.type === 'date' ? formatDate(parseDate(data[index][config.runtime.originalXAxis.dataKey])) : data[index][config.runtime.originalXAxis.dataKey]
160
+ let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
161
+ let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
162
+ const tooltip = `<div>
163
+ ${yAxisTooltip}<br />
164
+ ${xAxisTooltip}
165
+ </div>`
166
+
167
+ return (
168
+ <Group key={`deviation-bar-${config.orientation}-${seriesKey}-${index}`}>
169
+ <foreignObject x={barX} y={barY} width={barWidth} height={barHeight} style={{ border: `${borderWidth}px solid #333`, backgroundColor: barColor[barPosition], ...borderRadius }} data-tooltip-html={tooltip} data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} />
170
+ {config.yAxis.displayNumbersOnBar && (
171
+ <Text verticalAnchor='middle' x={textX} y={textY} {...textProps[barPosition]}>
172
+ {formatNumber(d[seriesKey], 'left')}
173
+ </Text>
174
+ )}
175
+
176
+ {config.isLollipopChart && config.lollipopShape === 'circle' && <circle cx={circleX} cy={circleY} r={lollipopShapeSize / 2} fill={barColor[barPosition]} style={{ filter: 'unset', opacity: 1 }} />}
177
+ {config.isLollipopChart && config.lollipopShape === 'square' && <rect x={squareX} y={squareY} width={lollipopShapeSize} height={lollipopShapeSize} fill={barColor[barPosition]} style={{ opacity: 1, filter: 'unset' }}></rect>}
178
+ </Group>
179
+ )
180
+ })}
181
+ {targetLabel.showLabel && (
182
+ <Text fontWeight='bold' dx={targetLabel.padding} verticalAnchor='middle' x={targetLabel.x} y={targetLabel.y}>
183
+ {targetLabel.text}
184
+ </Text>
185
+ )}
186
+
187
+ {shouldShowTargetLine && <Line from={{ x: targetX, y: 0 }} to={{ x: targetX, y: height }} stroke='#333' strokeWidth={2} />}
188
+ </Group>
189
+ </ErrorBoundary>
190
+ )
191
+ }