@cdc/chart 4.25.11 → 4.26.2
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.local.md +79 -0
- package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
- package/dist/cdcchart.js +51401 -50814
- package/examples/default.json +378 -0
- package/examples/feature/__data__/horizon-chart-data.json +373 -0
- package/examples/feature/annotations/index.json +3 -6
- package/examples/feature/horizon/horizon-chart.json +395 -0
- package/examples/feature/pie/planet-pie-example-config.json +48 -2
- package/examples/line-chart-states.json +1085 -0
- package/examples/private/123.json +694 -0
- package/examples/private/DEV-12100.json +1303 -0
- package/examples/private/anchor-issue.json +4094 -0
- package/examples/private/backwards-slider.json +10430 -0
- package/examples/private/cat-y.json +1235 -0
- package/examples/private/data-points.json +228 -0
- package/examples/private/georgia.csv +160 -0
- package/examples/private/height.json +3915 -0
- package/examples/private/links.json +569 -0
- package/examples/private/quadrant.txt +30 -0
- package/examples/private/test-forecast.json +5510 -0
- package/examples/private/timeline-data.json +1 -0
- package/examples/private/timeline.json +389 -0
- package/examples/private/warming-stripe-test.json +2578 -0
- package/examples/private/warming-stripes.json +4763 -0
- package/examples/radar-chart-simple.json +133 -0
- package/examples/radar-chart.json +148 -0
- package/examples/tech-adoption-with-links.json +560 -0
- package/index.html +1 -36
- package/package.json +59 -60
- package/src/CdcChartComponent.tsx +206 -89
- package/src/_stories/Chart.Anchors.stories.tsx +10 -0
- package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
- package/src/_stories/Chart.CI.stories.tsx +13 -0
- package/src/_stories/Chart.Combo.stories.tsx +17 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +4 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
- package/src/_stories/Chart.Filters.stories.tsx +4 -0
- package/src/_stories/Chart.Forecast.stories.tsx +4 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
- package/src/_stories/Chart.Patterns.stories.tsx +4 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +161 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +216 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +312 -0
- package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
- package/src/_stories/Chart.stories.tsx +45 -0
- package/src/_stories/Chart.tooltip.stories.tsx +7 -0
- package/src/_stories/ChartAnnotation.stories.tsx +10 -0
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +11 -6
- package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
- package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
- package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
- package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
- package/src/_stories/ChartBrush.stories.tsx +57 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +3 -5
- package/src/_stories/ChartEditor.stories.tsx +7 -0
- package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
- package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +34 -0
- package/src/_stories/_mock/brush_continuous.json +86 -0
- package/src/_stories/_mock/brush_date_large.json +176 -0
- package/src/_stories/_mock/brush_enabled.json +326 -0
- package/src/_stories/_mock/brush_mock.json +2 -69
- package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
- package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +11 -17
- package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
- package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
- package/src/components/Annotations/components/AnnotationList.styles.css +4 -10
- package/src/components/Annotations/components/AnnotationList.tsx +5 -4
- package/src/components/Annotations/components/findNearestDatum.ts +75 -85
- package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -2
- package/src/components/Axis/BottomAxis.tsx +270 -0
- package/src/components/Axis/Categorical.Axis.tsx +6 -7
- package/src/components/Axis/LeftAxis.tsx +404 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
- package/src/components/Axis/PairedBarAxis.tsx +186 -0
- package/src/components/Axis/README.md +94 -0
- package/src/components/Axis/RightAxis.tsx +108 -0
- package/src/components/Axis/axis.constants.ts +21 -0
- package/src/components/Axis/index.ts +7 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +178 -24
- 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 +6 -8
- package/src/components/BarChart/components/BarChart.tsx +7 -1
- package/src/components/BarChart/components/context.tsx +1 -0
- package/src/components/BarChart/helpers/useBarChart.ts +14 -2
- package/src/components/Brush/BrushSelector.tsx +1390 -0
- package/src/components/Brush/MiniChartPreview.tsx +400 -0
- package/src/components/DeviationBar.jsx +9 -7
- package/src/components/EditorPanel/EditorPanel.tsx +2734 -2595
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +60 -22
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +137 -30
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +2 -0
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +0 -1
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +30 -25
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +42 -28
- package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +81 -39
- package/src/components/HorizonChart/HorizonChart.tsx +131 -0
- package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
- package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
- package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
- package/src/components/HorizonChart/index.tsx +3 -0
- package/src/components/Legend/Legend.Component.tsx +52 -4
- package/src/components/Legend/Legend.tsx +4 -3
- package/src/components/Legend/LegendValueRange.tsx +77 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +164 -2
- package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
- package/src/components/Legend/helpers/index.ts +10 -6
- package/src/components/LineChart/helpers/README.md +292 -0
- package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
- package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
- package/src/components/LineChart/index.tsx +44 -8
- package/src/components/LinearChart/README.md +109 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
- package/src/components/LinearChart/linearChart.constants.ts +84 -0
- package/src/components/LinearChart/tests/LinearChart.test.tsx +201 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +129 -0
- package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
- package/src/components/LinearChart.tsx +338 -1082
- package/src/components/PairedBarChart.jsx +20 -3
- package/src/components/PieChart/PieChart.tsx +1 -1
- package/src/components/RadarChart/RadarAxis.tsx +78 -0
- package/src/components/RadarChart/RadarChart.tsx +298 -0
- package/src/components/RadarChart/RadarGrid.tsx +64 -0
- package/src/components/RadarChart/RadarPolygon.tsx +91 -0
- package/src/components/RadarChart/helpers.ts +83 -0
- package/src/components/RadarChart/index.tsx +3 -0
- package/src/components/Regions/components/Regions.tsx +365 -122
- package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +5 -1
- package/src/components/WarmingStripes/WarmingStripes.tsx +230 -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 +17 -2
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
- package/src/helpers/getExcludedData.ts +4 -0
- package/src/helpers/getMinMax.ts +12 -7
- package/src/helpers/handleChartAriaLabels.ts +19 -19
- package/src/helpers/handleLineType.ts +22 -18
- package/src/helpers/sizeHelpers.ts +0 -20
- package/src/helpers/smallMultiplesHelpers.ts +1 -1
- package/src/hooks/useChartHoverAnalytics.tsx +10 -9
- package/src/hooks/useProgrammaticTooltip.ts +23 -2
- package/src/hooks/useScales.ts +18 -1
- package/src/hooks/useTooltip.tsx +34 -10
- package/src/scss/DataTable.scss +0 -4
- package/src/scss/main.scss +22 -3
- package/src/selectors/README.md +68 -0
- package/src/store/chart.reducer.ts +2 -0
- package/src/test/CdcChart.test.jsx +1 -1
- package/src/types/ChartConfig.ts +21 -0
- package/src/types/ChartContext.ts +1 -0
- package/src/types/Horizon.ts +64 -0
- package/src/types/Label.ts +1 -0
- package/src/utils/analyticsTracking.ts +19 -0
- package/LICENSE +0 -201
- package/src/components/Annotations/components/helpers/index.tsx +0 -46
- 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
|
@@ -5,175 +5,418 @@ 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
|
|
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
|
|
13
64
|
}
|
|
14
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
|
+
|
|
15
72
|
// TODO: should regions be removed on categorical axis?
|
|
16
|
-
const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax }) => {
|
|
73
|
+
const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, xMax }) => {
|
|
17
74
|
const { parseDate, config } = useContext<ChartContext>(ConfigContext)
|
|
18
75
|
|
|
19
|
-
const {
|
|
20
|
-
|
|
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)
|
|
21
98
|
|
|
22
|
-
|
|
23
|
-
let from
|
|
99
|
+
let closestValue: unknown
|
|
24
100
|
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
27
153
|
const date = new Date(region.from)
|
|
28
154
|
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
29
|
-
from = xScale(parsedDate)
|
|
30
155
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
34
165
|
}
|
|
166
|
+
return from + scalePadding
|
|
167
|
+
}
|
|
35
168
|
|
|
36
|
-
|
|
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 => {
|
|
37
191
|
if (region.fromType === 'Previous Days') {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (xAxis.type === 'date') {
|
|
49
|
-
from = new Date(formatDate(xAxis.dateParseFormat, from)).getTime()
|
|
50
|
-
|
|
51
|
-
let closestDate = domain[0]
|
|
52
|
-
let minDiff = Math.abs(from - closestDate)
|
|
53
|
-
|
|
54
|
-
for (let i = 1; i < domain.length; i++) {
|
|
55
|
-
const diff = Math.abs(from - domain[i])
|
|
56
|
-
if (diff < minDiff) {
|
|
57
|
-
minDiff = diff
|
|
58
|
-
closestDate = domain[i]
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
from = closestDate
|
|
62
|
-
}
|
|
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
|
+
}
|
|
63
201
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
// ============================================
|
|
78
233
|
|
|
79
|
-
|
|
234
|
+
const getBarFromValue_Categorical = (region: Region): number => {
|
|
235
|
+
if (region.fromType === 'Previous Days') {
|
|
236
|
+
return calculatePreviousDaysFrom(region, 'categorical')
|
|
80
237
|
}
|
|
238
|
+
return xScale(region.from)
|
|
239
|
+
}
|
|
81
240
|
|
|
82
|
-
|
|
83
|
-
|
|
241
|
+
const getBarToValue_Categorical = (region: Region): number => {
|
|
242
|
+
if (region.toType === 'Last Date') {
|
|
243
|
+
return calculateBarLastDatePosition_Categorical()
|
|
84
244
|
}
|
|
245
|
+
let to = xScale(region.to)
|
|
246
|
+
return to + barWidth * totalBarsInGroup
|
|
247
|
+
}
|
|
85
248
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
scalePadding += xScale.bandwidth() / 2
|
|
90
|
-
}
|
|
91
|
-
from = from + scalePadding
|
|
249
|
+
const getBarFromValue_Date = (region: Region): number => {
|
|
250
|
+
if (region.fromType === 'Previous Days') {
|
|
251
|
+
return calculatePreviousDaysFrom(region, 'date')
|
|
92
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
|
+
}
|
|
93
262
|
|
|
94
|
-
|
|
95
|
-
|
|
263
|
+
const getBarToValue_Date = (region: Region): number => {
|
|
264
|
+
if (region.toType === 'Last Date') {
|
|
265
|
+
return calculateBarLastDatePosition_Date()
|
|
96
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)
|
|
97
274
|
|
|
98
|
-
return
|
|
275
|
+
return to + barWidth * totalBarsInGroup
|
|
99
276
|
}
|
|
100
277
|
|
|
101
|
-
const
|
|
102
|
-
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
|
+
}
|
|
103
289
|
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
290
|
+
const getBarToValue_DateTime = (region: Region): number => {
|
|
291
|
+
if (region.toType === 'Last Date') {
|
|
292
|
+
return calculateBarLastDatePosition_DateTime()
|
|
107
293
|
}
|
|
294
|
+
let to = xScale(parseDate(region.to).getTime())
|
|
295
|
+
return to - getBarOffset()
|
|
296
|
+
}
|
|
108
297
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
113
325
|
|
|
114
|
-
|
|
115
|
-
|
|
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)
|
|
116
342
|
}
|
|
117
343
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (visualizationType === 'Line' || visualizationType === 'Area Chart') {
|
|
129
|
-
let scalePadding = Number(config.yAxis.size)
|
|
130
|
-
if (xScale.bandwidth) {
|
|
131
|
-
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)
|
|
132
353
|
}
|
|
133
|
-
to = to + scalePadding
|
|
134
354
|
}
|
|
135
355
|
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
}
|
|
138
372
|
}
|
|
139
373
|
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
}
|
|
142
383
|
}
|
|
143
|
-
|
|
384
|
+
|
|
385
|
+
return 0
|
|
144
386
|
}
|
|
145
387
|
|
|
146
|
-
const getWidth = (to, from) => to - from
|
|
388
|
+
const getWidth = (to: number, from: number): number => Math.max(0, to - from)
|
|
147
389
|
|
|
148
|
-
if (regions
|
|
149
|
-
return regions.map(region => {
|
|
150
|
-
const from = getFromValue(region)
|
|
151
|
-
const to = getToValue(region)
|
|
152
|
-
const width = getWidth(to, from)
|
|
390
|
+
if (!regions || orientation !== 'vertical') return null
|
|
153
391
|
|
|
154
|
-
|
|
155
|
-
|
|
392
|
+
const chartStart = Number(config.yAxis.size || 0)
|
|
393
|
+
const chartEnd = xMax !== undefined ? chartStart + xMax : chartStart + 1000
|
|
156
394
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
395
|
+
return regions.map((region: Region) => {
|
|
396
|
+
const from = getFromValue(region)
|
|
397
|
+
const to = getToValue(region)
|
|
160
398
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
})
|
|
177
420
|
}
|
|
178
421
|
|
|
179
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]) =>
|
|
@@ -114,7 +114,11 @@ const SmallMultipleTile: React.FC<SmallMultipleTileProps> = ({
|
|
|
114
114
|
hideYAxisLabel: !isFirstInRow,
|
|
115
115
|
legend: {
|
|
116
116
|
...tileConfig.legend,
|
|
117
|
-
hide: true
|
|
117
|
+
hide: true // Hide legends for all small multiple tiles
|
|
118
|
+
},
|
|
119
|
+
xAxis: {
|
|
120
|
+
...tileConfig.xAxis,
|
|
121
|
+
brushActive: false // Hide brush slider for all small multiple tiles
|
|
118
122
|
},
|
|
119
123
|
showAreaUnderLine: config.smallMultiples?.showAreaUnderLine || false
|
|
120
124
|
}
|