@cdc/chart 4.25.8 → 4.25.11
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/.claude/settings.local.json +9 -0
- package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
- package/dist/cdcchart.js +44236 -40355
- package/examples/feature/__data__/planet-example-data.json +0 -30
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/grouped-bar-test.json +400 -0
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/d.json +382 -0
- package/examples/private/example-2.json +49784 -0
- package/examples/private/f2.json +1 -0
- package/examples/private/f4.json +1577 -0
- package/examples/private/forecast.json +1180 -0
- package/examples/private/lollipop.json +468 -0
- package/examples/private/na.json +913 -0
- package/examples/private/new.json +48756 -0
- package/examples/private/pie-chart-legend.json +904 -0
- package/examples/private/test-data.csv +28 -0
- package/examples/suppressed_tooltip.json +480 -0
- package/index.html +2 -133
- package/package.json +25 -7
- package/src/CdcChart.tsx +9 -13
- package/src/CdcChartComponent.tsx +403 -92
- package/src/_stories/Chart.Anchors.stories.tsx +2 -2
- package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
- package/src/_stories/Chart.CI.stories.tsx +1 -1
- package/src/_stories/Chart.Combo.stories.tsx +18 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
- package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
- package/src/_stories/Chart.Filters.stories.tsx +2 -2
- package/src/_stories/Chart.Forecast.stories.tsx +36 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
- package/src/_stories/Chart.Patterns.stories.tsx +20 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
- package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/Chart.stories.tsx +8 -5
- package/src/_stories/Chart.tooltip.stories.tsx +1 -1
- package/src/_stories/ChartAnnotation.stories.tsx +7 -4
- package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
- package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
- package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
- package/src/_stories/ChartEditor.stories.tsx +59 -60
- package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
- package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
- package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
- 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/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/_stories/_mock/stacked-pattern-test.json +520 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
- package/src/components/Annotations/components/findNearestDatum.ts +6 -41
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +161 -22
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
- package/src/components/BarChart/components/BarChart.Vertical.tsx +155 -22
- package/src/components/BarChart/helpers/index.ts +43 -4
- package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
- package/src/components/BarChart/helpers/useBarChart.ts +25 -3
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushChart.tsx +1 -1
- package/src/components/DeviationBar.jsx +9 -6
- package/src/components/EditorPanel/EditorPanel.tsx +563 -229
- package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +461 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +80 -67
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +188 -139
- package/src/components/EditorPanel/components/Panels/index.tsx +5 -1
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
- package/src/components/EditorPanel/editor-panel.scss +0 -20
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
- package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
- package/src/components/Forecasting/Forecasting.tsx +175 -27
- package/src/components/ForestPlot/ForestPlot.tsx +11 -7
- package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
- package/src/components/Legend/Legend.Component.tsx +114 -14
- package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/LegendWrapper.tsx +1 -1
- package/src/components/LineChart/LineChartProps.ts +0 -3
- package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
- package/src/components/LineChart/helpers.ts +1 -1
- package/src/components/LineChart/index.tsx +38 -15
- package/src/components/LinearChart.tsx +96 -84
- package/src/components/PairedBarChart.jsx +6 -4
- package/src/components/PieChart/PieChart.tsx +170 -54
- package/src/components/Regions/components/Regions.tsx +3 -24
- package/src/components/Sankey/components/Sankey.tsx +7 -1
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -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/data/initial-state.js +327 -293
- package/src/helpers/buildForecastPaletteMappings.ts +112 -0
- package/src/helpers/buildForecastPaletteOptions.ts +71 -0
- package/src/helpers/getColorScale.ts +82 -8
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
- package/src/helpers/getNewRuntime.ts +1 -1
- package/src/helpers/getTransformedData.ts +1 -1
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useChartHoverAnalytics.tsx +44 -0
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useReduceData.ts +105 -70
- package/src/hooks/useScales.ts +88 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +116 -29
- package/src/index.jsx +0 -2
- package/src/scss/main.scss +13 -80
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +5 -1
- package/src/test/CdcChart.test.jsx +8 -3
- package/src/types/ChartConfig.ts +53 -11
- package/src/types/ChartContext.ts +4 -0
- package/vite.config.js +1 -1
- package/vitest.config.ts +16 -0
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/coreStyles_chart.scss +0 -3
- package/src/helpers/configHelpers.ts +0 -28
- package/src/helpers/generateColorsArray.ts +0 -8
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
- package/src/hooks/useColorPalette.js +0 -76
|
@@ -1,37 +1,74 @@
|
|
|
1
1
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
2
|
+
import { useMemo } from 'react'
|
|
2
3
|
|
|
3
4
|
function useReduceData(config, data) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
return useMemo(() => {
|
|
6
|
+
if (!data || !config?.runtime?.seriesKeys) {
|
|
7
|
+
return { minValue: 0, maxValue: 0, existPositiveValue: false, isAllLine: false }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const isBar = config.series.every(({ type }) => type === 'Bar')
|
|
11
|
+
const isAllLine = config.series.every(({ type }) => ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg'].includes(type))
|
|
12
|
+
|
|
13
|
+
const cleanChars = value => {
|
|
14
|
+
if (value === null || value === '') {
|
|
15
|
+
return ''
|
|
16
|
+
}
|
|
17
|
+
return typeof value === 'string' ? value.replace(/[,$]/g, '') : value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getSeriesKey = seriesKey => {
|
|
21
|
+
const series = config.runtime.series.find(item => item.dataKey === seriesKey)
|
|
22
|
+
return series?.dynamicCategory ? series.originalDataKey : seriesKey
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const seriesKeysMap = new Map()
|
|
26
|
+
config.runtime.seriesKeys.forEach(key => {
|
|
27
|
+
seriesKeysMap.set(key, getSeriesKey(key))
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
let minValue = Infinity
|
|
31
|
+
let maxValue = -Infinity
|
|
32
|
+
let existPositiveValue = false
|
|
33
|
+
const stackedTotals = []
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < data.length; i++) {
|
|
36
|
+
const row = data[i]
|
|
37
|
+
let rowMax = -Infinity
|
|
38
|
+
let rowMin = Infinity
|
|
39
|
+
let stackedSum = 0
|
|
40
|
+
|
|
41
|
+
for (const key of config.runtime.seriesKeys) {
|
|
42
|
+
const seriesKey = seriesKeysMap.get(key)
|
|
43
|
+
const cleanValue = cleanChars(row[seriesKey])
|
|
44
|
+
|
|
45
|
+
if (isNumber(cleanValue)) {
|
|
46
|
+
const numValue = Number(cleanValue)
|
|
47
|
+
|
|
48
|
+
if (numValue > rowMax) rowMax = numValue
|
|
49
|
+
if (numValue < rowMin) rowMin = numValue
|
|
50
|
+
|
|
51
|
+
if (numValue >= 0) existPositiveValue = true
|
|
52
|
+
|
|
53
|
+
if (!isNaN(numValue)) stackedSum += numValue
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (rowMax > maxValue) maxValue = rowMax
|
|
58
|
+
if (rowMin < minValue) minValue = rowMin
|
|
59
|
+
|
|
60
|
+
if (!isNaN(stackedSum)) stackedTotals.push(stackedSum)
|
|
61
|
+
}
|
|
23
62
|
|
|
24
63
|
if (
|
|
25
64
|
(config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) &&
|
|
26
65
|
config.visualizationSubType === 'stacked'
|
|
27
66
|
) {
|
|
28
|
-
|
|
29
|
-
max = Math.max(...yTotals)
|
|
67
|
+
maxValue = Math.max(...stackedTotals)
|
|
30
68
|
}
|
|
31
69
|
|
|
32
70
|
if (config.visualizationSubType === 'stacked' && config.visualizationType === 'Area Chart') {
|
|
33
|
-
|
|
34
|
-
max = Math.max(...yTotals)
|
|
71
|
+
maxValue = Math.max(...stackedTotals)
|
|
35
72
|
}
|
|
36
73
|
|
|
37
74
|
if (
|
|
@@ -39,62 +76,60 @@ function useReduceData(config, data) {
|
|
|
39
76
|
config.series &&
|
|
40
77
|
config.series.dataKey
|
|
41
78
|
) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
79
|
+
let specialMax = -Infinity
|
|
80
|
+
for (const row of data) {
|
|
81
|
+
const cleanValue = cleanChars(row[config.series.dataKey])
|
|
82
|
+
if (isNumber(cleanValue)) {
|
|
83
|
+
const numValue = Number(cleanValue)
|
|
84
|
+
if (numValue > specialMax) specialMax = numValue
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
maxValue = specialMax
|
|
45
88
|
}
|
|
46
89
|
|
|
47
90
|
if (config.visualizationType === 'Combo' && config.visualizationSubType === 'stacked' && !isBar) {
|
|
48
91
|
if (config.runtime.barSeriesKeys && config.runtime.lineSeriesKeys) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
92
|
+
let barMax = -Infinity
|
|
93
|
+
let lineMax = -Infinity
|
|
94
|
+
|
|
95
|
+
for (const row of data) {
|
|
96
|
+
let barSum = 0
|
|
97
|
+
let rowLineMax = -Infinity
|
|
98
|
+
|
|
99
|
+
for (const key of config.runtime.barSeriesKeys) {
|
|
100
|
+
const cleanValue = cleanChars(row[key])
|
|
101
|
+
if (isNumber(cleanValue)) {
|
|
102
|
+
const numValue = Number(cleanValue)
|
|
103
|
+
if (!isNaN(numValue)) barSum += numValue
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const key of config.runtime.lineSeriesKeys) {
|
|
108
|
+
const cleanValue = cleanChars(row[key])
|
|
109
|
+
if (isNumber(cleanValue)) {
|
|
110
|
+
const numValue = Number(cleanValue)
|
|
111
|
+
if (numValue > rowLineMax) rowLineMax = numValue
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (barSum > barMax) barMax = barSum
|
|
116
|
+
if (rowLineMax > lineMax) lineMax = rowLineMax
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
maxValue = Math.max(barMax, lineMax)
|
|
57
120
|
}
|
|
58
121
|
}
|
|
59
122
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const getMinValueFromData = () => {
|
|
64
|
-
const minNumberFromData = Math.min(
|
|
65
|
-
...data.map(d =>
|
|
66
|
-
Math.min(
|
|
67
|
-
...config.runtime.seriesKeys.map(key => {
|
|
68
|
-
const seriesKey = getSeriesKey(key)
|
|
69
|
-
return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : Infinity
|
|
70
|
-
})
|
|
71
|
-
)
|
|
72
|
-
)
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
return String(minNumberFromData)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const findPositiveNum = () => {
|
|
79
|
-
if (!config.runtime.seriesKeys) {
|
|
80
|
-
return false
|
|
81
|
-
}
|
|
82
|
-
return config.runtime.seriesKeys.some(key => data.some(d => d[getSeriesKey(key)] >= 0))
|
|
83
|
-
}
|
|
123
|
+
if (minValue === Infinity) minValue = 0
|
|
124
|
+
if (maxValue === -Infinity) maxValue = 0
|
|
84
125
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
126
|
+
return {
|
|
127
|
+
minValue: Number(minValue),
|
|
128
|
+
maxValue: Number(maxValue),
|
|
129
|
+
existPositiveValue,
|
|
130
|
+
isAllLine
|
|
88
131
|
}
|
|
89
|
-
|
|
90
|
-
return typeof value === 'string' ? value.replace(/[,$]/g, '') : value
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const maxValue = Number(getMaxValueFromData())
|
|
94
|
-
const minValue = Number(getMinValueFromData())
|
|
95
|
-
const existPositiveValue = findPositiveNum()
|
|
96
|
-
|
|
97
|
-
return { minValue, maxValue, existPositiveValue, isAllLine }
|
|
132
|
+
}, [config, data])
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
export default useReduceData
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -13,6 +13,9 @@ import ConfigContext from '../ConfigContext'
|
|
|
13
13
|
import { ChartConfig } from '../types/ChartConfig'
|
|
14
14
|
import { ChartContext } from '../types/ChartContext'
|
|
15
15
|
import _ from 'lodash'
|
|
16
|
+
import { getYAxisAutoPadding } from '../helpers/getYAxisAutoPadding'
|
|
17
|
+
import getMinMax from '../helpers/getMinMax'
|
|
18
|
+
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
16
19
|
|
|
17
20
|
const scaleTypes = {
|
|
18
21
|
TIME: 'time',
|
|
@@ -27,23 +30,84 @@ export const TOP_PADDING = 10
|
|
|
27
30
|
type useScaleProps = {
|
|
28
31
|
config: ChartConfig // standard chart config
|
|
29
32
|
data: Object[] // standard data array
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
tableData: Object[] // table data for getMinMax
|
|
34
|
+
minValue: number // raw minimum value from data
|
|
35
|
+
maxValue: number // raw maximum value from data
|
|
36
|
+
existPositiveValue: boolean // whether data contains positive values
|
|
37
|
+
isAllLine: boolean // whether all series are line type
|
|
32
38
|
xAxisDataMapped: Object[] // array of x axis date/category items
|
|
33
39
|
xMax: number // chart svg width
|
|
34
40
|
yMax: number // chart svg height
|
|
41
|
+
needsYAxisAutoPadding?: boolean // whether Y-axis needs auto padding for label overflow
|
|
42
|
+
currentViewport?: string // current viewport for tick calculation
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
const useScales = (properties: useScaleProps) => {
|
|
38
|
-
let {
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
let {
|
|
47
|
+
xAxisDataMapped,
|
|
48
|
+
xMax,
|
|
49
|
+
yMax,
|
|
50
|
+
config,
|
|
51
|
+
data,
|
|
52
|
+
tableData,
|
|
53
|
+
minValue,
|
|
54
|
+
maxValue,
|
|
55
|
+
existPositiveValue,
|
|
56
|
+
isAllLine,
|
|
57
|
+
needsYAxisAutoPadding,
|
|
58
|
+
currentViewport
|
|
59
|
+
} = properties
|
|
60
|
+
|
|
61
|
+
const context = useContext<ChartContext>(ConfigContext)
|
|
62
|
+
const { rawData, dimensions, convertLineToBarGraph = false } = context
|
|
41
63
|
|
|
42
64
|
const [screenWidth] = dimensions
|
|
65
|
+
const isHorizontal = config.orientation === 'horizontal'
|
|
66
|
+
const { visualizationType, xAxis, forestPlot, runtime } = config
|
|
67
|
+
const isForestPlot = visualizationType === 'Forest Plot'
|
|
68
|
+
|
|
69
|
+
const minMaxProps = {
|
|
70
|
+
config,
|
|
71
|
+
minValue,
|
|
72
|
+
maxValue,
|
|
73
|
+
existPositiveValue,
|
|
74
|
+
data,
|
|
75
|
+
isAllLine,
|
|
76
|
+
tableData,
|
|
77
|
+
convertLineToBarGraph
|
|
78
|
+
}
|
|
79
|
+
let { min, max, leftMax, rightMax } = getMinMax(minMaxProps)
|
|
80
|
+
|
|
81
|
+
const yTickCount = countNumOfTicks({
|
|
82
|
+
axis: 'yAxis',
|
|
83
|
+
max,
|
|
84
|
+
runtime,
|
|
85
|
+
currentViewport,
|
|
86
|
+
isHorizontal,
|
|
87
|
+
data,
|
|
88
|
+
config,
|
|
89
|
+
min
|
|
90
|
+
})
|
|
91
|
+
const handleNumTicks = isForestPlot ? config.data.length : yTickCount
|
|
92
|
+
|
|
93
|
+
// Apply auto-padding if needed
|
|
94
|
+
if (needsYAxisAutoPadding && !isHorizontal) {
|
|
95
|
+
for (let i = 0; i < 3; i++) {
|
|
96
|
+
const scale = composeYScale({ min, max, yMax, config, leftMax })
|
|
97
|
+
const padding = getYAxisAutoPadding(scale, handleNumTicks, maxValue, minValue, config)
|
|
98
|
+
if (i === 0 || padding > 0) {
|
|
99
|
+
const adjustedConfig = { ...config, yAxis: { ...config.yAxis, scalePadding: padding, enablePadding: true } }
|
|
100
|
+
const result = getMinMax({ ...minMaxProps, config: adjustedConfig })
|
|
101
|
+
min = result.min
|
|
102
|
+
max = result.max
|
|
103
|
+
leftMax = result.leftMax
|
|
104
|
+
rightMax = result.rightMax
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
43
109
|
const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
|
|
44
110
|
const xAxisType = config.runtime.xAxis.type
|
|
45
|
-
const isHorizontal = config.orientation === 'horizontal'
|
|
46
|
-
const { visualizationType, xAxis, forestPlot } = config
|
|
47
111
|
const paddingRange = ['Area Chart', 'Forecasting'].includes(config.visualizationType) ? 1 : 1 - config.barThickness
|
|
48
112
|
// define scales
|
|
49
113
|
let xScale = null
|
|
@@ -59,7 +123,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
59
123
|
|
|
60
124
|
// handle Horizontal bars
|
|
61
125
|
if (isHorizontal) {
|
|
62
|
-
xScale = composeXScale({ min: min * 1.03,
|
|
126
|
+
xScale = composeXScale({ min: min * 1.03, max, xMax, config })
|
|
63
127
|
xScale.type = config.yAxis.type === 'logarithmic' ? scaleTypes.LOG : scaleTypes.LINEAR
|
|
64
128
|
yScale = getYScaleFunction(xAxisType, xAxisDataMapped)
|
|
65
129
|
yScale.rangeRound([0, yMax])
|
|
@@ -69,7 +133,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
69
133
|
// handle Vertical bars
|
|
70
134
|
if (!isHorizontal) {
|
|
71
135
|
xScale = composeScaleBand(xAxisDataMapped, [0, xMax], paddingRange)
|
|
72
|
-
yScale = composeYScale(
|
|
136
|
+
yScale = composeYScale({ min, max, yMax, config, leftMax })
|
|
73
137
|
seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
|
|
74
138
|
}
|
|
75
139
|
|
|
@@ -290,17 +354,29 @@ const useScales = (properties: useScaleProps) => {
|
|
|
290
354
|
}
|
|
291
355
|
}
|
|
292
356
|
}
|
|
293
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
xScale,
|
|
359
|
+
yScale,
|
|
360
|
+
seriesScale,
|
|
361
|
+
g1xScale,
|
|
362
|
+
g2xScale,
|
|
363
|
+
xScaleNoPadding,
|
|
364
|
+
xScaleAnnotation,
|
|
365
|
+
min,
|
|
366
|
+
max,
|
|
367
|
+
leftMax,
|
|
368
|
+
rightMax
|
|
369
|
+
}
|
|
294
370
|
}
|
|
295
371
|
|
|
296
372
|
export default useScales
|
|
297
373
|
|
|
298
|
-
|
|
374
|
+
const getFirstDayOfMonth = ms => {
|
|
299
375
|
const date = new Date(ms)
|
|
300
376
|
return new Date(date.getFullYear(), date.getMonth(), 1).getTime()
|
|
301
377
|
}
|
|
302
378
|
|
|
303
|
-
|
|
379
|
+
const dateFormatHasMonthButNoDays = dateFormat => {
|
|
304
380
|
return (
|
|
305
381
|
(dateFormat.includes('%b') ||
|
|
306
382
|
dateFormat.includes('%B') ||
|
|
@@ -352,28 +428,6 @@ export const getTickValues = (xAxisDataMapped, xScale, num, config) => {
|
|
|
352
428
|
}
|
|
353
429
|
}
|
|
354
430
|
|
|
355
|
-
// Ensure that the last tick is shown for charts with a "Date (Linear Scale)" scale
|
|
356
|
-
export const filterAndShiftLinearDateTicks = (config, axisProps, xAxisDataMapped, formatDate) => {
|
|
357
|
-
let ticks = axisProps.ticks
|
|
358
|
-
const filteredTickValues = getTicks(axisProps.scale, axisProps.numTicks)
|
|
359
|
-
if (filteredTickValues.length < xAxisDataMapped.length) {
|
|
360
|
-
let shift = 0
|
|
361
|
-
const lastIdx = xAxisDataMapped.indexOf(filteredTickValues[filteredTickValues.length - 1])
|
|
362
|
-
if (lastIdx < xAxisDataMapped.length - 1) {
|
|
363
|
-
shift = !config.xAxis.sortByRecentDate
|
|
364
|
-
? xAxisDataMapped.length - 1 - lastIdx
|
|
365
|
-
: xAxisDataMapped.indexOf(filteredTickValues[0]) * -1
|
|
366
|
-
}
|
|
367
|
-
ticks = filteredTickValues.map(value => {
|
|
368
|
-
return axisProps.ticks[axisProps.ticks.findIndex(tick => tick.value === value) + shift]
|
|
369
|
-
})
|
|
370
|
-
}
|
|
371
|
-
ticks.forEach((tick, i) => {
|
|
372
|
-
tick.formattedValue = formatDate(tick.value, i, ticks)
|
|
373
|
-
})
|
|
374
|
-
return ticks
|
|
375
|
-
}
|
|
376
|
-
|
|
377
431
|
/// helper functions
|
|
378
432
|
const composeXScale = ({ min, max, xMax, config }) => {
|
|
379
433
|
// Adjust min value if using logarithmic scale
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../ConfigContext'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Custom hook to handle synchronized tooltips in small multiples.
|
|
6
|
+
* This hook provides mouse event handlers that coordinate tooltip display across multiple chart tiles.
|
|
7
|
+
*
|
|
8
|
+
* @param xMax - The maximum x coordinate of the chart area
|
|
9
|
+
* @param yMax - The maximum y coordinate of the chart area
|
|
10
|
+
* @param getXValueFromCoordinate - Function to convert pixel x-coordinate to data value
|
|
11
|
+
* @returns Object with onMouseMove and onMouseLeave handlers, or null if not in small multiples
|
|
12
|
+
*/
|
|
13
|
+
export const useSmallMultipleSynchronization = (
|
|
14
|
+
xMax: number,
|
|
15
|
+
yMax: number,
|
|
16
|
+
getXValueFromCoordinate: (x: number) => any
|
|
17
|
+
) => {
|
|
18
|
+
const { config, handleSmallMultipleHover } = useContext(ConfigContext)
|
|
19
|
+
|
|
20
|
+
// If not in small multiples mode, return null handlers
|
|
21
|
+
if (!handleSmallMultipleHover) {
|
|
22
|
+
return {
|
|
23
|
+
onMouseMove: null,
|
|
24
|
+
onMouseLeave: null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const yAxisSize = Number(config.yAxis.size || 0)
|
|
29
|
+
|
|
30
|
+
const onMouseMove = (event: any) => {
|
|
31
|
+
const svgRect = event.currentTarget.getBoundingClientRect()
|
|
32
|
+
const x = event.clientX - svgRect.left
|
|
33
|
+
const y = event.clientY - svgRect.top
|
|
34
|
+
|
|
35
|
+
// Only trigger synchronized tooltips when mouse is over the valid chart area
|
|
36
|
+
// (to the right of the Y-axis and within chart bounds)
|
|
37
|
+
const isOverChartArea = x >= yAxisSize && x <= yAxisSize + xMax && y >= 0 && y <= yMax
|
|
38
|
+
|
|
39
|
+
if (isOverChartArea) {
|
|
40
|
+
const xAxisValue = getXValueFromCoordinate(x - yAxisSize)
|
|
41
|
+
if (xAxisValue !== null && xAxisValue !== undefined) {
|
|
42
|
+
handleSmallMultipleHover(xAxisValue, y)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If we're not over a valid area or couldn't get a value, hide synchronized tooltips
|
|
48
|
+
handleSmallMultipleHover(null, null)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const onMouseLeave = () => {
|
|
52
|
+
handleSmallMultipleHover(null, null)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
onMouseMove,
|
|
57
|
+
onMouseLeave
|
|
58
|
+
}
|
|
59
|
+
}
|