@cdc/chart 4.24.5 → 4.24.9

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 (87) hide show
  1. package/dist/cdcchart.js +44197 -38258
  2. package/examples/cases-year.json +13379 -0
  3. package/examples/feature/annotations/index.json +542 -0
  4. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
  5. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
  6. package/examples/xaxis.json +493 -0
  7. package/index.html +20 -10
  8. package/package.json +5 -4
  9. package/src/CdcChart.tsx +462 -172
  10. package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
  11. package/src/_stories/Chart.stories.tsx +18 -171
  12. package/src/_stories/ChartAnnotation.stories.tsx +32 -0
  13. package/src/_stories/_mock/annotation_category_mock.json +473 -0
  14. package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
  15. package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
  16. package/src/_stories/_mock/legend.gradient_mock.json +236 -0
  17. package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
  18. package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
  19. package/src/_stories/_mock/lollipop.json +171 -0
  20. package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
  21. package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
  22. package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
  23. package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
  24. package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
  25. package/src/components/Annotations/components/AnnotationList.tsx +42 -0
  26. package/src/components/Annotations/components/findNearestDatum.ts +138 -0
  27. package/src/components/Annotations/components/helpers/index.tsx +46 -0
  28. package/src/components/Annotations/index.tsx +13 -0
  29. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  30. package/src/components/AreaChart/components/AreaChart.jsx +1 -1
  31. package/src/components/Axis/Categorical.Axis.tsx +145 -0
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
  33. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
  34. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
  35. package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
  36. package/src/components/BarChart/helpers/index.ts +91 -0
  37. package/src/components/BrushChart.tsx +205 -0
  38. package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
  39. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
  40. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
  41. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
  42. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
  43. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
  44. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  45. package/src/components/EditorPanel/components/panels.scss +4 -0
  46. package/src/components/EditorPanel/editor-panel.scss +35 -3
  47. package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
  48. package/src/components/Legend/Legend.Component.tsx +185 -194
  49. package/src/components/Legend/Legend.Suppression.tsx +146 -0
  50. package/src/components/Legend/Legend.tsx +21 -5
  51. package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
  52. package/src/components/Legend/helpers/index.ts +35 -0
  53. package/src/components/LegendWrapper.tsx +26 -0
  54. package/src/components/LineChart/LineChartProps.ts +1 -15
  55. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
  56. package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
  57. package/src/components/LineChart/helpers.ts +72 -14
  58. package/src/components/LineChart/index.tsx +117 -42
  59. package/src/components/LinearChart.jsx +179 -136
  60. package/src/components/LinearChart.tsx +1366 -0
  61. package/src/components/PairedBarChart.jsx +9 -9
  62. package/src/components/PieChart/PieChart.tsx +75 -18
  63. package/src/components/Sankey/index.tsx +89 -30
  64. package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
  65. package/src/components/Sparkline/components/SparkLine.tsx +2 -2
  66. package/src/components/ZoomBrush.tsx +90 -44
  67. package/src/data/initial-state.js +25 -7
  68. package/src/helpers/handleChartTabbing.ts +8 -0
  69. package/src/helpers/isConvertLineToBarGraph.ts +4 -0
  70. package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
  71. package/src/hooks/useColorScale.ts +1 -1
  72. package/src/hooks/useLegendClasses.ts +68 -0
  73. package/src/hooks/useMinMax.ts +12 -7
  74. package/src/hooks/useScales.ts +58 -26
  75. package/src/hooks/useTooltip.tsx +135 -25
  76. package/src/scss/DataTable.scss +2 -1
  77. package/src/scss/main.scss +128 -28
  78. package/src/types/ChartConfig.ts +83 -10
  79. package/src/types/ChartContext.ts +14 -4
  80. package/tests-examples/helpers/testZeroValue.test.ts +30 -0
  81. package/LICENSE +0 -201
  82. package/src/components/BrushHandle.jsx +0 -17
  83. package/src/components/LineChart/index.scss +0 -1
  84. package/src/helpers/filterData.ts +0 -18
  85. package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
  86. package/src/hooks/useLegendClasses.js +0 -31
  87. /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
