@cdc/chart 4.25.10 → 4.26.1
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/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
- package/dist/cdcchart.js +44003 -43518
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/feature/pie/planet-pie-example-config.json +48 -2
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/DEV-12100.json +1303 -0
- package/examples/private/cat-y.json +1235 -0
- package/examples/private/data-points.json +228 -0
- package/examples/private/height.json +3915 -0
- package/examples/private/links.json +569 -0
- package/examples/private/na.json +913 -0
- package/examples/private/quadrant.txt +30 -0
- package/examples/private/test-data.csv +28 -0
- package/examples/private/test-forecast.json +5510 -0
- package/examples/private/warming-stripe-test.json +2578 -0
- package/examples/private/warming-stripes.json +4763 -0
- package/examples/tech-adoption-with-links.json +560 -0
- package/index.html +16 -140
- package/package.json +6 -5
- package/preview.html +1616 -0
- package/src/CdcChart.tsx +8 -11
- package/src/CdcChartComponent.tsx +329 -124
- package/src/_stories/Chart.Combo.stories.tsx +18 -0
- package/src/_stories/Chart.Forecast.stories.tsx +36 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
- package/src/_stories/Chart.Patterns.stories.tsx +2 -1
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/Chart.stories.tsx +8 -0
- package/src/_stories/ChartAnnotation.stories.tsx +6 -3
- package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
- package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
- package/src/_stories/ChartBrush.stories.tsx +50 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
- package/src/_stories/ChartEditor.stories.tsx +1 -2
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
- package/src/_stories/_mock/brush_enabled.json +326 -0
- package/src/_stories/_mock/brush_mock.json +2 -69
- package/src/_stories/_mock/combo.json +451 -0
- package/src/_stories/_mock/editor-test-configs.json +376 -0
- package/src/_stories/_mock/editor-test-datasets.json +477 -0
- package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
- package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
- package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
- package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
- package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
- package/src/_stories/_mock/pie_config.json +257 -62
- package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
- package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
- package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
- package/src/components/Annotations/components/findNearestDatum.ts +6 -41
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -7
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/Axis/Categorical.Axis.tsx +6 -7
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
- package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
- package/src/components/BarChart/components/context.tsx +1 -0
- package/src/components/BarChart/helpers/useBarChart.ts +14 -2
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushSelector.tsx +1258 -0
- package/src/components/Brush/MiniChartPreview.tsx +283 -0
- package/src/components/DeviationBar.jsx +9 -7
- package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/editor-panel.scss +0 -20
- package/src/components/EditorPanel/useEditorPermissions.ts +36 -31
- package/src/components/Forecasting/Forecasting.tsx +139 -21
- package/src/components/Legend/Legend.Component.tsx +16 -9
- package/src/components/Legend/Legend.tsx +3 -2
- package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/Legend/helpers/index.ts +10 -6
- package/src/components/LineChart/LineChartProps.ts +0 -3
- package/src/components/LineChart/helpers.ts +1 -1
- package/src/components/LineChart/index.tsx +36 -13
- package/src/components/LinearChart.tsx +559 -499
- package/src/components/PairedBarChart.jsx +20 -3
- package/src/components/Regions/components/Regions.tsx +366 -144
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
- package/src/components/SmallMultiples/index.ts +2 -0
- package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
- package/src/components/WarmingStripes/index.tsx +3 -0
- package/src/data/initial-state.js +16 -2
- package/src/helpers/buildForecastPaletteOptions.ts +0 -38
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
- package/src/helpers/getColorScale.ts +10 -0
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/sizeHelpers.ts +0 -20
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useChartHoverAnalytics.tsx +10 -9
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useScales.ts +98 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +91 -25
- package/src/scss/DataTable.scss +0 -4
- package/src/scss/main.scss +18 -83
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +4 -0
- package/src/test/CdcChart.test.jsx +1 -1
- package/src/types/ChartConfig.ts +27 -6
- package/src/types/ChartContext.ts +3 -0
- package/src/types/Label.ts +1 -0
- package/src/utils/analyticsTracking.ts +19 -0
- package/LICENSE +0 -201
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/components/Brush/BrushChart.tsx +0 -128
- package/src/components/Brush/BrushController.tsx +0 -71
- package/src/components/Brush/types.tsx +0 -8
- package/src/components/BrushChart.tsx +0 -223
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { useContext, FC, useMemo } from 'react'
|
|
2
|
+
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
|
3
|
+
import {
|
|
4
|
+
AccordionItem,
|
|
5
|
+
AccordionItemHeading,
|
|
6
|
+
AccordionItemPanel,
|
|
7
|
+
AccordionItemButton
|
|
8
|
+
} from 'react-accessible-accordion'
|
|
9
|
+
|
|
10
|
+
// core
|
|
11
|
+
import { TextField, Select, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
|
|
12
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
13
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
14
|
+
import { useDataColumns } from '@cdc/core/hooks/useDataColumns'
|
|
15
|
+
|
|
16
|
+
// contexts
|
|
17
|
+
import { ChartContext } from './../../../../types/ChartContext.js'
|
|
18
|
+
import { useEditorPermissions } from '../../useEditorPermissions.js'
|
|
19
|
+
import { useEditorPanelContext } from '../../EditorPanelContext.js'
|
|
20
|
+
import ConfigContext from '../../../../ConfigContext.js'
|
|
21
|
+
import { PanelProps } from '../PanelProps'
|
|
22
|
+
import { getTileKeys } from '../../../../helpers/smallMultiplesHelpers'
|
|
23
|
+
|
|
24
|
+
const PanelSmallMultiples: FC<PanelProps> = props => {
|
|
25
|
+
const { config, rawData, updateConfig } = useContext<ChartContext>(ConfigContext)
|
|
26
|
+
const { updateField } = useEditorPanelContext()
|
|
27
|
+
const { visSupportsSmallMultiples } = useEditorPermissions()
|
|
28
|
+
|
|
29
|
+
// Extract column names from data with memoization (replaces getColumns)
|
|
30
|
+
const allColumns = useDataColumns(rawData)
|
|
31
|
+
|
|
32
|
+
// Filter out series columns and confidence key columns (except lower and upper)
|
|
33
|
+
const filteredColumns = useMemo(() => {
|
|
34
|
+
const { lower, upper } = config.confidenceKeys || {}
|
|
35
|
+
return allColumns.filter(key => {
|
|
36
|
+
// Filter out series columns
|
|
37
|
+
if (config.series && config.series.some(series => series.dataKey === key)) {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
// Filter out confidence key columns (except lower and upper)
|
|
41
|
+
if (
|
|
42
|
+
config.confidenceKeys &&
|
|
43
|
+
Object.keys(config.confidenceKeys).includes(key) &&
|
|
44
|
+
((lower && upper) || lower || upper) &&
|
|
45
|
+
key !== lower &&
|
|
46
|
+
key !== upper
|
|
47
|
+
) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
return true
|
|
51
|
+
})
|
|
52
|
+
}, [allColumns, config.series, config.confidenceKeys])
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<>
|
|
56
|
+
{visSupportsSmallMultiples() && (
|
|
57
|
+
<AccordionItem>
|
|
58
|
+
<AccordionItemHeading>
|
|
59
|
+
<AccordionItemButton>Small Multiples</AccordionItemButton>
|
|
60
|
+
</AccordionItemHeading>
|
|
61
|
+
<AccordionItemPanel>
|
|
62
|
+
<Select
|
|
63
|
+
value={config.smallMultiples?.mode || ''}
|
|
64
|
+
fieldName='mode'
|
|
65
|
+
section='smallMultiples'
|
|
66
|
+
label='Tile Mode'
|
|
67
|
+
initial='Select Mode'
|
|
68
|
+
updateField={updateField}
|
|
69
|
+
options={[
|
|
70
|
+
{ label: 'By data series', value: 'by-series' },
|
|
71
|
+
{ label: 'By column values', value: 'by-column' }
|
|
72
|
+
]}
|
|
73
|
+
tooltip={
|
|
74
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
75
|
+
<Tooltip.Target>
|
|
76
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
77
|
+
</Tooltip.Target>
|
|
78
|
+
<Tooltip.Content>
|
|
79
|
+
<p>
|
|
80
|
+
Choose how to create multiple charts. "By Data Series" creates a tile for each configured data
|
|
81
|
+
series. "By Column Values" creates a tile for each unique value in the selected column.
|
|
82
|
+
</p>
|
|
83
|
+
</Tooltip.Content>
|
|
84
|
+
</Tooltip>
|
|
85
|
+
}
|
|
86
|
+
/>
|
|
87
|
+
{config.smallMultiples?.mode === 'by-column' && (
|
|
88
|
+
<Select
|
|
89
|
+
value={config.smallMultiples?.tileColumn || ''}
|
|
90
|
+
fieldName='tileColumn'
|
|
91
|
+
section='smallMultiples'
|
|
92
|
+
label='Tile By Column'
|
|
93
|
+
initial='Select Column'
|
|
94
|
+
updateField={updateField}
|
|
95
|
+
options={filteredColumns}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{config.smallMultiples?.mode && (
|
|
100
|
+
<>
|
|
101
|
+
<TextField
|
|
102
|
+
type='number'
|
|
103
|
+
value={config.smallMultiples?.tilesPerRowDesktop}
|
|
104
|
+
section='smallMultiples'
|
|
105
|
+
fieldName='tilesPerRowDesktop'
|
|
106
|
+
label='Tiles Per Row (Desktop)'
|
|
107
|
+
updateField={updateField}
|
|
108
|
+
min={1}
|
|
109
|
+
max={3}
|
|
110
|
+
tooltip={
|
|
111
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
112
|
+
<Tooltip.Target>
|
|
113
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
114
|
+
</Tooltip.Target>
|
|
115
|
+
<Tooltip.Content>
|
|
116
|
+
<p>
|
|
117
|
+
Number of chart tiles to display per row on desktop screens. Mobile will always show 1 tile
|
|
118
|
+
per row.
|
|
119
|
+
</p>
|
|
120
|
+
</Tooltip.Content>
|
|
121
|
+
</Tooltip>
|
|
122
|
+
}
|
|
123
|
+
/>
|
|
124
|
+
|
|
125
|
+
{/* Tile Ordering */}
|
|
126
|
+
{(() => {
|
|
127
|
+
const availableTiles = getTileKeys(config, rawData)
|
|
128
|
+
if (availableTiles.length === 0) return null
|
|
129
|
+
|
|
130
|
+
const tileOrderOptions = [
|
|
131
|
+
{
|
|
132
|
+
label: 'Ascending By Title',
|
|
133
|
+
value: 'asc'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: 'Descending By Title',
|
|
137
|
+
value: 'desc'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: 'Custom',
|
|
141
|
+
value: 'custom'
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
const currentOrderType = config.smallMultiples?.tileOrderType || 'asc'
|
|
146
|
+
|
|
147
|
+
const handleOrderTypeChange = orderType => {
|
|
148
|
+
const newConfig = {
|
|
149
|
+
...config,
|
|
150
|
+
smallMultiples: {
|
|
151
|
+
...config.smallMultiples,
|
|
152
|
+
tileOrderType: orderType
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// If switching to custom, initialize with current tile order
|
|
157
|
+
if (orderType === 'custom' && !config.smallMultiples?.tileOrder?.length) {
|
|
158
|
+
newConfig.smallMultiples.tileOrder = [...availableTiles]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
updateConfig(newConfig)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const handleCustomTileOrderChange = (sourceIndex, destinationIndex) => {
|
|
165
|
+
if (destinationIndex === null) return
|
|
166
|
+
|
|
167
|
+
const currentOrder = config.smallMultiples?.tileOrder || [...availableTiles]
|
|
168
|
+
const newOrder = [...currentOrder]
|
|
169
|
+
const [removed] = newOrder.splice(sourceIndex, 1)
|
|
170
|
+
newOrder.splice(destinationIndex, 0, removed)
|
|
171
|
+
|
|
172
|
+
updateConfig({
|
|
173
|
+
...config,
|
|
174
|
+
smallMultiples: {
|
|
175
|
+
...config.smallMultiples,
|
|
176
|
+
tileOrder: newOrder,
|
|
177
|
+
tileOrderType: 'custom'
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<>
|
|
184
|
+
<Select
|
|
185
|
+
value={currentOrderType}
|
|
186
|
+
options={tileOrderOptions}
|
|
187
|
+
label='Tile Order'
|
|
188
|
+
fieldName='tileOrderType'
|
|
189
|
+
section='smallMultiples'
|
|
190
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
191
|
+
handleOrderTypeChange(value)
|
|
192
|
+
}}
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
{currentOrderType === 'custom' && (
|
|
196
|
+
<DragDropContext
|
|
197
|
+
onDragEnd={({ source, destination }) =>
|
|
198
|
+
handleCustomTileOrderChange(source.index, destination?.index)
|
|
199
|
+
}
|
|
200
|
+
>
|
|
201
|
+
<Droppable droppableId='tile_order'>
|
|
202
|
+
{provided => (
|
|
203
|
+
<ul
|
|
204
|
+
{...provided.droppableProps}
|
|
205
|
+
className='sort-list'
|
|
206
|
+
ref={provided.innerRef}
|
|
207
|
+
style={{ marginTop: '1em' }}
|
|
208
|
+
>
|
|
209
|
+
{(config.smallMultiples?.tileOrder || availableTiles).map((tileKey, index) => (
|
|
210
|
+
<Draggable key={tileKey} draggableId={`tile-${tileKey}`} index={index}>
|
|
211
|
+
{(provided, snapshot) => (
|
|
212
|
+
<li>
|
|
213
|
+
<div
|
|
214
|
+
className={snapshot.isDragging ? 'currently-dragging' : ''}
|
|
215
|
+
style={provided.draggableProps.style}
|
|
216
|
+
ref={provided.innerRef}
|
|
217
|
+
{...provided.draggableProps}
|
|
218
|
+
{...provided.dragHandleProps}
|
|
219
|
+
>
|
|
220
|
+
{tileKey}
|
|
221
|
+
</div>
|
|
222
|
+
</li>
|
|
223
|
+
)}
|
|
224
|
+
</Draggable>
|
|
225
|
+
))}
|
|
226
|
+
{provided.placeholder}
|
|
227
|
+
</ul>
|
|
228
|
+
)}
|
|
229
|
+
</Droppable>
|
|
230
|
+
</DragDropContext>
|
|
231
|
+
)}
|
|
232
|
+
</>
|
|
233
|
+
)
|
|
234
|
+
})()}
|
|
235
|
+
|
|
236
|
+
{/* Color Mode */}
|
|
237
|
+
<Select
|
|
238
|
+
value={config.smallMultiples?.colorMode || 'different'}
|
|
239
|
+
options={[
|
|
240
|
+
{
|
|
241
|
+
label: 'Same Color',
|
|
242
|
+
value: 'same'
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
label: 'Different Colors',
|
|
246
|
+
value: 'different'
|
|
247
|
+
}
|
|
248
|
+
]}
|
|
249
|
+
label='Color Mode'
|
|
250
|
+
fieldName='colorMode'
|
|
251
|
+
section='smallMultiples'
|
|
252
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
253
|
+
updateConfig({
|
|
254
|
+
...config,
|
|
255
|
+
smallMultiples: {
|
|
256
|
+
...config.smallMultiples,
|
|
257
|
+
colorMode: value
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
}}
|
|
261
|
+
tooltip={
|
|
262
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
263
|
+
<Tooltip.Target>
|
|
264
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
265
|
+
</Tooltip.Target>
|
|
266
|
+
<Tooltip.Content>
|
|
267
|
+
<p>
|
|
268
|
+
When "Different Colors" is selected, each tile will use the next color in the configured color
|
|
269
|
+
palette.
|
|
270
|
+
</p>
|
|
271
|
+
</Tooltip.Content>
|
|
272
|
+
</Tooltip>
|
|
273
|
+
}
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
{/* Custom Tile Titles - only show for by-column mode */}
|
|
277
|
+
{config.smallMultiples?.mode === 'by-column' && (
|
|
278
|
+
<div>
|
|
279
|
+
<label style={{ marginTop: '1.5rem', marginBottom: '0.5rem' }}>Custom Tile Titles</label>
|
|
280
|
+
|
|
281
|
+
{(() => {
|
|
282
|
+
const availableTiles = getTileKeys(config, rawData)
|
|
283
|
+
if (availableTiles.length === 0) return null
|
|
284
|
+
|
|
285
|
+
const handleTitleChange = (tileKey, customTitle) => {
|
|
286
|
+
const newTitles = { ...config.smallMultiples?.tileTitles }
|
|
287
|
+
if (customTitle.trim() === '' || customTitle === tileKey) {
|
|
288
|
+
delete newTitles[tileKey] // Remove entry if empty or same as key
|
|
289
|
+
} else {
|
|
290
|
+
newTitles[tileKey] = customTitle
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
updateConfig({
|
|
294
|
+
...config,
|
|
295
|
+
smallMultiples: {
|
|
296
|
+
...config.smallMultiples,
|
|
297
|
+
tileTitles: newTitles
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div className='tile-titles-editor' style={{ maxWidth: '100%', overflow: 'hidden' }}>
|
|
304
|
+
{availableTiles.map(tileKey => {
|
|
305
|
+
const customTitle = config.smallMultiples?.tileTitles?.[tileKey] || ''
|
|
306
|
+
return (
|
|
307
|
+
<div
|
|
308
|
+
key={tileKey}
|
|
309
|
+
className='tile-title-row'
|
|
310
|
+
style={{
|
|
311
|
+
display: 'flex',
|
|
312
|
+
alignItems: 'center',
|
|
313
|
+
marginBottom: '0.75rem',
|
|
314
|
+
maxWidth: '100%'
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
317
|
+
<label
|
|
318
|
+
style={{
|
|
319
|
+
minWidth: '80px',
|
|
320
|
+
maxWidth: '120px',
|
|
321
|
+
marginRight: '0.75rem',
|
|
322
|
+
fontWeight: 'normal',
|
|
323
|
+
fontSize: '13px',
|
|
324
|
+
overflow: 'hidden',
|
|
325
|
+
textOverflow: 'ellipsis',
|
|
326
|
+
whiteSpace: 'nowrap',
|
|
327
|
+
flexShrink: 0
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
330
|
+
{tileKey}:
|
|
331
|
+
</label>
|
|
332
|
+
<input
|
|
333
|
+
type='text'
|
|
334
|
+
value={customTitle}
|
|
335
|
+
placeholder={tileKey}
|
|
336
|
+
onChange={event => handleTitleChange(tileKey, event.target.value)}
|
|
337
|
+
style={{
|
|
338
|
+
flex: 1,
|
|
339
|
+
minWidth: 0,
|
|
340
|
+
maxWidth: '200px',
|
|
341
|
+
fontSize: '13px',
|
|
342
|
+
padding: '4px 8px',
|
|
343
|
+
height: '30px',
|
|
344
|
+
border: '1px solid #ccc',
|
|
345
|
+
borderRadius: '3px'
|
|
346
|
+
}}
|
|
347
|
+
/>
|
|
348
|
+
</div>
|
|
349
|
+
)
|
|
350
|
+
})}
|
|
351
|
+
</div>
|
|
352
|
+
)
|
|
353
|
+
})()}
|
|
354
|
+
</div>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
<CheckBox
|
|
358
|
+
value={config.smallMultiples?.independentYAxis}
|
|
359
|
+
section='smallMultiples'
|
|
360
|
+
fieldName='independentYAxis'
|
|
361
|
+
label='Independent Y-Axis Scales'
|
|
362
|
+
updateField={updateField}
|
|
363
|
+
tooltip={
|
|
364
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
365
|
+
<Tooltip.Target>
|
|
366
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
367
|
+
</Tooltip.Target>
|
|
368
|
+
<Tooltip.Content>
|
|
369
|
+
<p>
|
|
370
|
+
When checked, the y-axis scale for each tile will be calculated separately. The chart's y-axis
|
|
371
|
+
min/max will override this setting if they are configured.
|
|
372
|
+
</p>
|
|
373
|
+
</Tooltip.Content>
|
|
374
|
+
</Tooltip>
|
|
375
|
+
}
|
|
376
|
+
/>
|
|
377
|
+
|
|
378
|
+
<CheckBox
|
|
379
|
+
value={config.smallMultiples?.synchronizedTooltips}
|
|
380
|
+
fieldName='synchronizedTooltips'
|
|
381
|
+
section='smallMultiples'
|
|
382
|
+
label='Synchronized Tooltips'
|
|
383
|
+
updateField={updateField}
|
|
384
|
+
tooltip={
|
|
385
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
386
|
+
<Tooltip.Target>
|
|
387
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
388
|
+
</Tooltip.Target>
|
|
389
|
+
<Tooltip.Content>
|
|
390
|
+
<p>
|
|
391
|
+
When checked, hovering over one chart will show synchronized tooltips on all other charts at
|
|
392
|
+
the same data point.
|
|
393
|
+
</p>
|
|
394
|
+
</Tooltip.Content>
|
|
395
|
+
</Tooltip>
|
|
396
|
+
}
|
|
397
|
+
/>
|
|
398
|
+
|
|
399
|
+
{config.visualizationType === 'Line' && (
|
|
400
|
+
<CheckBox
|
|
401
|
+
value={config.smallMultiples?.showAreaUnderLine}
|
|
402
|
+
fieldName='showAreaUnderLine'
|
|
403
|
+
section='smallMultiples'
|
|
404
|
+
label='Shade Area Under Lines'
|
|
405
|
+
updateField={updateField}
|
|
406
|
+
tooltip={
|
|
407
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
408
|
+
<Tooltip.Target>
|
|
409
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
410
|
+
</Tooltip.Target>
|
|
411
|
+
<Tooltip.Content>
|
|
412
|
+
<p>When checked, each tile chart will display a shaded area underneath the line.</p>
|
|
413
|
+
</Tooltip.Content>
|
|
414
|
+
</Tooltip>
|
|
415
|
+
}
|
|
416
|
+
/>
|
|
417
|
+
)}
|
|
418
|
+
</>
|
|
419
|
+
)}
|
|
420
|
+
</AccordionItemPanel>
|
|
421
|
+
</AccordionItem>
|
|
422
|
+
)}
|
|
423
|
+
</>
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export default PanelSmallMultiples
|
|
@@ -20,6 +20,7 @@ import InputToggle from '@cdc/core/components/inputs/InputToggle'
|
|
|
20
20
|
import { useColorPalette } from '@cdc/core/hooks/useColorPalette'
|
|
21
21
|
import { getCurrentPaletteName } from '@cdc/core/helpers/palettes/utils'
|
|
22
22
|
import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
|
|
23
|
+
import { isCoveDeveloperMode } from '@cdc/core/helpers/queryStringUtils'
|
|
23
24
|
import { ChartContext } from './../../../../types/ChartContext.js'
|
|
24
25
|
|
|
25
26
|
import { useEditorPermissions } from '../../useEditorPermissions.js'
|
|
@@ -28,6 +29,10 @@ import ConfigContext from '../../../../ConfigContext.js'
|
|
|
28
29
|
import { PanelProps } from '../PanelProps'
|
|
29
30
|
import { LineChartConfig } from '../../../../types/ChartConfig'
|
|
30
31
|
import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
|
|
32
|
+
import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
|
|
33
|
+
import { CustomColorsEditor } from '@cdc/core/components/CustomColorsEditor'
|
|
34
|
+
import { getColorScale } from '../../../../helpers/getColorScale'
|
|
35
|
+
import '@cdc/core/styles/v2/components/editor.scss'
|
|
31
36
|
import './panelVisual.styles.css'
|
|
32
37
|
|
|
33
38
|
const PanelVisual: FC<PanelProps> = props => {
|
|
@@ -40,7 +45,6 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
40
45
|
visHasBarBorders,
|
|
41
46
|
visCanAnimate,
|
|
42
47
|
visSupportsNonSequentialPallete,
|
|
43
|
-
headerColors,
|
|
44
48
|
visSupportsTooltipOpacity,
|
|
45
49
|
visSupportsTooltipLines,
|
|
46
50
|
visSupportsBarSpace,
|
|
@@ -86,7 +90,7 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
return (
|
|
89
|
-
<AccordionItem>
|
|
93
|
+
<AccordionItem className='panel-visual'>
|
|
90
94
|
<AccordionItemHeading>
|
|
91
95
|
<AccordionItemButton>Visual</AccordionItemButton>
|
|
92
96
|
</AccordionItemHeading>
|
|
@@ -247,24 +251,7 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
247
251
|
/>
|
|
248
252
|
</>
|
|
249
253
|
)}
|
|
250
|
-
{
|
|
251
|
-
<label className='header'>
|
|
252
|
-
<span className='edit-label'>Header Theme</span>
|
|
253
|
-
<ul className='color-palette'>
|
|
254
|
-
{headerColors.map(palette => (
|
|
255
|
-
<button
|
|
256
|
-
title={palette}
|
|
257
|
-
key={palette}
|
|
258
|
-
onClick={e => {
|
|
259
|
-
e.preventDefault()
|
|
260
|
-
updateConfig({ ...config, theme: palette })
|
|
261
|
-
}}
|
|
262
|
-
className={config.theme === palette ? 'selected ' + palette : palette}
|
|
263
|
-
></button>
|
|
264
|
-
))}
|
|
265
|
-
</ul>
|
|
266
|
-
</label>
|
|
267
|
-
{/* eslint-enable */}
|
|
254
|
+
<HeaderThemeSelector selectedTheme={config.theme} onThemeSelect={theme => updateConfig({ ...config, theme })} />
|
|
268
255
|
{(visSupportsNonSequentialPallete() || visSupportsNonSequentialPallete()) && (
|
|
269
256
|
<>
|
|
270
257
|
<label>
|
|
@@ -332,16 +319,89 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
332
319
|
colorIndices={[2, 4, 6]}
|
|
333
320
|
className='color-palette'
|
|
334
321
|
/>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
322
|
+
|
|
323
|
+
{config.visualizationType !== 'Warming Stripes' && (
|
|
324
|
+
<>
|
|
325
|
+
<span>Colorblind Safe</span>
|
|
326
|
+
<PaletteSelector
|
|
327
|
+
palettes={accessibleColors}
|
|
328
|
+
colorPalettes={colorPalettes}
|
|
329
|
+
config={config}
|
|
330
|
+
onPaletteSelect={handlePaletteSelection}
|
|
331
|
+
selectedPalette={getCurrentPaletteName(config)}
|
|
332
|
+
colorIndices={[2, 3, 5]}
|
|
333
|
+
className='color-palette'
|
|
334
|
+
/>
|
|
335
|
+
</>
|
|
336
|
+
)}
|
|
337
|
+
</>
|
|
338
|
+
)}
|
|
339
|
+
|
|
340
|
+
{isCoveDeveloperMode() && (visSupportsSequentialPallete() || visSupportsNonSequentialPallete()) && (
|
|
341
|
+
<>
|
|
342
|
+
<div className='mt-3'>
|
|
343
|
+
<label className='checkbox'>
|
|
344
|
+
<input
|
|
345
|
+
type='checkbox'
|
|
346
|
+
checked={
|
|
347
|
+
!!(config.general?.palette?.customColorsOrdered || config.general?.palette?.customColors)
|
|
348
|
+
}
|
|
349
|
+
onChange={e => {
|
|
350
|
+
const _state = cloneConfig(config)
|
|
351
|
+
// Ensure palette object exists
|
|
352
|
+
if (!_state.general.palette) {
|
|
353
|
+
_state.general.palette = {}
|
|
354
|
+
}
|
|
355
|
+
if (e.target.checked) {
|
|
356
|
+
// Extract colors from current color scale if runtime data available
|
|
357
|
+
if (config.runtime?.seriesLabelsAll && config.runtime.seriesLabelsAll.length > 0) {
|
|
358
|
+
const colorScale = getColorScale(config)
|
|
359
|
+
const extractedColors = config.runtime.seriesLabelsAll.map((label: string) =>
|
|
360
|
+
colorScale(label)
|
|
361
|
+
)
|
|
362
|
+
_state.general.palette.customColorsOrdered =
|
|
363
|
+
extractedColors.length > 0
|
|
364
|
+
? extractedColors
|
|
365
|
+
: ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
|
|
366
|
+
} else {
|
|
367
|
+
// Fallback to default colors if runtime not available
|
|
368
|
+
_state.general.palette.customColorsOrdered = ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
// Remove custom colors and revert to default palette
|
|
372
|
+
delete _state.general.palette.customColorsOrdered
|
|
373
|
+
delete _state.general.palette.customColors
|
|
374
|
+
// Set default palette if none exists
|
|
375
|
+
if (!_state.general.palette.name) {
|
|
376
|
+
_state.general.palette.name = 'qualitative_standard'
|
|
377
|
+
_state.general.palette.version = '2.0'
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
updateConfig(_state)
|
|
381
|
+
}}
|
|
382
|
+
/>
|
|
383
|
+
Use Custom Colors
|
|
384
|
+
</label>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
{(config.general?.palette?.customColorsOrdered || config.general?.palette?.customColors) && (
|
|
388
|
+
<div className='mt-2'>
|
|
389
|
+
<CustomColorsEditor
|
|
390
|
+
colors={config.general.palette.customColorsOrdered || config.general.palette.customColors}
|
|
391
|
+
onChange={newColors => {
|
|
392
|
+
const _state = cloneConfig(config)
|
|
393
|
+
if (!_state.general.palette) {
|
|
394
|
+
_state.general.palette = {}
|
|
395
|
+
}
|
|
396
|
+
_state.general.palette.customColorsOrdered = newColors
|
|
397
|
+
updateConfig(_state)
|
|
398
|
+
}}
|
|
399
|
+
label='Custom Color Order'
|
|
400
|
+
minColors={1}
|
|
401
|
+
maxColors={20}
|
|
402
|
+
/>
|
|
403
|
+
</div>
|
|
404
|
+
)}
|
|
345
405
|
</>
|
|
346
406
|
)}
|
|
347
407
|
</>
|
|
@@ -443,7 +503,6 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
443
503
|
value={config.dataCutoff}
|
|
444
504
|
type='number'
|
|
445
505
|
fieldName='dataCutoff'
|
|
446
|
-
className='number-narrow'
|
|
447
506
|
label='Data Cutoff'
|
|
448
507
|
updateField={updateField}
|
|
449
508
|
tooltip={
|
|
@@ -462,28 +521,17 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
462
521
|
/>
|
|
463
522
|
</>
|
|
464
523
|
)}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
config.yAxis.labelPlacement !== 'On Bar' && (
|
|
524
|
+
|
|
525
|
+
{(config.orientation !== 'horizontal' || config.visualizationType === 'Combo') &&
|
|
526
|
+
config.visualizationType !== 'Warming Stripes' && (
|
|
469
527
|
<TextField
|
|
528
|
+
value={config.barThickness}
|
|
470
529
|
type='number'
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
label=' Bar Thickness'
|
|
530
|
+
fieldName='barThickness'
|
|
531
|
+
label='Bar Thickness'
|
|
474
532
|
updateField={updateField}
|
|
475
|
-
min={15}
|
|
476
533
|
/>
|
|
477
534
|
)}
|
|
478
|
-
{(config.orientation !== 'horizontal' || config.visualizationType === 'Combo') && (
|
|
479
|
-
<TextField
|
|
480
|
-
value={config.barThickness}
|
|
481
|
-
type='number'
|
|
482
|
-
fieldName='barThickness'
|
|
483
|
-
label='Bar Thickness'
|
|
484
|
-
updateField={updateField}
|
|
485
|
-
/>
|
|
486
|
-
)}
|
|
487
535
|
{visSupportsBarSpace() && (
|
|
488
536
|
<TextField
|
|
489
537
|
type='number'
|
|
@@ -7,6 +7,7 @@ import Visual from './Panel.Visual'
|
|
|
7
7
|
import Sankey from './Panel.Sankey'
|
|
8
8
|
import Annotate from './Panel.Annotate'
|
|
9
9
|
import PatternSettings from './Panel.PatternSettings'
|
|
10
|
+
import SmallMultiples from './Panel.SmallMultiples'
|
|
10
11
|
|
|
11
12
|
const Panels = {
|
|
12
13
|
ForestPlot: ForestPlotSettings,
|
|
@@ -17,7 +18,8 @@ const Panels = {
|
|
|
17
18
|
Visual,
|
|
18
19
|
Sankey,
|
|
19
20
|
Annotate,
|
|
20
|
-
PatternSettings
|
|
21
|
+
PatternSettings,
|
|
22
|
+
SmallMultiples
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export default Panels
|
|
@@ -7,26 +7,6 @@
|
|
|
7
7
|
text-align: left;
|
|
8
8
|
|
|
9
9
|
span {
|
|
10
|
-
display: inline-block;
|
|
11
|
-
float: right;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.edit-block {
|
|
16
|
-
margin-top: 0;
|
|
17
|
-
padding: 1em;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.viewport-overrides {
|
|
22
|
-
button {
|
|
23
|
-
width: 100%;
|
|
24
|
-
padding: 1em;
|
|
25
|
-
margin-top: 1em;
|
|
26
|
-
text-align: left;
|
|
27
|
-
|
|
28
|
-
span {
|
|
29
|
-
display: inline-block;
|
|
30
10
|
float: right;
|
|
31
11
|
}
|
|
32
12
|
}
|