@cdc/chart 4.26.1 → 4.26.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.local.md +79 -0
- package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
- package/dist/cdcchart.js +45357 -43655
- 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/line-chart-states.json +1085 -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/CdcChartComponent.tsx +99 -18
- package/src/_stories/Chart.Anchors.stories.tsx +10 -0
- package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
- package/src/_stories/Chart.CI.stories.tsx +13 -0
- package/src/_stories/Chart.Combo.stories.tsx +17 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +4 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
- package/src/_stories/Chart.Filters.stories.tsx +4 -0
- package/src/_stories/Chart.Forecast.stories.tsx +4 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
- package/src/_stories/Chart.Patterns.stories.tsx +4 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +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.stories.tsx +37 -0
- package/src/_stories/Chart.tooltip.stories.tsx +7 -0
- package/src/_stories/ChartAnnotation.stories.tsx +10 -0
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
- package/src/_stories/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.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/components/Annotations/components/AnnotationDraggable.styles.css +11 -17
- package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
- package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
- package/src/components/Annotations/components/AnnotationList.styles.css +4 -10
- package/src/components/Annotations/components/AnnotationList.tsx +5 -4
- package/src/components/Annotations/components/findNearestDatum.ts +75 -85
- package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
- package/src/components/Axis/BottomAxis.tsx +270 -0
- package/src/components/Axis/LeftAxis.tsx +404 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
- package/src/components/Axis/PairedBarAxis.tsx +186 -0
- package/src/components/Axis/README.md +94 -0
- package/src/components/Axis/RightAxis.tsx +108 -0
- package/src/components/Axis/axis.constants.ts +21 -0
- package/src/components/Axis/index.ts +7 -0
- package/src/components/BarChart/components/BarChart.tsx +7 -1
- package/src/components/Brush/BrushSelector.tsx +154 -22
- package/src/components/Brush/MiniChartPreview.tsx +138 -21
- package/src/components/EditorPanel/EditorPanel.tsx +25 -11
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +60 -22
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +81 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +0 -1
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +21 -1
- package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
- 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/LegendValueRange.tsx +77 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +13 -0
- 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 +201 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +129 -0
- package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
- package/src/components/LinearChart.tsx +250 -1059
- package/src/components/PieChart/PieChart.tsx +1 -1
- package/src/components/RadarChart/RadarAxis.tsx +78 -0
- package/src/components/RadarChart/RadarChart.tsx +298 -0
- package/src/components/RadarChart/RadarGrid.tsx +64 -0
- package/src/components/RadarChart/RadarPolygon.tsx +91 -0
- package/src/components/RadarChart/helpers.ts +83 -0
- package/src/components/RadarChart/index.tsx +3 -0
- package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
- package/src/data/initial-state.js +14 -1
- package/src/helpers/getExcludedData.ts +4 -0
- package/src/helpers/handleChartAriaLabels.ts +19 -19
- package/src/helpers/handleLineType.ts +22 -18
- package/src/hooks/useProgrammaticTooltip.ts +23 -2
- package/src/hooks/useScales.ts +7 -0
- package/src/hooks/useTooltip.tsx +3 -0
- package/src/scss/main.scss +5 -0
- package/src/selectors/README.md +68 -0
- package/src/store/chart.reducer.ts +2 -0
- package/src/types/ChartConfig.ts +18 -0
- package/src/types/ChartContext.ts +1 -0
- package/src/types/Horizon.ts +64 -0
- package/preview.html +0 -1616
- package/src/components/Annotations/components/helpers/index.tsx +0 -46
|
@@ -10,25 +10,63 @@ interface MiniChartPreviewProps {
|
|
|
10
10
|
dataKey: string
|
|
11
11
|
xScale: any
|
|
12
12
|
miniYScale: any
|
|
13
|
+
miniRightYScale?: any
|
|
13
14
|
colorScale: any
|
|
14
15
|
config: any
|
|
15
16
|
xMax: number
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const MiniChartPreview = memo<MiniChartPreviewProps>(
|
|
19
|
-
({ series, tableData, dataKey, xScale, miniYScale, colorScale, config }) => {
|
|
20
|
+
({ series, tableData, dataKey, xScale, miniYScale, miniRightYScale, colorScale, config }) => {
|
|
20
21
|
if (!series || !series.length || !tableData || !tableData.length || !xScale || !miniYScale) {
|
|
21
22
|
return null
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const bandwidth = xScale.bandwidth?.() || 0
|
|
26
|
+
const isComboChart = config?.visualizationType === 'Combo'
|
|
25
27
|
const isBarChart = config?.visualizationType === 'Bar'
|
|
26
28
|
const isStacked = config?.visualizationSubType === 'stacked'
|
|
27
29
|
const isAreaChart = config?.visualizationType === 'Area Chart'
|
|
30
|
+
const barSeriesTypes = new Set(['Bar', 'Paired Bar', 'Deviation Bar', 'Combo'])
|
|
31
|
+
const barSeries = isComboChart ? series.filter(s => barSeriesTypes.has(s.type)) : series
|
|
32
|
+
const areaSeries = isComboChart ? series.filter(s => s.type === 'Area Chart') : series
|
|
33
|
+
const lineSeries = isComboChart
|
|
34
|
+
? series.filter(s => !barSeriesTypes.has(s.type) && s.type !== 'Area Chart')
|
|
35
|
+
: series
|
|
36
|
+
|
|
37
|
+
let barElements: React.ReactElement[] = []
|
|
28
38
|
|
|
29
39
|
// For bar charts, render rectangles
|
|
30
|
-
if (isBarChart) {
|
|
40
|
+
if (isBarChart || (isComboChart && barSeries.length > 0)) {
|
|
31
41
|
const bars: React.ReactElement[] = []
|
|
42
|
+
const barSeriesToRender = isBarChart ? series : barSeries
|
|
43
|
+
|
|
44
|
+
const barStrokeColor = config?.barHasBorder === 'true' ? '#000' : 'transparent'
|
|
45
|
+
const barStrokeWidth = config?.barHasBorder === 'true' ? 1 : 0
|
|
46
|
+
|
|
47
|
+
const getPatternUrl = (datum, seriesKey: string, value: string | number) => {
|
|
48
|
+
if (!config.legend?.patterns || Object.keys(config.legend.patterns).length === 0) {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const [patternKey, patternObj] of Object.entries(config.legend.patterns)) {
|
|
53
|
+
const pattern = patternObj as any
|
|
54
|
+
if (pattern.dataKey && pattern.dataValue) {
|
|
55
|
+
if (pattern.dataKey === seriesKey && String(value) === String(pattern.dataValue)) {
|
|
56
|
+
return `url(#chart-pattern-${patternKey})`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!config.runtime?.seriesLabels || !config.runtime.seriesLabels[pattern.dataKey]) {
|
|
60
|
+
const dataFieldValue = datum[pattern.dataKey]
|
|
61
|
+
if (String(dataFieldValue) === String(pattern.dataValue)) {
|
|
62
|
+
return `url(#chart-pattern-${patternKey})`
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
32
70
|
|
|
33
71
|
tableData.forEach((d, i) => {
|
|
34
72
|
const xVal = xScale(d[dataKey])
|
|
@@ -47,11 +85,12 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
|
|
|
47
85
|
let cumulativeValue = 0
|
|
48
86
|
const zeroY = miniYScale(0)
|
|
49
87
|
|
|
50
|
-
|
|
88
|
+
barSeriesToRender.forEach((s, seriesIndex) => {
|
|
51
89
|
const value = parseFloat(d[s.dataKey])
|
|
52
90
|
if (isNaN(value) || value === 0) return
|
|
53
91
|
|
|
54
92
|
const seriesColor = colorScale?.(config.runtime.seriesLabels?.[s.dataKey] || s.dataKey) || '#666'
|
|
93
|
+
const patternUrl = getPatternUrl(d, s.dataKey, value)
|
|
55
94
|
|
|
56
95
|
// Calculate the bottom and top of this segment
|
|
57
96
|
// For stacked bars, each segment sits on top of the previous one
|
|
@@ -72,22 +111,41 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
|
|
|
72
111
|
width={barWidth}
|
|
73
112
|
height={barHeight}
|
|
74
113
|
fill={seriesColor}
|
|
75
|
-
|
|
114
|
+
stroke={barStrokeColor}
|
|
115
|
+
strokeWidth={barStrokeWidth}
|
|
116
|
+
fillOpacity={1}
|
|
76
117
|
pointerEvents='none'
|
|
77
118
|
/>
|
|
78
119
|
)
|
|
120
|
+
if (patternUrl) {
|
|
121
|
+
bars.push(
|
|
122
|
+
<rect
|
|
123
|
+
key={`mini-bar-stacked-pattern-${i}-${seriesIndex}`}
|
|
124
|
+
x={x - barWidth / 2}
|
|
125
|
+
y={y}
|
|
126
|
+
width={barWidth}
|
|
127
|
+
height={barHeight}
|
|
128
|
+
fill={patternUrl}
|
|
129
|
+
stroke='transparent'
|
|
130
|
+
strokeWidth={0}
|
|
131
|
+
fillOpacity={1}
|
|
132
|
+
pointerEvents='none'
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
79
136
|
})
|
|
80
137
|
} else {
|
|
81
138
|
// For grouped bars, render bars side by side
|
|
82
|
-
const seriesCount =
|
|
139
|
+
const seriesCount = barSeriesToRender.length
|
|
83
140
|
const groupBarWidth = barWidth / seriesCount
|
|
84
141
|
const zeroY = miniYScale(0)
|
|
85
142
|
|
|
86
|
-
|
|
143
|
+
barSeriesToRender.forEach((s, seriesIndex) => {
|
|
87
144
|
const value = parseFloat(d[s.dataKey])
|
|
88
145
|
if (isNaN(value)) return
|
|
89
146
|
|
|
90
147
|
const seriesColor = colorScale?.(config.runtime.seriesLabels?.[s.dataKey] || s.dataKey) || '#666'
|
|
148
|
+
const patternUrl = getPatternUrl(d, s.dataKey, value)
|
|
91
149
|
|
|
92
150
|
// Calculate bar position and height
|
|
93
151
|
const valueY = miniYScale(value)
|
|
@@ -100,18 +158,39 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
|
|
|
100
158
|
key={`mini-bar-grouped-${i}-${seriesIndex}`}
|
|
101
159
|
x={barX}
|
|
102
160
|
y={y}
|
|
103
|
-
width={groupBarWidth
|
|
161
|
+
width={groupBarWidth}
|
|
104
162
|
height={barHeight}
|
|
105
163
|
fill={seriesColor}
|
|
106
|
-
|
|
164
|
+
stroke={barStrokeColor}
|
|
165
|
+
strokeWidth={barStrokeWidth}
|
|
166
|
+
fillOpacity={1}
|
|
107
167
|
pointerEvents='none'
|
|
108
168
|
/>
|
|
109
169
|
)
|
|
170
|
+
if (patternUrl) {
|
|
171
|
+
bars.push(
|
|
172
|
+
<rect
|
|
173
|
+
key={`mini-bar-grouped-pattern-${i}-${seriesIndex}`}
|
|
174
|
+
x={barX}
|
|
175
|
+
y={y}
|
|
176
|
+
width={groupBarWidth}
|
|
177
|
+
height={barHeight}
|
|
178
|
+
fill={patternUrl}
|
|
179
|
+
stroke='transparent'
|
|
180
|
+
strokeWidth={0}
|
|
181
|
+
fillOpacity={1}
|
|
182
|
+
pointerEvents='none'
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
110
186
|
})
|
|
111
187
|
}
|
|
112
188
|
})
|
|
113
189
|
|
|
114
|
-
|
|
190
|
+
barElements = bars
|
|
191
|
+
if (isBarChart && !isComboChart) {
|
|
192
|
+
return <>{barElements}</>
|
|
193
|
+
}
|
|
115
194
|
}
|
|
116
195
|
|
|
117
196
|
// For stacked area charts, use AreaStack
|
|
@@ -217,10 +296,10 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
|
|
|
217
296
|
)
|
|
218
297
|
}
|
|
219
298
|
|
|
220
|
-
// For line/area charts, render lines or areas
|
|
299
|
+
// For line/area charts (and combo line/area series), render lines or areas
|
|
221
300
|
return (
|
|
222
301
|
<>
|
|
223
|
-
{series.map((s, i) => {
|
|
302
|
+
{(isComboChart ? areaSeries : isAreaChart ? series : []).map((s, i) => {
|
|
224
303
|
const seriesKey = s.dataKey
|
|
225
304
|
const seriesColor = colorScale?.(config.runtime.seriesLabels?.[seriesKey] || seriesKey) || '#666'
|
|
226
305
|
|
|
@@ -231,25 +310,31 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
|
|
|
231
310
|
const curve = allCurves[seriesLineType] || allCurves.curveLinear
|
|
232
311
|
const strokeDasharray = handleLineType(seriesStyle)
|
|
233
312
|
|
|
234
|
-
//
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
313
|
+
// Use the right-axis scale when the series is on the right axis
|
|
314
|
+
const yScaleForSeries = s.axis === 'Right' && miniRightYScale ? miniRightYScale : miniYScale
|
|
315
|
+
|
|
316
|
+
// Filter to only valid data points, then sort by X position so the area
|
|
317
|
+
// renders left-to-right regardless of the original data order.
|
|
318
|
+
const validData = tableData
|
|
319
|
+
.filter(d => {
|
|
320
|
+
const xVal = xScale(d[dataKey])
|
|
321
|
+
const yVal = parseFloat(d[s.dataKey])
|
|
322
|
+
return xVal !== undefined && !isNaN(yVal)
|
|
323
|
+
})
|
|
324
|
+
.sort((a, b) => (xScale(a[dataKey]) ?? 0) - (xScale(b[dataKey]) ?? 0))
|
|
240
325
|
|
|
241
326
|
if (validData.length === 0) return null
|
|
242
327
|
|
|
243
328
|
const getX = d => xScale(d[dataKey]) + bandwidth / 2
|
|
244
|
-
const getY = d =>
|
|
329
|
+
const getY = d => yScaleForSeries(parseFloat(d[s.dataKey]))
|
|
245
330
|
|
|
246
|
-
return
|
|
331
|
+
return (
|
|
247
332
|
<AreaClosed
|
|
248
333
|
key={`mini-area-${seriesKey}-${i}`}
|
|
249
334
|
data={validData}
|
|
250
335
|
x={getX}
|
|
251
336
|
y={getY}
|
|
252
|
-
yScale={
|
|
337
|
+
yScale={yScaleForSeries}
|
|
253
338
|
fill={seriesColor}
|
|
254
339
|
fillOpacity={1}
|
|
255
340
|
stroke={seriesColor}
|
|
@@ -258,7 +343,39 @@ const MiniChartPreview = memo<MiniChartPreviewProps>(
|
|
|
258
343
|
curve={curve}
|
|
259
344
|
pointerEvents='none'
|
|
260
345
|
/>
|
|
261
|
-
)
|
|
346
|
+
)
|
|
347
|
+
})}
|
|
348
|
+
{barElements}
|
|
349
|
+
{(isAreaChart && !isComboChart ? [] : isComboChart ? lineSeries : series).map((s, i) => {
|
|
350
|
+
const seriesKey = s.dataKey
|
|
351
|
+
const seriesColor = colorScale?.(config.runtime.seriesLabels?.[seriesKey] || seriesKey) || '#666'
|
|
352
|
+
|
|
353
|
+
// Get series-specific styling
|
|
354
|
+
const seriesWeight = s.weight || 2
|
|
355
|
+
const seriesLineType = s.lineType || 'curveLinear'
|
|
356
|
+
const seriesStyle = s.type || 'solid'
|
|
357
|
+
const curve = allCurves[seriesLineType] || allCurves.curveLinear
|
|
358
|
+
const strokeDasharray = handleLineType(seriesStyle)
|
|
359
|
+
|
|
360
|
+
// Use the right-axis scale when the series is on the right axis
|
|
361
|
+
const yScaleForSeries = s.axis === 'Right' && miniRightYScale ? miniRightYScale : miniYScale
|
|
362
|
+
|
|
363
|
+
// Filter to only valid data points, then sort by X position so the line
|
|
364
|
+
// connects left-to-right regardless of the original data order.
|
|
365
|
+
const validData = tableData
|
|
366
|
+
.filter(d => {
|
|
367
|
+
const xVal = xScale(d[dataKey])
|
|
368
|
+
const yVal = parseFloat(d[s.dataKey])
|
|
369
|
+
return xVal !== undefined && !isNaN(yVal)
|
|
370
|
+
})
|
|
371
|
+
.sort((a, b) => (xScale(a[dataKey]) ?? 0) - (xScale(b[dataKey]) ?? 0))
|
|
372
|
+
|
|
373
|
+
if (validData.length === 0) return null
|
|
374
|
+
|
|
375
|
+
const getX = d => xScale(d[dataKey]) + bandwidth / 2
|
|
376
|
+
const getY = d => yScaleForSeries(parseFloat(d[s.dataKey]))
|
|
377
|
+
|
|
378
|
+
return (
|
|
262
379
|
<LinePath
|
|
263
380
|
key={`mini-line-${seriesKey}-${i}`}
|
|
264
381
|
data={validData}
|
|
@@ -653,6 +653,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
653
653
|
visHasLegendAxisAlign,
|
|
654
654
|
visHasLegendColorCategory,
|
|
655
655
|
visHasSelectableLegendValues,
|
|
656
|
+
visSupportsClickingLegend,
|
|
656
657
|
visSupportsDateCategoryAxis,
|
|
657
658
|
visSupportsDateCategoryAxisLabel,
|
|
658
659
|
visSupportsDateCategoryAxisLine,
|
|
@@ -756,6 +757,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
756
757
|
if (isDateScale(updatedConfig.xAxis) && !updatedConfig.xAxis.padding) {
|
|
757
758
|
updatedConfig.xAxis.padding = 0
|
|
758
759
|
}
|
|
760
|
+
// Default Radar charts to a taller height
|
|
761
|
+
if (updatedConfig.visualizationType === 'Radar' && updatedConfig.heights?.vertical <= 400) {
|
|
762
|
+
updatedConfig.heights.vertical = 400
|
|
763
|
+
}
|
|
759
764
|
// DEV-8008 - Remove Bar styling when Line is converted to Bar
|
|
760
765
|
if (updatedConfig.visualizationType === 'Line') {
|
|
761
766
|
updatedConfig.visualizationSubType = 'regular'
|
|
@@ -1892,6 +1897,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
1892
1897
|
</AccordionItem>
|
|
1893
1898
|
)}
|
|
1894
1899
|
<Panels.BoxPlot name='Measures' />
|
|
1900
|
+
<Panels.Radar name='Radar Chart Settings' />
|
|
1895
1901
|
{/* Left Value Axis */}
|
|
1896
1902
|
{visSupportsLeftValueAxis() && (
|
|
1897
1903
|
<AccordionItem>
|
|
@@ -4250,7 +4256,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
4250
4256
|
/>
|
|
4251
4257
|
|
|
4252
4258
|
<CheckBox
|
|
4253
|
-
display={
|
|
4259
|
+
display={
|
|
4260
|
+
config.visualizationType !== 'Radar' &&
|
|
4261
|
+
config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value)
|
|
4262
|
+
}
|
|
4254
4263
|
value={config.legend.hideSuppressedLabels}
|
|
4255
4264
|
section='legend'
|
|
4256
4265
|
fieldName='hideSuppressedLabels'
|
|
@@ -4274,7 +4283,10 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
4274
4283
|
}
|
|
4275
4284
|
/>
|
|
4276
4285
|
<CheckBox
|
|
4277
|
-
display={
|
|
4286
|
+
display={
|
|
4287
|
+
config.visualizationType !== 'Radar' &&
|
|
4288
|
+
config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value)
|
|
4289
|
+
}
|
|
4278
4290
|
value={config.legend.hideSuppressionLink}
|
|
4279
4291
|
section='legend'
|
|
4280
4292
|
fieldName='hideSuppressionLink'
|
|
@@ -4296,7 +4308,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
4296
4308
|
/>
|
|
4297
4309
|
|
|
4298
4310
|
<Select
|
|
4299
|
-
display={hasDynamicCategory || hasMultipleSeries}
|
|
4311
|
+
display={visSupportsClickingLegend() && (hasDynamicCategory || hasMultipleSeries)}
|
|
4300
4312
|
value={config.legend.behavior}
|
|
4301
4313
|
section='legend'
|
|
4302
4314
|
fieldName='behavior'
|
|
@@ -4580,14 +4592,16 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
4580
4592
|
)}
|
|
4581
4593
|
<Panels.Annotate name='Text Annotations' />
|
|
4582
4594
|
{/* {(config.visualizationType === 'Bar' || config.visualizationType === 'Line') && <Panels.DateHighlighting name='Date Highlighting' />} */}
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4595
|
+
{config.visualizationType !== 'Radar' && (
|
|
4596
|
+
<PanelMarkup
|
|
4597
|
+
name='Markup Variables'
|
|
4598
|
+
markupVariables={config.markupVariables || []}
|
|
4599
|
+
data={rawData}
|
|
4600
|
+
enableMarkupVariables={config.enableMarkupVariables || false}
|
|
4601
|
+
onMarkupVariablesChange={variables => updateField(null, null, 'markupVariables', variables)}
|
|
4602
|
+
onToggleEnable={enabled => updateField(null, null, 'enableMarkupVariables', enabled)}
|
|
4603
|
+
/>
|
|
4604
|
+
)}
|
|
4591
4605
|
<Panels.SmallMultiples name='Small Multiples' />
|
|
4592
4606
|
</Accordion>
|
|
4593
4607
|
{config.type !== 'Spark Line' && (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../../../ConfigContext.js'
|
|
3
|
+
import { useEditorPermissions } from '../../useEditorPermissions'
|
|
3
4
|
|
|
4
5
|
// CDC Core
|
|
5
6
|
import Accordion from '@cdc/core/components/ui/Accordion'
|
|
@@ -13,7 +14,8 @@ import { type PanelProps } from './../PanelProps'
|
|
|
13
14
|
import './../panels.scss'
|
|
14
15
|
|
|
15
16
|
const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
16
|
-
const { updateConfig, config,
|
|
17
|
+
const { updateConfig, config, transformedData } = useContext(ConfigContext)
|
|
18
|
+
const { visSupportsDataAnnotations } = useEditorPermissions()
|
|
17
19
|
|
|
18
20
|
const updateField = (section, subsection, fieldName, value) => {
|
|
19
21
|
if (subsection) {
|
|
@@ -39,11 +41,8 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const handleAnnotationUpdate = (value, property, index) => {
|
|
42
|
-
const svgContainer = document.querySelector('.chart-container > svg')?.getBoundingClientRect()
|
|
43
|
-
const newSvgDims = [svgContainer?.width, svgContainer?.height]
|
|
44
44
|
const annotations = [...config?.annotations]
|
|
45
45
|
annotations[index][property] = value
|
|
46
|
-
annotations[index].savedDimensions = newSvgDims
|
|
47
46
|
|
|
48
47
|
updateConfig({
|
|
49
48
|
...config,
|
|
@@ -52,15 +51,9 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
const handleAddAnnotation = () => {
|
|
55
|
-
// check if svg is animated svg or standard svg
|
|
56
|
-
const newSvgDims = [
|
|
57
|
-
svgRef?.current?.width?.baseVal?.value || svgRef?.current?.width,
|
|
58
|
-
svgRef?.current?.height?.baseVal?.value || svgRef?.current?.height
|
|
59
|
-
]
|
|
60
|
-
|
|
61
54
|
const newAnnotation = {
|
|
62
|
-
text: 'New
|
|
63
|
-
|
|
55
|
+
text: 'New annotation',
|
|
56
|
+
anchorMode: 'fixed',
|
|
64
57
|
fontSize: 16,
|
|
65
58
|
bezier: 10,
|
|
66
59
|
show: {
|
|
@@ -84,20 +77,12 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
84
77
|
subject: true,
|
|
85
78
|
label: true
|
|
86
79
|
},
|
|
87
|
-
seriesKey
|
|
80
|
+
// seriesKey and dataX are only set when switching to data mode
|
|
88
81
|
x: 50,
|
|
89
|
-
y:
|
|
90
|
-
xKey:
|
|
91
|
-
config.xAxis.type === 'date'
|
|
92
|
-
? new Date(config?.data?.[0]?.[config.xAxis.dataKey]).getTime()
|
|
93
|
-
: config.xAxis.type === 'categorical'
|
|
94
|
-
? '1/15/2016'
|
|
95
|
-
: '',
|
|
96
|
-
yKey: '',
|
|
82
|
+
y: 50,
|
|
97
83
|
dx: 20,
|
|
98
84
|
dy: -20,
|
|
99
85
|
opacity: '100',
|
|
100
|
-
savedDimensions: newSvgDims,
|
|
101
86
|
connectionType: 'line'
|
|
102
87
|
}
|
|
103
88
|
|
|
@@ -163,6 +148,59 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
163
148
|
onChange={e => handleAnnotationUpdate(e.target.value, 'text', index)}
|
|
164
149
|
/>
|
|
165
150
|
</label>
|
|
151
|
+
|
|
152
|
+
{visSupportsDataAnnotations() && (
|
|
153
|
+
<Select
|
|
154
|
+
label='Position Mode:'
|
|
155
|
+
value={annotation.anchorMode || 'fixed'}
|
|
156
|
+
options={[
|
|
157
|
+
{ value: 'fixed', label: 'Fixed position' },
|
|
158
|
+
{ value: 'data', label: 'Snap to data' }
|
|
159
|
+
]}
|
|
160
|
+
section='annotations'
|
|
161
|
+
subsection={null}
|
|
162
|
+
fieldName='anchorMode'
|
|
163
|
+
updateField={(section, subsection, fieldName, value) => {
|
|
164
|
+
const updatedAnnotations = _.cloneDeep(config?.annotations)
|
|
165
|
+
updatedAnnotations[index].anchorMode = value
|
|
166
|
+
|
|
167
|
+
// When switching to data mode, ensure seriesKey and dataX are initialized
|
|
168
|
+
if (value === 'data') {
|
|
169
|
+
if (!updatedAnnotations[index].seriesKey) {
|
|
170
|
+
updatedAnnotations[index].seriesKey = config.series?.[0]?.dataKey || ''
|
|
171
|
+
}
|
|
172
|
+
if (!updatedAnnotations[index].dataX) {
|
|
173
|
+
updatedAnnotations[index].dataX = transformedData?.[0]?.[config.xAxis.dataKey] || ''
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
updateConfig({
|
|
178
|
+
...config,
|
|
179
|
+
annotations: updatedAnnotations
|
|
180
|
+
})
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{visSupportsDataAnnotations() && annotation.anchorMode === 'data' && (
|
|
186
|
+
<Select
|
|
187
|
+
label='Series:'
|
|
188
|
+
value={annotation.seriesKey || config.series?.[0]?.dataKey || ''}
|
|
189
|
+
options={config.series.map(s => s.dataKey)}
|
|
190
|
+
section='annotations'
|
|
191
|
+
subsection={null}
|
|
192
|
+
fieldName='seriesKey'
|
|
193
|
+
updateField={(section, subsection, fieldName, value) => {
|
|
194
|
+
const updatedAnnotations = _.cloneDeep(config?.annotations)
|
|
195
|
+
updatedAnnotations[index].seriesKey = value || config.series?.[0]?.dataKey
|
|
196
|
+
updateConfig({
|
|
197
|
+
...config,
|
|
198
|
+
annotations: updatedAnnotations
|
|
199
|
+
})
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
)}
|
|
203
|
+
|
|
166
204
|
<label>
|
|
167
205
|
Opacity
|
|
168
206
|
<br />
|
|
@@ -206,6 +206,86 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
206
206
|
options={['Below Bar', 'On Date/Category Axis']}
|
|
207
207
|
/>
|
|
208
208
|
)}
|
|
209
|
+
{visualizationType === 'Horizon Chart' && (
|
|
210
|
+
<>
|
|
211
|
+
<TextField
|
|
212
|
+
type='number'
|
|
213
|
+
value={config.horizon?.numLayers || 4}
|
|
214
|
+
section='horizon'
|
|
215
|
+
fieldName='numLayers'
|
|
216
|
+
label='Number of Layers'
|
|
217
|
+
updateField={updateField}
|
|
218
|
+
min={1}
|
|
219
|
+
max={9}
|
|
220
|
+
onBlur={e => {
|
|
221
|
+
const parsed = Number(e.target.value)
|
|
222
|
+
if (!isNaN(parsed)) {
|
|
223
|
+
const value = Math.round(parsed)
|
|
224
|
+
const clamped = Math.min(9, Math.max(1, value))
|
|
225
|
+
if (clamped !== parsed) {
|
|
226
|
+
updateField('horizon', null, 'numLayers', clamped)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}}
|
|
230
|
+
tooltip={
|
|
231
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
232
|
+
<Tooltip.Target>
|
|
233
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
234
|
+
</Tooltip.Target>
|
|
235
|
+
<Tooltip.Content>
|
|
236
|
+
<p>
|
|
237
|
+
The number of layers determines how many "bands" the horizon chart is divided into. The optimal
|
|
238
|
+
number of layers may depend on the data range and the desired level of detail.
|
|
239
|
+
</p>
|
|
240
|
+
<hr />
|
|
241
|
+
<p>Valid range: 1-9. Defaults to 4 layers.</p>
|
|
242
|
+
</Tooltip.Content>
|
|
243
|
+
</Tooltip>
|
|
244
|
+
}
|
|
245
|
+
/>
|
|
246
|
+
<TextField
|
|
247
|
+
type='number'
|
|
248
|
+
value={config.horizon?.bandGap ?? 15}
|
|
249
|
+
section='horizon'
|
|
250
|
+
fieldName='bandGap'
|
|
251
|
+
label='Band Gap'
|
|
252
|
+
updateField={updateField}
|
|
253
|
+
tooltip={
|
|
254
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
255
|
+
<Tooltip.Target>
|
|
256
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
257
|
+
</Tooltip.Target>
|
|
258
|
+
<Tooltip.Content>
|
|
259
|
+
<p>The vertical spacing (in pixels) between each series row in the horizon chart.</p>
|
|
260
|
+
<hr />
|
|
261
|
+
<p>Defaults to 15 pixels.</p>
|
|
262
|
+
</Tooltip.Content>
|
|
263
|
+
</Tooltip>
|
|
264
|
+
}
|
|
265
|
+
/>
|
|
266
|
+
<TextField
|
|
267
|
+
type='number'
|
|
268
|
+
value={config.horizon?.bottomPadding ?? 15}
|
|
269
|
+
section='horizon'
|
|
270
|
+
fieldName='bottomPadding'
|
|
271
|
+
label='Bottom Padding'
|
|
272
|
+
updateField={updateField}
|
|
273
|
+
tooltip={
|
|
274
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
275
|
+
<Tooltip.Target>
|
|
276
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
277
|
+
</Tooltip.Target>
|
|
278
|
+
<Tooltip.Content>
|
|
279
|
+
<p>The padding (in pixels) below the last series row in the horizon chart.</p>
|
|
280
|
+
<hr />
|
|
281
|
+
<p>Defaults to 15 pixels.</p>
|
|
282
|
+
</Tooltip.Content>
|
|
283
|
+
</Tooltip>
|
|
284
|
+
}
|
|
285
|
+
/>
|
|
286
|
+
</>
|
|
287
|
+
)}
|
|
288
|
+
|
|
209
289
|
{visHasNumbersOnBars() ? (
|
|
210
290
|
<CheckBox
|
|
211
291
|
value={config.yAxis.displayNumbersOnBar}
|
|
@@ -316,7 +396,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
316
396
|
/>
|
|
317
397
|
</>
|
|
318
398
|
)}
|
|
319
|
-
{config.visualizationType !== 'Warming Stripes' && (
|
|
399
|
+
{config.visualizationType !== 'Warming Stripes' && config.visualizationType !== 'Radar' && (
|
|
320
400
|
<CheckBox
|
|
321
401
|
tooltip={
|
|
322
402
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
@@ -311,7 +311,7 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
311
311
|
updateConfig(updatedConfig)
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
if (config.visualizationType === 'Warming Stripes') return
|
|
314
|
+
if (config.visualizationType === 'Warming Stripes' || config.visualizationType === 'Radar') return
|
|
315
315
|
|
|
316
316
|
return (
|
|
317
317
|
<AccordionItem>
|