@@ -0,0 +1,72 @@
1
+ import React, { useContext, useState } from 'react'
2
+ import ConfigContext from '../../../ConfigContext'
3
+ import './AnnotationDropdown.styles.css'
4
+ import Icon from '@cdc/core/components/ui/Icon'
5
+ import Annotation from '..'
6
+ import { fontSizes } from '@cdc/core/helpers/cove/fontSettings'
7
+
8
+ const AnnotationDropdown = () => {
9
+ const { currentViewport: viewport, config } = useContext(ConfigContext)
10
+ const [expanded, setExpanded] = useState(false)
11
+
12
+ const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${fontSizes[config?.fontSize]}px`
13
+
14
+ const {
15
+ config: { annotations }
16
+ } = useContext(ConfigContext)
17
+
18
+ const limitHeight = {
19
+ maxHeight: config.table.limitHeight && `${config.table.height}px`,
20
+ OverflowY: 'scroll'
21
+ }
22
+
23
+ const handleAccordionClassName = () => {
24
+ const classNames = ['data-table-heading', 'annotation__dropdown-list']
25
+ if (!expanded) {
26
+ classNames.push('collapsed')
27
+ }
28
+
29
+ return classNames.join(' ')
30
+ }
31
+
32
+ const handleSectionClasses = () => {
33
+ const classes = [`data-table-container`, viewport, `d-block`, `d-lg-none`]
34
+
35
+ if (config.general.showAnnotationDropdown) {
36
+ classes.push('d-lg-block')
37
+ classes.splice(classes.indexOf('d-lg-none'), 1)
38
+ }
39
+ return classes.join(' ')
40
+ }
41
+
42
+ return (
43
+ <>
44
+ <section className={handleSectionClasses()}>
45
+ <div
46
+ style={{ fontSize: titleFontSize }}
47
+ role='button'
48
+ className={handleAccordionClassName()}
49
+ onClick={() => {
50
+ setExpanded(!expanded)
51
+ }}
52
+ tabIndex={0}
53
+ onKeyDown={e => {
54
+ if (e.keyCode === 13) {
55
+ setExpanded(!expanded)
56
+ }
57
+ }}
58
+ >
59
+ <Icon display={expanded ? 'minus' : 'plus'} base />
60
+ {config.general.annotationDropdownText === '' ? 'Annotations' : config?.general?.annotationDropdownText}
61
+ </div>
62
+ {expanded && (
63
+ <div className='table-container annotation-dropdown__panel' style={limitHeight}>
64
+ <Annotation.List useBootstrapVisibilityClasses={false} />
65
+ </div>
66
+ )}
67
+ </section>
68
+ </>
69
+ )
70
+ }
71
+
72
+ export default AnnotationDropdown
@@ -0,0 +1,45 @@
1
+ .cdc-open-viz-module {
2
+ .annotation__title-circle {
3
+ display: flex;
4
+ justify-content: center;
5
+ align-items: center;
6
+ border-radius: 50%;
7
+ padding: 8px;
8
+ width: 16px;
9
+ height: 16px;
10
+ margin-right: 5px;
11
+ line-height: 1.5rem;
12
+
13
+ border: 2px solid #666;
14
+ text-align: center;
15
+ font-size: 14px;
16
+ }
17
+
18
+ .annotation__title-wrapper {
19
+ display: flex;
20
+ flex-wrap: nowrap;
21
+ align-items: center;
22
+ }
23
+
24
+ .annotation__title-wrapper .annotation__title-text {
25
+ margin-left: 5px;
26
+ font-size: 16px;
27
+ }
28
+
29
+ .annotation__subtext {
30
+ font-size: 12px;
31
+ }
32
+
33
+ .annotation-list {
34
+ list-style: none;
35
+ }
36
+
37
+ .annotation-list li {
38
+ margin-top: 5px;
39
+ }
40
+
41
+ .cove-component__content {
42
+ container-type: inline-size;
43
+ container-name: content;
44
+ }
45
+ }
@@ -0,0 +1,42 @@
1
+ import React, { useContext } from 'react'
2
+ import ConfigContext from '../../../ConfigContext'
3
+ import './AnnotationList.styles.css'
4
+ import DOMPurify from 'dompurify'
5
+
6
+ type AnnotationListProps = {
7
+ useBootstrapVisibilityClasses?: boolean
8
+ }
9
+
10
+ const AnnotationList: React.FC<AnnotationListProps> = ({ useBootstrapVisibilityClasses = true }) => {
11
+ const { config } = useContext(ConfigContext)
12
+ const annotations = config.annotations || []
13
+
14
+ const ulClasses = () => {
15
+ const classes = ['annotation-list']
16
+ if (useBootstrapVisibilityClasses) {
17
+ classes.push('d-block', 'd-md-none')
18
+ }
19
+ return classes.join(' ')
20
+ }
21
+
22
+ const annotationListItems = annotations.map((annotation, annotationIndex) => {
23
+ const text = annotation.text || ''
24
+
25
+ // sanitize the text for setting dangerouslySetInnerHTML
26
+ const sanitizedData = () => ({
27
+ __html: DOMPurify.sanitize(text)
28
+ })
29
+ return (
30
+ <li key={`annotation-li-item__annotationIndex`}>
31
+ <div className='annotation__title-wrapper'>
32
+ <div className='annotation__title-circle'>{annotationIndex + 1}</div>
33
+ <p className='annotation__subtext' dangerouslySetInnerHTML={sanitizedData()} />
34
+ </div>
35
+ </li>
36
+ )
37
+ })
38
+
39
+ return <ul className={ulClasses()}>{annotationListItems}</ul>
40
+ }
41
+
42
+ export default AnnotationList
@@ -0,0 +1,138 @@
1
+ import { timeParse } from 'd3-time-format'
2
+
3
+ const getXValueFromCoordinate = (x, isClick = false) => {
4
+ if (visualizationType === 'Pie') return
5
+ if (orientation === 'horizontal') return
6
+
7
+ // Check the type of x equal to point or if the type of xAxis is equal to continuous or date
8
+ if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
9
+ let range = xScale.range()[1] - xScale.range()[0]
10
+ let eachBand = range / (xScale.domain().length + 1)
11
+
12
+ let numerator = x
13
+ const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
14
+ return xScale.domain()[index] // fixes off by 1 error
15
+ }
16
+
17
+ if (config.xAxis.type === 'date') {
18
+ const xValue = x // Assuming x is the coordinate on the chart
19
+ const xTimestamp = convertXValueToTimestamp(x, 0, xMax, xScale.domain())
20
+
21
+ // Calculate the closest date to the x coordinate
22
+ let closestDate = null
23
+ let minDistance = Number.MAX_VALUE
24
+
25
+ xScale.domain().forEach(timestamp => {
26
+ const distance = Math.abs(xTimestamp - timestamp)
27
+ if (distance < minDistance) {
28
+ minDistance = distance
29
+ closestDate = timestamp
30
+ }
31
+ })
32
+
33
+ return closestDate
34
+ }
35
+
36
+ return x
37
+ }
38
+
39
+ const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeriesKey }, xPosition) => {
40
+ const { xAxis, visualizationType, orientation } = config
41
+
42
+ const convertXValueToTimestamp = (xValue, minX, maxX, domain, xScale) => {
43
+ let ticks = []
44
+ if (config.xAxis.type === 'date-time') {
45
+ minX = new Date(minX)
46
+ maxX = new Date(maxX)
47
+ domain = domain.map(d => new Date(d))
48
+ ticks = xScale.ticks().map(d => new Date(d))
49
+ }
50
+
51
+ // Calculate the percentage position of xValue between minX and maxX
52
+ const percentage = (xValue - minX) / (maxX - minX)
53
+
54
+ // Calculate the index in the domain array corresponding to the percentage position
55
+ const index = Math.round(percentage * (domain.length - 1))
56
+
57
+ if (config.xAxis.type === 'date-time') {
58
+ return ticks[index]
59
+ }
60
+
61
+ // Return the timestamp from the domain array at the calculated index
62
+ return domain[index]
63
+ }
64
+
65
+ const getXValueFromCoordinate = (x, isClick = false) => {
66
+ if (visualizationType === 'Pie') return
67
+ if (orientation === 'horizontal') return
68
+
69
+ if (config.xAxis.type === 'date-time') {
70
+ // Calculate the percentage position of xValue between minX and maxX
71
+ const invertedValue = new Date(xScale.invert(x))
72
+ const ticks = config.data.map(d => new Date(d[config.xAxis.dataKey]).getTime())
73
+ let minDistance = Infinity
74
+ let closestDate = null
75
+
76
+ ticks.forEach(timestamp => {
77
+ const distance = Math.abs(invertedValue.getTime() - timestamp)
78
+ if (distance < minDistance) {
79
+ minDistance = distance
80
+ closestDate = timestamp
81
+ }
82
+ })
83
+
84
+ return new Date(closestDate).getTime()
85
+ }
86
+
87
+ // Check the type of x equal to point or if the type of xAxis is equal to continuous or date
88
+ if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
89
+ const range = xScale.range()[1] - xScale.range()[0]
90
+ const eachBand = range / (xScale.domain().length + 1)
91
+
92
+ let numerator = x
93
+ const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
94
+ return xScale.domain()[index] // fixes off by 1 error
95
+ }
96
+
97
+ if (config.xAxis.type === 'date') {
98
+ const xValue = x // Assuming x is the coordinate on the chart
99
+ const xTimestamp = convertXValueToTimestamp(x, 0, xMax, xScale.domain(), xScale)
100
+
101
+ // Calculate the closest date to the x coordinate
102
+ let closestDate = null
103
+ let minDistance = Number.MAX_VALUE
104
+
105
+ xScale.domain().forEach(timestamp => {
106
+ const distance = Math.abs(xTimestamp - timestamp)
107
+ if (distance < minDistance) {
108
+ minDistance = distance
109
+ closestDate = timestamp
110
+ }
111
+ })
112
+
113
+ return closestDate
114
+ }
115
+
116
+ return x
117
+ }
118
+
119
+ const xValue = getXValueFromCoordinate(xPosition - Number(config.yAxis.size || 0))
120
+
121
+ let closestSeries = []
122
+
123
+ if (!xValue) return { x: 0, y: 0 }
124
+
125
+ if (xAxis.type === 'categorical') {
126
+ closestSeries = config.data.filter(d => d[config.xAxis.dataKey] === xValue)
127
+ }
128
+
129
+ if (xAxis.type === 'date' || xAxis.type === 'date-time') {
130
+ closestSeries = config.data.filter(d => new Date(d[config.xAxis.dataKey]).getTime() === xValue)
131
+ }
132
+
133
+ const y = closestSeries[0][annotationSeriesKey] // Map each key to its corresponding value in data
134
+ const x = xValue
135
+ return { x, y }
136
+ }
137
+
138
+ export { findNearestDatum, getXValueFromCoordinate }
@@ -0,0 +1,46 @@
1
+ const applyBandScaleOffset = (num: number, config, xScale) => num + Number(config.yAxis.size) + xScale.bandwidth() / 2
2
+ const handleConnectionHorizontalType = (annotation, xScale, config) => {
3
+ const { connectionLocation } = annotation
4
+ if (connectionLocation === 'right') return 'end'
5
+ if (connectionLocation === 'left') return 'start'
6
+ if (connectionLocation === 'bottom' || connectionLocation === 'top') return 'middle'
7
+ return xScale(annotation.xKey) + annotation.dx < config.yAxis.size ? 'middle' : null
8
+ }
9
+
10
+ const handleConnectionVerticalType = (annotation, xScale, config) => {
11
+ const { connectionLocation } = annotation
12
+ if (connectionLocation === 'top') return 'start'
13
+ if (connectionLocation === 'bottom') return 'end'
14
+ if (connectionLocation === 'right' || connectionLocation === 'left') return 'middle'
15
+ return xScale(annotation.xKey) + annotation.dx < config.yAxis.size ? 'end' : null
16
+ }
17
+
18
+ const handleMobileXPosition = (annotation, xScale, config) => {
19
+ if (annotation.snapToNearestPoint) {
20
+ return Number(annotation.dx) + xScale(annotation.xKey) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size)
21
+ }
22
+ return Number(annotation.x) + Number(annotation.dx)
23
+ }
24
+
25
+ const handleMobileYPosition = (annotation, yScale, config) => {
26
+ if (annotation.snapToNearestPoint) {
27
+ return yScale(annotation.yKey) + Number(annotation.dy)
28
+ }
29
+ return Number(annotation.dy) + Number(annotation.y)
30
+ }
31
+
32
+ const handleTextX = (annotation, xScale, config) => {
33
+ if (annotation.snapToNearestPoint) {
34
+ return Number(annotation.dx) + Number(xScale(annotation.xKey)) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size) - 16 / 3
35
+ }
36
+ return Number(annotation.dx) + Number(annotation.x) - 16 / 3
37
+ }
38
+
39
+ const handleTextY = (annotation, yScale, config) => {
40
+ if (annotation.snapToNearestPoint) {
41
+ return yScale(annotation.yKey) + Number(annotation.dy) + 5
42
+ }
43
+ return Number(annotation.y) + Number(annotation.dy) + 16 / 3
44
+ }
45
+
46
+ export { applyBandScaleOffset, handleConnectionHorizontalType, handleConnectionVerticalType, handleMobileXPosition, handleMobileYPosition, handleTextX, handleTextY }
@@ -0,0 +1,13 @@
1
+ import AnnotationDraggable from './components/AnnotationDraggable'
2
+ import AnnotationList from './components/AnnotationList'
3
+ import AnnotationDropdown from './components/AnnotationDropdown'
4
+
5
+ const Annotation = {
6
+ Draggable: AnnotationDraggable,
7
+ // Mobile auto display
8
+ List: AnnotationList,
9
+ // Desktop Accessible Option
10
+ Dropdown: AnnotationDropdown
11
+ }
12
+
13
+ export default Annotation
@@ -14,7 +14,7 @@ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
14
14
  const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff, isDebug }) => {
15
15
  // import data from context
16
16
  let { transformedData, config, seriesHighlight, colorScale, rawData } = useContext(ConfigContext)
17
- const data = config.brush.active && config.brush.data?.length ? config.brush.data : transformedData
17
+ const data = config.brush?.active && config.brush.data?.length ? config.brush.data : transformedData
18
18
  // Draw transparent bars over the chart to get tooltip data
19
19
  // Turn DEBUG on for additional context.
20
20
  if (!data) return
@@ -15,7 +15,7 @@ const AreaChart = props => {
15
15
  const { xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff, isDebug, children } = props
16
16
  // import data from context
17
17
  let { transformedData, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale, rawData, brushConfig } = useContext(ConfigContext)
18
- const data = config.brush.active && brushConfig.data?.length ? brushConfig.data : transformedData
18
+ const data = config.brush?.active && brushConfig.data?.length ? brushConfig.data : transformedData
19
19
 
20
20
  if (!data) return
21
21
 
@@ -0,0 +1,145 @@
1
+ import React, { useContext } from 'react'
2
+ import { BarStack, Line } from '@visx/shape'
3
+ import { scaleBand, scaleLinear, scaleOrdinal } from '@visx/scale'
4
+ import { Group } from '@visx/group'
5
+ import { Text } from '@visx/text'
6
+ import ConfigContext from '../../ConfigContext'
7
+ import chroma from 'chroma-js'
8
+ import createBarElement from '@cdc/core/components/createBarElement'
9
+ import { useBarChart } from '../../hooks/useBarChart'
10
+
11
+ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
12
+ const { config, getTextWidth } = useContext(ConfigContext)
13
+ const { fontSize } = useBarChart()
14
+
15
+ const { orientation } = config
16
+
17
+ const getValidColor = (color, defaultColor = '#f1f1f1') => {
18
+ try {
19
+ return chroma(color).hex() // Returns the color if valid
20
+ } catch (e) {
21
+ return defaultColor
22
+ }
23
+ }
24
+
25
+ const categories = config.yAxis?.categories
26
+
27
+ const createDataShape = categories => {
28
+ const categoryObj = [...categories].reduce((acc, item) => {
29
+ acc[item.label] = item.height
30
+ return acc
31
+ }, {})
32
+
33
+ return categoryObj
34
+ }
35
+ const updateCategory = categoryObj => {
36
+ // Get all the heights in the object
37
+ const heights = Object.keys(categoryObj)
38
+
39
+ // Get the last label
40
+ const lastheight = heights[heights.length - 1]
41
+
42
+ // Check if the last label's value is empty
43
+ if (categoryObj[lastheight] === '') {
44
+ // Calculate the sum of the numeric values of all other heights
45
+ const sumOfValues = heights.slice(0, -1).reduce((sum, label) => {
46
+ const value = parseInt(categoryObj[label], 10)
47
+ return sum + (isNaN(value) ? 0 : value)
48
+ }, 0)
49
+
50
+ // Calculate the new value for the last emty height
51
+ const newValue = max - sumOfValues
52
+
53
+ // Update the last height with the new value
54
+ categoryObj[lastheight] = newValue.toString()
55
+ }
56
+
57
+ return [categoryObj]
58
+ }
59
+
60
+ const transformedData = updateCategory(createDataShape(categories))
61
+
62
+ // Define the scales
63
+ const xScaleValue = 0
64
+
65
+ const xScale = scaleBand<number>({
66
+ domain: [xScaleValue],
67
+ padding: 0,
68
+ range: [0, leftSize]
69
+ })
70
+
71
+ const yScale = scaleLinear({
72
+ domain: [0, max],
73
+ range: [yMax, 0],
74
+ clamp: true
75
+ })
76
+
77
+ const colorScale = scaleOrdinal({
78
+ domain: categories.map(d => d?.label),
79
+ range: categories.map(d => getValidColor(d?.color?.trim()))
80
+ })
81
+
82
+ const keys = Object.keys(transformedData[0])
83
+ return (
84
+ <Group left={leftSize - xScale.bandwidth()} top={0}>
85
+ <BarStack data={transformedData} keys={keys} x={() => xScale(xScaleValue)} xScale={xScale} yScale={yScale} color={colorScale}>
86
+ {barStacks =>
87
+ barStacks.map(barStack =>
88
+ barStack.bars.map(bar => {
89
+ const isLastIndex = config.yAxis.categories.length - 1 === barStack.index
90
+ const textSize = fontSize[config.fontSize] / 1.3
91
+ const textColor = chroma(bar.color).luminance() < 0.4 ? '#fff' : '#000'
92
+ const textWidth = getTextWidth(bar.key, `normal ${textSize}px sans-serif`)
93
+ const displayText = Number(textWidth) < bar.width && bar.height > textSize
94
+ const tooltip = `<ul>
95
+ <li class="tooltip-heading""> Label : ${bar.key} </li>
96
+ </li></ul>`
97
+ return (
98
+ <Group key={`${barStack.index}--${bar.index}--${orientation}`}>
99
+ <Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
100
+ {createBarElement({
101
+ type: 'axisBar',
102
+ config: config,
103
+ index: barStack.index,
104
+ background: colorScale(bar.key),
105
+ borderColor: '#333',
106
+ borderStyle: 'solid',
107
+ borderWidth: 0,
108
+ width: xScale.bandwidth(),
109
+ height: bar.height,
110
+ x: bar.x,
111
+ y: bar.y,
112
+ tooltipHtml: tooltip,
113
+ tooltipId: `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
114
+ })}
115
+ {/* Label for axis stacks */}
116
+ <Text // ignore
117
+ display={!displayText ? 'none' : 'block'}
118
+ key={`text-${barStack.index}-${bar.index}`}
119
+ x={bar.x + xScale.bandwidth() / 2}
120
+ y={bar.y + bar.height / 2}
121
+ fill={textColor}
122
+ textAnchor='middle'
123
+ verticalAnchor='middle'
124
+ style={{ fontSize: textSize }}
125
+ >
126
+ {bar.key}
127
+ </Text>
128
+ {/* gridLines */}
129
+ {config.runtime.yAxis.gridLines && <Line from={{ x: bar.x + xScale.bandwidth(), y: bar.y }} to={{ x: xMax + xScale.bandwidth(), y: bar.y }} stroke='rgba(0,0,0,0.3)' />}
130
+ {/* White background spacing between stackes */}
131
+ {!isLastIndex && <rect x={bar.x} y={bar.y} width={bar.width} height={1} fill={'#fff'}></rect>}
132
+ {/* Right side Axis line */}
133
+ <rect x={bar.x + bar.width} y={0} width={1} height={yMax} fill={'#000'}></rect>
134
+ </Group>
135
+ </Group>
136
+ )
137
+ })
138
+ )
139
+ }
140
+ </BarStack>
141
+ </Group>
142
+ )
143
+ }
144
+
145
+ export default CategoricalYAxis