@cdc/chart 4.23.1 → 4.23.3
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.
- package/dist/cdcchart.js +56289 -702
- package/examples/Barchart_with_negative.json +34 -0
- package/examples/area-chart.json +187 -0
- package/examples/big-small-test-bar.json +328 -0
- package/examples/big-small-test-line.json +328 -0
- package/examples/big-small-test-negative.json +328 -0
- package/examples/box-plot.json +1 -2
- package/examples/dynamic-legends.json +1 -1
- package/examples/example-bar-chart-nonnumeric.json +36 -0
- package/examples/example-bar-chart.json +36 -0
- package/examples/example-combo-bar-nonnumeric.json +105 -0
- package/examples/example-sparkline.json +76 -0
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +2 -2
- package/examples/gallery/line/line.json +1 -0
- package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
- package/examples/horizontal-chart-max-increase.json +38 -0
- package/examples/line-chart-max-increase.json +32 -0
- package/examples/line-chart-nonnumeric.json +32 -0
- package/examples/line-chart.json +21 -63
- package/examples/newdata.json +1 -1
- package/examples/planet-combo-example-config.json +143 -20
- package/examples/planet-deviation-config.json +168 -0
- package/examples/planet-deviation-data.json +38 -0
- package/examples/planet-example-config.json +139 -20
- package/examples/planet-example-data-max-increase.json +56 -0
- package/examples/planet-example-data-nonnumeric.json +56 -0
- package/examples/planet-example-data.json +9 -9
- package/examples/planet-pie-example-config-nonnumeric.json +30 -0
- package/examples/scatterplot-continuous.csv +17 -0
- package/examples/scatterplot.json +136 -0
- package/examples/sparkline-chart-nonnumeric.json +76 -0
- package/examples/stacked-vertical-bar-example-negative.json +154 -0
- package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
- package/index.html +91 -0
- package/package.json +33 -24
- package/src/{CdcChart.tsx → CdcChart.jsx} +196 -124
- package/src/components/AreaChart.jsx +198 -0
- package/src/components/{BarChart.tsx → BarChart.jsx} +154 -122
- package/src/components/BoxPlot.jsx +101 -0
- package/src/components/{DataTable.tsx → DataTable.jsx} +109 -28
- package/src/components/DeviationBar.jsx +191 -0
- package/src/components/{EditorPanel.js → EditorPanel.jsx} +676 -157
- package/src/components/{Filters.js → Filters.jsx} +6 -11
- package/src/components/Legend.jsx +316 -0
- package/src/components/{LineChart.tsx → LineChart.jsx} +22 -26
- package/src/components/{LinearChart.tsx → LinearChart.jsx} +214 -91
- package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +44 -78
- package/src/components/{PieChart.tsx → PieChart.jsx} +26 -44
- package/src/components/ScatterPlot.jsx +51 -0
- package/src/components/SparkLine.jsx +218 -0
- package/src/components/{useIntersectionObserver.tsx → useIntersectionObserver.jsx} +2 -2
- package/src/data/initial-state.js +51 -5
- package/src/hooks/useColorPalette.js +68 -0
- package/src/hooks/{useReduceData.ts → useReduceData.js} +26 -16
- package/src/hooks/useRightAxis.js +3 -1
- package/src/index.jsx +16 -0
- package/src/scss/DataTable.scss +22 -0
- package/src/scss/editor-panel.scss +5 -0
- package/src/scss/main.scss +30 -10
- package/src/test/CdcChart.test.jsx +6 -0
- package/vite.config.js +4 -0
- package/dist/495.js +0 -3
- package/dist/703.js +0 -1
- package/src/components/BoxPlot.js +0 -92
- package/src/components/Legend.js +0 -291
- package/src/components/SparkLine.js +0 -185
- package/src/hooks/useColorPalette.ts +0 -76
- package/src/index.html +0 -67
- package/src/index.tsx +0 -18
- /package/src/{context.tsx → ConfigContext.jsx} +0 -0
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useState, useMemo
|
|
1
|
+
import React, { useContext, useEffect, useState, useMemo } from 'react'
|
|
2
2
|
import { useTable, useSortBy, useResizeColumns, useBlockLayout } from 'react-table'
|
|
3
3
|
import Papa from 'papaparse'
|
|
4
4
|
import { Base64 } from 'js-base64'
|
|
5
5
|
|
|
6
6
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
7
|
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
8
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
8
9
|
|
|
9
|
-
import
|
|
10
|
+
import ConfigContext from '../ConfigContext'
|
|
10
11
|
|
|
11
|
-
import CoveMediaControls from '@cdc/core/
|
|
12
|
+
import CoveMediaControls from '@cdc/core/components/CoveMediaControls'
|
|
12
13
|
|
|
13
14
|
export default function DataTable() {
|
|
14
|
-
const { rawData, transformedData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes
|
|
15
|
-
|
|
16
|
-
// Debugging.
|
|
17
|
-
// if (config.visualizationType === 'Box Plot') return null
|
|
15
|
+
const { rawData, transformedData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes } = useContext(ConfigContext)
|
|
18
16
|
|
|
19
17
|
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
20
|
-
const [tableExpanded, setTableExpanded] = useState
|
|
18
|
+
const [tableExpanded, setTableExpanded] = useState(config.table.expanded)
|
|
21
19
|
const [accessibilityLabel, setAccessibilityLabel] = useState('')
|
|
22
20
|
|
|
23
|
-
const DownloadButton = ({ data }
|
|
21
|
+
const DownloadButton = ({ data }, type) => {
|
|
24
22
|
const fileName = `${config.title.substring(0, 50)}.csv`
|
|
25
23
|
|
|
26
24
|
const csvData = Papa.unparse(data)
|
|
@@ -55,17 +53,51 @@ export default function DataTable() {
|
|
|
55
53
|
const newTableColumns =
|
|
56
54
|
config.visualizationType === 'Pie'
|
|
57
55
|
? []
|
|
56
|
+
: config.visualizationType === 'Box Plot'
|
|
57
|
+
? [
|
|
58
|
+
{
|
|
59
|
+
Header: 'Measures',
|
|
60
|
+
Cell: props => {
|
|
61
|
+
const resolveName = () => {
|
|
62
|
+
let {
|
|
63
|
+
boxplot: { labels }
|
|
64
|
+
} = config
|
|
65
|
+
const columnLookup = {
|
|
66
|
+
columnMean: labels.mean,
|
|
67
|
+
columnMax: labels.maximum,
|
|
68
|
+
columnMin: labels.minimum,
|
|
69
|
+
columnIqr: labels.iqr,
|
|
70
|
+
columnCategory: 'Category',
|
|
71
|
+
columnMedian: labels.median,
|
|
72
|
+
columnFirstQuartile: labels.q1,
|
|
73
|
+
columnThirdQuartile: labels.q3,
|
|
74
|
+
columnOutliers: labels.outliers,
|
|
75
|
+
values: labels.values,
|
|
76
|
+
columnTotal: labels.total,
|
|
77
|
+
columnSd: 'Standard Deviation',
|
|
78
|
+
nonOutlierValues: 'Non Outliers'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let resolvedName = columnLookup[props.row.original[0]]
|
|
82
|
+
|
|
83
|
+
return resolvedName
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return resolveName()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
]
|
|
58
90
|
: [
|
|
59
91
|
{
|
|
60
92
|
Header: '',
|
|
61
93
|
Cell: ({ row }) => {
|
|
62
94
|
const seriesLabel = config.runtime.seriesLabels ? config.runtime.seriesLabels[row.original] : row.original
|
|
63
95
|
return (
|
|
64
|
-
|
|
96
|
+
<>
|
|
65
97
|
{config.visualizationType !== 'Pie' && (
|
|
66
98
|
<LegendCircle
|
|
67
99
|
fill={
|
|
68
|
-
// non
|
|
100
|
+
// non-dynamic leged
|
|
69
101
|
!config.legend.dynamicLegend
|
|
70
102
|
? colorScale(seriesLabel)
|
|
71
103
|
: // dynamic legend
|
|
@@ -77,30 +109,72 @@ export default function DataTable() {
|
|
|
77
109
|
/>
|
|
78
110
|
)}
|
|
79
111
|
<span>{seriesLabel}</span>
|
|
80
|
-
|
|
112
|
+
</>
|
|
81
113
|
)
|
|
82
114
|
},
|
|
83
115
|
id: 'series-label'
|
|
84
116
|
}
|
|
85
117
|
]
|
|
118
|
+
if (config.visualizationType !== 'Box Plot') {
|
|
119
|
+
data.forEach((d, index) => {
|
|
120
|
+
const resolveTableHeader = () => {
|
|
121
|
+
if (config.runtime[section].type === 'date') return formatDate(parseDate(d[config.runtime.originalXAxis.dataKey]))
|
|
122
|
+
if (config.runtime[section].type === 'continuous') return numberFormatter(d[config.runtime.originalXAxis.dataKey], 'bottom')
|
|
123
|
+
return d[config.runtime.originalXAxis.dataKey]
|
|
124
|
+
}
|
|
125
|
+
const newCol = {
|
|
126
|
+
Header: resolveTableHeader(),
|
|
127
|
+
Cell: ({ row }) => {
|
|
128
|
+
return <>{numberFormatter(d[row.original], 'left')}</>
|
|
129
|
+
},
|
|
130
|
+
id: `${d[config.runtime.originalXAxis.dataKey]}--${index}`,
|
|
131
|
+
canSort: true
|
|
132
|
+
}
|
|
86
133
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
134
|
+
newTableColumns.push(newCol)
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (config.visualizationType === 'Box Plot') {
|
|
139
|
+
config.boxplot.tableData.map((plot, index) => {
|
|
140
|
+
const newCol = {
|
|
141
|
+
Header: plot.columnCategory,
|
|
142
|
+
Cell: props => {
|
|
143
|
+
let resolveCell = () => {
|
|
144
|
+
if (Number(props.row.id) === 0) return true
|
|
145
|
+
if (Number(props.row.id) === 1) return plot.columnMax
|
|
146
|
+
if (Number(props.row.id) === 2) return plot.columnThirdQuartile
|
|
147
|
+
if (Number(props.row.id) === 3) return plot.columnMedian
|
|
148
|
+
if (Number(props.row.id) === 4) return plot.columnFirstQuartile
|
|
149
|
+
if (Number(props.row.id) === 5) return plot.columnMin
|
|
150
|
+
if (Number(props.row.id) === 6) return plot.columnTotal
|
|
151
|
+
if (Number(props.row.id) === 7) return plot.columnSd
|
|
152
|
+
if (Number(props.row.id) === 8) return plot.columnMean
|
|
153
|
+
if (Number(props.row.id) === 9) return plot.columnOutliers.length > 0 ? plot.columnOutliers.toString() : '-'
|
|
154
|
+
if (Number(props.row.id) === 10) return plot.values.length > 0 ? plot.values.toString() : '-'
|
|
155
|
+
return <p>-</p>
|
|
156
|
+
}
|
|
157
|
+
return resolveCell()
|
|
158
|
+
},
|
|
159
|
+
id: `${index}`,
|
|
160
|
+
canSort: false
|
|
161
|
+
}
|
|
96
162
|
|
|
97
|
-
|
|
98
|
-
|
|
163
|
+
return newTableColumns.push(newCol)
|
|
164
|
+
})
|
|
165
|
+
}
|
|
99
166
|
|
|
100
167
|
return newTableColumns
|
|
101
|
-
}, [config, colorScale])
|
|
168
|
+
}, [config, colorScale]) // eslint-disable-line
|
|
102
169
|
|
|
103
|
-
|
|
170
|
+
// prettier-ignore
|
|
171
|
+
const tableData = useMemo(() => (
|
|
172
|
+
config.visualizationType === 'Pie'
|
|
173
|
+
? [config.yAxis.dataKey]
|
|
174
|
+
: config.visualizationType === 'Box Plot'
|
|
175
|
+
? Object.entries(config.boxplot.tableData[0])
|
|
176
|
+
: config.runtime.seriesKeys),
|
|
177
|
+
[config.runtime.seriesKeys]) // eslint-disable-line
|
|
104
178
|
|
|
105
179
|
// Change accessibility label depending on expanded status
|
|
106
180
|
useEffect(() => {
|
|
@@ -125,8 +199,13 @@ export default function DataTable() {
|
|
|
125
199
|
}),
|
|
126
200
|
[]
|
|
127
201
|
)
|
|
128
|
-
|
|
129
202
|
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({ columns: tableColumns, data: tableData, defaultColumn }, useSortBy, useBlockLayout, useResizeColumns)
|
|
203
|
+
|
|
204
|
+
// sort continuous x axis scaling for data tables, ie. xAxis should read 1,2,3,4,5
|
|
205
|
+
if (config.xAxis.type === 'continuous' && headerGroups) {
|
|
206
|
+
data.sort((a, b) => a[config.xAxis.dataKey] - b[config.xAxis.dataKey])
|
|
207
|
+
}
|
|
208
|
+
|
|
130
209
|
return (
|
|
131
210
|
<ErrorBoundary component='DataTable'>
|
|
132
211
|
<CoveMediaControls.Section classes={['download-links']}>
|
|
@@ -148,6 +227,7 @@ export default function DataTable() {
|
|
|
148
227
|
}
|
|
149
228
|
}}
|
|
150
229
|
>
|
|
230
|
+
<Icon display={tableExpanded ? 'minus' : 'plus'} base />
|
|
151
231
|
{config.table.label}
|
|
152
232
|
</div>
|
|
153
233
|
<div className='table-container' hidden={!tableExpanded} style={{ maxHeight: config.table.limitHeight && `${config.table.height}px`, overflowY: 'scroll' }}>
|
|
@@ -182,7 +262,7 @@ export default function DataTable() {
|
|
|
182
262
|
{rows.map((row, index) => {
|
|
183
263
|
prepareRow(row)
|
|
184
264
|
return (
|
|
185
|
-
<tr {...row.getRowProps()} key={`tbody__tr-${index}`}>
|
|
265
|
+
<tr {...row.getRowProps()} key={`tbody__tr-${index}`} className={`row-${String(config.visualizationType).replace(' ', '-')}--${index}`}>
|
|
186
266
|
{row.cells.map((cell, index) => {
|
|
187
267
|
return (
|
|
188
268
|
<td tabIndex='0' {...cell.getCellProps()} key={`tbody__tr__td-${index}`} role='gridcell'>
|
|
@@ -195,7 +275,7 @@ export default function DataTable() {
|
|
|
195
275
|
})}
|
|
196
276
|
</tbody>
|
|
197
277
|
</table>
|
|
198
|
-
{config.regions && config.regions.length > 0 ? (
|
|
278
|
+
{config.regions && config.regions.length > 0 && !config.visualizationType === 'Box Plot' ? (
|
|
199
279
|
<table className='region-table data-table'>
|
|
200
280
|
<caption className='visually-hidden'>Table of the highlighted regions in the visualization</caption>
|
|
201
281
|
<thead>
|
|
@@ -207,6 +287,7 @@ export default function DataTable() {
|
|
|
207
287
|
</thead>
|
|
208
288
|
<tbody>
|
|
209
289
|
{config.regions.map((region, index) => {
|
|
290
|
+
if (config.visualizationType === 'Box Plot') return false
|
|
210
291
|
if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
|
|
211
292
|
|
|
212
293
|
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 * 1.05)))
|
|
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) || 15
|
|
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
|
+
}
|