@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
|
@@ -1,46 +1,130 @@
|
|
|
1
|
-
import { useContext,
|
|
1
|
+
import { useContext, useState } from 'react'
|
|
2
2
|
import ConfigContext from '../../../ConfigContext'
|
|
3
3
|
import DOMPurify from 'dompurify'
|
|
4
|
+
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
5
|
+
import { isMobileAnnotationViewport } from '@cdc/core/helpers/viewports'
|
|
4
6
|
|
|
5
7
|
// helpers
|
|
6
8
|
import { findNearestDatum } from './findNearestDatum'
|
|
7
9
|
|
|
8
|
-
// prettier-ignore
|
|
9
|
-
import {
|
|
10
|
-
applyBandScaleOffset,
|
|
11
|
-
handleConnectionHorizontalType,
|
|
12
|
-
handleConnectionVerticalType,
|
|
13
|
-
handleMobileXPosition,
|
|
14
|
-
handleMobileYPosition,
|
|
15
|
-
handleTextX,
|
|
16
|
-
handleTextY
|
|
17
|
-
} from './helpers'
|
|
18
|
-
|
|
19
10
|
// visx
|
|
20
11
|
import { HtmlLabel, CircleSubject, EditableAnnotation, Connector, Annotation as VisxAnnotation } from '@visx/annotation'
|
|
21
|
-
import { Drag } from '@visx/drag'
|
|
22
12
|
import { MarkerArrow } from '@visx/marker'
|
|
23
13
|
import { LinePath } from '@visx/shape'
|
|
24
14
|
|
|
25
15
|
// styles
|
|
26
16
|
import './AnnotationDraggable.styles.css'
|
|
27
17
|
|
|
28
|
-
const Annotations = ({
|
|
18
|
+
const Annotations = ({
|
|
19
|
+
xScale,
|
|
20
|
+
yScale,
|
|
21
|
+
xScaleAnnotation,
|
|
22
|
+
yScaleAnnotation,
|
|
23
|
+
xMax,
|
|
24
|
+
yMax,
|
|
25
|
+
seriesScale,
|
|
26
|
+
svgRef,
|
|
27
|
+
onDragStateChange
|
|
28
|
+
}) => {
|
|
29
29
|
// prettier-ignore
|
|
30
|
-
const { config, dimensions, isEditor, updateConfig, colorScale } = useContext(ConfigContext)
|
|
30
|
+
const { config, dimensions, isEditor, updateConfig, colorScale, transformedData, parseDate, currentViewport, visibleAnnotations } = useContext(ConfigContext)
|
|
31
31
|
|
|
32
32
|
// destructure config items here...
|
|
33
|
-
const { annotations } = config
|
|
33
|
+
const { annotations, visualizationType } = config
|
|
34
34
|
const [height] = dimensions
|
|
35
35
|
|
|
36
36
|
const AnnotationComponent = isEditor ? EditableAnnotation : VisxAnnotation
|
|
37
|
+
const isMobile = isMobileAnnotationViewport(currentViewport)
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Scale dx/dy offsets based on savedDimensions vs current dimensions.
|
|
41
|
+
* This ensures label positions scale proportionally when chart is resized.
|
|
42
|
+
* Falls back to unscaled values if savedDimensions is missing (backward compatible).
|
|
43
|
+
*/
|
|
44
|
+
const getScaledOffsets = (annotation: { dx: number; dy: number; savedDimensions?: [number, number] }) => {
|
|
45
|
+
const [savedWidth, savedHeight] = annotation.savedDimensions || []
|
|
46
|
+
|
|
47
|
+
const scaledDx = savedWidth && savedWidth > 0 ? (annotation.dx / savedWidth) * xMax : annotation.dx
|
|
48
|
+
const scaledDy = savedHeight && savedHeight > 0 ? (annotation.dy / savedHeight) * yMax : annotation.dy
|
|
49
|
+
|
|
50
|
+
return { scaledDx, scaledDy }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Track live drag position for real-time anchor calculations
|
|
54
|
+
const [liveDrag, setLiveDrag] = useState<{ index: number; dx: number } | null>(null)
|
|
55
|
+
|
|
56
|
+
// Helper to determine label anchoring when near chart edges
|
|
57
|
+
const getAnnotationAnchors = (annotationX: number, dx: number, labelWidth: number) => {
|
|
58
|
+
const endpointX = annotationX + dx
|
|
59
|
+
|
|
60
|
+
const leftOverflow = dx < 0 && endpointX - labelWidth < 0
|
|
61
|
+
const rightOverflow = dx > 0 && endpointX + labelWidth > xMax
|
|
62
|
+
|
|
63
|
+
if (leftOverflow || rightOverflow) {
|
|
64
|
+
// Center label above the endpoint
|
|
65
|
+
return { horizontalAnchor: 'middle' as const, verticalAnchor: 'end' as const }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Let visx auto-decide
|
|
69
|
+
return { horizontalAnchor: null, verticalAnchor: null }
|
|
70
|
+
}
|
|
37
71
|
|
|
38
72
|
return (
|
|
39
|
-
|
|
40
|
-
|
|
73
|
+
visibleAnnotations &&
|
|
74
|
+
visibleAnnotations.map((annotation, annotationIndex) => {
|
|
75
|
+
const originalIndex = annotations.indexOf(annotation)
|
|
41
76
|
const text = annotation.text || ''
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
// Calculate scaled dx/dy offsets based on savedDimensions
|
|
79
|
+
const { scaledDx, scaledDy } = getScaledOffsets(annotation)
|
|
80
|
+
|
|
81
|
+
// Default to absolute positioning
|
|
82
|
+
let annotationX = xScaleAnnotation(annotation.x)
|
|
83
|
+
let annotationY = yScaleAnnotation(annotation.y)
|
|
84
|
+
|
|
85
|
+
// Override with data-anchored positioning if applicable
|
|
86
|
+
if (annotation.anchorMode === 'data' && annotation.dataX !== undefined) {
|
|
87
|
+
const dataSource = transformedData || config.data
|
|
88
|
+
const dataPoint = dataSource.find(d => d[config.xAxis.dataKey] === annotation.dataX)
|
|
89
|
+
|
|
90
|
+
if (dataPoint) {
|
|
91
|
+
const dataYValue = dataPoint[annotation.seriesKey]
|
|
92
|
+
|
|
93
|
+
// For date/date-time axes, convert raw value to timestamp for scale
|
|
94
|
+
let xScaleInput = annotation.dataX
|
|
95
|
+
if (config.xAxis.type === 'date' || config.xAxis.type === 'date-time') {
|
|
96
|
+
xScaleInput = parseDate(xScaleInput, false)?.getTime()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if this annotation's series is a bar in a grouped bar situation
|
|
100
|
+
const annotationSeries = config.series?.find(s => s.dataKey === annotation.seriesKey)
|
|
101
|
+
const barSeriesCount = config.series?.filter(s => s.type === 'Bar').length || 0
|
|
102
|
+
const isGroupedBarAnnotation =
|
|
103
|
+
annotationSeries?.type === 'Bar' && barSeriesCount > 1 && config.visualizationSubType !== 'stacked'
|
|
104
|
+
|
|
105
|
+
if (isGroupedBarAnnotation && seriesScale) {
|
|
106
|
+
// Position at group start + series offset + half series bar width
|
|
107
|
+
const seriesOffset = seriesScale(annotation.seriesKey) || 0
|
|
108
|
+
const seriesBandwidth = seriesScale.bandwidth?.() || 0
|
|
109
|
+
annotationX = xScale(xScaleInput) + seriesOffset + seriesBandwidth / 2
|
|
110
|
+
} else {
|
|
111
|
+
// For lines, areas, single bars, etc - center on the data point
|
|
112
|
+
annotationX = xScale(xScaleInput) + (xScale.bandwidth?.() / 2 || 0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Adjust X for arrow markers based on label direction
|
|
116
|
+
if (annotation.marker === 'arrow' && Math.abs(annotation.dx) >= 100) {
|
|
117
|
+
const direction = annotation.dx > 0 ? 1 : -1
|
|
118
|
+
const relevantBandwidth =
|
|
119
|
+
isGroupedBarAnnotation && seriesScale ? seriesScale.bandwidth?.() : xScale.bandwidth?.()
|
|
120
|
+
const nudgeAmount = relevantBandwidth ? relevantBandwidth / 6 : 2
|
|
121
|
+
annotationX += direction * nudgeAmount
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Y position based on marker type
|
|
125
|
+
annotationY = yScale(dataYValue) - (annotation.marker === 'circle' ? 0 : 5)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
44
128
|
|
|
45
129
|
// sanitize the text for setting dangerouslySetInnerHTML
|
|
46
130
|
const sanitizedData = () => ({
|
|
@@ -49,47 +133,72 @@ const Annotations = ({ xScale, yScale, xScaleAnnotation, xMax, svgRef, onDragSta
|
|
|
49
133
|
|
|
50
134
|
return (
|
|
51
135
|
<AnnotationComponent
|
|
52
|
-
key={`annotation-${
|
|
53
|
-
width={
|
|
54
|
-
height={
|
|
55
|
-
dx={
|
|
56
|
-
dy={
|
|
136
|
+
key={`annotation-${originalIndex}-${annotation.x}-${annotation.y}-${annotation.dx}-${annotation.dy}`}
|
|
137
|
+
width={xMax}
|
|
138
|
+
height={yMax}
|
|
139
|
+
dx={scaledDx} // label position (scaled to current chart dimensions)
|
|
140
|
+
dy={scaledDy} // label position (scaled to current chart dimensions)
|
|
57
141
|
x={annotationX}
|
|
58
|
-
y={
|
|
142
|
+
y={annotationY}
|
|
59
143
|
canEditLabel={annotation.edit.label || false}
|
|
60
144
|
canEditSubject={(annotation.edit.subject && annotation.connectionType !== 'none') || false}
|
|
61
|
-
|
|
145
|
+
labelDragHandleProps={{ r: 15, stroke: 'red' }}
|
|
146
|
+
subjectDragHandleProps={{ r: 15, stroke: 'red' }}
|
|
147
|
+
onDragStart={() => {
|
|
148
|
+
onDragStateChange(true)
|
|
149
|
+
setLiveDrag({ index: annotationIndex, dx: scaledDx })
|
|
150
|
+
}}
|
|
151
|
+
onDragMove={props => {
|
|
152
|
+
setLiveDrag({ index: annotationIndex, dx: props.dx })
|
|
153
|
+
}}
|
|
62
154
|
onDragEnd={props => {
|
|
63
155
|
onDragStateChange(false)
|
|
156
|
+
setLiveDrag(null)
|
|
64
157
|
|
|
65
158
|
let updatedAnnotations = [...annotations]
|
|
66
159
|
|
|
67
|
-
|
|
68
|
-
|
|
160
|
+
// Current chart dimensions to save with the annotation
|
|
161
|
+
const currentDimensions: [number, number] = [xMax, yMax]
|
|
162
|
+
|
|
163
|
+
const isLabelOnlyDrag =
|
|
164
|
+
annotation.anchorMode === 'data'
|
|
165
|
+
? annotationX === props.x && annotationY === props.y
|
|
166
|
+
: annotation.x === xScaleAnnotation.invert(props.x) && annotation.y === yScaleAnnotation.invert(props.y)
|
|
167
|
+
|
|
168
|
+
if (isLabelOnlyDrag) {
|
|
169
|
+
updatedAnnotations[originalIndex] = {
|
|
170
|
+
...updatedAnnotations[originalIndex],
|
|
171
|
+
dx: props.dx,
|
|
172
|
+
dy: props.dy,
|
|
173
|
+
savedDimensions: currentDimensions
|
|
174
|
+
}
|
|
69
175
|
} else {
|
|
70
|
-
if (annotation.
|
|
71
|
-
let nearestDatum = findNearestDatum(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
176
|
+
if (annotation.anchorMode === 'data') {
|
|
177
|
+
let nearestDatum = findNearestDatum({
|
|
178
|
+
data: transformedData || config.data,
|
|
179
|
+
xScale,
|
|
180
|
+
xAxisType: config.xAxis.type,
|
|
181
|
+
xAxisDataKey: config.xAxis.dataKey,
|
|
182
|
+
seriesKey: annotation.seriesKey,
|
|
183
|
+
xPixel: props.x,
|
|
184
|
+
parseDate
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
if (nearestDatum) {
|
|
188
|
+
updatedAnnotations[originalIndex] = {
|
|
189
|
+
...updatedAnnotations[originalIndex],
|
|
190
|
+
dataX: nearestDatum.x,
|
|
191
|
+
x: xScaleAnnotation.invert(props.x),
|
|
192
|
+
y: yScaleAnnotation.invert(props.y),
|
|
193
|
+
savedDimensions: currentDimensions
|
|
194
|
+
}
|
|
87
195
|
}
|
|
88
196
|
} else {
|
|
89
|
-
updatedAnnotations[
|
|
90
|
-
...updatedAnnotations[
|
|
197
|
+
updatedAnnotations[originalIndex] = {
|
|
198
|
+
...updatedAnnotations[originalIndex],
|
|
91
199
|
x: xScaleAnnotation.invert(props.x),
|
|
92
|
-
y: props.y
|
|
200
|
+
y: yScaleAnnotation.invert(props.y),
|
|
201
|
+
savedDimensions: currentDimensions
|
|
93
202
|
}
|
|
94
203
|
}
|
|
95
204
|
}
|
|
@@ -100,94 +209,109 @@ const Annotations = ({ xScale, yScale, xScaleAnnotation, xMax, svgRef, onDragSta
|
|
|
100
209
|
})
|
|
101
210
|
}}
|
|
102
211
|
>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
212
|
+
{!isMobile &&
|
|
213
|
+
(() => {
|
|
214
|
+
const labelWidth = config.general.showAnnotationDropdown ? 186 : 150
|
|
215
|
+
// Use live dx during drag (already in current space), otherwise use scaled dx
|
|
216
|
+
const currentDx = liveDrag?.index === annotationIndex ? liveDrag.dx : scaledDx
|
|
217
|
+
const { horizontalAnchor, verticalAnchor } = getAnnotationAnchors(annotationX, currentDx, labelWidth)
|
|
218
|
+
return (
|
|
219
|
+
<HtmlLabel
|
|
220
|
+
className='annotation__desktop-label'
|
|
221
|
+
containerStyle={{ width: `${labelWidth}px` }}
|
|
222
|
+
horizontalAnchor={horizontalAnchor}
|
|
223
|
+
verticalAnchor={verticalAnchor}
|
|
224
|
+
showAnchorLine={false}
|
|
225
|
+
>
|
|
226
|
+
<div
|
|
227
|
+
style={{
|
|
228
|
+
borderRadius: 5, // Optional: set border radius
|
|
229
|
+
backgroundColor: `rgba(255, 255, 255, ${
|
|
230
|
+
annotation?.opacity ? Number(annotation?.opacity) / 100 : 1
|
|
231
|
+
})`,
|
|
232
|
+
padding: '10px',
|
|
233
|
+
width: 'auto',
|
|
234
|
+
display: config.general.showAnnotationDropdown ? 'inline-flex' : 'flex',
|
|
235
|
+
justifyContent: 'start',
|
|
236
|
+
flexDirection: 'row',
|
|
237
|
+
alignItems: 'center'
|
|
238
|
+
}}
|
|
239
|
+
// role='presentation'
|
|
240
|
+
tabIndex={0}
|
|
241
|
+
aria-label={`Annotation text that reads: ${annotation.text}`}
|
|
242
|
+
>
|
|
243
|
+
{config?.general?.showAnnotationDropdown && (
|
|
244
|
+
<>
|
|
245
|
+
<p
|
|
246
|
+
className='annotation__has-dropdown-number'
|
|
247
|
+
style={{ margin: '2px 6px', position: 'relative', left: '-4px' }}
|
|
248
|
+
>
|
|
249
|
+
{originalIndex + 1}
|
|
250
|
+
</p>
|
|
251
|
+
</>
|
|
252
|
+
)}
|
|
253
|
+
<div dangerouslySetInnerHTML={sanitizedData()} />
|
|
254
|
+
</div>
|
|
255
|
+
</HtmlLabel>
|
|
256
|
+
)
|
|
257
|
+
})()}
|
|
133
258
|
{annotation.connectionType === 'line' && (
|
|
134
|
-
<Connector type='line' pathProps={{ markerStart: `url(#marker-start--${
|
|
259
|
+
<Connector type='line' pathProps={{ markerStart: `url(#marker-start--${originalIndex})` }} />
|
|
135
260
|
)}
|
|
136
261
|
{annotation.connectionType === 'elbow' && (
|
|
137
|
-
<Connector type='elbow' pathProps={{ markerStart: `url(#marker-start--${
|
|
262
|
+
<Connector type='elbow' pathProps={{ markerStart: `url(#marker-start--${originalIndex})` }} />
|
|
138
263
|
)}
|
|
139
264
|
{annotation.connectionType === 'curve' && (
|
|
140
265
|
<LinePath
|
|
141
|
-
d={`M ${annotationX},${
|
|
142
|
-
Q ${annotationX +
|
|
143
|
-
|
|
144
|
-
} ${annotationX +
|
|
145
|
-
stroke=
|
|
146
|
-
strokeWidth='2'
|
|
266
|
+
d={`M ${annotationX},${annotationY}
|
|
267
|
+
Q ${annotationX + scaledDx / 2}, ${
|
|
268
|
+
annotationY + scaledDy / 2 + Number(annotation?.bezier) || 0
|
|
269
|
+
} ${annotationX + scaledDx},${annotationY + scaledDy}`}
|
|
270
|
+
stroke={APP_FONT_COLOR}
|
|
147
271
|
fill='none'
|
|
148
|
-
marker-start={`url(#marker-start--${
|
|
272
|
+
marker-start={`url(#marker-start--${originalIndex})`}
|
|
149
273
|
/>
|
|
150
274
|
)}
|
|
151
275
|
{annotation.marker === 'circle' && (
|
|
152
|
-
<CircleSubject
|
|
153
|
-
id={`marker-start--${index}`}
|
|
154
|
-
className='circle-subject'
|
|
155
|
-
stroke={colorScale(annotation.seriesKey)}
|
|
156
|
-
radius={8}
|
|
157
|
-
/>
|
|
276
|
+
<CircleSubject className='circle-subject' stroke={APP_FONT_COLOR} radius={8} />
|
|
158
277
|
)}
|
|
159
278
|
{annotation.marker === 'arrow' && (
|
|
160
279
|
<MarkerArrow
|
|
161
|
-
fill=
|
|
162
|
-
id={`marker-start--${
|
|
280
|
+
fill={APP_FONT_COLOR}
|
|
281
|
+
id={`marker-start--${originalIndex}`}
|
|
163
282
|
x={annotationX}
|
|
164
|
-
y={
|
|
165
|
-
stroke=
|
|
166
|
-
markerWidth={
|
|
283
|
+
y={annotationY}
|
|
284
|
+
stroke={APP_FONT_COLOR}
|
|
285
|
+
markerWidth={12}
|
|
167
286
|
size={10}
|
|
168
287
|
strokeWidth={1}
|
|
169
288
|
orient='auto-start-reverse'
|
|
170
289
|
markerUnits='userSpaceOnUse'
|
|
171
290
|
/>
|
|
172
291
|
)}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
292
|
+
{isMobile && (
|
|
293
|
+
<>
|
|
294
|
+
<circle
|
|
295
|
+
fill='white'
|
|
296
|
+
cx={annotationX + scaledDx}
|
|
297
|
+
cy={annotationY + scaledDy}
|
|
298
|
+
r={12}
|
|
299
|
+
className='annotation__mobile-label annotation__mobile-label-circle'
|
|
300
|
+
stroke={APP_FONT_COLOR}
|
|
301
|
+
/>
|
|
302
|
+
<text
|
|
303
|
+
height={16}
|
|
304
|
+
x={annotationX + scaledDx}
|
|
305
|
+
y={annotationY + scaledDy + 1}
|
|
306
|
+
fontSize={14}
|
|
307
|
+
className='annotation__mobile-label'
|
|
308
|
+
alignmentBaseline='middle'
|
|
309
|
+
textAnchor='middle'
|
|
310
|
+
>
|
|
311
|
+
{originalIndex + 1}
|
|
312
|
+
</text>
|
|
313
|
+
</>
|
|
314
|
+
)}
|
|
191
315
|
</AnnotationComponent>
|
|
192
316
|
)
|
|
193
317
|
})
|
|
@@ -3,17 +3,12 @@ import ConfigContext from '../../../ConfigContext'
|
|
|
3
3
|
import './AnnotationDropdown.styles.css'
|
|
4
4
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
5
5
|
import Annotation from '..'
|
|
6
|
-
import {
|
|
6
|
+
import { isMobileAnnotationViewport } from '@cdc/core/helpers/viewports'
|
|
7
7
|
|
|
8
8
|
const AnnotationDropdown = () => {
|
|
9
9
|
const { currentViewport: viewport, config } = useContext(ConfigContext)
|
|
10
10
|
const [expanded, setExpanded] = useState(false)
|
|
11
|
-
|
|
12
|
-
const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${APP_FONT_SIZE}px`
|
|
13
|
-
|
|
14
|
-
const {
|
|
15
|
-
config: { annotations }
|
|
16
|
-
} = useContext(ConfigContext)
|
|
11
|
+
const isMobile = isMobileAnnotationViewport(viewport)
|
|
17
12
|
|
|
18
13
|
const limitHeight = {
|
|
19
14
|
maxHeight: config.table.limitHeight && `${config.table.height}px`,
|
|
@@ -30,11 +25,13 @@ const AnnotationDropdown = () => {
|
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
const handleSectionClasses = () => {
|
|
33
|
-
const classes = [`data-table-container`, viewport, `
|
|
28
|
+
const classes = [`data-table-container`, viewport, `w-100`, 'mt-4']
|
|
34
29
|
|
|
35
|
-
if
|
|
36
|
-
|
|
37
|
-
classes.
|
|
30
|
+
// Show dropdown in mobile mode or if explicitly enabled
|
|
31
|
+
if (isMobile || config.general.showAnnotationDropdown) {
|
|
32
|
+
classes.push('d-block')
|
|
33
|
+
} else {
|
|
34
|
+
classes.push('d-none')
|
|
38
35
|
}
|
|
39
36
|
return classes.join(' ')
|
|
40
37
|
}
|
|
@@ -43,7 +40,6 @@ const AnnotationDropdown = () => {
|
|
|
43
40
|
<>
|
|
44
41
|
<section className={handleSectionClasses()}>
|
|
45
42
|
<div
|
|
46
|
-
style={{ fontSize: titleFontSize }}
|
|
47
43
|
role='button'
|
|
48
44
|
className={handleAccordionClassName()}
|
|
49
45
|
onClick={() => {
|
|
@@ -1,33 +1,27 @@
|
|
|
1
|
-
.
|
|
1
|
+
.cove-visualization.type-chart {
|
|
2
2
|
.annotation__title-circle {
|
|
3
|
-
display: flex;
|
|
4
|
-
justify-content: center;
|
|
5
3
|
align-items: center;
|
|
4
|
+
border: 1px solid #666;
|
|
6
5
|
border-radius: 50%;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
height:
|
|
10
|
-
|
|
6
|
+
display: flex;
|
|
7
|
+
font-size: 14px;
|
|
8
|
+
height: 24px;
|
|
9
|
+
justify-content: center;
|
|
11
10
|
line-height: 1.5rem;
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
margin-right: 5px;
|
|
12
|
+
padding: 8px;
|
|
14
13
|
text-align: center;
|
|
15
|
-
|
|
14
|
+
width: 24px;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
.annotation__title-wrapper {
|
|
18
|
+
align-items: center;
|
|
19
19
|
display: flex;
|
|
20
20
|
flex-wrap: nowrap;
|
|
21
|
-
align-items: center;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
.annotation__title-wrapper .annotation__title-text {
|
|
25
24
|
margin-left: 5px;
|
|
26
|
-
font-size: 16px;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.annotation__subtext {
|
|
30
|
-
font-size: 12px;
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
.annotation-list {
|
|
@@ -38,8 +32,8 @@
|
|
|
38
32
|
margin-top: 5px;
|
|
39
33
|
}
|
|
40
34
|
|
|
41
|
-
.cove-
|
|
42
|
-
container-type: inline-size;
|
|
35
|
+
.cove-visualization__body {
|
|
43
36
|
container-name: content;
|
|
37
|
+
container-type: inline-size;
|
|
44
38
|
}
|
|
45
39
|
}
|
|
@@ -8,7 +8,7 @@ type AnnotationListProps = {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const AnnotationList: React.FC<AnnotationListProps> = ({ useBootstrapVisibilityClasses = true }) => {
|
|
11
|
-
const { config } = useContext(ConfigContext)
|
|
11
|
+
const { config, visibleAnnotations } = useContext(ConfigContext)
|
|
12
12
|
const annotations = config.annotations || []
|
|
13
13
|
|
|
14
14
|
const ulClasses = () => {
|
|
@@ -19,7 +19,8 @@ const AnnotationList: React.FC<AnnotationListProps> = ({ useBootstrapVisibilityC
|
|
|
19
19
|
return classes.join(' ')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const annotationListItems =
|
|
22
|
+
const annotationListItems = visibleAnnotations.map(annotation => {
|
|
23
|
+
const originalIndex = annotations.indexOf(annotation)
|
|
23
24
|
const text = annotation.text || ''
|
|
24
25
|
|
|
25
26
|
// sanitize the text for setting dangerouslySetInnerHTML
|
|
@@ -27,9 +28,9 @@ const AnnotationList: React.FC<AnnotationListProps> = ({ useBootstrapVisibilityC
|
|
|
27
28
|
__html: DOMPurify.sanitize(text)
|
|
28
29
|
})
|
|
29
30
|
return (
|
|
30
|
-
<li key={`annotation-li-
|
|
31
|
+
<li key={`annotation-li-item__${originalIndex}`}>
|
|
31
32
|
<div className='annotation__title-wrapper'>
|
|
32
|
-
<div className='annotation__title-circle'>{
|
|
33
|
+
<div className='annotation__title-circle'>{originalIndex + 1}</div>
|
|
33
34
|
<p className='annotation__subtext' dangerouslySetInnerHTML={sanitizedData()} />
|
|
34
35
|
</div>
|
|
35
36
|
</li>
|