@cdc/chart 4.25.10 → 4.26.1
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-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
- package/dist/cdcchart.js +44003 -43518
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/feature/pie/planet-pie-example-config.json +48 -2
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/DEV-12100.json +1303 -0
- package/examples/private/cat-y.json +1235 -0
- package/examples/private/data-points.json +228 -0
- package/examples/private/height.json +3915 -0
- package/examples/private/links.json +569 -0
- package/examples/private/na.json +913 -0
- package/examples/private/quadrant.txt +30 -0
- package/examples/private/test-data.csv +28 -0
- package/examples/private/test-forecast.json +5510 -0
- package/examples/private/warming-stripe-test.json +2578 -0
- package/examples/private/warming-stripes.json +4763 -0
- package/examples/tech-adoption-with-links.json +560 -0
- package/index.html +16 -140
- package/package.json +6 -5
- package/preview.html +1616 -0
- package/src/CdcChart.tsx +8 -11
- package/src/CdcChartComponent.tsx +329 -124
- package/src/_stories/Chart.Combo.stories.tsx +18 -0
- package/src/_stories/Chart.Forecast.stories.tsx +36 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
- package/src/_stories/Chart.Patterns.stories.tsx +2 -1
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/Chart.stories.tsx +8 -0
- package/src/_stories/ChartAnnotation.stories.tsx +6 -3
- package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
- package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
- package/src/_stories/ChartBrush.stories.tsx +50 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
- package/src/_stories/ChartEditor.stories.tsx +1 -2
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
- package/src/_stories/_mock/brush_enabled.json +326 -0
- package/src/_stories/_mock/brush_mock.json +2 -69
- package/src/_stories/_mock/combo.json +451 -0
- package/src/_stories/_mock/editor-test-configs.json +376 -0
- package/src/_stories/_mock/editor-test-datasets.json +477 -0
- package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
- package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
- package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
- package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
- package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
- package/src/_stories/_mock/pie_config.json +257 -62
- package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
- package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
- package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
- package/src/components/Annotations/components/findNearestDatum.ts +6 -41
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -7
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/Axis/Categorical.Axis.tsx +6 -7
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
- package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
- package/src/components/BarChart/components/context.tsx +1 -0
- package/src/components/BarChart/helpers/useBarChart.ts +14 -2
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushSelector.tsx +1258 -0
- package/src/components/Brush/MiniChartPreview.tsx +283 -0
- package/src/components/DeviationBar.jsx +9 -7
- package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/editor-panel.scss +0 -20
- package/src/components/EditorPanel/useEditorPermissions.ts +36 -31
- package/src/components/Forecasting/Forecasting.tsx +139 -21
- package/src/components/Legend/Legend.Component.tsx +16 -9
- package/src/components/Legend/Legend.tsx +3 -2
- package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/Legend/helpers/index.ts +10 -6
- package/src/components/LineChart/LineChartProps.ts +0 -3
- package/src/components/LineChart/helpers.ts +1 -1
- package/src/components/LineChart/index.tsx +36 -13
- package/src/components/LinearChart.tsx +559 -499
- package/src/components/PairedBarChart.jsx +20 -3
- package/src/components/Regions/components/Regions.tsx +366 -144
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
- package/src/components/SmallMultiples/index.ts +2 -0
- package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
- package/src/components/WarmingStripes/index.tsx +3 -0
- package/src/data/initial-state.js +16 -2
- package/src/helpers/buildForecastPaletteOptions.ts +0 -38
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
- package/src/helpers/getColorScale.ts +10 -0
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/sizeHelpers.ts +0 -20
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useChartHoverAnalytics.tsx +10 -9
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useScales.ts +98 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +91 -25
- package/src/scss/DataTable.scss +0 -4
- package/src/scss/main.scss +18 -83
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +4 -0
- package/src/test/CdcChart.test.jsx +1 -1
- package/src/types/ChartConfig.ts +27 -6
- package/src/types/ChartContext.ts +3 -0
- package/src/types/Label.ts +1 -0
- package/src/utils/analyticsTracking.ts +19 -0
- package/LICENSE +0 -201
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/components/Brush/BrushChart.tsx +0 -128
- package/src/components/Brush/BrushController.tsx +0 -71
- package/src/components/Brush/types.tsx +0 -8
- package/src/components/BrushChart.tsx +0 -223
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
|
@@ -1,41 +1,3 @@
|
|
|
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
1
|
const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeriesKey }, xPosition) => {
|
|
40
2
|
const { xAxis, visualizationType, orientation } = config
|
|
41
3
|
|
|
@@ -62,7 +24,7 @@ const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeries
|
|
|
62
24
|
return domain[index]
|
|
63
25
|
}
|
|
64
26
|
|
|
65
|
-
const getXValueFromCoordinate =
|
|
27
|
+
const getXValueFromCoordinate = x => {
|
|
66
28
|
if (visualizationType === 'Pie') return
|
|
67
29
|
if (orientation === 'horizontal') return
|
|
68
30
|
|
|
@@ -85,7 +47,10 @@ const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeries
|
|
|
85
47
|
}
|
|
86
48
|
|
|
87
49
|
// Check the type of x equal to point or if the type of xAxis is equal to continuous or date
|
|
88
|
-
if (
|
|
50
|
+
if (
|
|
51
|
+
config.xAxis.type === 'categorical' ||
|
|
52
|
+
(visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')
|
|
53
|
+
) {
|
|
89
54
|
const range = xScale.range()[1] - xScale.range()[0]
|
|
90
55
|
const eachBand = range / (xScale.domain().length + 1)
|
|
91
56
|
|
|
@@ -135,4 +100,4 @@ const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeries
|
|
|
135
100
|
return { x, y }
|
|
136
101
|
}
|
|
137
102
|
|
|
138
|
-
export { findNearestDatum
|
|
103
|
+
export { findNearestDatum }
|
|
@@ -13,17 +13,20 @@ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
|
13
13
|
|
|
14
14
|
const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff }) => {
|
|
15
15
|
// import data from context
|
|
16
|
-
let { transformedData, config, seriesHighlight, colorScale, rawData } = useContext(ConfigContext)
|
|
17
|
-
const data = config.brush?.active && config.brush.data?.length ? config.brush.data : transformedData
|
|
16
|
+
let { transformedData: data, config, seriesHighlight, colorScale, rawData, parseDate } = useContext(ConfigContext)
|
|
18
17
|
// Draw transparent bars over the chart to get tooltip data
|
|
19
18
|
// Turn DEBUG on for additional context.
|
|
20
19
|
if (!data) return
|
|
21
20
|
|
|
22
21
|
const handleDateCategory = value => {
|
|
23
|
-
if (config.xAxis.type === 'categorical')
|
|
22
|
+
if (config.xAxis.type === 'categorical') {
|
|
23
|
+
return xScale(value) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
24
|
+
}
|
|
24
25
|
if (isDateScale(config.xAxis)) {
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const scaledValue = xScale(parseDate(value, false))
|
|
27
|
+
// Add bandwidth offset to center on band scales (date type)
|
|
28
|
+
// For date-time (time scale), bandwidth doesn't exist so no offset added
|
|
29
|
+
return scaledValue + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -65,7 +68,7 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
|
|
|
65
68
|
key={stack.key}
|
|
66
69
|
d={path(stack) || ''}
|
|
67
70
|
strokeWidth={2}
|
|
68
|
-
stroke={displayArea ?
|
|
71
|
+
stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[stack.key] : stack.key) : '#000' : 'transparent'}
|
|
69
72
|
fillOpacity={transparentArea ? 0.2 : 1}
|
|
70
73
|
fill={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[stack.key] : stack.key) : '#000' : 'transparent'}
|
|
71
74
|
/>
|
|
@@ -74,7 +77,7 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
|
|
|
74
77
|
}}
|
|
75
78
|
</AreaStack>
|
|
76
79
|
{/* prettier-ignore */}
|
|
77
|
-
<Bar width={Number(xMax)} height={Number(yMax)} fill={'transparent'}
|
|
80
|
+
<Bar width={Number(xMax)} height={Number(yMax)} fill={'transparent'} onMouseMove={e => handleTooltipMouseOver(e, rawData)} onMouseLeave={handleTooltipMouseOff} />
|
|
78
81
|
</Group>
|
|
79
82
|
</ErrorBoundary>
|
|
80
83
|
</svg>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import { BarStack, Line } from '@visx/shape'
|
|
3
|
-
import { scaleBand,
|
|
3
|
+
import { scaleBand, scaleOrdinal } from '@visx/scale'
|
|
4
4
|
import { Group } from '@visx/group'
|
|
5
5
|
import { Text } from '@visx/text'
|
|
6
6
|
import ConfigContext from '../../ConfigContext'
|
|
@@ -9,7 +9,7 @@ import createBarElement from '@cdc/core/components/createBarElement'
|
|
|
9
9
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
10
10
|
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
11
11
|
|
|
12
|
-
const CategoricalYAxis = ({ yMax, leftSize,
|
|
12
|
+
const CategoricalYAxis = ({ yScale, yMax, leftSize, xMax }) => {
|
|
13
13
|
const { config } = useContext(ConfigContext)
|
|
14
14
|
|
|
15
15
|
const { orientation } = config
|
|
@@ -24,6 +24,9 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
|
|
|
24
24
|
|
|
25
25
|
const categories = config.yAxis?.categories
|
|
26
26
|
|
|
27
|
+
// Get max from the yScale domain
|
|
28
|
+
const max = yScale.domain()[1]
|
|
29
|
+
|
|
27
30
|
const createDataShape = categories => {
|
|
28
31
|
const categoryObj = [...categories].reduce((acc, item) => {
|
|
29
32
|
acc[item.label] = item.height
|
|
@@ -68,11 +71,7 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
|
|
|
68
71
|
range: [0, leftSize]
|
|
69
72
|
})
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
domain: [0, max],
|
|
73
|
-
range: [yMax, 0],
|
|
74
|
-
clamp: true
|
|
75
|
-
})
|
|
74
|
+
// Use the yScale passed from useScales instead of creating a new one
|
|
76
75
|
|
|
77
76
|
const colorScale = scaleOrdinal({
|
|
78
77
|
domain: categories.map(d => d?.label),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
// Local context and hooks
|
|
4
4
|
import ConfigContext from '../../../ConfigContext'
|
|
@@ -13,7 +13,6 @@ import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
|
13
13
|
// CDC core components and helpers
|
|
14
14
|
import { getColorContrast, getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
15
15
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
16
|
-
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
17
16
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
18
17
|
import { getBarConfig, testZeroValue, getLollipopStemColor, getLollipopHeadColor } from '../helpers'
|
|
19
18
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
@@ -26,7 +25,7 @@ import _ from 'lodash'
|
|
|
26
25
|
import { getBarData } from '../helpers/getBarData'
|
|
27
26
|
import { getHorizontalBarHeights } from '../helpers/getBarHeights'
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
const BarChartHorizontal = () => {
|
|
30
29
|
const { xScale, yScale, yMax, seriesScale, barChart } = useContext<BarChartContextValues>(BarChartContext)
|
|
31
30
|
const {
|
|
32
31
|
isHorizontal,
|
|
@@ -36,6 +35,7 @@ export const BarChartHorizontal = () => {
|
|
|
36
35
|
isLabelBelowBar,
|
|
37
36
|
lollipopBarWidth,
|
|
38
37
|
lollipopShapeSize,
|
|
38
|
+
labelFontSize,
|
|
39
39
|
getHighlightedBarColorByValue,
|
|
40
40
|
getHighlightedBarByValue,
|
|
41
41
|
getAdditionalColumn,
|
|
@@ -54,13 +54,11 @@ export const BarChartHorizontal = () => {
|
|
|
54
54
|
formatDate,
|
|
55
55
|
parseDate,
|
|
56
56
|
setSharedFilter,
|
|
57
|
-
|
|
57
|
+
vizViewport
|
|
58
58
|
} = useContext<ChartContext>(ConfigContext)
|
|
59
59
|
|
|
60
60
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
61
61
|
|
|
62
|
-
const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
|
|
63
|
-
|
|
64
62
|
const hasConfidenceInterval = [config.confidenceKeys?.upper, config.confidenceKeys?.lower].every(
|
|
65
63
|
v => v != null && String(v).trim() !== ''
|
|
66
64
|
)
|
|
@@ -209,7 +207,7 @@ export const BarChartHorizontal = () => {
|
|
|
209
207
|
isVertical: false,
|
|
210
208
|
yAxisValue,
|
|
211
209
|
barWidth: 0,
|
|
212
|
-
labelFontSize:
|
|
210
|
+
labelFontSize: labelFontSize
|
|
213
211
|
})
|
|
214
212
|
|
|
215
213
|
const barPosition = !isPositiveBar ? 'below' : 'above'
|
|
@@ -217,7 +215,7 @@ export const BarChartHorizontal = () => {
|
|
|
217
215
|
const barDefaultLabel = !config.yAxis.displayNumbersOnBar || absentDataLabel ? '' : yAxisValue
|
|
218
216
|
|
|
219
217
|
// check if bar text/value string fits into each bars.
|
|
220
|
-
const textWidth = getTextWidth(barDefaultLabel)
|
|
218
|
+
const textWidth = getTextWidth(barDefaultLabel, `normal ${labelFontSize}px sans-serif`)
|
|
221
219
|
const textFits = Number(textWidth) < defaultBarWidth - 5
|
|
222
220
|
|
|
223
221
|
// control text position
|
|
@@ -447,7 +445,7 @@ export const BarChartHorizontal = () => {
|
|
|
447
445
|
return (
|
|
448
446
|
<Text // prettier-ignore
|
|
449
447
|
key={index}
|
|
450
|
-
fontSize={
|
|
448
|
+
fontSize={labelFontSize}
|
|
451
449
|
display={displayBar ? 'block' : 'none'}
|
|
452
450
|
opacity={transparentBar ? 0.5 : 1}
|
|
453
451
|
x={barX}
|
|
@@ -473,6 +471,7 @@ export const BarChartHorizontal = () => {
|
|
|
473
471
|
dx={textPadding}
|
|
474
472
|
verticalAnchor='middle'
|
|
475
473
|
textAnchor={textAnchor}
|
|
474
|
+
fontSize={labelFontSize}
|
|
476
475
|
>
|
|
477
476
|
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
478
477
|
</Text>
|
|
@@ -488,6 +487,7 @@ export const BarChartHorizontal = () => {
|
|
|
488
487
|
dx={-textPadding}
|
|
489
488
|
verticalAnchor='middle'
|
|
490
489
|
textAnchor={bar.value < 0 ? 'end' : 'start'}
|
|
490
|
+
fontSize={labelFontSize}
|
|
491
491
|
>
|
|
492
492
|
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
493
493
|
</Text>
|
|
@@ -496,12 +496,17 @@ export const BarChartHorizontal = () => {
|
|
|
496
496
|
display={displayBar ? 'block' : 'none'}
|
|
497
497
|
x={bar.y}
|
|
498
498
|
opacity={transparentBar ? 0.5 : 1}
|
|
499
|
-
y={
|
|
499
|
+
y={
|
|
500
|
+
config.isLollipopChart
|
|
501
|
+
? barHeight * bar.index + lollipopBarWidth / 2
|
|
502
|
+
: config.barHeight / 2 + config.barHeight * bar.index
|
|
503
|
+
}
|
|
500
504
|
fill={labelColor}
|
|
501
505
|
dx={absentDataLabel === 'N/A' ? 20 : textPadding}
|
|
502
|
-
dy={
|
|
506
|
+
dy={0}
|
|
503
507
|
verticalAnchor='middle'
|
|
504
508
|
textAnchor={absentDataLabel === 'N/A' ? 'middle' : textAnchor}
|
|
509
|
+
fontSize={labelFontSize}
|
|
505
510
|
>
|
|
506
511
|
{absentDataLabel}
|
|
507
512
|
</Text>
|
|
@@ -510,31 +515,180 @@ export const BarChartHorizontal = () => {
|
|
|
510
515
|
<Text // prettier-ignore
|
|
511
516
|
display={displayBar ? 'block' : 'none'}
|
|
512
517
|
x={bar.y}
|
|
513
|
-
y={
|
|
518
|
+
y={barHeight * bar.index + lollipopBarWidth / 2}
|
|
514
519
|
fill={APP_FONT_COLOR}
|
|
515
520
|
dx={textPaddingLollipop}
|
|
516
521
|
textAnchor={textAnchorLollipop}
|
|
517
522
|
verticalAnchor='middle'
|
|
518
523
|
fontWeight={'normal'}
|
|
524
|
+
fontSize={labelFontSize}
|
|
519
525
|
>
|
|
520
526
|
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
521
527
|
</Text>
|
|
522
528
|
)}
|
|
523
|
-
{isLabelBelowBar &&
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
?
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
529
|
+
{isLabelBelowBar &&
|
|
530
|
+
!config.yAxis.hideLabel &&
|
|
531
|
+
(() => {
|
|
532
|
+
const label =
|
|
533
|
+
config.runtime.yAxis.type === 'date'
|
|
534
|
+
? formatDate(parseDate(dataValue))
|
|
535
|
+
: isHorizontal
|
|
536
|
+
? dataValue
|
|
537
|
+
: formatNumber(dataValue)
|
|
538
|
+
if (typeof label === 'string') {
|
|
539
|
+
// 1. Check for HTML <a> tag and extract its text and href
|
|
540
|
+
const aTagMatch = label.match(/<a [^>]*href=["']?([^"'>\s]+)["']?[^>]*>(.*?)<\/a>/i)
|
|
541
|
+
if (aTagMatch) {
|
|
542
|
+
const href = aTagMatch[1].startsWith('http') ? aTagMatch[1] : `https://${aTagMatch[1]}`
|
|
543
|
+
const linkText = aTagMatch[2]
|
|
544
|
+
return (
|
|
545
|
+
<foreignObject
|
|
546
|
+
x={config.yAxis.hideAxis ? 0 : 5}
|
|
547
|
+
y={barGroup.height}
|
|
548
|
+
width={120}
|
|
549
|
+
height={24}
|
|
550
|
+
style={{ overflow: 'visible' }}
|
|
551
|
+
>
|
|
552
|
+
<a
|
|
553
|
+
href={href}
|
|
554
|
+
target='_blank'
|
|
555
|
+
rel='noopener noreferrer'
|
|
556
|
+
style={
|
|
557
|
+
config.tooltips.singleSeries
|
|
558
|
+
? {
|
|
559
|
+
color: '#0071bc',
|
|
560
|
+
textDecoration: 'underline',
|
|
561
|
+
fontSize: 12,
|
|
562
|
+
fontFamily: 'inherit',
|
|
563
|
+
display: 'inline-block',
|
|
564
|
+
width: '100%'
|
|
565
|
+
}
|
|
566
|
+
: {
|
|
567
|
+
color: 'inherit',
|
|
568
|
+
textDecoration: 'none',
|
|
569
|
+
fontSize: 12,
|
|
570
|
+
fontFamily: 'inherit',
|
|
571
|
+
display: 'inline-block',
|
|
572
|
+
width: '100%'
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
>
|
|
576
|
+
{linkText}
|
|
577
|
+
</a>
|
|
578
|
+
</foreignObject>
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
// 2. Check for markdown link
|
|
582
|
+
const mdMatch = label.match(/\[([^\]]+)\]\((https?:\/\/[^\s]+|www\.[^\s]+)\)/i)
|
|
583
|
+
if (mdMatch) {
|
|
584
|
+
const href = mdMatch[2].startsWith('http') ? mdMatch[2] : `https://${mdMatch[2]}`
|
|
585
|
+
const linkText = mdMatch[1]
|
|
586
|
+
return (
|
|
587
|
+
<foreignObject
|
|
588
|
+
x={config.yAxis.hideAxis ? 0 : 5}
|
|
589
|
+
y={barGroup.height}
|
|
590
|
+
width={120}
|
|
591
|
+
height={24}
|
|
592
|
+
style={{ overflow: 'visible' }}
|
|
593
|
+
>
|
|
594
|
+
<a
|
|
595
|
+
href={href}
|
|
596
|
+
target='_blank'
|
|
597
|
+
rel='noopener noreferrer'
|
|
598
|
+
style={
|
|
599
|
+
config.tooltips.singleSeries
|
|
600
|
+
? {
|
|
601
|
+
color: '#0071bc',
|
|
602
|
+
textDecoration: 'underline',
|
|
603
|
+
fontSize: 12,
|
|
604
|
+
fontFamily: 'inherit',
|
|
605
|
+
display: 'inline-block',
|
|
606
|
+
width: '100%'
|
|
607
|
+
}
|
|
608
|
+
: {
|
|
609
|
+
color: 'inherit',
|
|
610
|
+
textDecoration: 'none',
|
|
611
|
+
fontSize: 12,
|
|
612
|
+
fontFamily: 'inherit',
|
|
613
|
+
display: 'inline-block',
|
|
614
|
+
width: '100%'
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
>
|
|
618
|
+
{linkText}
|
|
619
|
+
</a>
|
|
620
|
+
</foreignObject>
|
|
621
|
+
)
|
|
622
|
+
}
|
|
623
|
+
// 3. Check for plain URL
|
|
624
|
+
if (/(https?:\/\/|www\.)/i.test(label)) {
|
|
625
|
+
try {
|
|
626
|
+
const urlObj = new URL(label.startsWith('http') ? label : `https://${label}`)
|
|
627
|
+
const linkText = urlObj.hostname.replace(/^www\./, '')
|
|
628
|
+
return (
|
|
629
|
+
<foreignObject
|
|
630
|
+
x={config.yAxis.hideAxis ? 0 : 5}
|
|
631
|
+
y={barGroup.height}
|
|
632
|
+
width={120}
|
|
633
|
+
height={24}
|
|
634
|
+
style={{ overflow: 'visible' }}
|
|
635
|
+
>
|
|
636
|
+
<a
|
|
637
|
+
href={urlObj.href}
|
|
638
|
+
target='_blank'
|
|
639
|
+
rel='noopener noreferrer'
|
|
640
|
+
style={
|
|
641
|
+
config.tooltips.singleSeries
|
|
642
|
+
? {
|
|
643
|
+
color: '#0071bc',
|
|
644
|
+
textDecoration: 'underline',
|
|
645
|
+
fontSize: 12,
|
|
646
|
+
fontFamily: 'inherit',
|
|
647
|
+
display: 'inline-block',
|
|
648
|
+
width: '100%'
|
|
649
|
+
}
|
|
650
|
+
: {
|
|
651
|
+
color: 'inherit',
|
|
652
|
+
textDecoration: 'none',
|
|
653
|
+
fontSize: 12,
|
|
654
|
+
fontFamily: 'inherit',
|
|
655
|
+
display: 'inline-block',
|
|
656
|
+
width: '100%'
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
>
|
|
660
|
+
{linkText}
|
|
661
|
+
</a>
|
|
662
|
+
</foreignObject>
|
|
663
|
+
)
|
|
664
|
+
} catch {
|
|
665
|
+
return (
|
|
666
|
+
<Text
|
|
667
|
+
x={config.yAxis.hideAxis ? 0 : 5}
|
|
668
|
+
y={barGroup.height}
|
|
669
|
+
dy={4}
|
|
670
|
+
verticalAnchor={'start'}
|
|
671
|
+
textAnchor={'start'}
|
|
672
|
+
>
|
|
673
|
+
{label}
|
|
674
|
+
</Text>
|
|
675
|
+
)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// Not a link, render as normal
|
|
680
|
+
return (
|
|
681
|
+
<Text
|
|
682
|
+
x={config.yAxis.hideAxis ? 0 : 5}
|
|
683
|
+
y={barGroup.height}
|
|
684
|
+
dy={4}
|
|
685
|
+
verticalAnchor={'start'}
|
|
686
|
+
textAnchor={'start'}
|
|
687
|
+
>
|
|
688
|
+
{label}
|
|
689
|
+
</Text>
|
|
690
|
+
)
|
|
691
|
+
})()}
|
|
538
692
|
|
|
539
693
|
{config.isLollipopChart && config.lollipopShape === 'circle' && (
|
|
540
694
|
<circle
|
|
@@ -38,6 +38,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
38
38
|
hoveredBar,
|
|
39
39
|
isHorizontal,
|
|
40
40
|
isLabelBelowBar,
|
|
41
|
+
labelFontSize,
|
|
41
42
|
onMouseLeaveBar,
|
|
42
43
|
onMouseOverBar,
|
|
43
44
|
barStackedSeriesKeys
|
|
@@ -155,7 +156,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
155
156
|
const yAxisTooltip = config.runtime.yAxis.label
|
|
156
157
|
? `${config.runtime.yAxis.label}: ${yAxisValue}`
|
|
157
158
|
: yAxisValue
|
|
158
|
-
const textWidth = getTextWidth(xAxisValue)
|
|
159
|
+
const textWidth = getTextWidth(xAxisValue, `normal ${labelFontSize}px sans-serif`)
|
|
159
160
|
const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
|
|
160
161
|
const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${xAxisValue}`
|
|
161
162
|
const tooltip = `<ul>
|
|
@@ -286,6 +287,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
286
287
|
fill={labelColor}
|
|
287
288
|
textAnchor='middle'
|
|
288
289
|
verticalAnchor='middle'
|
|
290
|
+
fontSize={labelFontSize}
|
|
289
291
|
>
|
|
290
292
|
{xAxisValue}
|
|
291
293
|
</Text>
|
|
@@ -17,13 +17,12 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
|
17
17
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
18
18
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
19
19
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
20
|
-
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
21
20
|
// Types
|
|
22
21
|
import { type ChartContext } from '../../../types/ChartContext'
|
|
23
22
|
import _ from 'lodash'
|
|
24
23
|
import { getBarData } from '../helpers/getBarData'
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
const BarChartVertical = () => {
|
|
27
26
|
const { xScale, yScale, xMax, yMax, seriesScale, convertLineToBarGraph, barChart } =
|
|
28
27
|
useContext<BarChartContextValues>(BarChartContext)
|
|
29
28
|
const {
|
|
@@ -32,6 +31,7 @@ export const BarChartVertical = () => {
|
|
|
32
31
|
getAdditionalColumn,
|
|
33
32
|
getHighlightedBarByValue,
|
|
34
33
|
getHighlightedBarColorByValue,
|
|
34
|
+
labelFontSize,
|
|
35
35
|
lollipopBarWidth,
|
|
36
36
|
lollipopShapeSize,
|
|
37
37
|
onMouseLeaveBar,
|
|
@@ -46,6 +46,7 @@ export const BarChartVertical = () => {
|
|
|
46
46
|
colorScale,
|
|
47
47
|
config,
|
|
48
48
|
currentViewport,
|
|
49
|
+
vizViewport,
|
|
49
50
|
dashboardConfig,
|
|
50
51
|
tableData,
|
|
51
52
|
formatDate,
|
|
@@ -58,8 +59,6 @@ export const BarChartVertical = () => {
|
|
|
58
59
|
|
|
59
60
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
60
61
|
|
|
61
|
-
const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
|
|
62
|
-
|
|
63
62
|
const root = document.documentElement
|
|
64
63
|
|
|
65
64
|
let data = transformedData
|
|
@@ -241,7 +240,7 @@ export const BarChartVertical = () => {
|
|
|
241
240
|
barWidth,
|
|
242
241
|
isVertical: true,
|
|
243
242
|
yAxisValue,
|
|
244
|
-
labelFontSize:
|
|
243
|
+
labelFontSize: labelFontSize
|
|
245
244
|
})
|
|
246
245
|
// configure colors
|
|
247
246
|
let labelColor = APP_FONT_COLOR
|
|
@@ -480,7 +479,7 @@ export const BarChartVertical = () => {
|
|
|
480
479
|
verticalAnchor={verticalAnchor}
|
|
481
480
|
fill={fillColor}
|
|
482
481
|
textAnchor='middle'
|
|
483
|
-
fontSize={
|
|
482
|
+
fontSize={labelFontSize}
|
|
484
483
|
>
|
|
485
484
|
{pd.iconCode}
|
|
486
485
|
</Text>
|
|
@@ -493,7 +492,7 @@ export const BarChartVertical = () => {
|
|
|
493
492
|
y={barY - BAR_LABEL_PADDING}
|
|
494
493
|
fill={labelColor}
|
|
495
494
|
textAnchor='middle'
|
|
496
|
-
fontSize={
|
|
495
|
+
fontSize={labelFontSize}
|
|
497
496
|
>
|
|
498
497
|
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
499
498
|
</Text>
|
|
@@ -504,7 +503,7 @@ export const BarChartVertical = () => {
|
|
|
504
503
|
y={barY - BAR_LABEL_PADDING}
|
|
505
504
|
fill={labelColor}
|
|
506
505
|
textAnchor='middle'
|
|
507
|
-
fontSize={config.isLollipopChart ? null :
|
|
506
|
+
fontSize={config.isLollipopChart ? null : labelFontSize}
|
|
508
507
|
>
|
|
509
508
|
{absentDataLabel}
|
|
510
509
|
</Text>
|
|
@@ -567,7 +566,7 @@ export const BarChartVertical = () => {
|
|
|
567
566
|
}}
|
|
568
567
|
</BarGroup>
|
|
569
568
|
|
|
570
|
-
<Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} />
|
|
569
|
+
<Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} xMax={xMax} />
|
|
571
570
|
</Group>
|
|
572
571
|
)
|
|
573
572
|
)
|
|
@@ -5,10 +5,20 @@ import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
|
5
5
|
import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
|
|
6
6
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
7
7
|
import { getVizSubType, getVizTitle } from '@cdc/core/helpers/metrics/utils'
|
|
8
|
+
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
8
9
|
|
|
9
10
|
export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, configContext) => {
|
|
10
|
-
const {
|
|
11
|
-
|
|
11
|
+
const {
|
|
12
|
+
config,
|
|
13
|
+
colorPalettes,
|
|
14
|
+
tableData,
|
|
15
|
+
updateConfig,
|
|
16
|
+
parseDate,
|
|
17
|
+
formatDate,
|
|
18
|
+
seriesHighlight,
|
|
19
|
+
interactionLabel,
|
|
20
|
+
vizViewport
|
|
21
|
+
} = configContext
|
|
12
22
|
const { orientation } = config
|
|
13
23
|
const dispatch = useContext(ChartDispatchContext)
|
|
14
24
|
const [hoveredBar, setHoveredBar] = useState(null)
|
|
@@ -20,6 +30,7 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
|
|
|
20
30
|
const isLabelBelowBar = config.yAxis.labelPlacement === 'Below Bar'
|
|
21
31
|
const displayNumbersOnBar = config.yAxis.displayNumbersOnBar
|
|
22
32
|
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
33
|
+
const labelFontSize = isMobileFontViewport(vizViewport) ? 13 : 16
|
|
23
34
|
|
|
24
35
|
const isRounded = config.barStyle === 'rounded'
|
|
25
36
|
const isStacked = config.visualizationSubType === 'stacked'
|
|
@@ -241,6 +252,7 @@ export const useBarChart = (handleTooltipMouseOver, handleTooltipMouseOff, confi
|
|
|
241
252
|
stackCount,
|
|
242
253
|
barStackedSeriesKeys,
|
|
243
254
|
hasMultipleSeries,
|
|
255
|
+
labelFontSize,
|
|
244
256
|
applyRadius,
|
|
245
257
|
assignColorsToValues,
|
|
246
258
|
getHighlightedBarColorByValue,
|
|
@@ -29,7 +29,7 @@ export const handleTooltip = (boxplot, columnCategory, key, q1, q3, median, iqr,
|
|
|
29
29
|
`
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const calculateBoxPlotStats = (values: number[]) => {
|
|
33
33
|
if (!values || values.length === 0) return {}
|
|
34
34
|
|
|
35
35
|
// Sort the values
|
|
@@ -82,7 +82,7 @@ const getValuesBySeriesKey = (group: string, config, data) => {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// Helper to calculate outliers based on IQR
|
|
85
|
-
|
|
85
|
+
const calculateOutliers = (values: number[], firstQuartile: number, thirdQuartile: number) => {
|
|
86
86
|
const iqr = thirdQuartile - firstQuartile
|
|
87
87
|
const lowerBound = firstQuartile - 1.5 * iqr
|
|
88
88
|
const upperBound = thirdQuartile + 1.5 * iqr
|
|
@@ -90,7 +90,7 @@ export const calculateOutliers = (values: number[], firstQuartile: number, third
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// Helper to calculate non-outliers based on IQR
|
|
93
|
-
|
|
93
|
+
const calculateNonOutliers = (values: number[], firstQuartile: number, thirdQuartile: number): number[] => {
|
|
94
94
|
const iqr = thirdQuartile - firstQuartile
|
|
95
95
|
const lowerBound = firstQuartile - 1.5 * iqr
|
|
96
96
|
const upperBound = thirdQuartile + 1.5 * iqr
|