@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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useRef } from 'react'
|
|
2
1
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
3
2
|
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
3
|
+
import { hasTrackedHover, markHoverTracked } from '../utils/analyticsTracking'
|
|
4
4
|
|
|
5
5
|
type UseChartHoverAnalyticsParams = {
|
|
6
6
|
config: any
|
|
@@ -9,14 +9,16 @@ type UseChartHoverAnalyticsParams = {
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Hook to track analytics when user enters the chart area
|
|
12
|
-
* Fires once per
|
|
12
|
+
* Fires once per visualization, persists across component remounts
|
|
13
13
|
*/
|
|
14
14
|
export const useChartHoverAnalytics = ({ config, interactionLabel = '' }: UseChartHoverAnalyticsParams) => {
|
|
15
|
-
const hasTrackedRef = useRef(false)
|
|
16
|
-
|
|
17
15
|
const handleChartMouseEnter = () => {
|
|
18
|
-
// Only track if we have an interaction label
|
|
19
|
-
if (!interactionLabel
|
|
16
|
+
// Only track if we have an interaction label
|
|
17
|
+
if (!interactionLabel) return
|
|
18
|
+
|
|
19
|
+
// Use unique ID to track per visualization
|
|
20
|
+
const vizId = String(config.runtime.uniqueId)
|
|
21
|
+
if (hasTrackedHover(vizId)) return
|
|
20
22
|
|
|
21
23
|
// Publish the analytics event
|
|
22
24
|
publishAnalyticsEvent({
|
|
@@ -29,12 +31,11 @@ export const useChartHoverAnalytics = ({ config, interactionLabel = '' }: UseCha
|
|
|
29
31
|
})
|
|
30
32
|
|
|
31
33
|
// Mark as tracked so we don't fire again
|
|
32
|
-
|
|
34
|
+
markHoverTracked(vizId)
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
const handleChartMouseLeave = () => {
|
|
36
|
-
//
|
|
37
|
-
hasTrackedRef.current = false
|
|
38
|
+
// No-op: We no longer reset tracking on mouse leave
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
return {
|
|
@@ -8,6 +8,7 @@ interface UseProgrammaticTooltipProps {
|
|
|
8
8
|
setShowHoverLine: (show: boolean) => void
|
|
9
9
|
handleTooltipMouseOver: (event: MouseEvent, additionalChartData?: any) => void
|
|
10
10
|
hideTooltip: () => void
|
|
11
|
+
setSynchronizedXValue?: (value: any) => void
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -21,7 +22,8 @@ export const useProgrammaticTooltip = ({
|
|
|
21
22
|
setPoint,
|
|
22
23
|
setShowHoverLine,
|
|
23
24
|
handleTooltipMouseOver,
|
|
24
|
-
hideTooltip
|
|
25
|
+
hideTooltip,
|
|
26
|
+
setSynchronizedXValue
|
|
25
27
|
}: UseProgrammaticTooltipProps) => {
|
|
26
28
|
// Internal SVG ref for DOM manipulation
|
|
27
29
|
const internalSvgRef = useRef<SVGSVGElement>(null)
|
|
@@ -50,6 +52,15 @@ export const useProgrammaticTooltip = ({
|
|
|
50
52
|
* @param {number} yCoordinate - Exact Y coordinate to use
|
|
51
53
|
*/
|
|
52
54
|
triggerTooltipAtDataValue: (xAxisValue: any, yCoordinate: number) => {
|
|
55
|
+
// Warming Stripes positions rects by index (with data sampling), not via xScale,
|
|
56
|
+
// so synthetic mouse events won't map to the correct data points.
|
|
57
|
+
// Route through synchronizedXValue state instead, which WarmingStripes
|
|
58
|
+
// resolves to the matching stripe and calls showTooltip directly.
|
|
59
|
+
if (config.visualizationType === 'Warming Stripes') {
|
|
60
|
+
setSynchronizedXValue?.(xAxisValue)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
const pixelX = getCoordinateFromXValue(xAxisValue)
|
|
54
65
|
const adjustedX = pixelX + Number(config.yAxis.size || 0)
|
|
55
66
|
|
|
@@ -86,10 +97,20 @@ export const useProgrammaticTooltip = ({
|
|
|
86
97
|
hideTooltip: () => {
|
|
87
98
|
hideTooltip()
|
|
88
99
|
setShowHoverLine(false)
|
|
100
|
+
setSynchronizedXValue?.(null)
|
|
89
101
|
}
|
|
90
102
|
})
|
|
91
103
|
},
|
|
92
|
-
[
|
|
104
|
+
[
|
|
105
|
+
getCoordinateFromXValue,
|
|
106
|
+
config.yAxis.size,
|
|
107
|
+
config.visualizationType,
|
|
108
|
+
setPoint,
|
|
109
|
+
setShowHoverLine,
|
|
110
|
+
handleTooltipMouseOver,
|
|
111
|
+
hideTooltip,
|
|
112
|
+
setSynchronizedXValue
|
|
113
|
+
]
|
|
93
114
|
)
|
|
94
115
|
|
|
95
116
|
return internalSvgRef
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -121,6 +121,11 @@ const useScales = (properties: useScaleProps) => {
|
|
|
121
121
|
range: [0, xMax]
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
+
let yScaleAnnotation = scaleLinear({
|
|
125
|
+
domain: [0, 100],
|
|
126
|
+
range: [0, yMax]
|
|
127
|
+
})
|
|
128
|
+
|
|
124
129
|
// handle Horizontal bars
|
|
125
130
|
if (isHorizontal) {
|
|
126
131
|
xScale = composeXScale({ min: min * 1.03, max, xMax, config })
|
|
@@ -133,7 +138,17 @@ const useScales = (properties: useScaleProps) => {
|
|
|
133
138
|
// handle Vertical bars
|
|
134
139
|
if (!isHorizontal) {
|
|
135
140
|
xScale = composeScaleBand(xAxisDataMapped, [0, xMax], paddingRange)
|
|
136
|
-
|
|
141
|
+
// For categorical y-axis, use [0, max] domain and [yMax, 0] range to match CategoricalYAxis
|
|
142
|
+
// This ensures line data aligns with categorical bars and bars go to 100% height
|
|
143
|
+
if (config.yAxis.type === 'categorical') {
|
|
144
|
+
yScale = scaleLinear({
|
|
145
|
+
domain: [0, max],
|
|
146
|
+
range: [yMax, 0],
|
|
147
|
+
clamp: true
|
|
148
|
+
})
|
|
149
|
+
} else {
|
|
150
|
+
yScale = composeYScale({ min, max, yMax, config, leftMax })
|
|
151
|
+
}
|
|
137
152
|
seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
|
|
138
153
|
}
|
|
139
154
|
|
|
@@ -362,6 +377,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
362
377
|
g2xScale,
|
|
363
378
|
xScaleNoPadding,
|
|
364
379
|
xScaleAnnotation,
|
|
380
|
+
yScaleAnnotation,
|
|
365
381
|
min,
|
|
366
382
|
max,
|
|
367
383
|
leftMax,
|
|
@@ -377,6 +393,7 @@ const getFirstDayOfMonth = ms => {
|
|
|
377
393
|
}
|
|
378
394
|
|
|
379
395
|
const dateFormatHasMonthButNoDays = dateFormat => {
|
|
396
|
+
if (!dateFormat) return false
|
|
380
397
|
return (
|
|
381
398
|
(dateFormat.includes('%b') ||
|
|
382
399
|
dateFormat.includes('%B') ||
|
package/src/hooks/useTooltip.tsx
CHANGED
|
@@ -105,7 +105,7 @@ export const useTooltip = props => {
|
|
|
105
105
|
addColCommas: column.commas
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const pieColumnData = additionalChartData?.
|
|
108
|
+
const pieColumnData = additionalChartData?.data?.[column.name]
|
|
109
109
|
const columnData =
|
|
110
110
|
config.tooltips.singleSeries && visualizationType === 'Line'
|
|
111
111
|
? resolvedScaleValues.filter(
|
|
@@ -145,7 +145,7 @@ export const useTooltip = props => {
|
|
|
145
145
|
tooltipItems.push(
|
|
146
146
|
[config.xAxis.dataKey, pieData[config.xAxis.dataKey]],
|
|
147
147
|
[
|
|
148
|
-
config.runtime.yAxis.dataKey,
|
|
148
|
+
config.runtime.yAxis.label || config.runtime.yAxis.dataKey,
|
|
149
149
|
showPiePercent ? pctString(actualPieValue) : formatNumber(pieData[config.runtime.yAxis.dataKey])
|
|
150
150
|
],
|
|
151
151
|
showPiePercent ? [] : ['Percent', pctString(pctOf360)]
|
|
@@ -260,11 +260,30 @@ export const useTooltip = props => {
|
|
|
260
260
|
const dataXPosition = eventSvgCoords.x + 10
|
|
261
261
|
const dataYPosition = eventSvgCoords.y
|
|
262
262
|
|
|
263
|
+
// Helper to strip <a> tags and only show link text
|
|
264
|
+
function stripLinkTags(str) {
|
|
265
|
+
if (typeof str !== 'string') return str
|
|
266
|
+
// Remove HTML <a> tags, keep inner text
|
|
267
|
+
return str.replace(/<a [^>]*>(.*?)<\/a>/gi, '$1')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Strip link tags from all tooltip values
|
|
271
|
+
const cleanTooltipItems = [...tooltipItems, ...additionalTooltipItems].map(item => {
|
|
272
|
+
// item can be [key, value] or [key, value, axisPosition]
|
|
273
|
+
if (Array.isArray(item)) {
|
|
274
|
+
// Only strip from value (item[1])
|
|
275
|
+
const newItem = [...item]
|
|
276
|
+
newItem[1] = stripLinkTags(newItem[1])
|
|
277
|
+
return newItem
|
|
278
|
+
}
|
|
279
|
+
return item
|
|
280
|
+
})
|
|
281
|
+
|
|
263
282
|
const tooltipInformation = {
|
|
264
283
|
tooltipLeft: dataXPosition,
|
|
265
284
|
tooltipTop: dataYPosition,
|
|
266
285
|
tooltipData: {
|
|
267
|
-
data:
|
|
286
|
+
data: cleanTooltipItems,
|
|
268
287
|
dataXPosition,
|
|
269
288
|
dataYPosition
|
|
270
289
|
}
|
|
@@ -523,14 +542,16 @@ export const useTooltip = props => {
|
|
|
523
542
|
includedSeries.push(...dynamicDataCategories)
|
|
524
543
|
|
|
525
544
|
if (config.visualizationType === 'Forecasting') {
|
|
526
|
-
config.runtime.series
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
545
|
+
config.runtime.series
|
|
546
|
+
.filter(s => s.type === 'Forecasting' && s.confidenceIntervals)
|
|
547
|
+
.forEach(s => {
|
|
548
|
+
s.confidenceIntervals.forEach(c => {
|
|
549
|
+
if (c.showInTooltip) {
|
|
550
|
+
includedSeries.push(c.high)
|
|
551
|
+
includedSeries.push(c.low)
|
|
552
|
+
}
|
|
553
|
+
})
|
|
532
554
|
})
|
|
533
|
-
})
|
|
534
555
|
}
|
|
535
556
|
|
|
536
557
|
const colNames = Object.values(config.columns).map(column => column.name)
|
|
@@ -571,6 +592,7 @@ export const useTooltip = props => {
|
|
|
571
592
|
case 'Line':
|
|
572
593
|
case 'Area Chart':
|
|
573
594
|
case 'Pie':
|
|
595
|
+
case 'Horizon Chart':
|
|
574
596
|
return common
|
|
575
597
|
case 'Combo':
|
|
576
598
|
return [...common, ...ciItems]
|
|
@@ -579,6 +601,8 @@ export const useTooltip = props => {
|
|
|
579
601
|
|
|
580
602
|
case 'Bar':
|
|
581
603
|
return orientation === 'vertical' ? common : [runtime.yAxis.dataKey, ...runtime?.seriesKeys]
|
|
604
|
+
case 'Warming Stripes':
|
|
605
|
+
return common
|
|
582
606
|
default:
|
|
583
607
|
throw new Error('No visualization type found in handleTooltipMouseOver')
|
|
584
608
|
}
|
package/src/scss/DataTable.scss
CHANGED
package/src/scss/main.scss
CHANGED
|
@@ -50,9 +50,6 @@
|
|
|
50
50
|
.subtext,
|
|
51
51
|
.subtext--responsive-ticks,
|
|
52
52
|
.section-subtext {
|
|
53
|
-
&--brush-active {
|
|
54
|
-
margin-top: 3rem !important;
|
|
55
|
-
}
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
.type-pie {
|
|
@@ -149,6 +146,11 @@
|
|
|
149
146
|
cursor: pointer;
|
|
150
147
|
transition: 0.2s all;
|
|
151
148
|
|
|
149
|
+
&.not-clickable,
|
|
150
|
+
&.not-clickable .legend-item {
|
|
151
|
+
cursor: default;
|
|
152
|
+
}
|
|
153
|
+
|
|
152
154
|
&.inactive {
|
|
153
155
|
opacity: 0.5;
|
|
154
156
|
transition: 0.2s all;
|
|
@@ -325,6 +327,23 @@
|
|
|
325
327
|
margin-bottom: 2.5em;
|
|
326
328
|
}
|
|
327
329
|
|
|
330
|
+
// Brush touch support
|
|
331
|
+
.brush-overlay {
|
|
332
|
+
touch-action: none;
|
|
333
|
+
-webkit-touch-callout: none;
|
|
334
|
+
-webkit-user-select: none;
|
|
335
|
+
user-select: none;
|
|
336
|
+
|
|
337
|
+
.visx-brush,
|
|
338
|
+
.visx-brush-overlay,
|
|
339
|
+
.visx-brush-selection,
|
|
340
|
+
.visx-brush-handle-left,
|
|
341
|
+
.visx-brush-handle-right {
|
|
342
|
+
touch-action: none;
|
|
343
|
+
cursor: pointer;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
328
347
|
svg.dragging-annotation * {
|
|
329
348
|
user-select: none;
|
|
330
349
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Config Selectors
|
|
2
|
+
|
|
3
|
+
Typed selector functions and hooks for extracting memoizable slices of chart configuration.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
These selectors help:
|
|
8
|
+
1. **Reduce coupling** - Components depend on specific config slices, not the entire config object
|
|
9
|
+
2. **Improve memoization** - Hooks use specific dependencies instead of the full config
|
|
10
|
+
3. **Enhance testability** - Selectors can be easily mocked in tests
|
|
11
|
+
4. **Document config usage** - Selectors serve as documentation for which config properties are used together
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Direct Selectors
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { selectAxisConfig, selectVisualizationConfig } from '../selectors'
|
|
19
|
+
|
|
20
|
+
// In a component
|
|
21
|
+
const axisConfig = selectAxisConfig(config)
|
|
22
|
+
// Returns: { xAxis, yAxis, runtime: { xAxis, yAxis, originalXAxis } }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Hook Versions (Memoized)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { useAxisConfig, useVisualizationConfig } from '../selectors'
|
|
29
|
+
|
|
30
|
+
// In a component - automatically memoized
|
|
31
|
+
const axisConfig = useAxisConfig(config)
|
|
32
|
+
const vizConfig = useVisualizationConfig(config)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Available Selectors
|
|
36
|
+
|
|
37
|
+
| Selector | Hook | Description |
|
|
38
|
+
|----------|------|-------------|
|
|
39
|
+
| `selectAxisConfig` | `useAxisConfig` | X/Y axis configuration |
|
|
40
|
+
| `selectVisualizationConfig` | `useVisualizationConfig` | Chart type and orientation |
|
|
41
|
+
| `selectDisplayConfig` | `useDisplayConfig` | Animation, debug flags |
|
|
42
|
+
| `selectTooltipConfig` | `useTooltipConfig` | Tooltip settings |
|
|
43
|
+
| `selectLegendConfig` | - | Legend position and visibility |
|
|
44
|
+
| `selectDataFormatConfig` | - | Number formatting options |
|
|
45
|
+
| `selectChartMessages` | - | UI message strings |
|
|
46
|
+
| `selectBrushConfig` | - | Brush/filter settings |
|
|
47
|
+
| `selectForestPlotConfig` | - | Forest plot specific options |
|
|
48
|
+
| `selectSmallMultiplesConfig` | - | Small multiples mode |
|
|
49
|
+
|
|
50
|
+
## Type Exports
|
|
51
|
+
|
|
52
|
+
Each selector has a corresponding type export:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import type { AxisConfig, VisualizationConfig } from '../selectors'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Adding New Selectors
|
|
59
|
+
|
|
60
|
+
1. Add the selector function that extracts config properties
|
|
61
|
+
2. Optionally add a hook version with proper dependencies
|
|
62
|
+
3. Export the type using `ReturnType<typeof selectXxx>`
|
|
63
|
+
4. Update this README
|
|
64
|
+
|
|
65
|
+
## Notes
|
|
66
|
+
|
|
67
|
+
- Selectors are created but integration into LinearChart is deferred
|
|
68
|
+
- Future work: gradually replace direct `config.x.y` accesses with selector usage
|
package/src/types/ChartConfig.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Axis } from '@cdc/core/types/Axis'
|
|
2
2
|
import { MarkupConfig } from '@cdc/core/types/MarkupVariable'
|
|
3
3
|
import { type ForestPlotConfigSettings } from './ForestPlot'
|
|
4
|
+
import { type HorizonConfigSettings } from './Horizon'
|
|
4
5
|
import { type Column } from '@cdc/core/types/Column'
|
|
5
6
|
import { type Series } from '@cdc/core/types/Series'
|
|
6
7
|
import { Runtime } from '@cdc/core/types/Runtime'
|
|
@@ -18,6 +19,7 @@ type General = CoreGeneral & {
|
|
|
18
19
|
customColors?: string[]
|
|
19
20
|
customColorsOrdered?: string[]
|
|
20
21
|
}
|
|
22
|
+
useIntelligentLineChartLabels?: boolean
|
|
21
23
|
}
|
|
22
24
|
import { type Link } from './../components/Sankey/types'
|
|
23
25
|
import { type DataDescription } from '@cdc/core/types/DataDescription'
|
|
@@ -39,15 +41,18 @@ export type VisualizationType =
|
|
|
39
41
|
| 'Box Plot'
|
|
40
42
|
| 'Deviation Bar'
|
|
41
43
|
| 'Forest Plot'
|
|
44
|
+
| 'Horizon Chart'
|
|
42
45
|
| 'Line'
|
|
43
46
|
| 'Paired Bar'
|
|
44
47
|
| 'Pie'
|
|
48
|
+
| 'Radar'
|
|
45
49
|
| 'Scatter Plot'
|
|
46
50
|
| 'Spark Line'
|
|
47
51
|
| 'Combo'
|
|
48
52
|
| 'Forecasting'
|
|
49
53
|
| 'Sankey'
|
|
50
54
|
| 'Bump Chart'
|
|
55
|
+
| 'Warming Stripes'
|
|
51
56
|
export interface PreliminaryDataItem {
|
|
52
57
|
column: string
|
|
53
58
|
displayLegend: boolean
|
|
@@ -103,6 +108,7 @@ type Legend = CoreLegend & {
|
|
|
103
108
|
order: 'dataColumn' | 'asc' | 'desc'
|
|
104
109
|
orderedValues: Label[]
|
|
105
110
|
tickRotation: string
|
|
111
|
+
warmingStripesIntervals?: number
|
|
106
112
|
hideBorder: {
|
|
107
113
|
side: boolean
|
|
108
114
|
topBottom: boolean
|
|
@@ -170,6 +176,7 @@ export type AllChartsConfig = {
|
|
|
170
176
|
mobileVertical: number
|
|
171
177
|
}
|
|
172
178
|
highlightedBarValues: { value: any; color: string; borderWidth: number; legendLabel: string }[]
|
|
179
|
+
horizon?: HorizonConfigSettings
|
|
173
180
|
introText: string
|
|
174
181
|
isLollipopChart: boolean
|
|
175
182
|
isLegendValue: boolean
|
|
@@ -218,6 +225,7 @@ export type AllChartsConfig = {
|
|
|
218
225
|
table: Table
|
|
219
226
|
tipRounding: string
|
|
220
227
|
title: string
|
|
228
|
+
titleStyle?: 'legacy' | 'large' | 'small'
|
|
221
229
|
tooltips: {
|
|
222
230
|
singleSeries: boolean
|
|
223
231
|
opacity: number
|
|
@@ -264,6 +272,19 @@ export type AllChartsConfig = {
|
|
|
264
272
|
default: string
|
|
265
273
|
}
|
|
266
274
|
}
|
|
275
|
+
radar?: {
|
|
276
|
+
gridRings: number
|
|
277
|
+
showGridRings: boolean
|
|
278
|
+
gridRingStyle: 'polygons' | 'circles'
|
|
279
|
+
scaleMin: number
|
|
280
|
+
scaleMax: number | string
|
|
281
|
+
showFill: boolean
|
|
282
|
+
fillOpacity: number
|
|
283
|
+
showPoints: boolean
|
|
284
|
+
pointRadius: number
|
|
285
|
+
strokeWidth: number
|
|
286
|
+
axisLabelOffset: number
|
|
287
|
+
}
|
|
267
288
|
} & MarkupConfig
|
|
268
289
|
|
|
269
290
|
type ForestPlotConfig = {
|
|
@@ -36,6 +36,7 @@ type SharedChartContext = {
|
|
|
36
36
|
setLegendIsolateValues?: Function
|
|
37
37
|
svgRef?: React.RefObject<SVGSVGElement>
|
|
38
38
|
handleSmallMultipleHover?: (xAxisValue: any, yCoordinate: number) => void
|
|
39
|
+
visibleAnnotations?: Annotation[]
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// Line Chart Specific Context
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration settings for Horizon Chart visualization
|
|
3
|
+
*
|
|
4
|
+
* Horizon charts display multiple time series as stacked rows,
|
|
5
|
+
* with each series shown as layered bands to compress vertical space.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Palette configuration structure (matches general.palette structure)
|
|
10
|
+
*/
|
|
11
|
+
export type HorizonPalette = {
|
|
12
|
+
name?: string
|
|
13
|
+
version?: '1.0' | '2.0'
|
|
14
|
+
isReversed?: boolean
|
|
15
|
+
customColors?: string[]
|
|
16
|
+
customColorsOrdered?: string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type HorizonConfigSettings = {
|
|
20
|
+
/**
|
|
21
|
+
* Number of horizon layers/bands per series
|
|
22
|
+
* More layers = finer granularity, but denser visual
|
|
23
|
+
* @default 4
|
|
24
|
+
*/
|
|
25
|
+
numLayers: number
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Rendering mode for data values
|
|
29
|
+
* - 'offset': All values treated as positive (absolute values)
|
|
30
|
+
* - 'mirror': Negative values mirrored with contrasting colors. Not currently used.
|
|
31
|
+
* @default 'offset'
|
|
32
|
+
*/
|
|
33
|
+
mode: 'offset' | 'mirror'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Gap in pixels between series bands
|
|
37
|
+
* Bands will fill available chart height minus gaps
|
|
38
|
+
* @default 15
|
|
39
|
+
*/
|
|
40
|
+
bandGap: number
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Padding in pixels below the bottom band (above x-axis)
|
|
44
|
+
* @default 15
|
|
45
|
+
*/
|
|
46
|
+
bottomPadding: number
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Optional secondary palette for negative values (mirror mode only)
|
|
50
|
+
* Uses same structure as general.palette for consistency
|
|
51
|
+
* Not exposed in UI initially - scaffolded for future use
|
|
52
|
+
*/
|
|
53
|
+
negativePalette?: HorizonPalette
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Default configuration for Horizon charts
|
|
58
|
+
*/
|
|
59
|
+
export const HORIZON_DEFAULTS: HorizonConfigSettings = {
|
|
60
|
+
numLayers: 4,
|
|
61
|
+
mode: 'offset',
|
|
62
|
+
bandGap: 15,
|
|
63
|
+
bottomPadding: 15
|
|
64
|
+
}
|
package/src/types/Label.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared analytics tracking to ensure events only fire once per visualization
|
|
3
|
+
* Tracks at module level to persist across component remounts
|
|
4
|
+
*/
|
|
5
|
+
const trackedHoverEvents = new Set<string>()
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if a hover event has been tracked for a given visualization ID
|
|
9
|
+
*/
|
|
10
|
+
export const hasTrackedHover = (vizId: string): boolean => {
|
|
11
|
+
return trackedHoverEvents.has(vizId)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Mark a visualization as having had its hover event tracked
|
|
16
|
+
*/
|
|
17
|
+
export const markHoverTracked = (vizId: string): void => {
|
|
18
|
+
trackedHoverEvents.add(vizId)
|
|
19
|
+
}
|