@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,11 +1,12 @@
|
|
|
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'
|
|
6
7
|
import Button from '@cdc/core/components/elements/Button'
|
|
7
8
|
import { CheckBox, Select } from '@cdc/core/components/EditorPanel/Inputs'
|
|
8
|
-
import
|
|
9
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
9
10
|
|
|
10
11
|
// types
|
|
11
12
|
import { type PanelProps } from './../PanelProps'
|
|
@@ -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,13 +148,66 @@ 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 />
|
|
169
207
|
<input
|
|
170
208
|
type='range'
|
|
171
209
|
onChange={e => {
|
|
172
|
-
const updatedAnnotations =
|
|
210
|
+
const updatedAnnotations = cloneDeep(config?.annotations)
|
|
173
211
|
updatedAnnotations[index].opacity = e.target.value
|
|
174
212
|
updateConfig({
|
|
175
213
|
...config,
|
|
@@ -187,7 +225,7 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
187
225
|
fieldName={`${index}.edit.subject`}
|
|
188
226
|
label='Edit Subject'
|
|
189
227
|
updateField={(section, subsection, fieldName, value) => {
|
|
190
|
-
const updatedAnnotations =
|
|
228
|
+
const updatedAnnotations = cloneDeep(config?.annotations)
|
|
191
229
|
updatedAnnotations[index].edit.subject = value
|
|
192
230
|
updateConfig({
|
|
193
231
|
...config,
|
|
@@ -202,7 +240,7 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
202
240
|
fieldName={`${index}.edit.label`}
|
|
203
241
|
label='Edit Label'
|
|
204
242
|
updateField={(section, subsection, fieldName, value) => {
|
|
205
|
-
const updatedAnnotations =
|
|
243
|
+
const updatedAnnotations = cloneDeep(config?.annotations)
|
|
206
244
|
updatedAnnotations[index].edit.label = value
|
|
207
245
|
updateConfig({
|
|
208
246
|
...config,
|
|
@@ -219,7 +257,7 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
219
257
|
subsection={null}
|
|
220
258
|
fieldName='connectionType'
|
|
221
259
|
updateField={(section, subsection, fieldName, value) => {
|
|
222
|
-
const updatedAnnotations =
|
|
260
|
+
const updatedAnnotations = cloneDeep(config?.annotations)
|
|
223
261
|
updatedAnnotations[index].connectionType = value
|
|
224
262
|
updateConfig({
|
|
225
263
|
...config,
|
|
@@ -239,7 +277,7 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
239
277
|
max='20'
|
|
240
278
|
value={config?.annotations[index]?.bezier || 0}
|
|
241
279
|
onChange={e => {
|
|
242
|
-
const updatedAnnotations =
|
|
280
|
+
const updatedAnnotations = cloneDeep(config?.annotations)
|
|
243
281
|
updatedAnnotations[index].bezier = e.target.value
|
|
244
282
|
updateConfig({
|
|
245
283
|
...config,
|
|
@@ -259,7 +297,7 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
259
297
|
subsection={null}
|
|
260
298
|
fieldName='marker'
|
|
261
299
|
updateField={(section, subsection, fieldName, value) => {
|
|
262
|
-
const updatedAnnotations =
|
|
300
|
+
const updatedAnnotations = cloneDeep(config?.annotations)
|
|
263
301
|
updatedAnnotations[index].marker = value
|
|
264
302
|
updateConfig({
|
|
265
303
|
...config,
|
|
@@ -320,84 +320,6 @@ const ForestPlotSettings: FC<PanelProps> = ({ name }) => {
|
|
|
320
320
|
<br />
|
|
321
321
|
<hr />
|
|
322
322
|
<br />
|
|
323
|
-
<h4>Width Settings</h4>
|
|
324
|
-
|
|
325
|
-
<label>
|
|
326
|
-
<span className='edit-label column-heading'>Chart Offset Left (%)</span>
|
|
327
|
-
<input
|
|
328
|
-
type='number'
|
|
329
|
-
min={0}
|
|
330
|
-
max={100}
|
|
331
|
-
value={config.forestPlot.leftWidthOffset || 0}
|
|
332
|
-
onChange={e => {
|
|
333
|
-
updateConfig({
|
|
334
|
-
...config,
|
|
335
|
-
forestPlot: {
|
|
336
|
-
...config.forestPlot,
|
|
337
|
-
leftWidthOffset: e.target.value
|
|
338
|
-
}
|
|
339
|
-
})
|
|
340
|
-
}}
|
|
341
|
-
/>
|
|
342
|
-
</label>
|
|
343
|
-
|
|
344
|
-
<label>
|
|
345
|
-
<span className='edit-label column-heading'>Chart Offset Left Mobile(%)</span>
|
|
346
|
-
<input
|
|
347
|
-
type='number'
|
|
348
|
-
min={0}
|
|
349
|
-
max={100}
|
|
350
|
-
value={config.forestPlot.leftWidthOffsetMobile || 0}
|
|
351
|
-
onChange={e => {
|
|
352
|
-
updateConfig({
|
|
353
|
-
...config,
|
|
354
|
-
forestPlot: {
|
|
355
|
-
...config.forestPlot,
|
|
356
|
-
leftWidthOffsetMobile: e.target.value
|
|
357
|
-
}
|
|
358
|
-
})
|
|
359
|
-
}}
|
|
360
|
-
/>
|
|
361
|
-
</label>
|
|
362
|
-
|
|
363
|
-
<label>
|
|
364
|
-
<span className='edit-label column-heading'>Chart Offset Right (%)</span>
|
|
365
|
-
<input
|
|
366
|
-
type='number'
|
|
367
|
-
min={0}
|
|
368
|
-
max={100}
|
|
369
|
-
value={config.forestPlot.rightWidthOffset || 0}
|
|
370
|
-
onChange={e => {
|
|
371
|
-
updateConfig({
|
|
372
|
-
...config,
|
|
373
|
-
forestPlot: {
|
|
374
|
-
...config.forestPlot,
|
|
375
|
-
rightWidthOffset: e.target.value
|
|
376
|
-
}
|
|
377
|
-
})
|
|
378
|
-
}}
|
|
379
|
-
/>
|
|
380
|
-
</label>
|
|
381
|
-
|
|
382
|
-
<label>
|
|
383
|
-
<span className='edit-label column-heading'>Chart Offset Right Mobile(%)</span>
|
|
384
|
-
<input
|
|
385
|
-
type='number'
|
|
386
|
-
min={0}
|
|
387
|
-
max={100}
|
|
388
|
-
value={config.forestPlot.rightWidthOffsetMobile || 0}
|
|
389
|
-
onChange={e => {
|
|
390
|
-
updateConfig({
|
|
391
|
-
...config,
|
|
392
|
-
forestPlot: {
|
|
393
|
-
...config.forestPlot,
|
|
394
|
-
rightWidthOffsetMobile: e.target.value
|
|
395
|
-
}
|
|
396
|
-
})
|
|
397
|
-
}}
|
|
398
|
-
/>
|
|
399
|
-
</label>
|
|
400
|
-
|
|
401
323
|
<TextField
|
|
402
324
|
type='number'
|
|
403
325
|
min={20}
|
|
@@ -47,6 +47,22 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
const handleTitleStyleChange = (newTitleStyle: string) => {
|
|
51
|
+
updateConfig({
|
|
52
|
+
...config,
|
|
53
|
+
titleStyle: newTitleStyle,
|
|
54
|
+
visual:
|
|
55
|
+
newTitleStyle === 'legacy'
|
|
56
|
+
? config.visual
|
|
57
|
+
: {
|
|
58
|
+
...config.visual,
|
|
59
|
+
border: undefined,
|
|
60
|
+
borderColorTheme: undefined,
|
|
61
|
+
accent: undefined
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
return (
|
|
51
67
|
<AccordionItem>
|
|
52
68
|
{' '}
|
|
@@ -206,6 +222,86 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
206
222
|
options={['Below Bar', 'On Date/Category Axis']}
|
|
207
223
|
/>
|
|
208
224
|
)}
|
|
225
|
+
{visualizationType === 'Horizon Chart' && (
|
|
226
|
+
<>
|
|
227
|
+
<TextField
|
|
228
|
+
type='number'
|
|
229
|
+
value={config.horizon?.numLayers || 4}
|
|
230
|
+
section='horizon'
|
|
231
|
+
fieldName='numLayers'
|
|
232
|
+
label='Number of Layers'
|
|
233
|
+
updateField={updateField}
|
|
234
|
+
min={1}
|
|
235
|
+
max={9}
|
|
236
|
+
onBlur={e => {
|
|
237
|
+
const parsed = Number(e.target.value)
|
|
238
|
+
if (!isNaN(parsed)) {
|
|
239
|
+
const value = Math.round(parsed)
|
|
240
|
+
const clamped = Math.min(9, Math.max(1, value))
|
|
241
|
+
if (clamped !== parsed) {
|
|
242
|
+
updateField('horizon', null, 'numLayers', clamped)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}}
|
|
246
|
+
tooltip={
|
|
247
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
248
|
+
<Tooltip.Target>
|
|
249
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
250
|
+
</Tooltip.Target>
|
|
251
|
+
<Tooltip.Content>
|
|
252
|
+
<p>
|
|
253
|
+
The number of layers determines how many "bands" the horizon chart is divided into. The optimal
|
|
254
|
+
number of layers may depend on the data range and the desired level of detail.
|
|
255
|
+
</p>
|
|
256
|
+
<hr />
|
|
257
|
+
<p>Valid range: 1-9. Defaults to 4 layers.</p>
|
|
258
|
+
</Tooltip.Content>
|
|
259
|
+
</Tooltip>
|
|
260
|
+
}
|
|
261
|
+
/>
|
|
262
|
+
<TextField
|
|
263
|
+
type='number'
|
|
264
|
+
value={config.horizon?.bandGap ?? 15}
|
|
265
|
+
section='horizon'
|
|
266
|
+
fieldName='bandGap'
|
|
267
|
+
label='Band Gap'
|
|
268
|
+
updateField={updateField}
|
|
269
|
+
tooltip={
|
|
270
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
271
|
+
<Tooltip.Target>
|
|
272
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
273
|
+
</Tooltip.Target>
|
|
274
|
+
<Tooltip.Content>
|
|
275
|
+
<p>The vertical spacing (in pixels) between each series row in the horizon chart.</p>
|
|
276
|
+
<hr />
|
|
277
|
+
<p>Defaults to 15 pixels.</p>
|
|
278
|
+
</Tooltip.Content>
|
|
279
|
+
</Tooltip>
|
|
280
|
+
}
|
|
281
|
+
/>
|
|
282
|
+
<TextField
|
|
283
|
+
type='number'
|
|
284
|
+
value={config.horizon?.bottomPadding ?? 15}
|
|
285
|
+
section='horizon'
|
|
286
|
+
fieldName='bottomPadding'
|
|
287
|
+
label='Bottom Padding'
|
|
288
|
+
updateField={updateField}
|
|
289
|
+
tooltip={
|
|
290
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
291
|
+
<Tooltip.Target>
|
|
292
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
293
|
+
</Tooltip.Target>
|
|
294
|
+
<Tooltip.Content>
|
|
295
|
+
<p>The padding (in pixels) below the last series row in the horizon chart.</p>
|
|
296
|
+
<hr />
|
|
297
|
+
<p>Defaults to 15 pixels.</p>
|
|
298
|
+
</Tooltip.Content>
|
|
299
|
+
</Tooltip>
|
|
300
|
+
}
|
|
301
|
+
/>
|
|
302
|
+
</>
|
|
303
|
+
)}
|
|
304
|
+
|
|
209
305
|
{visHasNumbersOnBars() ? (
|
|
210
306
|
<CheckBox
|
|
211
307
|
value={config.yAxis.displayNumbersOnBar}
|
|
@@ -316,7 +412,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
316
412
|
/>
|
|
317
413
|
</>
|
|
318
414
|
)}
|
|
319
|
-
{config.visualizationType !== 'Warming Stripes' && (
|
|
415
|
+
{config.visualizationType !== 'Warming Stripes' && config.visualizationType !== 'Radar' && (
|
|
320
416
|
<CheckBox
|
|
321
417
|
tooltip={
|
|
322
418
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
@@ -395,12 +491,12 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
395
491
|
value={config.titleStyle}
|
|
396
492
|
fieldName='titleStyle'
|
|
397
493
|
label='Title Style'
|
|
398
|
-
updateField={updateField}
|
|
399
494
|
options={[
|
|
400
495
|
{ value: 'small', label: 'Small (h3)' },
|
|
401
496
|
{ value: 'large', label: 'Large (h2)' },
|
|
402
497
|
{ value: 'legacy', label: 'Legacy' }
|
|
403
498
|
]}
|
|
499
|
+
onChange={event => handleTitleStyleChange(event.target.value)}
|
|
404
500
|
tooltip={
|
|
405
501
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
406
502
|
<Tooltip.Target>
|
|
@@ -499,6 +595,28 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
499
595
|
}
|
|
500
596
|
/>
|
|
501
597
|
)}
|
|
598
|
+
<Select
|
|
599
|
+
value={config.locale}
|
|
600
|
+
fieldName='locale'
|
|
601
|
+
label='Language for dates and numbers'
|
|
602
|
+
updateField={updateField}
|
|
603
|
+
options={[
|
|
604
|
+
{ value: 'en-US', label: 'English (en-US)' },
|
|
605
|
+
{ value: 'es-MX', label: 'Spanish (es-MX)' }
|
|
606
|
+
]}
|
|
607
|
+
tooltip={
|
|
608
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
609
|
+
<Tooltip.Target>
|
|
610
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
611
|
+
</Tooltip.Target>
|
|
612
|
+
<Tooltip.Content>
|
|
613
|
+
<p>
|
|
614
|
+
Change the language (locale) for this visualization to alter the way dates and numbers are formatted.
|
|
615
|
+
</p>
|
|
616
|
+
</Tooltip.Content>
|
|
617
|
+
</Tooltip>
|
|
618
|
+
}
|
|
619
|
+
/>
|
|
502
620
|
</AccordionItemPanel>
|
|
503
621
|
</AccordionItem>
|
|
504
622
|
)
|
|
@@ -16,7 +16,7 @@ import { ChartContext } from '../../../../types/ChartContext'
|
|
|
16
16
|
import { PanelProps } from '../PanelProps'
|
|
17
17
|
import { checkColorContrast, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
|
|
18
18
|
import { getColorScale } from '../../../../helpers/getColorScale'
|
|
19
|
-
import
|
|
19
|
+
import { sanitizeToSvgId } from '@cdc/core/helpers/cove/string'
|
|
20
20
|
|
|
21
21
|
const PanelPatternSettings: FC<PanelProps> = props => {
|
|
22
22
|
const { config, updateConfig, transformedData } = useContext<ChartContext>(ConfigContext)
|
|
@@ -69,19 +69,6 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Get unique values from a specific data field for dropdown options
|
|
73
|
-
const getDataValueOptions = (dataKey: string) => {
|
|
74
|
-
if (!dataKey || !Array.isArray(transformedData) || transformedData.length === 0) {
|
|
75
|
-
return []
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const uniqueValues = Array.from(new Set(transformedData.map(row => row[dataKey])))
|
|
79
|
-
.filter(val => val !== undefined && val !== null && val !== '')
|
|
80
|
-
.sort()
|
|
81
|
-
|
|
82
|
-
return uniqueValues.map(value => ({ value: String(value), label: String(value) }))
|
|
83
|
-
}
|
|
84
|
-
|
|
85
72
|
const getFieldOptions = () => {
|
|
86
73
|
if (!Array.isArray(transformedData) || transformedData.length === 0) return []
|
|
87
74
|
|
|
@@ -282,11 +269,6 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
282
269
|
[field]: value
|
|
283
270
|
}
|
|
284
271
|
|
|
285
|
-
// Clear dataValue if dataKey is being cleared or set to 'Select'
|
|
286
|
-
if (field === 'dataKey' && (value === 'Select' || value === '')) {
|
|
287
|
-
updatedPattern.dataValue = ''
|
|
288
|
-
}
|
|
289
|
-
|
|
290
272
|
const newPatterns = {
|
|
291
273
|
...(legendCfg.patterns || {}),
|
|
292
274
|
[patternKey]: updatedPattern
|
|
@@ -311,7 +293,7 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
311
293
|
updateConfig(updatedConfig)
|
|
312
294
|
}
|
|
313
295
|
|
|
314
|
-
if (config.visualizationType === 'Warming Stripes') return
|
|
296
|
+
if (config.visualizationType === 'Warming Stripes' || config.visualizationType === 'Radar') return
|
|
315
297
|
|
|
316
298
|
return (
|
|
317
299
|
<AccordionItem>
|
|
@@ -333,14 +315,18 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
333
315
|
{/* Individual Pattern Configurations */}
|
|
334
316
|
{Object.entries(currentPatterns).map(([patternKey, pattern], index) => {
|
|
335
317
|
const p: LegendPattern = pattern || {}
|
|
336
|
-
const
|
|
318
|
+
const domPatternKey = `${sanitizeToSvgId(patternKey)}-${index}`
|
|
337
319
|
|
|
338
320
|
return (
|
|
339
321
|
<Accordion allowZeroExpanded key={`pattern-accordion-${index}`}>
|
|
340
322
|
<AccordionItem>
|
|
341
323
|
<AccordionItemHeading>
|
|
342
324
|
<AccordionItemButton>
|
|
343
|
-
{p.dataKey && p.dataValue
|
|
325
|
+
{p.dataKey && p.dataValue
|
|
326
|
+
? `${p.dataKey}: ${p.dataValue}`
|
|
327
|
+
: p.dataValue
|
|
328
|
+
? `All Series: ${p.dataValue}`
|
|
329
|
+
: `Pattern ${index + 1}`}
|
|
344
330
|
</AccordionItemButton>
|
|
345
331
|
</AccordionItemHeading>
|
|
346
332
|
<AccordionItemPanel>
|
|
@@ -359,32 +345,28 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
359
345
|
value={p.dataKey || ''}
|
|
360
346
|
options={fieldOptions}
|
|
361
347
|
initial='Select Data Key'
|
|
362
|
-
fieldName={`pattern-datakey-${
|
|
348
|
+
fieldName={`pattern-datakey-${domPatternKey}`}
|
|
363
349
|
updateField={(section, subsection, fieldName, value) =>
|
|
364
350
|
handlePatternUpdate(patternKey, 'dataKey', value)
|
|
365
351
|
}
|
|
366
352
|
/>
|
|
367
353
|
|
|
368
|
-
{
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
/>
|
|
379
|
-
</label>
|
|
380
|
-
</>
|
|
381
|
-
)}
|
|
354
|
+
<label htmlFor={`pattern-datavalue-${domPatternKey}`}>
|
|
355
|
+
Data Value:
|
|
356
|
+
<input
|
|
357
|
+
type='text'
|
|
358
|
+
id={`pattern-datavalue-${domPatternKey}`}
|
|
359
|
+
value={p.dataValue || ''}
|
|
360
|
+
onChange={e => handlePatternUpdate(patternKey, 'dataValue', e.target.value)}
|
|
361
|
+
placeholder='Enter data value'
|
|
362
|
+
/>
|
|
363
|
+
</label>
|
|
382
364
|
|
|
383
|
-
<label htmlFor={`pattern-label-${
|
|
365
|
+
<label htmlFor={`pattern-label-${domPatternKey}`}>
|
|
384
366
|
Label (optional):
|
|
385
367
|
<input
|
|
386
368
|
type='text'
|
|
387
|
-
id={`pattern-label-${
|
|
369
|
+
id={`pattern-label-${domPatternKey}`}
|
|
388
370
|
value={p.label || ''}
|
|
389
371
|
onChange={e => handlePatternUpdate(patternKey, 'label', e.target.value)}
|
|
390
372
|
/>
|
|
@@ -394,7 +376,7 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
394
376
|
label='Pattern Type:'
|
|
395
377
|
value={p.shape || 'circles'}
|
|
396
378
|
options={patternTypes}
|
|
397
|
-
fieldName={`pattern-type-${
|
|
379
|
+
fieldName={`pattern-type-${domPatternKey}`}
|
|
398
380
|
updateField={(section, subsection, fieldName, value) =>
|
|
399
381
|
handlePatternUpdate(patternKey, 'shape', value)
|
|
400
382
|
}
|
|
@@ -404,14 +386,14 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
404
386
|
label='Pattern Size:'
|
|
405
387
|
value={getPatternSizeText(p.patternSize || 8)}
|
|
406
388
|
options={patternSizes}
|
|
407
|
-
fieldName={`pattern-size-${
|
|
389
|
+
fieldName={`pattern-size-${domPatternKey}`}
|
|
408
390
|
updateField={(section, subsection, fieldName, value) =>
|
|
409
391
|
handlePatternUpdate(patternKey, 'patternSize', getPatternSizeNumeric(value))
|
|
410
392
|
}
|
|
411
393
|
/>
|
|
412
394
|
|
|
413
395
|
<div className='mt-3'>
|
|
414
|
-
<label htmlFor={`pattern-color-${
|
|
396
|
+
<label htmlFor={`pattern-color-${domPatternKey}`}>
|
|
415
397
|
Pattern Color
|
|
416
398
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
417
399
|
<Tooltip.Target>
|
|
@@ -430,7 +412,7 @@ const PanelPatternSettings: FC<PanelProps> = props => {
|
|
|
430
412
|
<input
|
|
431
413
|
type='text'
|
|
432
414
|
value={p.color || ''}
|
|
433
|
-
id={`pattern-color-${
|
|
415
|
+
id={`pattern-color-${domPatternKey}`}
|
|
434
416
|
onChange={e => handlePatternUpdate(patternKey, 'color', e.target.value)}
|
|
435
417
|
placeholder='#666666'
|
|
436
418
|
/>
|