@cdc/chart 4.26.1 → 4.26.3
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/LICENSE +201 -0
- package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
- package/dist/cdcchart.js +54742 -49796
- package/examples/data/data-with-metadata.json +10 -0
- 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 +2 -1
- package/examples/line-chart-states.json +1085 -0
- package/examples/metadata-variables.json +58 -0
- package/examples/private/123.json +694 -0
- package/examples/private/anchor-issue.json +4094 -0
- package/examples/private/backwards-slider.json +10430 -0
- package/examples/private/georgia.csv +160 -0
- package/examples/private/timeline-data.json +1 -0
- package/examples/private/timeline.json +389 -0
- package/examples/radar-chart-simple.json +133 -0
- package/examples/radar-chart.json +148 -0
- package/index.html +1 -31
- package/package.json +57 -59
- package/src/CdcChart.tsx +8 -4
- package/src/CdcChartComponent.tsx +398 -284
- 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 +78 -0
- package/src/_stories/Chart.Defaults.stories.tsx +95 -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 +13 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +19 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +25 -10
- package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
- package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
- package/src/_stories/Chart.stories.tsx +72 -1
- 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 +97 -38
- package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
- 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 +7 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
- 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 +7 -0
- package/src/_stories/_mock/brush_continuous.json +86 -0
- package/src/_stories/_mock/brush_date_large.json +176 -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/_stories/_mock/paired-bar-abbr.json +421 -0
- package/src/_stories/_mock/pie_custom_colors.json +268 -0
- package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +14 -20
- 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 +12 -18
- 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/Axis/BottomAxis.tsx +277 -0
- package/src/components/Axis/LeftAxis.tsx +404 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
- package/src/components/Axis/PairedBarAxis.tsx +192 -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 +12 -28
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
- package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
- package/src/components/BarChart/components/BarChart.tsx +7 -1
- package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
- package/src/components/BarChart/helpers/useBarChart.ts +3 -0
- package/src/components/Brush/BrushSelector.tsx +155 -22
- package/src/components/Brush/MiniChartPreview.tsx +133 -21
- package/src/components/EditorPanel/EditorPanel.tsx +81 -54
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +67 -29
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +120 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +25 -43
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -3
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +66 -43
- package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
- package/src/components/EditorPanel/editor-panel.scss +1 -1
- package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
- package/src/components/ForestPlot/ForestPlot.tsx +26 -22
- 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 +1 -1
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
- package/src/components/Legend/LegendValueRange.tsx +77 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +16 -2
- package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
- 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 +278 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +131 -0
- package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
- package/src/components/LinearChart.tsx +268 -1057
- package/src/components/PieChart/PieChart.tsx +20 -5
- 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 +6 -6
- package/src/components/Sankey/components/Sankey.tsx +3 -3
- package/src/components/Sankey/sankey.scss +1 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/Sparkline/index.scss +4 -2
- package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
- package/src/data/initial-state.js +37 -15
- package/src/data/legacy-defaults.ts +18 -0
- package/src/helpers/abbreviateNumber.ts +24 -17
- package/src/helpers/getChartPatternId.ts +17 -0
- package/src/helpers/getExcludedData.ts +4 -0
- package/src/helpers/getMinMax.ts +16 -2
- package/src/helpers/handleChartAriaLabels.ts +19 -19
- package/src/helpers/handleLineType.ts +22 -18
- package/src/helpers/seriesColumnSettings.ts +114 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
- package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
- package/src/hooks/useProgrammaticTooltip.ts +23 -2
- package/src/hooks/useRightAxis.ts +14 -0
- package/src/hooks/useScales.ts +99 -56
- package/src/hooks/useTooltip.tsx +23 -3
- package/src/scss/main.scss +157 -79
- package/src/selectors/README.md +68 -0
- package/src/store/chart.reducer.ts +2 -0
- package/src/test/CdcChart.test.jsx +2 -2
- package/src/types/ChartConfig.ts +22 -0
- package/src/types/ChartContext.ts +1 -0
- package/src/types/Horizon.ts +64 -0
- package/tests/fixtures/chart-config-with-metadata.json +29 -0
- package/tests/fixtures/data-with-metadata.json +10 -0
- package/preview.html +0 -1616
- package/src/components/Annotations/components/helpers/index.tsx +0 -46
|
@@ -91,7 +91,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
91
91
|
? new Date(domain[domain.length - 1] as string | number).getTime()
|
|
92
92
|
: new Date(region.to)
|
|
93
93
|
|
|
94
|
-
const toFormatted = formatDate(config.xAxis.dateParseFormat, toRefDate)
|
|
94
|
+
const toFormatted = formatDate(config.xAxis.dateParseFormat, toRefDate, config.locale)
|
|
95
95
|
const toDate = new Date(toFormatted)
|
|
96
96
|
const fromDate = new Date(toDate)
|
|
97
97
|
fromDate.setDate(fromDate.getDate() - previousDays)
|
|
@@ -99,7 +99,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
99
99
|
let closestValue: unknown
|
|
100
100
|
|
|
101
101
|
if (axisType === 'date') {
|
|
102
|
-
const fromTime = new Date(formatDate(xAxis.dateParseFormat, fromDate)).getTime()
|
|
102
|
+
const fromTime = new Date(formatDate(xAxis.dateParseFormat, fromDate, config.locale)).getTime()
|
|
103
103
|
closestValue = findClosestDate(fromTime, domain as number[], d => d)
|
|
104
104
|
} else if (axisType === 'categorical') {
|
|
105
105
|
const fromTime = fromDate.getTime()
|
|
@@ -151,7 +151,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
151
151
|
// For date scale (band), we need to find the value in the domain
|
|
152
152
|
// Parse the region date to match the format in the domain
|
|
153
153
|
const date = new Date(region.from)
|
|
154
|
-
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
154
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
155
155
|
|
|
156
156
|
// For band scales, find the closest date in the domain
|
|
157
157
|
const domain = xScale.domain() as number[]
|
|
@@ -193,7 +193,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
193
193
|
return from + Number(config.yAxis.size)
|
|
194
194
|
}
|
|
195
195
|
const date = new Date(region.from)
|
|
196
|
-
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
196
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
197
197
|
let from = xScale(parsedDate)
|
|
198
198
|
// For date-time, xScale returns correct position (no bandwidth), just add left padding
|
|
199
199
|
return from + Number(config.yAxis.size)
|
|
@@ -252,7 +252,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
252
252
|
}
|
|
253
253
|
// For date scale (band), we need to find the value in the domain
|
|
254
254
|
const date = new Date(region.from)
|
|
255
|
-
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
255
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
256
256
|
|
|
257
257
|
// For band scales, find the closest date in the domain
|
|
258
258
|
const domain = xScale.domain() as number[]
|
|
@@ -281,7 +281,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
281
281
|
from = calculatePreviousDaysFrom(region, 'date-time')
|
|
282
282
|
} else {
|
|
283
283
|
const date = new Date(region.from)
|
|
284
|
-
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
284
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
285
285
|
from = xScale(parsedDate)
|
|
286
286
|
}
|
|
287
287
|
return from - getBarOffset()
|
|
@@ -226,7 +226,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
226
226
|
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
227
227
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
228
228
|
>
|
|
229
|
-
{typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
|
|
229
|
+
{typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value}
|
|
230
230
|
</Text>
|
|
231
231
|
<Text
|
|
232
232
|
width={linkLength()}
|
|
@@ -281,7 +281,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
281
281
|
>
|
|
282
282
|
<tspan className={classStyle}>
|
|
283
283
|
{sankeyConfig.nodeValueStyle.textBefore +
|
|
284
|
-
(typeof node.value === 'number' ? node.value.toLocaleString() : node.value) +
|
|
284
|
+
(typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value) +
|
|
285
285
|
sankeyConfig.nodeValueStyle.textAfter}
|
|
286
286
|
</tspan>
|
|
287
287
|
</text>
|
|
@@ -394,7 +394,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
394
394
|
textAnchor='start'
|
|
395
395
|
style={{ pointerEvents: 'none' }}
|
|
396
396
|
>
|
|
397
|
-
{typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
|
|
397
|
+
{typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value}
|
|
398
398
|
</Text>
|
|
399
399
|
<Text
|
|
400
400
|
x={node.x0! + textPositionHorizontal}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
.small-multiples-container {
|
|
2
|
-
width: 100%;
|
|
3
2
|
display: flex;
|
|
4
3
|
flex-direction: column;
|
|
4
|
+
width: 100%;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
.small-multiples-grid {
|
|
8
8
|
display: grid;
|
|
9
|
-
width: 100%;
|
|
10
9
|
flex: 1;
|
|
10
|
+
width: 100%;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.small-multiple-tile {
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.tile-title {
|
|
23
|
-
margin: 0;
|
|
24
23
|
font-weight: 700;
|
|
25
|
-
text-align: left;
|
|
26
24
|
line-height: 1.3;
|
|
25
|
+
margin: 0;
|
|
26
|
+
text-align: left;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.tile-chart {
|
|
30
|
-
width: 100%;
|
|
31
30
|
flex-shrink: 0;
|
|
31
|
+
width: 100%;
|
|
32
32
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useContext, useState } from 'react'
|
|
1
|
+
import { useContext, useState, useCallback, useEffect } from 'react'
|
|
2
2
|
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
import { Group } from '@visx/group'
|
|
4
4
|
import { scaleSequential } from 'd3-scale'
|
|
@@ -6,7 +6,6 @@ import { interpolateRgbBasis } from 'd3-interpolate'
|
|
|
6
6
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
7
7
|
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
8
8
|
import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
|
|
9
|
-
import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
|
|
10
9
|
import { getFallbackColorPalette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
|
|
11
10
|
import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
|
|
12
11
|
import { hasTrackedHover, markHoverTracked } from '../../utils/analyticsTracking'
|
|
@@ -16,10 +15,26 @@ type WarmingStripesProps = {
|
|
|
16
15
|
yScale: any
|
|
17
16
|
xMax: number
|
|
18
17
|
yMax: number
|
|
18
|
+
synchronizedXValue?: any
|
|
19
|
+
showTooltip: (args: any) => void
|
|
20
|
+
handleTooltipMouseOff: () => void
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
const WarmingStripes = ({
|
|
22
|
-
|
|
23
|
+
const WarmingStripes = ({
|
|
24
|
+
xMax,
|
|
25
|
+
yMax,
|
|
26
|
+
synchronizedXValue,
|
|
27
|
+
showTooltip,
|
|
28
|
+
handleTooltipMouseOff
|
|
29
|
+
}: WarmingStripesProps) => {
|
|
30
|
+
const {
|
|
31
|
+
transformedData: data,
|
|
32
|
+
config,
|
|
33
|
+
formatNumber,
|
|
34
|
+
interactionLabel,
|
|
35
|
+
currentViewport,
|
|
36
|
+
handleSmallMultipleHover
|
|
37
|
+
} = useContext(ConfigContext)
|
|
23
38
|
|
|
24
39
|
const [currentHover, setCurrentHover] = useState<number | null>(null)
|
|
25
40
|
|
|
@@ -28,18 +43,14 @@ const WarmingStripes = ({ xMax, yMax }: WarmingStripesProps) => {
|
|
|
28
43
|
const valueKey = config.runtime.seriesKeys?.[0]
|
|
29
44
|
const xAxisDataKey = config.runtime.originalXAxis?.dataKey || config.xAxis?.dataKey
|
|
30
45
|
|
|
31
|
-
if (!valueKey || !xAxisDataKey || !data || data.length === 0) {
|
|
32
|
-
return null
|
|
33
|
-
}
|
|
34
|
-
|
|
35
46
|
// Determine max stripes based on viewport
|
|
36
47
|
const isMobile = ['xxs', 'xs', 'sm', 'md'].includes(currentViewport)
|
|
37
48
|
const maxStripes = isMobile ? 60 : 200
|
|
38
49
|
|
|
39
50
|
// Sample data if we have more than the max allowed stripes
|
|
40
|
-
let displayData = data
|
|
41
|
-
if (
|
|
42
|
-
const step =
|
|
51
|
+
let displayData = data || []
|
|
52
|
+
if (displayData.length > maxStripes) {
|
|
53
|
+
const step = displayData.length / maxStripes
|
|
43
54
|
displayData = []
|
|
44
55
|
for (let i = 0; i < maxStripes; i++) {
|
|
45
56
|
const index = Math.floor(i * step)
|
|
@@ -48,7 +59,7 @@ const WarmingStripes = ({ xMax, yMax }: WarmingStripesProps) => {
|
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
// Calculate the min and max values for the color scale
|
|
51
|
-
const values =
|
|
62
|
+
const values = displayData.map(d => Number(d[valueKey])).filter(v => !isNaN(v))
|
|
52
63
|
const minValue = Math.min(...values)
|
|
53
64
|
const maxValue = Math.max(...values)
|
|
54
65
|
|
|
@@ -94,15 +105,54 @@ const WarmingStripes = ({ xMax, yMax }: WarmingStripesProps) => {
|
|
|
94
105
|
// Calculate stripe width based on available space and display data
|
|
95
106
|
const stripeWidth = xMax / displayData.length
|
|
96
107
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
// Build tooltip data in COVE format and trigger showTooltip
|
|
109
|
+
const showStripeTooltip = useCallback(
|
|
110
|
+
(item: any, index: number, mouseY?: number) => {
|
|
111
|
+
const value = Number(item[valueKey])
|
|
112
|
+
if (isNaN(value)) return
|
|
113
|
+
|
|
114
|
+
const formattedValue = formatNumber(value, 'left')
|
|
115
|
+
|
|
116
|
+
// Pass raw x-axis value — TooltipListItem handles date formatting
|
|
117
|
+
const tooltipItems = [
|
|
118
|
+
[xAxisDataKey, item[xAxisDataKey]],
|
|
119
|
+
[valueKey, formattedValue, 'left']
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
const dataXPosition = index * stripeWidth + stripeWidth / 2 + Number(config.yAxis.size)
|
|
123
|
+
const dataYPosition = mouseY ?? yMax / 2
|
|
124
|
+
|
|
125
|
+
showTooltip({
|
|
126
|
+
tooltipLeft: dataXPosition + 10,
|
|
127
|
+
tooltipTop: dataYPosition,
|
|
128
|
+
tooltipData: {
|
|
129
|
+
data: tooltipItems,
|
|
130
|
+
dataXPosition: dataXPosition + 10,
|
|
131
|
+
dataYPosition
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
},
|
|
135
|
+
[valueKey, xAxisDataKey, stripeWidth, config.yAxis.size, yMax, formatNumber, showTooltip]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// Handle incoming synchronized tooltip from sibling small multiple tiles
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (synchronizedXValue === null || synchronizedXValue === undefined) {
|
|
141
|
+
setCurrentHover(null)
|
|
142
|
+
handleTooltipMouseOff()
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const matchIndex = displayData.findIndex(item => String(item[xAxisDataKey]) === String(synchronizedXValue))
|
|
147
|
+
|
|
148
|
+
if (matchIndex >= 0) {
|
|
149
|
+
setCurrentHover(matchIndex)
|
|
150
|
+
showStripeTooltip(displayData[matchIndex], matchIndex)
|
|
151
|
+
}
|
|
152
|
+
}, [synchronizedXValue])
|
|
101
153
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<strong>${config.runtime.seriesLabels?.[valueKey] || valueKey}:</strong> ${formattedValue}
|
|
105
|
-
</div>`
|
|
154
|
+
if (!valueKey || !xAxisDataKey || !data || data.length === 0) {
|
|
155
|
+
return null
|
|
106
156
|
}
|
|
107
157
|
|
|
108
158
|
return (
|
|
@@ -126,13 +176,10 @@ const WarmingStripes = ({ xMax, yMax }: WarmingStripesProps) => {
|
|
|
126
176
|
fill={fillColor}
|
|
127
177
|
fillOpacity={isMuted ? 0.5 : 1}
|
|
128
178
|
stroke='none'
|
|
129
|
-
data-tooltip-html={handleTooltip(item)}
|
|
130
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
131
179
|
tabIndex={-1}
|
|
132
180
|
style={{ cursor: 'pointer', transition: 'fill-opacity 0.2s ease' }}
|
|
133
|
-
onMouseEnter={
|
|
181
|
+
onMouseEnter={e => {
|
|
134
182
|
if (currentHover !== index) {
|
|
135
|
-
// Only publish analytics event once per visualization (shared tracking)
|
|
136
183
|
const vizId = String(config.runtime.uniqueId)
|
|
137
184
|
if (!hasTrackedHover(vizId)) {
|
|
138
185
|
publishAnalyticsEvent({
|
|
@@ -148,8 +195,31 @@ const WarmingStripes = ({ xMax, yMax }: WarmingStripesProps) => {
|
|
|
148
195
|
}
|
|
149
196
|
setCurrentHover(index)
|
|
150
197
|
}
|
|
198
|
+
|
|
199
|
+
// Show COVE tooltip using mouse Y position
|
|
200
|
+
const svgEl = (e.currentTarget as SVGRectElement).ownerSVGElement
|
|
201
|
+
const svgRect = svgEl?.getBoundingClientRect()
|
|
202
|
+
const mouseY = svgRect ? e.clientY - svgRect.top : yMax / 2
|
|
203
|
+
showStripeTooltip(item, index, mouseY)
|
|
204
|
+
|
|
205
|
+
if (handleSmallMultipleHover) {
|
|
206
|
+
handleSmallMultipleHover(item[xAxisDataKey], yMax / 2)
|
|
207
|
+
}
|
|
208
|
+
}}
|
|
209
|
+
onMouseMove={e => {
|
|
210
|
+
// Update tooltip Y position as mouse moves within the stripe
|
|
211
|
+
const svgEl = (e.currentTarget as SVGRectElement).ownerSVGElement
|
|
212
|
+
const svgRect = svgEl?.getBoundingClientRect()
|
|
213
|
+
const mouseY = svgRect ? e.clientY - svgRect.top : yMax / 2
|
|
214
|
+
showStripeTooltip(item, index, mouseY)
|
|
215
|
+
}}
|
|
216
|
+
onMouseLeave={() => {
|
|
217
|
+
setCurrentHover(null)
|
|
218
|
+
handleTooltipMouseOff()
|
|
219
|
+
if (handleSmallMultipleHover) {
|
|
220
|
+
handleSmallMultipleHover(null, null)
|
|
221
|
+
}
|
|
151
222
|
}}
|
|
152
|
-
onMouseLeave={() => setCurrentHover(null)}
|
|
153
223
|
/>
|
|
154
224
|
)
|
|
155
225
|
})}
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
.warming-stripes-gradient-legend__title {
|
|
7
|
+
font-size: 16px;
|
|
7
8
|
font-weight: 600;
|
|
8
9
|
margin-bottom: 0.5rem;
|
|
9
|
-
font-size: 16px;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.warming-stripes-gradient-legend__description {
|
|
13
|
-
margin-top: 0.5rem;
|
|
14
|
-
margin-bottom: 1rem;
|
|
15
|
-
font-size: 14px;
|
|
16
13
|
color: #555;
|
|
14
|
+
font-size: 14px;
|
|
15
|
+
margin-bottom: 1rem;
|
|
16
|
+
margin-top: 0.5rem;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.warming-stripes-gradient-legend__container {
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
|
|
23
23
|
.warming-stripes-gradient-legend__svg {
|
|
24
24
|
display: block;
|
|
25
|
-
width: 100%;
|
|
26
25
|
overflow: visible;
|
|
26
|
+
width: 100%;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.warming-stripes-gradient-legend__series-label {
|
|
30
|
-
|
|
31
|
-
margin-top: 0.5rem;
|
|
30
|
+
color: #333;
|
|
32
31
|
font-size: 14px;
|
|
33
32
|
font-weight: 500;
|
|
34
|
-
|
|
33
|
+
margin-top: 0.5rem;
|
|
34
|
+
text-align: center;
|
|
35
35
|
}
|
|
@@ -45,7 +45,8 @@ const createInitialState = () => {
|
|
|
45
45
|
showSuppressedSymbol: true,
|
|
46
46
|
showZeroValueData: true,
|
|
47
47
|
hideNullValue: true,
|
|
48
|
-
palette: paletteDefaults
|
|
48
|
+
palette: paletteDefaults,
|
|
49
|
+
useIntelligentLineChartLabels: false
|
|
49
50
|
},
|
|
50
51
|
padding: {
|
|
51
52
|
left: 5,
|
|
@@ -53,12 +54,12 @@ const createInitialState = () => {
|
|
|
53
54
|
},
|
|
54
55
|
preliminaryData: [],
|
|
55
56
|
yAxis: {
|
|
56
|
-
hideAxis:
|
|
57
|
+
hideAxis: true,
|
|
57
58
|
displayNumbersOnBar: false,
|
|
58
59
|
hideLabel: false,
|
|
59
|
-
hideTicks:
|
|
60
|
+
hideTicks: true,
|
|
60
61
|
size: 50,
|
|
61
|
-
gridLines:
|
|
62
|
+
gridLines: true,
|
|
62
63
|
enablePadding: false,
|
|
63
64
|
min: '',
|
|
64
65
|
max: '',
|
|
@@ -72,7 +73,7 @@ const createInitialState = () => {
|
|
|
72
73
|
rightAxisLabelColor: '#1c1d1f',
|
|
73
74
|
rightAxisTickLabelColor: '#1c1d1f',
|
|
74
75
|
rightAxisTickColor: '#1c1d1f',
|
|
75
|
-
numTicks:
|
|
76
|
+
numTicks: 4,
|
|
76
77
|
axisPadding: 0,
|
|
77
78
|
scalePadding: 10,
|
|
78
79
|
tickRotation: 0,
|
|
@@ -132,7 +133,8 @@ const createInitialState = () => {
|
|
|
132
133
|
labelColor: '#1c1d1f',
|
|
133
134
|
tickLabelColor: '#1c1d1f',
|
|
134
135
|
tickColor: '#1c1d1f',
|
|
135
|
-
numTicks:
|
|
136
|
+
numTicks: 6,
|
|
137
|
+
dateDisplayFormat: '%b. %-d %Y',
|
|
136
138
|
labelOffset: 0,
|
|
137
139
|
axisPadding: 200,
|
|
138
140
|
target: 0,
|
|
@@ -141,11 +143,15 @@ const createInitialState = () => {
|
|
|
141
143
|
showYearsOnce: false,
|
|
142
144
|
sortByRecentDate: false,
|
|
143
145
|
brushActive: false,
|
|
144
|
-
brushDefaultRecentDateCount: undefined
|
|
146
|
+
brushDefaultRecentDateCount: undefined,
|
|
147
|
+
viewportNumTicks: {
|
|
148
|
+
xs: 4,
|
|
149
|
+
xxs: 4
|
|
150
|
+
}
|
|
145
151
|
},
|
|
146
152
|
table: {
|
|
147
153
|
label: 'Data Table',
|
|
148
|
-
expanded:
|
|
154
|
+
expanded: false,
|
|
149
155
|
limitHeight: false,
|
|
150
156
|
height: '',
|
|
151
157
|
caption: '',
|
|
@@ -155,7 +161,7 @@ const createInitialState = () => {
|
|
|
155
161
|
indexLabel: '',
|
|
156
162
|
download: false,
|
|
157
163
|
showVertical: true,
|
|
158
|
-
dateDisplayFormat: '',
|
|
164
|
+
dateDisplayFormat: '%B %-d, %Y',
|
|
159
165
|
showMissingDataLabel: true,
|
|
160
166
|
showSuppressedSymbol: true,
|
|
161
167
|
collapsible: true
|
|
@@ -195,7 +201,7 @@ const createInitialState = () => {
|
|
|
195
201
|
side: false,
|
|
196
202
|
topBottom: true
|
|
197
203
|
},
|
|
198
|
-
position: '
|
|
204
|
+
position: 'top',
|
|
199
205
|
orderedValues: [],
|
|
200
206
|
patterns: {},
|
|
201
207
|
patternField: ''
|
|
@@ -223,7 +229,7 @@ const createInitialState = () => {
|
|
|
223
229
|
},
|
|
224
230
|
labels: false,
|
|
225
231
|
dataFormat: {
|
|
226
|
-
commas:
|
|
232
|
+
commas: true,
|
|
227
233
|
prefix: '',
|
|
228
234
|
suffix: '',
|
|
229
235
|
abbreviated: false,
|
|
@@ -234,9 +240,13 @@ const createInitialState = () => {
|
|
|
234
240
|
filters: [],
|
|
235
241
|
confidenceKeys: {},
|
|
236
242
|
visual: {
|
|
237
|
-
border:
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
border: false,
|
|
244
|
+
borderColorTheme: false,
|
|
245
|
+
accent: false,
|
|
246
|
+
background: false,
|
|
247
|
+
hideBackgroundColor: false,
|
|
248
|
+
tp5Treatment: false,
|
|
249
|
+
tp5Background: false,
|
|
240
250
|
verticalHoverLine: false,
|
|
241
251
|
horizontalHoverLine: false,
|
|
242
252
|
lineDatapointSymbol: 'none',
|
|
@@ -249,7 +259,7 @@ const createInitialState = () => {
|
|
|
249
259
|
tooltips: {
|
|
250
260
|
opacity: 90,
|
|
251
261
|
singleSeries: false,
|
|
252
|
-
dateDisplayFormat: ''
|
|
262
|
+
dateDisplayFormat: '%B %-d, %Y'
|
|
253
263
|
},
|
|
254
264
|
forestPlot: {
|
|
255
265
|
startAt: 0,
|
|
@@ -298,6 +308,18 @@ const createInitialState = () => {
|
|
|
298
308
|
area: {
|
|
299
309
|
isStacked: false
|
|
300
310
|
},
|
|
311
|
+
radar: {
|
|
312
|
+
gridRings: 5,
|
|
313
|
+
showGridRings: true,
|
|
314
|
+
gridRingStyle: 'polygons',
|
|
315
|
+
scaleMin: 0,
|
|
316
|
+
scaleMax: '',
|
|
317
|
+
fillOpacity: 0.3,
|
|
318
|
+
showPoints: true,
|
|
319
|
+
pointRadius: 4,
|
|
320
|
+
strokeWidth: 2,
|
|
321
|
+
axisLabelOffset: 15
|
|
322
|
+
},
|
|
301
323
|
sankey: {
|
|
302
324
|
title: {
|
|
303
325
|
defaultColor: 'black'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Preserves the OLD default values for properties changed in initial-state.js.
|
|
2
|
+
// When the backfill loop fills a missing property, it uses these values instead
|
|
3
|
+
// of the current defaults so that existing configs aren't visually affected.
|
|
4
|
+
//
|
|
5
|
+
// - Changed defaults: record the ORIGINAL value before any changes.
|
|
6
|
+
// - New properties: set to `undefined` so they are not backfilled at all.
|
|
7
|
+
//
|
|
8
|
+
// See backfillDefaults() in @cdc/core for the shared fill logic.
|
|
9
|
+
export const LEGACY_CHART_DEFAULTS: Record<string, Record<string, unknown>> = {
|
|
10
|
+
general: { useIntelligentLineChartLabels: undefined },
|
|
11
|
+
yAxis: { hideAxis: false, hideTicks: false, gridLines: false, numTicks: '' },
|
|
12
|
+
xAxis: { numTicks: '', dateDisplayFormat: undefined, viewportNumTicks: undefined },
|
|
13
|
+
table: { expanded: true, dateDisplayFormat: '' },
|
|
14
|
+
legend: { position: 'right' },
|
|
15
|
+
dataFormat: { commas: false },
|
|
16
|
+
tooltips: { dateDisplayFormat: '' },
|
|
17
|
+
visual: { border: false, accent: false, background: false }
|
|
18
|
+
}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
const abbreviationUnits: Record<string, { K: string; M: string; B: string }> = {
|
|
2
|
+
'es-MX': { K: ' mil', M: ' M', B: ' mil M' }
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const defaultUnits = { K: 'K', M: 'M', B: 'B' }
|
|
6
|
+
|
|
7
|
+
export const abbreviateNumber = (num, locale?: string) => {
|
|
8
|
+
const units = (locale && abbreviationUnits[locale]) || defaultUnits
|
|
9
|
+
let unit = ''
|
|
10
|
+
let absNum = Math.abs(num)
|
|
11
|
+
|
|
12
|
+
if (absNum >= 1e9) {
|
|
13
|
+
unit = units.B
|
|
14
|
+
num = num / 1e9
|
|
15
|
+
} else if (absNum >= 1e6) {
|
|
16
|
+
unit = units.M
|
|
17
|
+
num = num / 1e6
|
|
18
|
+
} else if (absNum >= 1e3) {
|
|
19
|
+
unit = units.K
|
|
20
|
+
num = num / 1e3
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return num + unit
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { sanitizeToSvgId } from '@cdc/core/helpers/cove/string'
|
|
2
|
+
|
|
3
|
+
const getStableKeyHash = (input: string): string => {
|
|
4
|
+
// djb2 variant, deterministic and cheap for short editor keys
|
|
5
|
+
let hash = 5381
|
|
6
|
+
for (let i = 0; i < input.length; i++) {
|
|
7
|
+
hash = (hash * 33) ^ input.charCodeAt(i)
|
|
8
|
+
}
|
|
9
|
+
return (hash >>> 0).toString(36)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const getChartPatternId = (patternKey: string): string => {
|
|
13
|
+
const rawKey = String(patternKey)
|
|
14
|
+
const sanitizedKey = sanitizeToSvgId(rawKey)
|
|
15
|
+
const hashSuffix = getStableKeyHash(rawKey)
|
|
16
|
+
return `chart-pattern-${sanitizedKey}-${hashSuffix}`
|
|
17
|
+
}
|
|
@@ -2,6 +2,10 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
|
2
2
|
import _ from 'lodash'
|
|
3
3
|
import { ChartConfig } from '../types/ChartConfig'
|
|
4
4
|
export const getExcludedData = (newConfig: ChartConfig, data: object[]) => {
|
|
5
|
+
if (!Array.isArray(data)) {
|
|
6
|
+
console.warn('COVE: getExcludedData received non-array data:', typeof data)
|
|
7
|
+
return []
|
|
8
|
+
}
|
|
5
9
|
let newExcludedData = data
|
|
6
10
|
if (newConfig.exclusions && newConfig.exclusions.active) {
|
|
7
11
|
if (newConfig.xAxis.type === 'categorical' && newConfig.exclusions.keys?.length > 0) {
|
package/src/helpers/getMinMax.ts
CHANGED
|
@@ -37,8 +37,8 @@ const getMinMax = ({
|
|
|
37
37
|
let leftMax = 0
|
|
38
38
|
let rightMax = 0
|
|
39
39
|
|
|
40
|
-
if (!data) {
|
|
41
|
-
return { min, max }
|
|
40
|
+
if (!data || !config.runtime) {
|
|
41
|
+
return { min, max, leftMax, rightMax }
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const { visualizationType, series } = config
|
|
@@ -248,6 +248,20 @@ const getMinMax = ({
|
|
|
248
248
|
min = min / 1.1
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// Enforce smallest left axis max so small-data charts don't show misleading decimal ticks
|
|
252
|
+
const smallestLeftAxisMaxRaw = config.yAxis.smallestLeftAxisMax
|
|
253
|
+
if (smallestLeftAxisMaxRaw !== null && smallestLeftAxisMaxRaw !== '') {
|
|
254
|
+
const smallestLeftAxisMax = Number(smallestLeftAxisMaxRaw)
|
|
255
|
+
if (!Number.isNaN(smallestLeftAxisMax)) {
|
|
256
|
+
if (max < smallestLeftAxisMax) {
|
|
257
|
+
max = smallestLeftAxisMax
|
|
258
|
+
}
|
|
259
|
+
if (leftMax < smallestLeftAxisMax) {
|
|
260
|
+
leftMax = smallestLeftAxisMax
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
251
265
|
return { min, max, leftMax, rightMax }
|
|
252
266
|
}
|
|
253
267
|
export default getMinMax
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
export const handleChartAriaLabels =
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
}
|
|
1
|
+
export const handleChartAriaLabels = state => {
|
|
2
|
+
try {
|
|
3
|
+
if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
|
|
4
|
+
let ariaLabel = ''
|
|
5
|
+
|
|
6
|
+
if (state.visualizationType) {
|
|
7
|
+
ariaLabel += `${state.visualizationType} chart`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (state.title && state.visualizationType) {
|
|
11
|
+
ariaLabel += ` with the title: ${state.title}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return ariaLabel
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error('COVE: ', e.message) // eslint-disable-line
|
|
17
|
+
return 'Data visualization container'
|
|
18
|
+
}
|
|
19
|
+
}
|