@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
|
@@ -8,12 +8,21 @@ import ConfigContext from '../ConfigContext'
|
|
|
8
8
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
9
9
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
10
10
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
11
|
+
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
11
12
|
|
|
12
13
|
const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
13
|
-
const {
|
|
14
|
+
const {
|
|
15
|
+
config,
|
|
16
|
+
colorScale,
|
|
17
|
+
transformedData: data,
|
|
18
|
+
formatNumber,
|
|
19
|
+
seriesHighlight,
|
|
20
|
+
vizViewport
|
|
21
|
+
} = useContext(ConfigContext)
|
|
14
22
|
|
|
15
23
|
if (!config || config?.series?.length < 2) return
|
|
16
24
|
|
|
25
|
+
const labelFontSize = isMobileFontViewport(vizViewport) ? 13 : 16
|
|
17
26
|
const borderWidth = config.barHasBorder === 'true' ? 1 : 0
|
|
18
27
|
const halfWidth = width / 2
|
|
19
28
|
const offset = 1.02 // Offset of the left bar from the Axis
|
|
@@ -109,7 +118,10 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
109
118
|
const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
|
|
110
119
|
config.heights.horizontal = totalheight
|
|
111
120
|
// check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
|
|
112
|
-
const textWidth = getTextWidth(
|
|
121
|
+
const textWidth = getTextWidth(
|
|
122
|
+
formatNumber(d[groupOne.dataKey], 'left'),
|
|
123
|
+
`normal ${labelFontSize}px sans-serif`
|
|
124
|
+
)
|
|
113
125
|
const textFits = textWidth < barWidth - 5 // minus padding dx(5)
|
|
114
126
|
|
|
115
127
|
return (
|
|
@@ -141,6 +153,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
141
153
|
x={halfWidth - barWidth}
|
|
142
154
|
y={y + config.barHeight / 2}
|
|
143
155
|
fill={textFits ? groupOne.labelColor : '#000'}
|
|
156
|
+
fontSize={labelFontSize}
|
|
144
157
|
>
|
|
145
158
|
{formatNumber(d[groupOne.dataKey], 'left')}
|
|
146
159
|
</Text>
|
|
@@ -168,7 +181,10 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
168
181
|
const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
|
|
169
182
|
config.heights.horizontal = totalheight
|
|
170
183
|
// check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
|
|
171
|
-
const textWidth = getTextWidth(
|
|
184
|
+
const textWidth = getTextWidth(
|
|
185
|
+
formatNumber(d[groupTwo.dataKey], 'left'),
|
|
186
|
+
`normal ${labelFontSize}px sans-serif`
|
|
187
|
+
)
|
|
172
188
|
const isTextFits = textWidth < barWidth - 5 // minus padding dx(5)
|
|
173
189
|
|
|
174
190
|
return (
|
|
@@ -207,6 +223,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
207
223
|
x={halfWidth + barWidth}
|
|
208
224
|
y={y + config.barHeight / 2}
|
|
209
225
|
fill={isTextFits ? groupTwo.labelColor : '#000'}
|
|
226
|
+
fontSize={labelFontSize}
|
|
210
227
|
>
|
|
211
228
|
{formatNumber(d[groupTwo.dataKey], 'left')}
|
|
212
229
|
</Text>
|
|
@@ -1,200 +1,422 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../../ConfigContext'
|
|
3
3
|
import { ChartContext } from '../../../types/ChartContext'
|
|
4
4
|
import { Text } from '@visx/text'
|
|
5
5
|
import { Group } from '@visx/group'
|
|
6
6
|
import { formatDate, isDateScale } from '@cdc/core/helpers/cove/date.js'
|
|
7
7
|
|
|
8
|
+
// Constants for visualization types
|
|
9
|
+
const VIZ_TYPES = {
|
|
10
|
+
BAR: 'Bar',
|
|
11
|
+
LINE: 'Line',
|
|
12
|
+
AREA: 'Area Chart',
|
|
13
|
+
COMBO: 'Combo'
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
type Region = {
|
|
17
|
+
from: string
|
|
18
|
+
to: string
|
|
19
|
+
fromType?: 'Fixed' | 'Previous Days'
|
|
20
|
+
toType?: 'Fixed' | 'Last Date'
|
|
21
|
+
label: string
|
|
22
|
+
background: string
|
|
23
|
+
color: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type XScale = {
|
|
27
|
+
(value: unknown): number
|
|
28
|
+
domain: () => unknown[]
|
|
29
|
+
bandwidth?: () => number
|
|
30
|
+
}
|
|
31
|
+
|
|
8
32
|
type RegionsProps = {
|
|
9
|
-
xScale:
|
|
33
|
+
xScale: XScale
|
|
10
34
|
yMax: number
|
|
11
35
|
barWidth?: number
|
|
12
36
|
totalBarsInGroup?: number
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
37
|
+
xMax?: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type HighlightedAreaProps = {
|
|
41
|
+
x: number
|
|
42
|
+
width: number
|
|
43
|
+
yMax: number
|
|
44
|
+
background: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const HighlightedArea: React.FC<HighlightedAreaProps> = ({ x, width, yMax, background }) => (
|
|
48
|
+
<rect x={x} y={0} width={width} height={yMax} fill={background} opacity={0.3} />
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
/** Find the closest date in domain to a target date */
|
|
52
|
+
const findClosestDate = <T,>(targetTime: number, domain: T[], getTime: (d: T) => number): T => {
|
|
53
|
+
let closest = domain[0]
|
|
54
|
+
let minDiff = Math.abs(targetTime - getTime(closest))
|
|
55
|
+
|
|
56
|
+
for (let i = 1; i < domain.length; i++) {
|
|
57
|
+
const diff = Math.abs(targetTime - getTime(domain[i]))
|
|
58
|
+
if (diff < minDiff) {
|
|
59
|
+
minDiff = diff
|
|
60
|
+
closest = domain[i]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return closest
|
|
19
64
|
}
|
|
20
65
|
|
|
66
|
+
/** Check if visualization type is line-like (Line or Area Chart) */
|
|
67
|
+
const isLineLike = (type: string): boolean => type === VIZ_TYPES.LINE || type === VIZ_TYPES.AREA
|
|
68
|
+
|
|
69
|
+
/** Check if visualization type is bar-like (Bar or Combo) */
|
|
70
|
+
const isBarLike = (type: string): boolean => type === VIZ_TYPES.BAR || type === VIZ_TYPES.COMBO
|
|
71
|
+
|
|
21
72
|
// TODO: should regions be removed on categorical axis?
|
|
22
|
-
const Regions: React.FC<RegionsProps> = ({
|
|
23
|
-
xScale,
|
|
24
|
-
barWidth = 0,
|
|
25
|
-
totalBarsInGroup = 1,
|
|
26
|
-
yMax,
|
|
27
|
-
handleTooltipMouseOff,
|
|
28
|
-
handleTooltipMouseOver,
|
|
29
|
-
handleTooltipClick,
|
|
30
|
-
tooltipData,
|
|
31
|
-
showTooltip,
|
|
32
|
-
hideTooltip
|
|
33
|
-
}) => {
|
|
73
|
+
const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, xMax }) => {
|
|
34
74
|
const { parseDate, config } = useContext<ChartContext>(ConfigContext)
|
|
35
75
|
|
|
36
|
-
const {
|
|
37
|
-
|
|
76
|
+
const { regions, visualizationType, orientation, xAxis } = config
|
|
77
|
+
|
|
78
|
+
const getBarOffset = (): number => (barWidth * totalBarsInGroup) / 2
|
|
79
|
+
|
|
80
|
+
// ============================================
|
|
81
|
+
// HELPER FUNCTIONS FOR PREVIOUS DAYS
|
|
82
|
+
// ============================================
|
|
83
|
+
|
|
84
|
+
const calculatePreviousDaysFrom = (region: Region, axisType: string): number => {
|
|
85
|
+
const previousDays = Number(region.from) || 0
|
|
86
|
+
const domain = xScale.domain()
|
|
87
|
+
|
|
88
|
+
// Determine the "to" reference date
|
|
89
|
+
const toRefDate =
|
|
90
|
+
region.toType === 'Last Date'
|
|
91
|
+
? new Date(domain[domain.length - 1] as string | number).getTime()
|
|
92
|
+
: new Date(region.to)
|
|
93
|
+
|
|
94
|
+
const toFormatted = formatDate(config.xAxis.dateParseFormat, toRefDate)
|
|
95
|
+
const toDate = new Date(toFormatted)
|
|
96
|
+
const fromDate = new Date(toDate)
|
|
97
|
+
fromDate.setDate(fromDate.getDate() - previousDays)
|
|
38
98
|
|
|
39
|
-
|
|
40
|
-
let from
|
|
99
|
+
let closestValue: unknown
|
|
41
100
|
|
|
42
|
-
|
|
43
|
-
|
|
101
|
+
if (axisType === 'date') {
|
|
102
|
+
const fromTime = new Date(formatDate(xAxis.dateParseFormat, fromDate)).getTime()
|
|
103
|
+
closestValue = findClosestDate(fromTime, domain as number[], d => d)
|
|
104
|
+
} else if (axisType === 'categorical') {
|
|
105
|
+
const fromTime = fromDate.getTime()
|
|
106
|
+
closestValue = findClosestDate(fromTime, domain as string[], d => new Date(d).getTime())
|
|
107
|
+
} else if (axisType === 'date-time') {
|
|
108
|
+
closestValue = fromDate.getTime()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return xScale(closestValue)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================
|
|
115
|
+
// LINE/AREA CHART LOGIC
|
|
116
|
+
// ============================================
|
|
117
|
+
|
|
118
|
+
const getLineFromValue_Categorical = (region: Region): number => {
|
|
119
|
+
let from: number
|
|
120
|
+
if (region.fromType === 'Previous Days') {
|
|
121
|
+
from = calculatePreviousDaysFrom(region, 'categorical')
|
|
122
|
+
} else {
|
|
123
|
+
from = xScale(region.from)
|
|
124
|
+
}
|
|
125
|
+
// Add left padding (yAxis.size) + half bandwidth to center on the category
|
|
126
|
+
let scalePadding = Number(config.yAxis.size)
|
|
127
|
+
if (xScale.bandwidth) {
|
|
128
|
+
scalePadding += xScale.bandwidth() / 2
|
|
129
|
+
}
|
|
130
|
+
return from + scalePadding
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const getLineToValue_Categorical = (region: Region): number => {
|
|
134
|
+
if (region.toType === 'Last Date') {
|
|
135
|
+
return calculateLineLastDatePosition_Categorical()
|
|
136
|
+
}
|
|
137
|
+
let to = xScale(region.to)
|
|
138
|
+
// Add left padding (yAxis.size) + half bandwidth
|
|
139
|
+
let scalePadding = Number(config.yAxis.size)
|
|
140
|
+
if (xScale.bandwidth) {
|
|
141
|
+
scalePadding += xScale.bandwidth() / 2
|
|
142
|
+
}
|
|
143
|
+
return to + scalePadding
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const getLineFromValue_Date = (region: Region): number => {
|
|
147
|
+
let from: number
|
|
148
|
+
if (region.fromType === 'Previous Days') {
|
|
149
|
+
from = calculatePreviousDaysFrom(region, 'date')
|
|
150
|
+
} else {
|
|
151
|
+
// For date scale (band), we need to find the value in the domain
|
|
152
|
+
// Parse the region date to match the format in the domain
|
|
44
153
|
const date = new Date(region.from)
|
|
45
154
|
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
46
|
-
from = xScale(parsedDate)
|
|
47
155
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
156
|
+
// For band scales, find the closest date in the domain
|
|
157
|
+
const domain = xScale.domain() as number[]
|
|
158
|
+
const closestDate = findClosestDate(parsedDate, domain, d => d)
|
|
159
|
+
from = xScale(closestDate)
|
|
160
|
+
}
|
|
161
|
+
// Add left padding (yAxis.size) + half bandwidth
|
|
162
|
+
let scalePadding = Number(config.yAxis.size)
|
|
163
|
+
if (xScale.bandwidth) {
|
|
164
|
+
scalePadding += xScale.bandwidth() / 2
|
|
51
165
|
}
|
|
166
|
+
return from + scalePadding
|
|
167
|
+
}
|
|
52
168
|
|
|
53
|
-
|
|
169
|
+
const getLineToValue_Date = (region: Region): number => {
|
|
170
|
+
if (region.toType === 'Last Date') {
|
|
171
|
+
return calculateLineLastDatePosition_Date()
|
|
172
|
+
}
|
|
173
|
+
// For date scale (band), we need to find the value in the domain
|
|
174
|
+
// Parse the region date to match the format in the domain
|
|
175
|
+
const parsedDate = parseDate(region.to).getTime()
|
|
176
|
+
|
|
177
|
+
// For band scales, find the closest date in the domain
|
|
178
|
+
const domain = xScale.domain() as number[]
|
|
179
|
+
const closestDate = findClosestDate(parsedDate, domain, d => d)
|
|
180
|
+
let to = xScale(closestDate)
|
|
181
|
+
|
|
182
|
+
// Add left padding (yAxis.size) + half bandwidth
|
|
183
|
+
let scalePadding = Number(config.yAxis.size)
|
|
184
|
+
if (xScale.bandwidth) {
|
|
185
|
+
scalePadding += xScale.bandwidth() / 2
|
|
186
|
+
}
|
|
187
|
+
return to + scalePadding
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const getLineFromValue_DateTime = (region: Region): number => {
|
|
54
191
|
if (region.fromType === 'Previous Days') {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (xAxis.type === 'date') {
|
|
66
|
-
from = new Date(formatDate(xAxis.dateParseFormat, from)).getTime()
|
|
67
|
-
|
|
68
|
-
let closestDate = domain[0]
|
|
69
|
-
let minDiff = Math.abs(from - closestDate)
|
|
70
|
-
|
|
71
|
-
for (let i = 1; i < domain.length; i++) {
|
|
72
|
-
const diff = Math.abs(from - domain[i])
|
|
73
|
-
if (diff < minDiff) {
|
|
74
|
-
minDiff = diff
|
|
75
|
-
closestDate = domain[i]
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
from = closestDate
|
|
79
|
-
}
|
|
192
|
+
const from = calculatePreviousDaysFrom(region, 'date-time')
|
|
193
|
+
return from + Number(config.yAxis.size)
|
|
194
|
+
}
|
|
195
|
+
const date = new Date(region.from)
|
|
196
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
197
|
+
let from = xScale(parsedDate)
|
|
198
|
+
// For date-time, xScale returns correct position (no bandwidth), just add left padding
|
|
199
|
+
return from + Number(config.yAxis.size)
|
|
200
|
+
}
|
|
80
201
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
202
|
+
const getLineToValue_DateTime = (region: Region): number => {
|
|
203
|
+
if (region.toType === 'Last Date') {
|
|
204
|
+
return calculateLineLastDatePosition_DateTime()
|
|
205
|
+
}
|
|
206
|
+
let to = xScale(parseDate(region.to).getTime())
|
|
207
|
+
return to + Number(config.yAxis.size)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const calculateLineLastDatePosition_Categorical = (): number => {
|
|
211
|
+
const chartStart = Number(config.yAxis.size || 0)
|
|
212
|
+
// Extend to the right edge of the chart
|
|
213
|
+
return chartStart + (xMax || 0)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const calculateLineLastDatePosition_Date = (): number => {
|
|
217
|
+
const chartStart = Number(config.yAxis.size || 0)
|
|
218
|
+
// For date scale line charts with Last Date, extend to the right edge of the chart
|
|
219
|
+
return chartStart + (xMax || 0)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const calculateLineLastDatePosition_DateTime = (): number => {
|
|
223
|
+
const domain = xScale.domain()
|
|
224
|
+
const lastDate = domain[domain.length - 1]
|
|
225
|
+
const lastDatePosition = xScale(lastDate)
|
|
226
|
+
// Match the non-Last Date logic: just add yAxis.size
|
|
227
|
+
return Number(lastDatePosition + Number(config.yAxis.size))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================
|
|
231
|
+
// BAR CHART LOGIC
|
|
232
|
+
// ============================================
|
|
95
233
|
|
|
96
|
-
|
|
234
|
+
const getBarFromValue_Categorical = (region: Region): number => {
|
|
235
|
+
if (region.fromType === 'Previous Days') {
|
|
236
|
+
return calculatePreviousDaysFrom(region, 'categorical')
|
|
97
237
|
}
|
|
238
|
+
return xScale(region.from)
|
|
239
|
+
}
|
|
98
240
|
|
|
99
|
-
|
|
100
|
-
|
|
241
|
+
const getBarToValue_Categorical = (region: Region): number => {
|
|
242
|
+
if (region.toType === 'Last Date') {
|
|
243
|
+
return calculateBarLastDatePosition_Categorical()
|
|
101
244
|
}
|
|
245
|
+
let to = xScale(region.to)
|
|
246
|
+
return to + barWidth * totalBarsInGroup
|
|
247
|
+
}
|
|
102
248
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
scalePadding += xScale.bandwidth() / 2
|
|
107
|
-
}
|
|
108
|
-
from = from + scalePadding
|
|
249
|
+
const getBarFromValue_Date = (region: Region): number => {
|
|
250
|
+
if (region.fromType === 'Previous Days') {
|
|
251
|
+
return calculatePreviousDaysFrom(region, 'date')
|
|
109
252
|
}
|
|
253
|
+
// For date scale (band), we need to find the value in the domain
|
|
254
|
+
const date = new Date(region.from)
|
|
255
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
256
|
+
|
|
257
|
+
// For band scales, find the closest date in the domain
|
|
258
|
+
const domain = xScale.domain() as number[]
|
|
259
|
+
const closestDate = findClosestDate(parsedDate, domain, d => d)
|
|
260
|
+
return xScale(closestDate)
|
|
261
|
+
}
|
|
110
262
|
|
|
111
|
-
|
|
112
|
-
|
|
263
|
+
const getBarToValue_Date = (region: Region): number => {
|
|
264
|
+
if (region.toType === 'Last Date') {
|
|
265
|
+
return calculateBarLastDatePosition_Date()
|
|
113
266
|
}
|
|
267
|
+
// For date scale (band), we need to find the value in the domain
|
|
268
|
+
const parsedDate = parseDate(region.to).getTime()
|
|
269
|
+
|
|
270
|
+
// For band scales, find the closest date in the domain
|
|
271
|
+
const domain = xScale.domain() as number[]
|
|
272
|
+
const closestDate = findClosestDate(parsedDate, domain, d => d)
|
|
273
|
+
let to = xScale(closestDate)
|
|
114
274
|
|
|
115
|
-
return
|
|
275
|
+
return to + barWidth * totalBarsInGroup
|
|
116
276
|
}
|
|
117
277
|
|
|
118
|
-
const
|
|
119
|
-
let
|
|
278
|
+
const getBarFromValue_DateTime = (region: Region): number => {
|
|
279
|
+
let from: number
|
|
280
|
+
if (region.fromType === 'Previous Days') {
|
|
281
|
+
from = calculatePreviousDaysFrom(region, 'date-time')
|
|
282
|
+
} else {
|
|
283
|
+
const date = new Date(region.from)
|
|
284
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
285
|
+
from = xScale(parsedDate)
|
|
286
|
+
}
|
|
287
|
+
return from - getBarOffset()
|
|
288
|
+
}
|
|
120
289
|
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
290
|
+
const getBarToValue_DateTime = (region: Region): number => {
|
|
291
|
+
if (region.toType === 'Last Date') {
|
|
292
|
+
return calculateBarLastDatePosition_DateTime()
|
|
124
293
|
}
|
|
294
|
+
let to = xScale(parseDate(region.to).getTime())
|
|
295
|
+
return to - getBarOffset()
|
|
296
|
+
}
|
|
125
297
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
298
|
+
const calculateBarLastDatePosition_Categorical = (): number => {
|
|
299
|
+
const domain = xScale.domain()
|
|
300
|
+
const lastDate = domain[domain.length - 1]
|
|
301
|
+
const lastDatePosition = xScale(lastDate)
|
|
302
|
+
const bandwidth = xScale.bandwidth ? xScale.bandwidth() : 0
|
|
303
|
+
// For categorical bars, extend to the end of the last bar
|
|
304
|
+
// Don't add chartStart - xScale already returns positions in the chart coordinate space
|
|
305
|
+
return xMax
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const calculateBarLastDatePosition_Date = (): number => {
|
|
309
|
+
const domain = xScale.domain()
|
|
310
|
+
const lastDate = domain[domain.length - 1]
|
|
311
|
+
const lastDatePosition = xScale(lastDate)
|
|
312
|
+
const offset = barWidth * totalBarsInGroup
|
|
313
|
+
// Don't add chartStart - xScale already returns positions in chart coordinate space
|
|
314
|
+
return Number(lastDatePosition + offset)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const calculateBarLastDatePosition_DateTime = (): number => {
|
|
318
|
+
const domain = xScale.domain()
|
|
319
|
+
const lastDate = domain[domain.length - 1]
|
|
320
|
+
const lastDatePosition = xScale(lastDate)
|
|
321
|
+
// For date-time bars, don't add chartStart - xScale returns positions in chart coordinate space
|
|
322
|
+
// Also don't subtract barOffset since we want to extend to the edge
|
|
323
|
+
return Number(lastDatePosition)
|
|
324
|
+
}
|
|
130
325
|
|
|
131
|
-
|
|
132
|
-
|
|
326
|
+
// ============================================
|
|
327
|
+
// MAIN ROUTING FUNCTIONS
|
|
328
|
+
// ============================================
|
|
329
|
+
|
|
330
|
+
const getFromValue = (region: Region): number => {
|
|
331
|
+
const isLine = isLineLike(visualizationType)
|
|
332
|
+
const isBar = isBarLike(visualizationType)
|
|
333
|
+
|
|
334
|
+
// LINE/AREA CHARTS
|
|
335
|
+
if (isLine) {
|
|
336
|
+
if (xAxis.type === 'categorical') {
|
|
337
|
+
return getLineFromValue_Categorical(region)
|
|
338
|
+
} else if (xAxis.type === 'date') {
|
|
339
|
+
return getLineFromValue_Date(region)
|
|
340
|
+
} else if (xAxis.type === 'date-time') {
|
|
341
|
+
return getLineFromValue_DateTime(region)
|
|
133
342
|
}
|
|
134
343
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (visualizationType === 'Line' || visualizationType === 'Area Chart') {
|
|
146
|
-
let scalePadding = Number(config.yAxis.size)
|
|
147
|
-
if (xScale.bandwidth) {
|
|
148
|
-
scalePadding += xScale.bandwidth() / 2
|
|
344
|
+
|
|
345
|
+
// BAR CHARTS
|
|
346
|
+
if (isBar) {
|
|
347
|
+
if (xAxis.type === 'categorical') {
|
|
348
|
+
return getBarFromValue_Categorical(region)
|
|
349
|
+
} else if (xAxis.type === 'date') {
|
|
350
|
+
return getBarFromValue_Date(region)
|
|
351
|
+
} else if (xAxis.type === 'date-time') {
|
|
352
|
+
return getBarFromValue_DateTime(region)
|
|
149
353
|
}
|
|
150
|
-
to = to + scalePadding
|
|
151
354
|
}
|
|
152
355
|
|
|
153
|
-
|
|
154
|
-
|
|
356
|
+
return 0
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const getToValue = (region: Region): number => {
|
|
360
|
+
const isLine = isLineLike(visualizationType)
|
|
361
|
+
const isBar = isBarLike(visualizationType)
|
|
362
|
+
|
|
363
|
+
// LINE/AREA CHARTS
|
|
364
|
+
if (isLine) {
|
|
365
|
+
if (xAxis.type === 'categorical') {
|
|
366
|
+
return getLineToValue_Categorical(region)
|
|
367
|
+
} else if (xAxis.type === 'date') {
|
|
368
|
+
return getLineToValue_Date(region)
|
|
369
|
+
} else if (xAxis.type === 'date-time') {
|
|
370
|
+
return getLineToValue_DateTime(region)
|
|
371
|
+
}
|
|
155
372
|
}
|
|
156
373
|
|
|
157
|
-
|
|
158
|
-
|
|
374
|
+
// BAR CHARTS
|
|
375
|
+
if (isBar) {
|
|
376
|
+
if (xAxis.type === 'categorical') {
|
|
377
|
+
return getBarToValue_Categorical(region)
|
|
378
|
+
} else if (xAxis.type === 'date') {
|
|
379
|
+
return getBarToValue_Date(region)
|
|
380
|
+
} else if (xAxis.type === 'date-time') {
|
|
381
|
+
return getBarToValue_DateTime(region)
|
|
382
|
+
}
|
|
159
383
|
}
|
|
160
|
-
|
|
384
|
+
|
|
385
|
+
return 0
|
|
161
386
|
}
|
|
162
387
|
|
|
163
|
-
const getWidth = (to, from) => to - from
|
|
388
|
+
const getWidth = (to: number, from: number): number => Math.max(0, to - from)
|
|
164
389
|
|
|
165
|
-
if (regions
|
|
166
|
-
return regions.map(region => {
|
|
167
|
-
const from = getFromValue(region)
|
|
168
|
-
const to = getToValue(region)
|
|
169
|
-
const width = getWidth(to, from)
|
|
390
|
+
if (!regions || orientation !== 'vertical') return null
|
|
170
391
|
|
|
171
|
-
|
|
172
|
-
|
|
392
|
+
const chartStart = Number(config.yAxis.size || 0)
|
|
393
|
+
const chartEnd = xMax !== undefined ? chartStart + xMax : chartStart + 1000
|
|
173
394
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
395
|
+
return regions.map((region: Region) => {
|
|
396
|
+
const from = getFromValue(region)
|
|
397
|
+
const to = getToValue(region)
|
|
177
398
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
399
|
+
// Validate computed positions
|
|
400
|
+
if (from === undefined || isNaN(from) || to === undefined || isNaN(to)) {
|
|
401
|
+
return null
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Clip region to visible chart area
|
|
405
|
+
const clippedFrom = Math.max(chartStart, from)
|
|
406
|
+
const clippedTo = Math.min(chartEnd, to)
|
|
407
|
+
const width = getWidth(clippedTo, clippedFrom)
|
|
408
|
+
|
|
409
|
+
if (width <= 0) return null
|
|
410
|
+
|
|
411
|
+
return (
|
|
412
|
+
<Group height={100} fill='red' className='regions regions-group--line' key={region.label} pointerEvents='none'>
|
|
413
|
+
<HighlightedArea x={clippedFrom} width={width} yMax={yMax} background={region.background} />
|
|
414
|
+
<Text x={clippedFrom + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
|
|
415
|
+
{region.label}
|
|
416
|
+
</Text>
|
|
417
|
+
</Group>
|
|
418
|
+
)
|
|
419
|
+
})
|
|
198
420
|
}
|
|
199
421
|
|
|
200
422
|
export default Regions
|
|
@@ -42,8 +42,8 @@ const ScatterPlot = ({ xScale, yScale }) => {
|
|
|
42
42
|
? `${config.runtime.seriesLabels[s] || ''}<br/>`
|
|
43
43
|
: ''
|
|
44
44
|
}
|
|
45
|
-
${config.xAxis.label}: ${formatNumber(item[config.xAxis.dataKey], 'bottom')} <br/>
|
|
46
|
-
${config.yAxis.label}: ${formatNumber(item[s], 'left')}<br/>
|
|
45
|
+
${config.runtime?.xAxis?.label || config.xAxis.label}: ${formatNumber(item[config.xAxis.dataKey], 'bottom')} <br/>
|
|
46
|
+
${config.runtime?.yAxis?.label || config.yAxis.label}: ${formatNumber(item[s], 'left')}<br/>
|
|
47
47
|
${additionalColumns
|
|
48
48
|
.map(
|
|
49
49
|
([label, name, options]) =>
|