@cdc/chart 4.26.3 → 4.26.4
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/CONFIG.md +169 -0
- package/README.md +42 -17
- package/dist/cdcchart.js +34725 -33730
- package/examples/feature/bar/additional-column-tooltip.json +3 -3
- package/examples/feature/deviation/planet-deviation-config.json +2 -2
- package/examples/feature/paired-bar/paired-bar-example.json +2 -2
- package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
- package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
- package/examples/feature/tests-covid/covid-example-config.json +2 -2
- package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
- package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
- package/examples/metadata-variables.json +1 -1
- package/examples/minimal-example.json +24 -0
- package/examples/private/test2.json +35012 -0
- package/package.json +4 -4
- package/src/CdcChartComponent.tsx +9 -6
- package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
- package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
- package/src/_stories/{Chart.SmallMultiples.stories.tsx → Chart.SmallMultiples.smoke.stories.tsx} +17 -1
- package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +12 -0
- package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +88 -85
- package/src/_stories/ChartEditor.Editor.stories.tsx +23 -0
- package/src/_stories/ChartLine.DashedDates.smoke.stories.tsx +23 -0
- package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
- package/src/_stories/_mock/paired-bar-abbr.json +1 -1
- package/src/_stories/_mock/paired-bar.json +1 -1
- package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -1
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
- package/src/components/Axis/BottomAxis.tsx +1 -1
- package/src/components/Axis/LeftAxis.tsx +2 -4
- package/src/components/Axis/LeftAxisGridlines.tsx +1 -3
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +9 -5
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +9 -5
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +9 -5
- package/src/components/BarChart/components/BarChart.Vertical.tsx +9 -6
- package/src/components/BarChart/components/BarChart.tsx +2 -6
- package/src/components/BarChart/helpers/useBarChart.ts +1 -1
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
- package/src/components/EditorPanel/EditorPanel.tsx +633 -908
- package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +216 -165
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +168 -116
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +154 -109
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +12 -26
- package/src/components/EditorPanel/useEditorPermissions.ts +1 -10
- package/src/components/Forecasting/Forecasting.tsx +2 -2
- package/src/components/HorizonChart/HorizonChart.tsx +10 -2
- package/src/components/HoverLine/HoverLine.tsx +6 -8
- package/src/components/Legend/Legend.Component.tsx +4 -4
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +5 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
- package/src/components/Legend/helpers/index.ts +0 -2
- package/src/components/LineChart/LineChartProps.ts +1 -0
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +3 -2
- package/src/components/LineChart/index.tsx +11 -4
- package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
- package/src/components/LinearChart/tests/LinearChart.test.tsx +147 -0
- package/src/components/LinearChart.tsx +98 -41
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/PieChart/PieChart.tsx +11 -9
- package/src/components/Regions/components/Regions.tsx +30 -20
- package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +1 -0
- package/src/components/Sparkline/components/SparkLine.tsx +1 -1
- package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
- package/src/data/initial-state.js +1 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +193 -0
- package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
- package/src/helpers/tooltipHelpers.ts +176 -0
- package/src/hooks/useHighlightedBars.ts +9 -16
- package/src/hooks/useProgrammaticTooltip.ts +16 -2
- package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
- package/src/hooks/useTooltip.tsx +167 -38
- package/src/test/CdcChart.test.jsx +24 -0
- package/src/types/ChartConfig.ts +1 -0
- package/LICENSE +0 -201
- package/examples/chart-regression-2.json +0 -2360
- package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
- package/examples/feature/__data__/city-temperature.json +0 -2198
- package/examples/feature/bar/Barchart_with_negative.json +0 -34
- package/examples/feature/boxplot/box-plot-data.json +0 -71
- package/examples/feature/boxplot/boxplot-data.json +0 -156
- package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
- package/examples/feature/forest-plot/log.json +0 -26
- package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
- package/examples/region-issue.json +0 -2065
- package/examples/xaxis.json +0 -493
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
- /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
- /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
- /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
- /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
- /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
- /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
- /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
- /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
- /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.CustomColors.stories.tsx → Chart.CustomColors.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Defaults.stories.tsx → Chart.Defaults.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.SmallestLeftAxisMax.stories.tsx → Chart.SmallestLeftAxisMax.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
- /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
|
@@ -35,6 +35,7 @@ import { useTickFormatters } from './LinearChart/utils/tickFormatting'
|
|
|
35
35
|
import { isLegendWrapViewport, isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
36
36
|
import { calcInitialHeight } from '../helpers/sizeHelpers'
|
|
37
37
|
import { calculateHorizontalBarCategoryLabelWidth } from '../helpers/calculateHorizontalBarCategoryLabelWidth'
|
|
38
|
+
import { calculateLeftYAxisWidth } from '../helpers/calculateLeftYAxisWidth'
|
|
38
39
|
|
|
39
40
|
// Hooks
|
|
40
41
|
import useReduceData from '../hooks/useReduceData'
|
|
@@ -51,6 +52,7 @@ import Annotation from './Annotations'
|
|
|
51
52
|
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
52
53
|
import HoverLine from './HoverLine/HoverLine'
|
|
53
54
|
import { SmallMultiples } from './SmallMultiples'
|
|
55
|
+
import { type TooltipDisplayData } from '../helpers/tooltipHelpers'
|
|
54
56
|
|
|
55
57
|
type LinearChartProps = {
|
|
56
58
|
parentWidth: number
|
|
@@ -62,6 +64,7 @@ const BOTTOM_LABEL_PADDING = 9
|
|
|
62
64
|
const X_TICK_LABEL_PADDING = 4.5
|
|
63
65
|
const DEFAULT_TICK_LENGTH = 8
|
|
64
66
|
const DEFAULT_MAX_TICK_ROTATION = 90
|
|
67
|
+
const DEFAULT_LEFT_Y_AXIS_WIDTH = 50
|
|
65
68
|
|
|
66
69
|
// Font sizes
|
|
67
70
|
const TICK_LABEL_FONT_SIZE = 16
|
|
@@ -88,10 +91,7 @@ const WARMING_STRIPES_HEIGHT = 78
|
|
|
88
91
|
// Time constants
|
|
89
92
|
const MONTH_AS_MS = 1000 * 60 * 60 * 24 * 30
|
|
90
93
|
|
|
91
|
-
type TooltipData =
|
|
92
|
-
dataXPosition?: number
|
|
93
|
-
dataYPosition?: number
|
|
94
|
-
}
|
|
94
|
+
type TooltipData = Partial<TooltipDisplayData>
|
|
95
95
|
|
|
96
96
|
type UseTooltipReturn<T = TooltipData> = {
|
|
97
97
|
tooltipData: T | null
|
|
@@ -176,8 +176,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
176
176
|
const isDateTime = config.xAxis.type === 'date-time'
|
|
177
177
|
const inlineLabelHasNoSpace = !inlineLabel?.includes(' ')
|
|
178
178
|
const needsYAxisAutoPadding = inlineLabel && !inlineLabelHasNoSpace
|
|
179
|
-
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
180
|
-
const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
|
|
181
179
|
const tickLabelFontSize = isMobileFontViewport(vizViewport) ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
|
|
182
180
|
const axisLabelFontSize = isMobileFontViewport(vizViewport) ? AXIS_LABEL_FONT_SIZE_SMALL : AXIS_LABEL_FONT_SIZE
|
|
183
181
|
const GET_TEXT_WIDTH_FONT = `normal ${tickLabelFontSize}px Nunito, sans-serif`
|
|
@@ -258,11 +256,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
258
256
|
|
|
259
257
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data })
|
|
260
258
|
|
|
261
|
-
// State for computed
|
|
262
|
-
const [
|
|
263
|
-
|
|
264
|
-
// Use computed width if available, otherwise fall back to config value
|
|
265
|
-
const yAxisWidth = computedYAxisWidth ?? Number(runtime.yAxis.size)
|
|
259
|
+
// State for computed left-axis width - shared across all linear-chart types.
|
|
260
|
+
const [currentYAxisWidth, setCurrentYAxisWidth] = useState<number>(DEFAULT_LEFT_Y_AXIS_WIDTH)
|
|
261
|
+
const yAxisWidth = currentYAxisWidth
|
|
266
262
|
|
|
267
263
|
// Chart width calculation using the current y-axis width
|
|
268
264
|
const xMax = parentWidth - yAxisWidth - (hasRightAxis ? config.yAxis.rightAxisSize : 0)
|
|
@@ -303,39 +299,77 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
303
299
|
shouldAbbreviate
|
|
304
300
|
})
|
|
305
301
|
|
|
306
|
-
//
|
|
302
|
+
// Horizontal charts swap axes at runtime, but the left-side category label visibility
|
|
303
|
+
// still follows config.yAxis.hideLabel to match the actual axis renderers.
|
|
307
304
|
const categoryLabelSpace = useMemo(() => {
|
|
305
|
+
if (config.yAxis.hideLabel) return 0
|
|
306
|
+
|
|
308
307
|
return calculateHorizontalBarCategoryLabelWidth({
|
|
309
308
|
yScale,
|
|
310
309
|
chartWidth: parentWidth,
|
|
311
310
|
formatDate,
|
|
312
311
|
parseDate,
|
|
313
312
|
tickLabelFont: GET_TEXT_WIDTH_FONT,
|
|
314
|
-
xAxisType:
|
|
313
|
+
xAxisType: runtime.xAxis?.type,
|
|
315
314
|
labelPlacement: config.yAxis.labelPlacement
|
|
316
315
|
})
|
|
317
|
-
}, [
|
|
316
|
+
}, [
|
|
317
|
+
config.yAxis.hideLabel,
|
|
318
|
+
config.yAxis.labelPlacement,
|
|
319
|
+
yScale,
|
|
320
|
+
parentWidth,
|
|
321
|
+
formatDate,
|
|
322
|
+
parseDate,
|
|
323
|
+
GET_TEXT_WIDTH_FONT,
|
|
324
|
+
runtime.xAxis?.type
|
|
325
|
+
])
|
|
318
326
|
|
|
319
327
|
const horizontalYAxisLabelSpace = runtime.yAxis.label && !config.hideYAxisLabel ? 30 : 0
|
|
320
328
|
|
|
321
|
-
// Update y-axis width state when computed value changes (for horizontal bar charts)
|
|
322
|
-
useEffect(() => {
|
|
323
|
-
if (isHorizontal && config.visualizationType === 'Bar') {
|
|
324
|
-
const newWidth = categoryLabelSpace + horizontalYAxisLabelSpace
|
|
325
|
-
if (newWidth !== computedYAxisWidth) {
|
|
326
|
-
setComputedYAxisWidth(newWidth)
|
|
327
|
-
}
|
|
328
|
-
} else if (computedYAxisWidth !== null) {
|
|
329
|
-
// Reset to null for non-horizontal bar charts so we use config value
|
|
330
|
-
setComputedYAxisWidth(null)
|
|
331
|
-
}
|
|
332
|
-
}, [isHorizontal, config.visualizationType, categoryLabelSpace, horizontalYAxisLabelSpace, computedYAxisWidth])
|
|
333
|
-
|
|
334
329
|
const [yTickCount, xTickCount] = ['yAxis', 'xAxis'].map(axis =>
|
|
335
330
|
countNumOfTicks({ axis, max, runtime, currentViewport, isHorizontal, data, config, min })
|
|
336
331
|
)
|
|
337
332
|
const handleNumTicks = isForestPlot ? config.data.length : yTickCount
|
|
338
333
|
|
|
334
|
+
const calculatedYAxisWidth = useMemo(() => {
|
|
335
|
+
if (isHorizontal && config.visualizationType === 'Bar') {
|
|
336
|
+
return categoryLabelSpace + horizontalYAxisLabelSpace
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return calculateLeftYAxisWidth({
|
|
340
|
+
config,
|
|
341
|
+
data,
|
|
342
|
+
yScale,
|
|
343
|
+
numTicks: handleNumTicks,
|
|
344
|
+
parentWidth,
|
|
345
|
+
tickLabelFont: GET_TEXT_WIDTH_FONT,
|
|
346
|
+
axisLabelFontSize,
|
|
347
|
+
handleLeftTickFormatting,
|
|
348
|
+
categoryLabelSpace,
|
|
349
|
+
horizontalYAxisLabelSpace
|
|
350
|
+
})
|
|
351
|
+
}, [
|
|
352
|
+
config,
|
|
353
|
+
data,
|
|
354
|
+
yScale,
|
|
355
|
+
handleNumTicks,
|
|
356
|
+
parentWidth,
|
|
357
|
+
isHorizontal,
|
|
358
|
+
GET_TEXT_WIDTH_FONT,
|
|
359
|
+
axisLabelFontSize,
|
|
360
|
+
handleLeftTickFormatting,
|
|
361
|
+
categoryLabelSpace,
|
|
362
|
+
horizontalYAxisLabelSpace
|
|
363
|
+
])
|
|
364
|
+
|
|
365
|
+
useLayoutEffect(() => {
|
|
366
|
+
if (parentWidth <= 0) return
|
|
367
|
+
|
|
368
|
+
if (calculatedYAxisWidth !== currentYAxisWidth) {
|
|
369
|
+
setCurrentYAxisWidth(calculatedYAxisWidth)
|
|
370
|
+
}
|
|
371
|
+
}, [calculatedYAxisWidth, currentYAxisWidth, parentWidth])
|
|
372
|
+
|
|
339
373
|
// Tooltip Helpers
|
|
340
374
|
|
|
341
375
|
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } =
|
|
@@ -353,6 +387,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
353
387
|
xScale,
|
|
354
388
|
yScale,
|
|
355
389
|
seriesScale,
|
|
390
|
+
yAxisWidth,
|
|
356
391
|
showTooltip,
|
|
357
392
|
hideTooltip,
|
|
358
393
|
interactionLabel
|
|
@@ -387,7 +422,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
387
422
|
return manualStep
|
|
388
423
|
}, [config.xAxis.manualStep, config.xAxis.viewportStepCount, currentViewport])
|
|
389
424
|
|
|
390
|
-
const smallMultiplesSync = useSmallMultipleSynchronization(xMax, yMax, getXValueFromCoordinate)
|
|
425
|
+
const smallMultiplesSync = useSmallMultipleSynchronization(xMax, yMax, yAxisWidth, getXValueFromCoordinate)
|
|
391
426
|
|
|
392
427
|
const onMouseMove = event => {
|
|
393
428
|
const svgRect = event.currentTarget.getBoundingClientRect()
|
|
@@ -416,7 +451,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
416
451
|
const internalSvgRef = useProgrammaticTooltip({
|
|
417
452
|
svgRef,
|
|
418
453
|
getCoordinateFromXValue,
|
|
454
|
+
getXValueFromCoordinate,
|
|
419
455
|
config,
|
|
456
|
+
yAxisWidth,
|
|
420
457
|
setPoint,
|
|
421
458
|
setShowHoverLine,
|
|
422
459
|
handleTooltipMouseOver,
|
|
@@ -589,7 +626,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
589
626
|
xMax={xMax}
|
|
590
627
|
yAxisWidth={yAxisWidth}
|
|
591
628
|
numTicks={handleNumTicks}
|
|
592
|
-
yLabelOffset={yLabelOffset}
|
|
593
629
|
axisLabelFontSize={axisLabelFontSize}
|
|
594
630
|
/>
|
|
595
631
|
)}
|
|
@@ -673,8 +709,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
673
709
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
674
710
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
675
711
|
className='anchor-y'
|
|
676
|
-
from={{ x: Number(
|
|
677
|
-
to={{ x: Number(
|
|
712
|
+
from={{ x: Number(yAxisWidth), y: position - middleOffset }}
|
|
713
|
+
to={{ x: Number(yAxisWidth) + Number(xMax), y: position - middleOffset }}
|
|
678
714
|
/>
|
|
679
715
|
)
|
|
680
716
|
})}
|
|
@@ -710,8 +746,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
710
746
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
711
747
|
fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
712
748
|
className='anchor-x'
|
|
713
|
-
from={{ x: Number(anchorPosition) + Number(
|
|
714
|
-
to={{ x: Number(anchorPosition) + Number(
|
|
749
|
+
from={{ x: Number(anchorPosition) + Number(yAxisWidth), y: 0 }}
|
|
750
|
+
to={{ x: Number(anchorPosition) + Number(yAxisWidth), y: yMax }}
|
|
715
751
|
/>
|
|
716
752
|
)
|
|
717
753
|
})}
|
|
@@ -728,6 +764,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
728
764
|
tooltipData={tooltipData}
|
|
729
765
|
yMax={yMax}
|
|
730
766
|
xMax={xMax}
|
|
767
|
+
yAxisWidth={yAxisWidth}
|
|
731
768
|
/>
|
|
732
769
|
)}
|
|
733
770
|
{isNoDataAvailable && (
|
|
@@ -741,8 +778,22 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
741
778
|
)}
|
|
742
779
|
{showHoverLine && (
|
|
743
780
|
<>
|
|
744
|
-
<HoverLine
|
|
745
|
-
|
|
781
|
+
<HoverLine
|
|
782
|
+
xMax={xMax}
|
|
783
|
+
yMax={yMax}
|
|
784
|
+
point={point}
|
|
785
|
+
tooltipData={tooltipData}
|
|
786
|
+
orientation='horizontal'
|
|
787
|
+
yAxisWidth={yAxisWidth}
|
|
788
|
+
/>
|
|
789
|
+
<HoverLine
|
|
790
|
+
xMax={xMax}
|
|
791
|
+
yMax={yMax}
|
|
792
|
+
point={point}
|
|
793
|
+
tooltipData={tooltipData}
|
|
794
|
+
orientation='vertical'
|
|
795
|
+
yAxisWidth={yAxisWidth}
|
|
796
|
+
/>
|
|
746
797
|
</>
|
|
747
798
|
)}
|
|
748
799
|
<Group left={yAxisWidth}>
|
|
@@ -778,7 +829,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
778
829
|
suffixWidth={suffixWidth}
|
|
779
830
|
horizontalYAxisLabelSpace={horizontalYAxisLabelSpace}
|
|
780
831
|
categoryLabelSpace={categoryLabelSpace}
|
|
781
|
-
yLabelOffset={yLabelOffset}
|
|
782
832
|
/>
|
|
783
833
|
)}
|
|
784
834
|
{config.yAxis.type === 'categorical' && config.orientation === 'vertical' && (
|
|
@@ -839,10 +889,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
839
889
|
</svg>
|
|
840
890
|
{!isDraggingAnnotation &&
|
|
841
891
|
tooltipData &&
|
|
842
|
-
|
|
892
|
+
tooltipData.data?.length > 0 &&
|
|
843
893
|
tooltipOpen &&
|
|
844
894
|
showTooltip &&
|
|
845
|
-
!tooltipData?.data?.some(
|
|
895
|
+
!tooltipData?.data?.some(row => row?.value === undefined) &&
|
|
846
896
|
tooltipData.dataYPosition &&
|
|
847
897
|
tooltipData.dataXPosition &&
|
|
848
898
|
!config.tooltips.singleSeries && (
|
|
@@ -860,9 +910,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
860
910
|
>
|
|
861
911
|
<ul>
|
|
862
912
|
{typeof tooltipData === 'object' &&
|
|
863
|
-
|
|
864
|
-
.filter(
|
|
865
|
-
.map((
|
|
913
|
+
tooltipData.data
|
|
914
|
+
.filter(row => row && row.value !== undefined)
|
|
915
|
+
.map((row, index) => (
|
|
916
|
+
<TooltipListItem
|
|
917
|
+
row={row}
|
|
918
|
+
index={index}
|
|
919
|
+
key={index}
|
|
920
|
+
useMarkerColumn={tooltipData.useMarkerColumn}
|
|
921
|
+
/>
|
|
922
|
+
))}
|
|
866
923
|
</ul>
|
|
867
924
|
</TooltipWithBounds>
|
|
868
925
|
</>
|
|
@@ -10,7 +10,7 @@ import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
|
10
10
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
11
11
|
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
12
12
|
|
|
13
|
-
const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
13
|
+
const PairedBarChart = ({ width, height, originalWidth, yAxisWidth }) => {
|
|
14
14
|
const {
|
|
15
15
|
config,
|
|
16
16
|
colorScale,
|
|
@@ -93,7 +93,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
93
93
|
id='cdc-visualization__paired-bar-chart'
|
|
94
94
|
width={originalWidth}
|
|
95
95
|
height={height}
|
|
96
|
-
viewBox={`0 0 ${width +
|
|
96
|
+
viewBox={`0 0 ${width + yAxisWidth} ${height}`}
|
|
97
97
|
role='img'
|
|
98
98
|
tabIndex={0}
|
|
99
99
|
>
|
|
@@ -24,14 +24,9 @@ import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
|
|
|
24
24
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
25
25
|
import { scaleOrdinal } from '@visx/scale'
|
|
26
26
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
27
|
+
import { type TooltipDisplayData } from '../../helpers/tooltipHelpers'
|
|
27
28
|
|
|
28
|
-
type TooltipData =
|
|
29
|
-
data: {
|
|
30
|
-
[key: string]: string | number
|
|
31
|
-
}
|
|
32
|
-
dataXPosition: number
|
|
33
|
-
dataYPosition: number
|
|
34
|
-
}
|
|
29
|
+
type TooltipData = TooltipDisplayData
|
|
35
30
|
|
|
36
31
|
type PieChartProps = {
|
|
37
32
|
parentWidth?: number
|
|
@@ -426,7 +421,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
426
421
|
<div ref={triggerRef} />
|
|
427
422
|
{!isDraggingAnnotation &&
|
|
428
423
|
tooltipData &&
|
|
429
|
-
|
|
424
|
+
tooltipData.data?.length > 0 &&
|
|
430
425
|
tooltipOpen &&
|
|
431
426
|
showTooltip &&
|
|
432
427
|
tooltipData.dataYPosition &&
|
|
@@ -442,7 +437,14 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
442
437
|
>
|
|
443
438
|
<ul>
|
|
444
439
|
{typeof tooltipData === 'object' &&
|
|
445
|
-
|
|
440
|
+
tooltipData.data.map((row, index) => (
|
|
441
|
+
<TooltipListItem
|
|
442
|
+
row={row}
|
|
443
|
+
index={index}
|
|
444
|
+
key={index}
|
|
445
|
+
useMarkerColumn={tooltipData.useMarkerColumn}
|
|
446
|
+
/>
|
|
447
|
+
))}
|
|
446
448
|
</ul>
|
|
447
449
|
</TooltipWithBounds>
|
|
448
450
|
</>
|
|
@@ -10,7 +10,8 @@ const VIZ_TYPES = {
|
|
|
10
10
|
BAR: 'Bar',
|
|
11
11
|
LINE: 'Line',
|
|
12
12
|
AREA: 'Area Chart',
|
|
13
|
-
COMBO: 'Combo'
|
|
13
|
+
COMBO: 'Combo',
|
|
14
|
+
HORIZON: 'Horizon Chart'
|
|
14
15
|
} as const
|
|
15
16
|
|
|
16
17
|
type Region = {
|
|
@@ -35,6 +36,7 @@ type RegionsProps = {
|
|
|
35
36
|
barWidth?: number
|
|
36
37
|
totalBarsInGroup?: number
|
|
37
38
|
xMax?: number
|
|
39
|
+
yAxisWidth?: number
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
type HighlightedAreaProps = {
|
|
@@ -63,14 +65,22 @@ const findClosestDate = <T,>(targetTime: number, domain: T[], getTime: (d: T) =>
|
|
|
63
65
|
return closest
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
/** Check if visualization type is line-like (Line or
|
|
67
|
-
const isLineLike = (type: string): boolean =>
|
|
68
|
+
/** Check if visualization type is line-like (Line, Area Chart, or Horizon Chart) */
|
|
69
|
+
const isLineLike = (type: string): boolean =>
|
|
70
|
+
type === VIZ_TYPES.LINE || type === VIZ_TYPES.AREA || type === VIZ_TYPES.HORIZON
|
|
68
71
|
|
|
69
72
|
/** Check if visualization type is bar-like (Bar or Combo) */
|
|
70
73
|
const isBarLike = (type: string): boolean => type === VIZ_TYPES.BAR || type === VIZ_TYPES.COMBO
|
|
71
74
|
|
|
72
75
|
// TODO: should regions be removed on categorical axis?
|
|
73
|
-
const Regions: React.FC<RegionsProps> = ({
|
|
76
|
+
const Regions: React.FC<RegionsProps> = ({
|
|
77
|
+
xScale,
|
|
78
|
+
barWidth = 0,
|
|
79
|
+
totalBarsInGroup = 1,
|
|
80
|
+
yMax,
|
|
81
|
+
xMax,
|
|
82
|
+
yAxisWidth = 0
|
|
83
|
+
}) => {
|
|
74
84
|
const { parseDate, config } = useContext<ChartContext>(ConfigContext)
|
|
75
85
|
|
|
76
86
|
const { regions, visualizationType, orientation, xAxis } = config
|
|
@@ -122,8 +132,8 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
122
132
|
} else {
|
|
123
133
|
from = xScale(region.from)
|
|
124
134
|
}
|
|
125
|
-
// Add left padding (
|
|
126
|
-
let scalePadding =
|
|
135
|
+
// Add left padding (yAxisWidth) + half bandwidth to center on the category
|
|
136
|
+
let scalePadding = yAxisWidth
|
|
127
137
|
if (xScale.bandwidth) {
|
|
128
138
|
scalePadding += xScale.bandwidth() / 2
|
|
129
139
|
}
|
|
@@ -135,8 +145,8 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
135
145
|
return calculateLineLastDatePosition_Categorical()
|
|
136
146
|
}
|
|
137
147
|
let to = xScale(region.to)
|
|
138
|
-
// Add left padding (
|
|
139
|
-
let scalePadding =
|
|
148
|
+
// Add left padding (yAxisWidth) + half bandwidth
|
|
149
|
+
let scalePadding = yAxisWidth
|
|
140
150
|
if (xScale.bandwidth) {
|
|
141
151
|
scalePadding += xScale.bandwidth() / 2
|
|
142
152
|
}
|
|
@@ -158,8 +168,8 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
158
168
|
const closestDate = findClosestDate(parsedDate, domain, d => d)
|
|
159
169
|
from = xScale(closestDate)
|
|
160
170
|
}
|
|
161
|
-
// Add left padding (
|
|
162
|
-
let scalePadding =
|
|
171
|
+
// Add left padding (yAxisWidth) + half bandwidth
|
|
172
|
+
let scalePadding = yAxisWidth
|
|
163
173
|
if (xScale.bandwidth) {
|
|
164
174
|
scalePadding += xScale.bandwidth() / 2
|
|
165
175
|
}
|
|
@@ -179,8 +189,8 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
179
189
|
const closestDate = findClosestDate(parsedDate, domain, d => d)
|
|
180
190
|
let to = xScale(closestDate)
|
|
181
191
|
|
|
182
|
-
// Add left padding (
|
|
183
|
-
let scalePadding =
|
|
192
|
+
// Add left padding (yAxisWidth) + half bandwidth
|
|
193
|
+
let scalePadding = yAxisWidth
|
|
184
194
|
if (xScale.bandwidth) {
|
|
185
195
|
scalePadding += xScale.bandwidth() / 2
|
|
186
196
|
}
|
|
@@ -190,13 +200,13 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
190
200
|
const getLineFromValue_DateTime = (region: Region): number => {
|
|
191
201
|
if (region.fromType === 'Previous Days') {
|
|
192
202
|
const from = calculatePreviousDaysFrom(region, 'date-time')
|
|
193
|
-
return from +
|
|
203
|
+
return from + yAxisWidth
|
|
194
204
|
}
|
|
195
205
|
const date = new Date(region.from)
|
|
196
206
|
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
197
207
|
let from = xScale(parsedDate)
|
|
198
208
|
// For date-time, xScale returns correct position (no bandwidth), just add left padding
|
|
199
|
-
return from +
|
|
209
|
+
return from + yAxisWidth
|
|
200
210
|
}
|
|
201
211
|
|
|
202
212
|
const getLineToValue_DateTime = (region: Region): number => {
|
|
@@ -204,17 +214,17 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
204
214
|
return calculateLineLastDatePosition_DateTime()
|
|
205
215
|
}
|
|
206
216
|
let to = xScale(parseDate(region.to).getTime())
|
|
207
|
-
return to +
|
|
217
|
+
return to + yAxisWidth
|
|
208
218
|
}
|
|
209
219
|
|
|
210
220
|
const calculateLineLastDatePosition_Categorical = (): number => {
|
|
211
|
-
const chartStart =
|
|
221
|
+
const chartStart = yAxisWidth
|
|
212
222
|
// Extend to the right edge of the chart
|
|
213
223
|
return chartStart + (xMax || 0)
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
const calculateLineLastDatePosition_Date = (): number => {
|
|
217
|
-
const chartStart =
|
|
227
|
+
const chartStart = yAxisWidth
|
|
218
228
|
// For date scale line charts with Last Date, extend to the right edge of the chart
|
|
219
229
|
return chartStart + (xMax || 0)
|
|
220
230
|
}
|
|
@@ -223,8 +233,8 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
223
233
|
const domain = xScale.domain()
|
|
224
234
|
const lastDate = domain[domain.length - 1]
|
|
225
235
|
const lastDatePosition = xScale(lastDate)
|
|
226
|
-
// Match the non-Last Date logic: just add
|
|
227
|
-
return Number(lastDatePosition +
|
|
236
|
+
// Match the non-Last Date logic: just add yAxisWidth
|
|
237
|
+
return Number(lastDatePosition + yAxisWidth)
|
|
228
238
|
}
|
|
229
239
|
|
|
230
240
|
// ============================================
|
|
@@ -389,7 +399,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
389
399
|
|
|
390
400
|
if (!regions || orientation !== 'vertical') return null
|
|
391
401
|
|
|
392
|
-
const chartStart =
|
|
402
|
+
const chartStart = yAxisWidth
|
|
393
403
|
const chartEnd = xMax !== undefined ? chartStart + xMax : chartStart + 1000
|
|
394
404
|
|
|
395
405
|
return regions.map((region: Region) => {
|
|
@@ -4,22 +4,21 @@ import { Group } from '@visx/group'
|
|
|
4
4
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
5
5
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
6
6
|
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
7
|
+
import { buildSeriesTooltipListHtml } from '../../helpers/tooltipHelpers'
|
|
7
8
|
|
|
8
|
-
const ScatterPlot = ({ xScale, yScale }) => {
|
|
9
|
+
const ScatterPlot = ({ xScale, yScale, yAxisWidth }) => {
|
|
9
10
|
const {
|
|
10
11
|
transformedData: data,
|
|
11
12
|
config,
|
|
12
13
|
tableData,
|
|
13
14
|
formatNumber,
|
|
14
15
|
seriesHighlight,
|
|
15
|
-
colorPalettes,
|
|
16
16
|
colorScale,
|
|
17
17
|
interactionLabel
|
|
18
18
|
} = useContext(ConfigContext)
|
|
19
19
|
|
|
20
20
|
// TODO: copied from line chart should probably be a constant somewhere.
|
|
21
21
|
const circleRadii = 4.5
|
|
22
|
-
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
23
22
|
|
|
24
23
|
// Track current hover for analytics
|
|
25
24
|
const [currentHover, setCurrentHover] = useState({ dataIndex: null, seriesKey: null })
|
|
@@ -37,23 +36,26 @@ const ScatterPlot = ({ xScale, yScale }) => {
|
|
|
37
36
|
}
|
|
38
37
|
])
|
|
39
38
|
const handleTooltip = (item, s, dataIndex) => `<div>
|
|
40
|
-
${
|
|
41
|
-
config
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
${buildSeriesTooltipListHtml({
|
|
40
|
+
config,
|
|
41
|
+
colorScale,
|
|
42
|
+
heading: `${config.runtime?.xAxis?.label || config.xAxis.label}: ${formatNumber(
|
|
43
|
+
item[config.xAxis.dataKey],
|
|
44
|
+
'bottom'
|
|
45
|
+
)}`,
|
|
46
|
+
seriesKey: s,
|
|
47
|
+
seriesText: `${config.runtime.seriesLabels[s] || s}: ${formatNumber(item[s], 'left')}`,
|
|
48
|
+
extraRows: additionalColumns
|
|
49
|
+
.map(
|
|
50
|
+
([label, name, options]) =>
|
|
51
|
+
`${label} : ${formatColNumber(tableData[dataIndex][name], 'left', false, config, options)}`
|
|
52
|
+
)
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
})}
|
|
53
55
|
</div>`
|
|
54
56
|
|
|
55
57
|
return (
|
|
56
|
-
<Group className='scatter-plot' left={
|
|
58
|
+
<Group className='scatter-plot' left={yAxisWidth}>
|
|
57
59
|
{data.map((item, dataIndex) => {
|
|
58
60
|
// prettier-ignore
|
|
59
61
|
return config.runtime.seriesKeys.map((s, index) => {
|
|
@@ -114,6 +114,7 @@ const SmallMultipleTile: React.FC<SmallMultipleTileProps> = ({
|
|
|
114
114
|
hideYAxisLabel: !isFirstInRow,
|
|
115
115
|
legend: {
|
|
116
116
|
...tileConfig.legend,
|
|
117
|
+
tooltipLegendVisible: !config.legend?.hide,
|
|
117
118
|
hide: true // Hide legends for all small multiple tiles
|
|
118
119
|
},
|
|
119
120
|
xAxis: {
|
|
@@ -34,7 +34,7 @@ const SparkLine: React.FC<SparkLineProps> = props => {
|
|
|
34
34
|
const margin = { top: 5, right: 20, bottom: 10, left: 10 }
|
|
35
35
|
const height = Number(parentHeight)
|
|
36
36
|
|
|
37
|
-
const xMax = width -
|
|
37
|
+
const xMax = width - 50
|
|
38
38
|
const yMax = height - margin.top - 20
|
|
39
39
|
|
|
40
40
|
const getXAxisData = d =>
|
|
@@ -15,6 +15,7 @@ type WarmingStripesProps = {
|
|
|
15
15
|
yScale: any
|
|
16
16
|
xMax: number
|
|
17
17
|
yMax: number
|
|
18
|
+
yAxisWidth: number
|
|
18
19
|
synchronizedXValue?: any
|
|
19
20
|
showTooltip: (args: any) => void
|
|
20
21
|
handleTooltipMouseOff: () => void
|
|
@@ -23,6 +24,7 @@ type WarmingStripesProps = {
|
|
|
23
24
|
const WarmingStripes = ({
|
|
24
25
|
xMax,
|
|
25
26
|
yMax,
|
|
27
|
+
yAxisWidth,
|
|
26
28
|
synchronizedXValue,
|
|
27
29
|
showTooltip,
|
|
28
30
|
handleTooltipMouseOff
|
|
@@ -119,7 +121,7 @@ const WarmingStripes = ({
|
|
|
119
121
|
[valueKey, formattedValue, 'left']
|
|
120
122
|
]
|
|
121
123
|
|
|
122
|
-
const dataXPosition = index * stripeWidth + stripeWidth / 2 +
|
|
124
|
+
const dataXPosition = index * stripeWidth + stripeWidth / 2 + yAxisWidth
|
|
123
125
|
const dataYPosition = mouseY ?? yMax / 2
|
|
124
126
|
|
|
125
127
|
showTooltip({
|
|
@@ -132,7 +134,7 @@ const WarmingStripes = ({
|
|
|
132
134
|
}
|
|
133
135
|
})
|
|
134
136
|
},
|
|
135
|
-
[valueKey, xAxisDataKey, stripeWidth,
|
|
137
|
+
[valueKey, xAxisDataKey, stripeWidth, yAxisWidth, yMax, formatNumber, showTooltip]
|
|
136
138
|
)
|
|
137
139
|
|
|
138
140
|
// Handle incoming synchronized tooltip from sibling small multiple tiles
|
|
@@ -156,7 +158,7 @@ const WarmingStripes = ({
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
return (
|
|
159
|
-
<Group className='warming-stripes' left={
|
|
161
|
+
<Group className='warming-stripes' left={yAxisWidth}>
|
|
160
162
|
{displayData.map((item, index) => {
|
|
161
163
|
const value = Number(item[valueKey])
|
|
162
164
|
if (isNaN(value)) return null
|