@cdc/chart 4.25.10 → 4.25.11
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 +36258 -34658
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/na.json +913 -0
- package/examples/private/test-data.csv +28 -0
- package/index.html +2 -121
- package/package.json +4 -4
- package/src/CdcChart.tsx +8 -11
- package/src/CdcChartComponent.tsx +256 -87
- 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.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/ChartAnnotation.stories.tsx +6 -3
- package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
- package/src/_stories/ChartEditor.stories.tsx +1 -2
- 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/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 -6
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -4
- package/src/components/BarChart/components/BarChart.Vertical.tsx +3 -2
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushChart.tsx +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +199 -190
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +102 -55
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +75 -21
- 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 +7 -15
- package/src/components/Forecasting/Forecasting.tsx +139 -21
- package/src/components/Legend/Legend.Component.tsx +16 -9
- package/src/components/Legend/helpers/createFormatLabels.tsx +181 -181
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- 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 +75 -80
- package/src/components/Regions/components/Regions.tsx +3 -24
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -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/data/initial-state.js +13 -1
- package/src/helpers/buildForecastPaletteOptions.ts +0 -38
- package/src/helpers/getColorScale.ts +10 -0
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useScales.ts +88 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +60 -15
- package/src/scss/main.scss +1 -80
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +4 -0
- package/src/types/ChartConfig.ts +24 -6
- package/src/types/ChartContext.ts +3 -0
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
|
@@ -2,7 +2,6 @@ import React, { useContext } from 'react'
|
|
|
2
2
|
import ConfigContext from '../../../../ConfigContext'
|
|
3
3
|
|
|
4
4
|
// Core
|
|
5
|
-
import InputSelect from '@cdc/core/components/inputs/InputSelect'
|
|
6
5
|
import Check from '@cdc/core/assets/icon-check.svg'
|
|
7
6
|
import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
8
7
|
import { colorPalettesChartV1, colorPalettesChartV2, sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
@@ -98,12 +97,12 @@ const SeriesDropdownLineType = props => {
|
|
|
98
97
|
})
|
|
99
98
|
|
|
100
99
|
return (
|
|
101
|
-
<
|
|
100
|
+
<Select
|
|
102
101
|
initial='Select an option'
|
|
103
102
|
value={series.lineType ? series.lineType : 'curveLinear'}
|
|
104
103
|
label='Series Line Type'
|
|
105
|
-
|
|
106
|
-
changeLineType(index,
|
|
104
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
105
|
+
changeLineType(index, value)
|
|
107
106
|
}}
|
|
108
107
|
options={options}
|
|
109
108
|
/>
|
|
@@ -118,35 +117,35 @@ const SeriesDropdownSeriesType = props => {
|
|
|
118
117
|
|
|
119
118
|
const getOptions = () => {
|
|
120
119
|
if (config.visualizationType === 'Combo') {
|
|
121
|
-
return
|
|
122
|
-
Bar: 'Bar',
|
|
123
|
-
Line: 'Line',
|
|
124
|
-
'dashed-sm': 'Small Dashed',
|
|
125
|
-
'dashed-md': 'Medium Dashed',
|
|
126
|
-
'dashed-lg': 'Large Dashed',
|
|
127
|
-
'Area Chart': 'Area Chart',
|
|
128
|
-
Forecasting: 'Forecasting'
|
|
129
|
-
|
|
120
|
+
return [
|
|
121
|
+
{ value: 'Bar', label: 'Bar' },
|
|
122
|
+
{ value: 'Line', label: 'Line' },
|
|
123
|
+
{ value: 'dashed-sm', label: 'Small Dashed' },
|
|
124
|
+
{ value: 'dashed-md', label: 'Medium Dashed' },
|
|
125
|
+
{ value: 'dashed-lg', label: 'Large Dashed' },
|
|
126
|
+
{ value: 'Area Chart', label: 'Area Chart' },
|
|
127
|
+
{ value: 'Forecasting', label: 'Forecasting' }
|
|
128
|
+
]
|
|
130
129
|
}
|
|
131
130
|
if (config.visualizationType === 'Line' || config.visualizationType === 'Bump Chart') {
|
|
132
|
-
return
|
|
133
|
-
Line: 'Line',
|
|
134
|
-
'dashed-sm': 'Small Dashed',
|
|
135
|
-
'dashed-md': 'Medium Dashed',
|
|
136
|
-
'dashed-lg': 'Large Dashed'
|
|
137
|
-
|
|
131
|
+
return [
|
|
132
|
+
{ value: 'Line', label: 'Line' },
|
|
133
|
+
{ value: 'dashed-sm', label: 'Small Dashed' },
|
|
134
|
+
{ value: 'dashed-md', label: 'Medium Dashed' },
|
|
135
|
+
{ value: 'dashed-lg', label: 'Large Dashed' }
|
|
136
|
+
]
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
// Allowable changes
|
|
142
141
|
if (!['Line', 'Combo', 'Bump Chart'].includes(config.visualizationType)) return
|
|
143
142
|
return (
|
|
144
|
-
<
|
|
143
|
+
<Select
|
|
145
144
|
initial='Select an option'
|
|
146
145
|
value={series.type}
|
|
147
146
|
label='Series Type'
|
|
148
|
-
|
|
149
|
-
updateSeries(index,
|
|
147
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
148
|
+
updateSeries(index, value, 'type')
|
|
150
149
|
}}
|
|
151
150
|
options={getOptions()}
|
|
152
151
|
/>
|
|
@@ -162,13 +161,13 @@ const SeriesDropdownForecastingStage = props => {
|
|
|
162
161
|
// Only combo charts are allowed to have different options
|
|
163
162
|
|
|
164
163
|
return (
|
|
165
|
-
<
|
|
164
|
+
<Select
|
|
166
165
|
initial='Select an option'
|
|
167
166
|
value={series.stageColumn}
|
|
168
167
|
label='Add Forecasting Stages'
|
|
169
|
-
|
|
168
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
170
169
|
let stageObjects = []
|
|
171
|
-
let tempGroups = new Set(rawData?.map(item => item[
|
|
170
|
+
let tempGroups = new Set(rawData?.map(item => item[value])) // [estimate, forecast, etc.]
|
|
172
171
|
tempGroups = Array.from(tempGroups) // convert set to array
|
|
173
172
|
|
|
174
173
|
tempGroups = tempGroups.filter(group => group !== undefined) // removes undefined
|
|
@@ -176,7 +175,7 @@ const SeriesDropdownForecastingStage = props => {
|
|
|
176
175
|
tempGroups.forEach(group => stageObjects.push({ key: group }))
|
|
177
176
|
|
|
178
177
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
179
|
-
copyOfSeries[index] = { ...copyOfSeries[index], stages: stageObjects, stageColumn:
|
|
178
|
+
copyOfSeries[index] = { ...copyOfSeries[index], stages: stageObjects, stageColumn: value }
|
|
180
179
|
|
|
181
180
|
updateConfig({
|
|
182
181
|
...config,
|
|
@@ -200,19 +199,19 @@ const SeriesDropdownForecastingColumn = props => {
|
|
|
200
199
|
if (!series.stageColumn) return
|
|
201
200
|
|
|
202
201
|
let tempGroups = new Set(rawData.map(item => item[series.stageColumn])) // [estimate, forecast, etc.]
|
|
203
|
-
|
|
202
|
+
let tempGroupsArray = Array.from(tempGroups) // convert set to array
|
|
204
203
|
|
|
205
|
-
|
|
204
|
+
tempGroupsArray = tempGroupsArray.filter(group => group !== undefined) // removes undefined
|
|
206
205
|
|
|
207
206
|
return (
|
|
208
|
-
<
|
|
207
|
+
<Select
|
|
209
208
|
initial='Select an option'
|
|
210
209
|
value={series.stageItem}
|
|
211
210
|
label='Forecasting Item Column'
|
|
212
|
-
|
|
213
|
-
updateSeries(index,
|
|
211
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
212
|
+
updateSeries(index, value, 'stageItem')
|
|
214
213
|
}}
|
|
215
|
-
options={
|
|
214
|
+
options={tempGroupsArray}
|
|
216
215
|
/>
|
|
217
216
|
)
|
|
218
217
|
}
|
|
@@ -229,17 +228,17 @@ const SeriesDropdownAxisPosition = props => {
|
|
|
229
228
|
return
|
|
230
229
|
}
|
|
231
230
|
return (
|
|
232
|
-
<
|
|
231
|
+
<Select
|
|
233
232
|
initial='Select an option'
|
|
234
233
|
value={series.axis ? series.axis : 'Left'}
|
|
235
234
|
label='Series Axis'
|
|
236
|
-
|
|
237
|
-
updateSeries(index,
|
|
238
|
-
}}
|
|
239
|
-
options={{
|
|
240
|
-
['Left']: 'Left',
|
|
241
|
-
['Right']: 'Right'
|
|
235
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
236
|
+
updateSeries(index, value, 'axis')
|
|
242
237
|
}}
|
|
238
|
+
options={[
|
|
239
|
+
{ value: 'Left', label: 'Left' },
|
|
240
|
+
{ value: 'Right', label: 'Right' }
|
|
241
|
+
]}
|
|
243
242
|
/>
|
|
244
243
|
)
|
|
245
244
|
}
|
|
@@ -267,17 +266,23 @@ const SeriesDropdownForecastColor = props => {
|
|
|
267
266
|
|
|
268
267
|
// For dropdown options, only show version-specific palettes
|
|
269
268
|
const processedPalettes = updatePaletteNames(forecastPalettes)
|
|
270
|
-
const
|
|
269
|
+
const paletteOptionsObject = buildForecastPaletteOptions(processedPalettes, paletteVersion)
|
|
270
|
+
|
|
271
|
+
// Convert object to array format for Select component
|
|
272
|
+
const paletteOptions = Object.entries(paletteOptionsObject).map(([value, label]) => ({
|
|
273
|
+
value,
|
|
274
|
+
label
|
|
275
|
+
}))
|
|
271
276
|
|
|
272
277
|
return series?.stages?.map((stage, stageIndex) => (
|
|
273
|
-
<
|
|
278
|
+
<Select
|
|
274
279
|
key={`${stage}--${stageIndex}`}
|
|
275
280
|
initial='Select an option'
|
|
276
281
|
value={config.series?.[index].stages?.[stageIndex].color || 'Select'}
|
|
277
282
|
label={`${stage.key} Series Color`}
|
|
278
|
-
|
|
283
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
279
284
|
if (handleForecastPaletteSelection) {
|
|
280
|
-
handleForecastPaletteSelection(
|
|
285
|
+
handleForecastPaletteSelection(value, index, stageIndex)
|
|
281
286
|
}
|
|
282
287
|
}}
|
|
283
288
|
options={paletteOptions}
|
|
@@ -325,7 +330,7 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
325
330
|
</>
|
|
326
331
|
</AccordionItemHeading>
|
|
327
332
|
<AccordionItemPanel>
|
|
328
|
-
<
|
|
333
|
+
<Select
|
|
329
334
|
initial='Select an option'
|
|
330
335
|
value={
|
|
331
336
|
config.series[index].confidenceIntervals[ciIndex].low
|
|
@@ -333,9 +338,9 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
333
338
|
: 'Select'
|
|
334
339
|
}
|
|
335
340
|
label='Low Confidence Interval'
|
|
336
|
-
|
|
341
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
337
342
|
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
338
|
-
copiedConfidenceArray[ciIndex].low =
|
|
343
|
+
copiedConfidenceArray[ciIndex].low = value
|
|
339
344
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
340
345
|
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: copiedConfidenceArray }
|
|
341
346
|
updateConfig({
|
|
@@ -345,7 +350,7 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
345
350
|
}}
|
|
346
351
|
options={getColumns()}
|
|
347
352
|
/>
|
|
348
|
-
<
|
|
353
|
+
<Select
|
|
349
354
|
initial='Select an option'
|
|
350
355
|
value={
|
|
351
356
|
config.series[index].confidenceIntervals[ciIndex].high
|
|
@@ -353,9 +358,9 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
353
358
|
: 'Select'
|
|
354
359
|
}
|
|
355
360
|
label='High Confidence Interval'
|
|
356
|
-
|
|
361
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
357
362
|
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
358
|
-
copiedConfidenceArray[ciIndex].high =
|
|
363
|
+
copiedConfidenceArray[ciIndex].high = value
|
|
359
364
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
360
365
|
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: copiedConfidenceArray }
|
|
361
366
|
updateConfig({
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { useContext, FC } 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
|
+
|
|
15
|
+
// contexts
|
|
16
|
+
import { ChartContext } from './../../../../types/ChartContext.js'
|
|
17
|
+
import { useEditorPermissions } from '../../useEditorPermissions.js'
|
|
18
|
+
import { useEditorPanelContext } from '../../EditorPanelContext.js'
|
|
19
|
+
import ConfigContext from '../../../../ConfigContext.js'
|
|
20
|
+
import { PanelProps } from '../PanelProps'
|
|
21
|
+
import { getTileKeys } from '../../../../helpers/smallMultiplesHelpers'
|
|
22
|
+
|
|
23
|
+
const PanelSmallMultiples: FC<PanelProps> = props => {
|
|
24
|
+
const { config, rawData, updateConfig } = useContext<ChartContext>(ConfigContext)
|
|
25
|
+
const { updateField } = useEditorPanelContext()
|
|
26
|
+
const { visSupportsSmallMultiples } = useEditorPermissions()
|
|
27
|
+
|
|
28
|
+
const getColumns = (filter = true) => {
|
|
29
|
+
let columns = {}
|
|
30
|
+
rawData?.forEach(row => {
|
|
31
|
+
Object.keys(row).forEach(columnName => (columns[columnName] = true))
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (filter) {
|
|
35
|
+
const { lower, upper } = config.confidenceKeys || {}
|
|
36
|
+
Object.keys(columns).forEach(key => {
|
|
37
|
+
if (
|
|
38
|
+
(config.series && config.series.filter(series => series.dataKey === key).length > 0) ||
|
|
39
|
+
(config.confidenceKeys &&
|
|
40
|
+
Object.keys(config.confidenceKeys).includes(key) &&
|
|
41
|
+
((lower && upper) || lower || upper) &&
|
|
42
|
+
key !== lower &&
|
|
43
|
+
key !== upper)
|
|
44
|
+
) {
|
|
45
|
+
delete columns[key]
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Object.keys(columns)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
{visSupportsSmallMultiples() && (
|
|
56
|
+
<AccordionItem>
|
|
57
|
+
<AccordionItemHeading>
|
|
58
|
+
<AccordionItemButton>Small Multiples</AccordionItemButton>
|
|
59
|
+
</AccordionItemHeading>
|
|
60
|
+
<AccordionItemPanel>
|
|
61
|
+
<Select
|
|
62
|
+
value={config.smallMultiples?.mode || ''}
|
|
63
|
+
fieldName='mode'
|
|
64
|
+
section='smallMultiples'
|
|
65
|
+
label='Tile Mode'
|
|
66
|
+
initial='Select Mode'
|
|
67
|
+
updateField={updateField}
|
|
68
|
+
options={[
|
|
69
|
+
{ label: 'By data series', value: 'by-series' },
|
|
70
|
+
{ label: 'By column values', value: 'by-column' }
|
|
71
|
+
]}
|
|
72
|
+
tooltip={
|
|
73
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
74
|
+
<Tooltip.Target>
|
|
75
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
76
|
+
</Tooltip.Target>
|
|
77
|
+
<Tooltip.Content>
|
|
78
|
+
<p>
|
|
79
|
+
Choose how to create multiple charts. "By Data Series" creates a tile for each configured data
|
|
80
|
+
series. "By Column Values" creates a tile for each unique value in the selected column.
|
|
81
|
+
</p>
|
|
82
|
+
</Tooltip.Content>
|
|
83
|
+
</Tooltip>
|
|
84
|
+
}
|
|
85
|
+
/>
|
|
86
|
+
{config.smallMultiples?.mode === 'by-column' && (
|
|
87
|
+
<Select
|
|
88
|
+
value={config.smallMultiples?.tileColumn || ''}
|
|
89
|
+
fieldName='tileColumn'
|
|
90
|
+
section='smallMultiples'
|
|
91
|
+
label='Tile By Column'
|
|
92
|
+
initial='Select Column'
|
|
93
|
+
updateField={updateField}
|
|
94
|
+
options={getColumns()}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{config.smallMultiples?.mode && (
|
|
99
|
+
<>
|
|
100
|
+
<TextField
|
|
101
|
+
type='number'
|
|
102
|
+
value={config.smallMultiples?.tilesPerRowDesktop}
|
|
103
|
+
section='smallMultiples'
|
|
104
|
+
fieldName='tilesPerRowDesktop'
|
|
105
|
+
label='Tiles Per Row (Desktop)'
|
|
106
|
+
updateField={updateField}
|
|
107
|
+
min={1}
|
|
108
|
+
max={3}
|
|
109
|
+
tooltip={
|
|
110
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
111
|
+
<Tooltip.Target>
|
|
112
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
113
|
+
</Tooltip.Target>
|
|
114
|
+
<Tooltip.Content>
|
|
115
|
+
<p>
|
|
116
|
+
Number of chart tiles to display per row on desktop screens. Mobile will always show 1 tile
|
|
117
|
+
per row.
|
|
118
|
+
</p>
|
|
119
|
+
</Tooltip.Content>
|
|
120
|
+
</Tooltip>
|
|
121
|
+
}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
{/* Tile Ordering */}
|
|
125
|
+
{(() => {
|
|
126
|
+
const availableTiles = getTileKeys(config, rawData)
|
|
127
|
+
if (availableTiles.length === 0) return null
|
|
128
|
+
|
|
129
|
+
const tileOrderOptions = [
|
|
130
|
+
{
|
|
131
|
+
label: 'Ascending By Title',
|
|
132
|
+
value: 'asc'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: 'Descending By Title',
|
|
136
|
+
value: 'desc'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
label: 'Custom',
|
|
140
|
+
value: 'custom'
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
const currentOrderType = config.smallMultiples?.tileOrderType || 'asc'
|
|
145
|
+
|
|
146
|
+
const handleOrderTypeChange = orderType => {
|
|
147
|
+
const newConfig = {
|
|
148
|
+
...config,
|
|
149
|
+
smallMultiples: {
|
|
150
|
+
...config.smallMultiples,
|
|
151
|
+
tileOrderType: orderType
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// If switching to custom, initialize with current tile order
|
|
156
|
+
if (orderType === 'custom' && !config.smallMultiples?.tileOrder?.length) {
|
|
157
|
+
newConfig.smallMultiples.tileOrder = [...availableTiles]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
updateConfig(newConfig)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const handleCustomTileOrderChange = (sourceIndex, destinationIndex) => {
|
|
164
|
+
if (destinationIndex === null) return
|
|
165
|
+
|
|
166
|
+
const currentOrder = config.smallMultiples?.tileOrder || [...availableTiles]
|
|
167
|
+
const newOrder = [...currentOrder]
|
|
168
|
+
const [removed] = newOrder.splice(sourceIndex, 1)
|
|
169
|
+
newOrder.splice(destinationIndex, 0, removed)
|
|
170
|
+
|
|
171
|
+
updateConfig({
|
|
172
|
+
...config,
|
|
173
|
+
smallMultiples: {
|
|
174
|
+
...config.smallMultiples,
|
|
175
|
+
tileOrder: newOrder,
|
|
176
|
+
tileOrderType: 'custom'
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<>
|
|
183
|
+
<Select
|
|
184
|
+
value={currentOrderType}
|
|
185
|
+
options={tileOrderOptions}
|
|
186
|
+
label='Tile Order'
|
|
187
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
188
|
+
handleOrderTypeChange(value)
|
|
189
|
+
}}
|
|
190
|
+
/>
|
|
191
|
+
|
|
192
|
+
{currentOrderType === 'custom' && (
|
|
193
|
+
<DragDropContext
|
|
194
|
+
onDragEnd={({ source, destination }) =>
|
|
195
|
+
handleCustomTileOrderChange(source.index, destination?.index)
|
|
196
|
+
}
|
|
197
|
+
>
|
|
198
|
+
<Droppable droppableId='tile_order'>
|
|
199
|
+
{provided => (
|
|
200
|
+
<ul
|
|
201
|
+
{...provided.droppableProps}
|
|
202
|
+
className='sort-list'
|
|
203
|
+
ref={provided.innerRef}
|
|
204
|
+
style={{ marginTop: '1em' }}
|
|
205
|
+
>
|
|
206
|
+
{(config.smallMultiples?.tileOrder || availableTiles).map((tileKey, index) => (
|
|
207
|
+
<Draggable key={tileKey} draggableId={`tile-${tileKey}`} index={index}>
|
|
208
|
+
{(provided, snapshot) => (
|
|
209
|
+
<li>
|
|
210
|
+
<div
|
|
211
|
+
className={snapshot.isDragging ? 'currently-dragging' : ''}
|
|
212
|
+
style={provided.draggableProps.style}
|
|
213
|
+
ref={provided.innerRef}
|
|
214
|
+
{...provided.draggableProps}
|
|
215
|
+
{...provided.dragHandleProps}
|
|
216
|
+
>
|
|
217
|
+
{tileKey}
|
|
218
|
+
</div>
|
|
219
|
+
</li>
|
|
220
|
+
)}
|
|
221
|
+
</Draggable>
|
|
222
|
+
))}
|
|
223
|
+
{provided.placeholder}
|
|
224
|
+
</ul>
|
|
225
|
+
)}
|
|
226
|
+
</Droppable>
|
|
227
|
+
</DragDropContext>
|
|
228
|
+
)}
|
|
229
|
+
</>
|
|
230
|
+
)
|
|
231
|
+
})()}
|
|
232
|
+
|
|
233
|
+
{/* Color Mode */}
|
|
234
|
+
<Select
|
|
235
|
+
value={config.smallMultiples?.colorMode || 'different'}
|
|
236
|
+
options={[
|
|
237
|
+
{
|
|
238
|
+
label: 'Same Color',
|
|
239
|
+
value: 'same'
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
label: 'Different Colors',
|
|
243
|
+
value: 'different'
|
|
244
|
+
}
|
|
245
|
+
]}
|
|
246
|
+
label='Color Mode'
|
|
247
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
248
|
+
updateConfig({
|
|
249
|
+
...config,
|
|
250
|
+
smallMultiples: {
|
|
251
|
+
...config.smallMultiples,
|
|
252
|
+
colorMode: value
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
}}
|
|
256
|
+
tooltip={
|
|
257
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
258
|
+
<Tooltip.Target>
|
|
259
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
260
|
+
</Tooltip.Target>
|
|
261
|
+
<Tooltip.Content>
|
|
262
|
+
<p>
|
|
263
|
+
When "Different Colors" is selected, each tile will use the next color in the configured color
|
|
264
|
+
palette.
|
|
265
|
+
</p>
|
|
266
|
+
</Tooltip.Content>
|
|
267
|
+
</Tooltip>
|
|
268
|
+
}
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
{/* Custom Tile Titles - only show for by-column mode */}
|
|
272
|
+
{config.smallMultiples?.mode === 'by-column' && (
|
|
273
|
+
<div>
|
|
274
|
+
<label style={{ marginTop: '1.5rem', marginBottom: '0.5rem' }}>Custom Tile Titles</label>
|
|
275
|
+
|
|
276
|
+
{(() => {
|
|
277
|
+
const availableTiles = getTileKeys(config, rawData)
|
|
278
|
+
if (availableTiles.length === 0) return null
|
|
279
|
+
|
|
280
|
+
const handleTitleChange = (tileKey, customTitle) => {
|
|
281
|
+
const newTitles = { ...config.smallMultiples?.tileTitles }
|
|
282
|
+
if (customTitle.trim() === '' || customTitle === tileKey) {
|
|
283
|
+
delete newTitles[tileKey] // Remove entry if empty or same as key
|
|
284
|
+
} else {
|
|
285
|
+
newTitles[tileKey] = customTitle
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
updateConfig({
|
|
289
|
+
...config,
|
|
290
|
+
smallMultiples: {
|
|
291
|
+
...config.smallMultiples,
|
|
292
|
+
tileTitles: newTitles
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<div className='tile-titles-editor' style={{ maxWidth: '100%', overflow: 'hidden' }}>
|
|
299
|
+
{availableTiles.map(tileKey => {
|
|
300
|
+
const customTitle = config.smallMultiples?.tileTitles?.[tileKey] || ''
|
|
301
|
+
return (
|
|
302
|
+
<div
|
|
303
|
+
key={tileKey}
|
|
304
|
+
className='tile-title-row'
|
|
305
|
+
style={{
|
|
306
|
+
display: 'flex',
|
|
307
|
+
alignItems: 'center',
|
|
308
|
+
marginBottom: '0.75rem',
|
|
309
|
+
maxWidth: '100%'
|
|
310
|
+
}}
|
|
311
|
+
>
|
|
312
|
+
<label
|
|
313
|
+
style={{
|
|
314
|
+
minWidth: '80px',
|
|
315
|
+
maxWidth: '120px',
|
|
316
|
+
marginRight: '0.75rem',
|
|
317
|
+
fontWeight: 'normal',
|
|
318
|
+
fontSize: '13px',
|
|
319
|
+
overflow: 'hidden',
|
|
320
|
+
textOverflow: 'ellipsis',
|
|
321
|
+
whiteSpace: 'nowrap',
|
|
322
|
+
flexShrink: 0
|
|
323
|
+
}}
|
|
324
|
+
>
|
|
325
|
+
{tileKey}:
|
|
326
|
+
</label>
|
|
327
|
+
<input
|
|
328
|
+
type='text'
|
|
329
|
+
value={customTitle}
|
|
330
|
+
placeholder={tileKey}
|
|
331
|
+
onChange={event => handleTitleChange(tileKey, event.target.value)}
|
|
332
|
+
style={{
|
|
333
|
+
flex: 1,
|
|
334
|
+
minWidth: 0,
|
|
335
|
+
maxWidth: '200px',
|
|
336
|
+
fontSize: '13px',
|
|
337
|
+
padding: '4px 8px',
|
|
338
|
+
height: '30px',
|
|
339
|
+
border: '1px solid #ccc',
|
|
340
|
+
borderRadius: '3px'
|
|
341
|
+
}}
|
|
342
|
+
/>
|
|
343
|
+
</div>
|
|
344
|
+
)
|
|
345
|
+
})}
|
|
346
|
+
</div>
|
|
347
|
+
)
|
|
348
|
+
})()}
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
|
|
352
|
+
<CheckBox
|
|
353
|
+
value={config.smallMultiples?.independentYAxis}
|
|
354
|
+
section='smallMultiples'
|
|
355
|
+
fieldName='independentYAxis'
|
|
356
|
+
label='Independent Y-Axis Scales'
|
|
357
|
+
updateField={updateField}
|
|
358
|
+
tooltip={
|
|
359
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
360
|
+
<Tooltip.Target>
|
|
361
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
362
|
+
</Tooltip.Target>
|
|
363
|
+
<Tooltip.Content>
|
|
364
|
+
<p>
|
|
365
|
+
When checked, the y-axis scale for each tile will be calculated separately. The chart's y-axis
|
|
366
|
+
min/max will override this setting if they are configured.
|
|
367
|
+
</p>
|
|
368
|
+
</Tooltip.Content>
|
|
369
|
+
</Tooltip>
|
|
370
|
+
}
|
|
371
|
+
/>
|
|
372
|
+
|
|
373
|
+
<CheckBox
|
|
374
|
+
value={config.smallMultiples?.synchronizedTooltips}
|
|
375
|
+
fieldName='synchronizedTooltips'
|
|
376
|
+
section='smallMultiples'
|
|
377
|
+
label='Synchronized Tooltips'
|
|
378
|
+
updateField={updateField}
|
|
379
|
+
tooltip={
|
|
380
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
381
|
+
<Tooltip.Target>
|
|
382
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
383
|
+
</Tooltip.Target>
|
|
384
|
+
<Tooltip.Content>
|
|
385
|
+
<p>
|
|
386
|
+
When checked, hovering over one chart will show synchronized tooltips on all other charts at
|
|
387
|
+
the same data point.
|
|
388
|
+
</p>
|
|
389
|
+
</Tooltip.Content>
|
|
390
|
+
</Tooltip>
|
|
391
|
+
}
|
|
392
|
+
/>
|
|
393
|
+
|
|
394
|
+
{config.visualizationType === 'Line' && (
|
|
395
|
+
<CheckBox
|
|
396
|
+
value={config.smallMultiples?.showAreaUnderLine}
|
|
397
|
+
fieldName='showAreaUnderLine'
|
|
398
|
+
section='smallMultiples'
|
|
399
|
+
label='Shade Area Under Lines'
|
|
400
|
+
updateField={updateField}
|
|
401
|
+
tooltip={
|
|
402
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
403
|
+
<Tooltip.Target>
|
|
404
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
405
|
+
</Tooltip.Target>
|
|
406
|
+
<Tooltip.Content>
|
|
407
|
+
<p>When checked, each tile chart will display a shaded area underneath the line.</p>
|
|
408
|
+
</Tooltip.Content>
|
|
409
|
+
</Tooltip>
|
|
410
|
+
}
|
|
411
|
+
/>
|
|
412
|
+
)}
|
|
413
|
+
</>
|
|
414
|
+
)}
|
|
415
|
+
</AccordionItemPanel>
|
|
416
|
+
</AccordionItem>
|
|
417
|
+
)}
|
|
418
|
+
</>
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export default PanelSmallMultiples
|