@cdc/chart 4.24.10 → 4.24.12-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +35019 -34301
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +126 -14
- package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
- package/examples/private/DEV-8850-2.json +493 -0
- package/examples/private/DEV-9822.json +574 -0
- package/examples/private/DEV-9840.json +553 -0
- package/examples/private/DEV-9850-3.json +461 -0
- package/examples/private/chart.json +1084 -0
- package/examples/private/ci_formatted.json +202 -0
- package/examples/private/ci_issue.json +3016 -0
- package/examples/private/completed.json +634 -0
- package/examples/private/dem-data-long.csv +20 -0
- package/examples/private/dem-data-long.json +36 -0
- package/examples/private/demographic_data.csv +157 -0
- package/examples/private/demographic_data.json +2654 -0
- package/examples/private/demographic_dynamic.json +443 -0
- package/examples/private/demographic_standard.json +560 -0
- package/examples/private/ehdi.json +29939 -0
- package/examples/private/test.json +493 -0
- package/index.html +10 -7
- package/package.json +2 -2
- package/src/CdcChart.tsx +132 -152
- package/src/_stories/Chart.Anchors.stories.tsx +31 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +37 -6
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
- package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
- package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/short_dates.json +288 -0
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
- package/src/components/BarChart/helpers/getBarData.ts +28 -0
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
- package/src/components/BoxPlot/BoxPlot.tsx +131 -0
- package/src/components/BoxPlot/helpers/index.ts +54 -0
- package/src/components/BrushChart.tsx +23 -26
- package/src/components/EditorPanel/EditorPanel.tsx +117 -139
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +122 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
- package/src/components/Legend/Legend.Component.tsx +11 -12
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
- package/src/components/Legend/helpers/index.ts +2 -1
- package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
- package/src/components/LineChart/helpers.ts +49 -43
- package/src/components/LineChart/index.tsx +135 -83
- package/src/components/LinearChart.tsx +196 -181
- package/src/components/PieChart/PieChart.tsx +7 -1
- package/src/components/Sankey/components/ColumnList.tsx +19 -0
- package/src/components/Sankey/components/Sankey.tsx +479 -0
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
- package/src/components/Sankey/index.tsx +1 -492
- package/src/components/Sankey/sankey.scss +22 -21
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +7 -12
- package/src/helpers/countNumOfTicks.ts +57 -0
- package/src/helpers/getQuartiles.ts +15 -18
- package/src/hooks/useMinMax.ts +44 -16
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +90 -35
- package/src/hooks/useTooltip.tsx +59 -50
- package/src/scss/DataTable.scss +5 -0
- package/src/scss/main.scss +6 -20
- package/src/types/ChartConfig.ts +6 -19
- package/src/types/ChartContext.ts +4 -1
- package/src/types/ForestPlot.ts +8 -0
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
- package/src/hooks/useLegendClasses.ts +0 -72
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { memo, useContext } from 'react'
|
|
2
2
|
import { useEditorPermissions } from '../../useEditorPermissions.js'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AccordionItem,
|
|
5
|
+
AccordionItemHeading,
|
|
6
|
+
AccordionItemPanel,
|
|
7
|
+
AccordionItemButton
|
|
8
|
+
} from 'react-accessible-accordion'
|
|
4
9
|
import { type ChartConfig } from '../../../../types/ChartConfig.js'
|
|
5
10
|
import { TextField, Select } from '@cdc/core/components/EditorPanel/Inputs'
|
|
6
11
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
@@ -58,7 +63,7 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
|
|
|
58
63
|
<div className='edit-block' key={`region-${i}`}>
|
|
59
64
|
<button
|
|
60
65
|
type='button'
|
|
61
|
-
className='remove-column'
|
|
66
|
+
className='btn btn-danger remove-column'
|
|
62
67
|
onClick={event => {
|
|
63
68
|
event.preventDefault()
|
|
64
69
|
removeColumn(i)
|
|
@@ -68,8 +73,18 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
|
|
|
68
73
|
</button>
|
|
69
74
|
<TextField value={label} label='Region Label' fieldName='label' i={i} updateField={updateField} />
|
|
70
75
|
<div className='two-col-inputs'>
|
|
71
|
-
<TextField
|
|
72
|
-
|
|
76
|
+
<TextField
|
|
77
|
+
value={color}
|
|
78
|
+
label='Text Color'
|
|
79
|
+
fieldName='color'
|
|
80
|
+
updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
|
|
81
|
+
/>
|
|
82
|
+
<TextField
|
|
83
|
+
value={background}
|
|
84
|
+
label='Background'
|
|
85
|
+
fieldName='background'
|
|
86
|
+
updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
|
|
87
|
+
/>
|
|
73
88
|
</div>
|
|
74
89
|
|
|
75
90
|
<Select
|
|
@@ -91,11 +106,17 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
|
|
|
91
106
|
options={fromOptions}
|
|
92
107
|
/>
|
|
93
108
|
|
|
94
|
-
{(config.regions[i].fromType === 'Fixed' ||
|
|
109
|
+
{(config.regions[i].fromType === 'Fixed' ||
|
|
110
|
+
config.regions[i].fromType === 'Previous Days' ||
|
|
111
|
+
!config.regions[i].fromType) && (
|
|
95
112
|
<>
|
|
96
113
|
<TextField
|
|
97
114
|
value={from}
|
|
98
|
-
label={
|
|
115
|
+
label={
|
|
116
|
+
config.regions[i].fromType === 'Fixed' || !config.regions[i]?.fromType
|
|
117
|
+
? 'From Value'
|
|
118
|
+
: 'Previous Number of Days'
|
|
119
|
+
}
|
|
99
120
|
fieldName='from'
|
|
100
121
|
updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
|
|
101
122
|
tooltip={
|
|
@@ -104,7 +125,10 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
|
|
|
104
125
|
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
105
126
|
</Tooltip.Target>
|
|
106
127
|
<Tooltip.Content>
|
|
107
|
-
<p>
|
|
128
|
+
<p>
|
|
129
|
+
When using categorical (linear scale) match the data set value. When using date (linear / date
|
|
130
|
+
time scale) match the x-axis value.
|
|
131
|
+
</p>
|
|
108
132
|
</Tooltip.Content>
|
|
109
133
|
</Tooltip>
|
|
110
134
|
}
|
|
@@ -131,13 +155,20 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
|
|
|
131
155
|
options={toOptions}
|
|
132
156
|
/>
|
|
133
157
|
|
|
134
|
-
{(config.regions[i].toType === 'Fixed' || !config.regions[i].toType) &&
|
|
158
|
+
{(config.regions[i].toType === 'Fixed' || !config.regions[i].toType) && (
|
|
159
|
+
<TextField
|
|
160
|
+
value={to}
|
|
161
|
+
label='To Value'
|
|
162
|
+
fieldName='to'
|
|
163
|
+
updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
135
166
|
</div>
|
|
136
167
|
))}
|
|
137
168
|
{!config.regions && <p style={{ textAlign: 'center' }}>There are currently no regions.</p>}
|
|
138
169
|
<button
|
|
139
170
|
type='button'
|
|
140
|
-
className='btn full-width'
|
|
171
|
+
className='btn btn-primary full-width'
|
|
141
172
|
onClick={e => {
|
|
142
173
|
e.preventDefault()
|
|
143
174
|
addColumn()
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from 'react-accessible-accordion'
|
|
12
12
|
import EditorPanelContext, { type EditorPanelContext as EPContext } from '../../EditorPanelContext'
|
|
13
13
|
|
|
14
|
-
const SankeySettings =
|
|
14
|
+
const SankeySettings: React.FC<PanelProps> = props => {
|
|
15
15
|
const { config, updateConfig } = useContext(ConfigContext)
|
|
16
16
|
const data = config.data?.[0]
|
|
17
17
|
const { updateField } = useContext<EPContext>(EditorPanelContext)
|
|
@@ -109,7 +109,7 @@ const SankeySettings = () => {
|
|
|
109
109
|
onChange={e => updateStoryNode('segmentTextAfter', e.target.value, i)}
|
|
110
110
|
/>
|
|
111
111
|
</label>
|
|
112
|
-
<Button onClick={e => removeStoryNode(i)} className='btn
|
|
112
|
+
<Button onClick={e => removeStoryNode(i)} className='btn btn-danger full-width'>
|
|
113
113
|
Remove Story Node
|
|
114
114
|
</Button>
|
|
115
115
|
</div>
|
|
@@ -117,7 +117,7 @@ const SankeySettings = () => {
|
|
|
117
117
|
{data?.storyNodeText?.length < 3 && (
|
|
118
118
|
<button
|
|
119
119
|
type='button'
|
|
120
|
-
className='btn full-width'
|
|
120
|
+
className='btn btn-primary full-width'
|
|
121
121
|
onClick={e => {
|
|
122
122
|
e.preventDefault()
|
|
123
123
|
addStoryNode()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext } from 'react'
|
|
1
|
+
import React, { useContext, useMemo } from 'react'
|
|
2
2
|
import ConfigContext from '../../../../ConfigContext'
|
|
3
3
|
|
|
4
4
|
// Core
|
|
@@ -7,10 +7,18 @@ import Check from '@cdc/core/assets/icon-check.svg'
|
|
|
7
7
|
import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
8
8
|
import { sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
9
9
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
10
|
+
import { Select } from '@cdc/core/components/EditorPanel/Inputs'
|
|
10
11
|
|
|
11
12
|
// Third Party
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
Accordion,
|
|
15
|
+
AccordionItem,
|
|
16
|
+
AccordionItemHeading,
|
|
17
|
+
AccordionItemPanel,
|
|
18
|
+
AccordionItemButton
|
|
19
|
+
} from 'react-accessible-accordion'
|
|
13
20
|
import { Draggable } from '@hello-pangea/dnd'
|
|
21
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
14
22
|
|
|
15
23
|
const SeriesContext = React.createContext({})
|
|
16
24
|
|
|
@@ -48,7 +56,11 @@ const SeriesWrapper = props => {
|
|
|
48
56
|
updateConfig({ ...config, series })
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
return
|
|
59
|
+
return (
|
|
60
|
+
<SeriesContext.Provider value={{ updateSeries, supportedRightAxisTypes, getColumns, selectComponent }}>
|
|
61
|
+
{props.children}
|
|
62
|
+
</SeriesContext.Provider>
|
|
63
|
+
)
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
const SeriesDropdownLineType = props => {
|
|
@@ -245,7 +257,9 @@ const SeriesDropdownForecastColor = props => {
|
|
|
245
257
|
<InputSelect
|
|
246
258
|
key={`${stage}--${stageIndex}`}
|
|
247
259
|
initial='Select an option'
|
|
248
|
-
value={
|
|
260
|
+
value={
|
|
261
|
+
config.series?.[index].stages?.[stageIndex].color ? config.series?.[index].stages?.[stageIndex].color : 'Select'
|
|
262
|
+
}
|
|
249
263
|
label={`${stage.key} Series Color`}
|
|
250
264
|
onChange={event => {
|
|
251
265
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
@@ -315,55 +329,33 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
315
329
|
<AccordionItemPanel>
|
|
316
330
|
<div className='input-group'>
|
|
317
331
|
<label htmlFor='showInTooltip'>Show In Tooltip</label>
|
|
318
|
-
<div
|
|
319
|
-
|
|
332
|
+
<div
|
|
333
|
+
className={'cove-input__checkbox--small'}
|
|
334
|
+
onClick={e => updateShowInTooltip(e, index, ciIndex)}
|
|
335
|
+
>
|
|
336
|
+
<div
|
|
337
|
+
className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`}
|
|
338
|
+
style={{ backgroundColor: '' }}
|
|
339
|
+
>
|
|
320
340
|
{showInTooltip && <Check className='' style={{ fill: '#025eaa' }} />}
|
|
321
341
|
</div>
|
|
322
|
-
<input
|
|
342
|
+
<input
|
|
343
|
+
className='cove-input--hidden'
|
|
344
|
+
type='checkbox'
|
|
345
|
+
name={'showInTooltip'}
|
|
346
|
+
checked={showInTooltip ? showInTooltip : false}
|
|
347
|
+
readOnly
|
|
348
|
+
/>
|
|
323
349
|
</div>
|
|
324
350
|
</div>
|
|
325
351
|
|
|
326
|
-
{/* <label>
|
|
327
|
-
High Label
|
|
328
|
-
<input
|
|
329
|
-
type='text'
|
|
330
|
-
key={`series-ci-high-label-${index}`}
|
|
331
|
-
value={series.confidenceIntervals[index]?.highLabel ? series.confidenceIntervals[index]?.highLabel : ''}
|
|
332
|
-
onChange={e => {
|
|
333
|
-
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
334
|
-
copiedConfidenceArray[ciIndex].highLabel = e.target.value
|
|
335
|
-
const copyOfSeries = [...config.series] // copy the entire series array
|
|
336
|
-
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: copiedConfidenceArray }
|
|
337
|
-
updateConfig({
|
|
338
|
-
...config,
|
|
339
|
-
series: copyOfSeries
|
|
340
|
-
})
|
|
341
|
-
}}
|
|
342
|
-
/>
|
|
343
|
-
</label> */}
|
|
344
|
-
|
|
345
|
-
{/* <label>
|
|
346
|
-
Low label
|
|
347
|
-
<input
|
|
348
|
-
type='text'
|
|
349
|
-
key={`series-ci-high-label-${index}`}
|
|
350
|
-
value={series.confidenceIntervals[index]?.lowLabel ? series.confidenceIntervals[index]?.lowLabel : ''}
|
|
351
|
-
onChange={e => {
|
|
352
|
-
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
353
|
-
copiedConfidenceArray[ciIndex].lowLabel = e.target.value
|
|
354
|
-
const copyOfSeries = [...config.series] // copy the entire series array
|
|
355
|
-
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: copiedConfidenceArray }
|
|
356
|
-
updateConfig({
|
|
357
|
-
...config,
|
|
358
|
-
series: copyOfSeries
|
|
359
|
-
})
|
|
360
|
-
}}
|
|
361
|
-
/>
|
|
362
|
-
</label> */}
|
|
363
|
-
|
|
364
352
|
<InputSelect
|
|
365
353
|
initial='Select an option'
|
|
366
|
-
value={
|
|
354
|
+
value={
|
|
355
|
+
config.series[index].confidenceIntervals[ciIndex].low
|
|
356
|
+
? config.series[index].confidenceIntervals[ciIndex].low
|
|
357
|
+
: 'Select'
|
|
358
|
+
}
|
|
367
359
|
label='Low Confidence Interval'
|
|
368
360
|
onChange={e => {
|
|
369
361
|
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
@@ -379,7 +371,11 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
379
371
|
/>
|
|
380
372
|
<InputSelect
|
|
381
373
|
initial='Select an option'
|
|
382
|
-
value={
|
|
374
|
+
value={
|
|
375
|
+
config.series[index].confidenceIntervals[ciIndex].high
|
|
376
|
+
? config.series[index].confidenceIntervals[ciIndex].high
|
|
377
|
+
: 'Select'
|
|
378
|
+
}
|
|
383
379
|
label='High Confidence Interval'
|
|
384
380
|
onChange={e => {
|
|
385
381
|
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
@@ -399,7 +395,7 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
399
395
|
})}
|
|
400
396
|
</Accordion>
|
|
401
397
|
<button
|
|
402
|
-
className='btn full-width'
|
|
398
|
+
className='btn btn-primary full-width'
|
|
403
399
|
onClick={e => {
|
|
404
400
|
e.preventDefault()
|
|
405
401
|
let copiedIndex = null
|
|
@@ -409,7 +405,10 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
409
405
|
copiedIndex = []
|
|
410
406
|
}
|
|
411
407
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
412
|
-
copyOfSeries[index] = {
|
|
408
|
+
copyOfSeries[index] = {
|
|
409
|
+
...copyOfSeries[index],
|
|
410
|
+
confidenceIntervals: [...copiedIndex, { high: '', low: '' }]
|
|
411
|
+
} // update the nested array
|
|
413
412
|
updateConfig({
|
|
414
413
|
...config,
|
|
415
414
|
series: copyOfSeries
|
|
@@ -468,7 +467,19 @@ const SeriesInputWeight = props => {
|
|
|
468
467
|
const SeriesInputName = props => {
|
|
469
468
|
const { series, index: i } = props
|
|
470
469
|
const { config, updateConfig } = useContext(ConfigContext)
|
|
471
|
-
const adjustableNameSeriesTypes = [
|
|
470
|
+
const adjustableNameSeriesTypes = [
|
|
471
|
+
'Bump Chart',
|
|
472
|
+
'Bar',
|
|
473
|
+
'Line',
|
|
474
|
+
'Area Chart',
|
|
475
|
+
'Combo',
|
|
476
|
+
'Deviation',
|
|
477
|
+
'Paired',
|
|
478
|
+
'Scatter',
|
|
479
|
+
'dashed-sm',
|
|
480
|
+
'dashed-md',
|
|
481
|
+
'dashed-lg'
|
|
482
|
+
]
|
|
472
483
|
|
|
473
484
|
if (!adjustableNameSeriesTypes.includes(series.type)) return
|
|
474
485
|
|
|
@@ -532,7 +543,13 @@ const SeriesDisplayInTooltip = props => {
|
|
|
532
543
|
<div className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`} style={{ backgroundColor: '' }}>
|
|
533
544
|
{series.tooltip && <Check className='' style={{ fill: '#025eaa' }} />}
|
|
534
545
|
</div>
|
|
535
|
-
<input
|
|
546
|
+
<input
|
|
547
|
+
className='cove-input--hidden'
|
|
548
|
+
type='checkbox'
|
|
549
|
+
name={`series-tooltip--${index}`}
|
|
550
|
+
checked={series.tooltip ? series.tooltip : false}
|
|
551
|
+
readOnly
|
|
552
|
+
/>
|
|
536
553
|
</div>
|
|
537
554
|
</div>
|
|
538
555
|
</>
|
|
@@ -591,17 +608,33 @@ const SeriesButtonRemove = props => {
|
|
|
591
608
|
|
|
592
609
|
const SeriesItem = props => {
|
|
593
610
|
const { config } = useContext(ConfigContext)
|
|
594
|
-
|
|
611
|
+
const { updateSeries, getColumns } = useContext(SeriesContext)
|
|
595
612
|
const { series, getItemStyle, sortableItemStyles, chartsWithOptions, index: i } = props
|
|
596
|
-
|
|
613
|
+
const showDynamicCategory =
|
|
614
|
+
['Bar', 'Line'].includes(config.visualizationType) &&
|
|
615
|
+
config.visualizationSubType !== 'Stacked' &&
|
|
616
|
+
!config.series.find(s => s.dynamicCategory && s.dataKey !== series.dataKey)
|
|
597
617
|
return (
|
|
598
618
|
<Draggable key={series.dataKey} draggableId={`draggableFilter-${series.dataKey}`} index={i}>
|
|
599
619
|
{(provided, snapshot) => (
|
|
600
|
-
<div
|
|
620
|
+
<div
|
|
621
|
+
key={i}
|
|
622
|
+
className={snapshot.isDragging ? 'currently-dragging' : ''}
|
|
623
|
+
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style, sortableItemStyles)}
|
|
624
|
+
ref={provided.innerRef}
|
|
625
|
+
{...provided.draggableProps}
|
|
626
|
+
{...provided.dragHandleProps}
|
|
627
|
+
>
|
|
601
628
|
<Accordion allowZeroExpanded>
|
|
602
629
|
<AccordionItem className='series-item series-item--chart'>
|
|
603
630
|
<AccordionItemHeading className='series-item__title'>
|
|
604
|
-
<AccordionItemButton
|
|
631
|
+
<AccordionItemButton
|
|
632
|
+
className={
|
|
633
|
+
chartsWithOptions.includes(config.visualizationType)
|
|
634
|
+
? 'accordion__button'
|
|
635
|
+
: 'accordion__button hide-arrow'
|
|
636
|
+
}
|
|
637
|
+
>
|
|
605
638
|
<Icon display='move' size={15} style={{ cursor: 'default' }} />
|
|
606
639
|
{series.dataKey}
|
|
607
640
|
<Series.Button.Remove series={series} index={i} />
|
|
@@ -610,6 +643,30 @@ const SeriesItem = props => {
|
|
|
610
643
|
{chartsWithOptions.includes(config.visualizationType) && (
|
|
611
644
|
<AccordionItemPanel>
|
|
612
645
|
<Series.Input.Name series={series} index={i} />
|
|
646
|
+
{showDynamicCategory && (
|
|
647
|
+
<Select
|
|
648
|
+
label='Dynamic Category'
|
|
649
|
+
value={series.dynamicCategory}
|
|
650
|
+
options={['- Select - ', ...getColumns().filter(col => series.dataKey !== col)]}
|
|
651
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
652
|
+
if (value === '- Select -') value = ''
|
|
653
|
+
updateSeries(i, value, 'dynamicCategory')
|
|
654
|
+
}}
|
|
655
|
+
tooltip={
|
|
656
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
657
|
+
<Tooltip.Target>
|
|
658
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
659
|
+
</Tooltip.Target>
|
|
660
|
+
<Tooltip.Content>
|
|
661
|
+
<p>
|
|
662
|
+
This field is Optional. If you have a dynamic data series you can select the category
|
|
663
|
+
field here. You can only add one dynamic category per visualization.
|
|
664
|
+
</p>
|
|
665
|
+
</Tooltip.Content>
|
|
666
|
+
</Tooltip>
|
|
667
|
+
}
|
|
668
|
+
/>
|
|
669
|
+
)}
|
|
613
670
|
<Series.Input.Weight series={series} index={i} />
|
|
614
671
|
<Series.Dropdown.SeriesType series={series} index={i} />
|
|
615
672
|
<Series.Dropdown.AxisPosition series={series} index={i} />
|
|
@@ -630,7 +687,16 @@ const SeriesItem = props => {
|
|
|
630
687
|
const SeriesList = props => {
|
|
631
688
|
const { series, getItemStyle, sortableItemStyles, chartsWithOptions } = props
|
|
632
689
|
return series.map((series, i) => {
|
|
633
|
-
return
|
|
690
|
+
return (
|
|
691
|
+
<SeriesItem
|
|
692
|
+
getItemStyle={getItemStyle}
|
|
693
|
+
sortableItemStyles={sortableItemStyles}
|
|
694
|
+
chartsWithOptions={chartsWithOptions}
|
|
695
|
+
series={series}
|
|
696
|
+
index={i}
|
|
697
|
+
key={`series-list-${i}`}
|
|
698
|
+
/>
|
|
699
|
+
)
|
|
634
700
|
})
|
|
635
701
|
}
|
|
636
702
|
|
|
@@ -435,8 +435,7 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
435
435
|
min={15}
|
|
436
436
|
/>
|
|
437
437
|
)}
|
|
438
|
-
{(
|
|
439
|
-
config.visualizationType === 'Combo') && (
|
|
438
|
+
{(config.orientation !== 'horizontal' || config.visualizationType === 'Combo') && (
|
|
440
439
|
<TextField
|
|
441
440
|
value={config.barThickness}
|
|
442
441
|
type='number'
|
|
@@ -44,6 +44,18 @@ export const useEditorPermissions = () => {
|
|
|
44
44
|
return true
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
const visSupportsDateCategoryAxisMin = () => {
|
|
48
|
+
const enabledCharts = ['Scatter Plot']
|
|
49
|
+
if (enabledCharts.includes(visualizationType)) return true
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const visSupportsDateCategoryAxisMax = () => {
|
|
54
|
+
const enabledCharts = ['Scatter Plot']
|
|
55
|
+
if (enabledCharts.includes(visualizationType)) return true
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
const visSupportsSuperTitle = () => {
|
|
48
60
|
const disabledCharts = ['Spark Line']
|
|
49
61
|
if (disabledCharts.includes(visualizationType)) return false
|
|
@@ -88,7 +100,7 @@ export const useEditorPermissions = () => {
|
|
|
88
100
|
const visHasLegend = () => {
|
|
89
101
|
switch (visualizationType) {
|
|
90
102
|
case 'Box Plot':
|
|
91
|
-
return
|
|
103
|
+
return true
|
|
92
104
|
case 'Forest Plot':
|
|
93
105
|
return false
|
|
94
106
|
case 'Spark Line':
|
|
@@ -141,7 +153,6 @@ export const useEditorPermissions = () => {
|
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
const visHasBrushChart = () => {
|
|
144
|
-
return false
|
|
145
156
|
if (config.xAxis.type === 'categorical') return false
|
|
146
157
|
return ['Line', 'Bar', 'Area Chart', 'Combo'].includes(visualizationType) && orientation === 'vertical'
|
|
147
158
|
}
|
|
@@ -372,6 +383,10 @@ export const useEditorPermissions = () => {
|
|
|
372
383
|
)
|
|
373
384
|
}
|
|
374
385
|
|
|
386
|
+
const visSupportsYPadding = () => {
|
|
387
|
+
return !config.dataFormat.onlyShowTopPrefixSuffix || !config.dataFormat.suffix?.includes(' ')
|
|
388
|
+
}
|
|
389
|
+
|
|
375
390
|
const visHasSingleSeriesTooltip = () => {
|
|
376
391
|
if (visualizationType === 'Bar' || visualizationType === 'Line') {
|
|
377
392
|
return true
|
|
@@ -414,6 +429,8 @@ export const useEditorPermissions = () => {
|
|
|
414
429
|
visSupportsChartHeight,
|
|
415
430
|
visSupportsMobileChartHeight,
|
|
416
431
|
visSupportsDateCategoryAxis,
|
|
432
|
+
visSupportsDateCategoryAxisMin,
|
|
433
|
+
visSupportsDateCategoryAxisMax,
|
|
417
434
|
visSupportsDateCategoryAxisLabel,
|
|
418
435
|
visSupportsDateCategoryAxisLine,
|
|
419
436
|
visSupportsDateCategoryAxisTicks,
|
|
@@ -443,6 +460,7 @@ export const useEditorPermissions = () => {
|
|
|
443
460
|
visSupportsValueAxisMax,
|
|
444
461
|
visSupportsValueAxisMin,
|
|
445
462
|
visSupportsDynamicSeries,
|
|
463
|
+
visSupportsYPadding,
|
|
446
464
|
visHasSingleSeriesTooltip,
|
|
447
465
|
visHasCategoricalAxis
|
|
448
466
|
}
|
|
@@ -2,14 +2,14 @@ import parse from 'html-react-parser'
|
|
|
2
2
|
import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
|
|
3
3
|
import LegendShape from '@cdc/core/components/LegendShape'
|
|
4
4
|
import Button from '@cdc/core/components/elements/Button'
|
|
5
|
-
import
|
|
5
|
+
import { getLegendClasses } from './helpers/getLegendClasses'
|
|
6
6
|
import { useHighlightedBars } from '../../hooks/useHighlightedBars'
|
|
7
7
|
import { handleLineType } from '../../helpers/handleLineType'
|
|
8
8
|
|
|
9
9
|
import { getMarginTop, getGradientConfig, getMarginBottom } from './helpers/index'
|
|
10
10
|
import { Line } from '@visx/shape'
|
|
11
11
|
import { Label } from '../../types/Label'
|
|
12
|
-
import { ChartConfig } from '../../types/ChartConfig'
|
|
12
|
+
import { ChartConfig, ViewportSize } from '../../types/ChartConfig'
|
|
13
13
|
import { ColorScale } from '../../types/ChartContext'
|
|
14
14
|
import { forwardRef, useState } from 'react'
|
|
15
15
|
import LegendSuppression from './Legend.Suppression'
|
|
@@ -17,10 +17,12 @@ import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
|
|
|
17
17
|
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
18
18
|
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
19
19
|
|
|
20
|
+
const LEGEND_PADDING = 30
|
|
21
|
+
|
|
20
22
|
export interface LegendProps {
|
|
21
23
|
colorScale: ColorScale
|
|
22
24
|
config: ChartConfig
|
|
23
|
-
currentViewport:
|
|
25
|
+
currentViewport: ViewportSize
|
|
24
26
|
formatLabels: (labels: Label[]) => Label[]
|
|
25
27
|
highlight: Function
|
|
26
28
|
highlightReset: Function
|
|
@@ -46,7 +48,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
46
48
|
},
|
|
47
49
|
ref
|
|
48
50
|
) => {
|
|
49
|
-
const { innerClasses, containerClasses } =
|
|
51
|
+
const { innerClasses, containerClasses } = getLegendClasses(config)
|
|
50
52
|
const { runtime, legend } = config
|
|
51
53
|
|
|
52
54
|
const [hasSuppression, setHasSuppression] = useState(false)
|
|
@@ -78,7 +80,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
78
80
|
config={config}
|
|
79
81
|
{...getGradientConfig(config, formatLabels, colorScale)}
|
|
80
82
|
dimensions={dimensions}
|
|
81
|
-
|
|
83
|
+
parentPaddingToSubtract={legend.hideBorder ? 0 : LEGEND_PADDING}
|
|
82
84
|
/>
|
|
83
85
|
|
|
84
86
|
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
@@ -129,7 +131,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
129
131
|
}}
|
|
130
132
|
role='button'
|
|
131
133
|
>
|
|
132
|
-
|
|
134
|
+
<>
|
|
133
135
|
{config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
|
|
134
136
|
<svg width={40} height={25}>
|
|
135
137
|
<Line
|
|
@@ -141,17 +143,14 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
141
143
|
/>
|
|
142
144
|
</svg>
|
|
143
145
|
) : (
|
|
144
|
-
|
|
146
|
+
<>
|
|
145
147
|
<LegendShape
|
|
146
148
|
shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
|
|
147
|
-
viewport={currentViewport}
|
|
148
|
-
margin='0'
|
|
149
149
|
fill={label.value}
|
|
150
|
-
display={true}
|
|
151
150
|
/>
|
|
152
|
-
|
|
151
|
+
</>
|
|
153
152
|
)}
|
|
154
|
-
|
|
153
|
+
</>
|
|
155
154
|
|
|
156
155
|
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
157
156
|
{label.text}
|
|
@@ -17,6 +17,7 @@ const Legend = forwardRef((props, ref) => {
|
|
|
17
17
|
transformedData: data,
|
|
18
18
|
currentViewport,
|
|
19
19
|
dimensions,
|
|
20
|
+
getTextWidth
|
|
20
21
|
} = useContext(ConfigContext)
|
|
21
22
|
if (!config.legend) return null
|
|
22
23
|
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
|
|
@@ -24,22 +25,21 @@ const Legend = forwardRef((props, ref) => {
|
|
|
24
25
|
const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
|
|
25
26
|
|
|
26
27
|
return (
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
28
|
+
<Fragment>
|
|
29
|
+
<LegendComponent
|
|
30
|
+
getTextWidth={getTextWidth}
|
|
31
|
+
dimensions={dimensions}
|
|
32
|
+
ref={ref}
|
|
33
|
+
skipId={props.skipId || 'legend'}
|
|
34
|
+
config={config}
|
|
35
|
+
colorScale={colorScale}
|
|
36
|
+
seriesHighlight={seriesHighlight}
|
|
37
|
+
highlight={highlight}
|
|
38
|
+
highlightReset={highlightReset}
|
|
39
|
+
currentViewport={currentViewport}
|
|
40
|
+
formatLabels={createLegendLabels}
|
|
41
|
+
/>
|
|
42
|
+
</Fragment>
|
|
43
43
|
)
|
|
44
44
|
})
|
|
45
45
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ChartConfig } from './../../../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export const getLegendClasses = (config: ChartConfig) => {
|
|
4
|
+
const { position, singleRow, reverseLabelOrder, verticalSorted, hideBorder } = config.legend
|
|
5
|
+
const containerClasses = ['legend-container']
|
|
6
|
+
const innerClasses = ['legend-container__inner']
|
|
7
|
+
|
|
8
|
+
// Handle legend positioning
|
|
9
|
+
switch (position) {
|
|
10
|
+
case 'left':
|
|
11
|
+
containerClasses.push('left')
|
|
12
|
+
break
|
|
13
|
+
case 'right':
|
|
14
|
+
containerClasses.push('right')
|
|
15
|
+
break
|
|
16
|
+
case 'bottom':
|
|
17
|
+
containerClasses.push('bottom')
|
|
18
|
+
innerClasses.push('double-column', 'bottom')
|
|
19
|
+
break
|
|
20
|
+
case 'top':
|
|
21
|
+
containerClasses.push('top')
|
|
22
|
+
innerClasses.push('double-column', 'top')
|
|
23
|
+
break
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Handle single row configuration for 'bottom' and 'top' positions
|
|
27
|
+
if (['bottom', 'top'].includes(position) && singleRow) {
|
|
28
|
+
innerClasses.push('single-row')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Reverse label order
|
|
32
|
+
if (reverseLabelOrder) {
|
|
33
|
+
innerClasses.push('d-flex', 'flex-column-reverse')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Vertical sorting for 'bottom' and 'top' positions
|
|
37
|
+
if (['bottom', 'top'].includes(position) && verticalSorted) {
|
|
38
|
+
innerClasses.push('vertical-sorted')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Configure border classes
|
|
42
|
+
if (hideBorder.side && (['right', 'left'].includes(position) || !position)) {
|
|
43
|
+
containerClasses.push('border-0')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (hideBorder.topBottom && ['top', 'bottom'].includes(position)) {
|
|
47
|
+
containerClasses.push('border-0')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (hideBorder.topBottom && ['top'].includes(position)) {
|
|
51
|
+
containerClasses.push('p-0')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
containerClasses,
|
|
56
|
+
innerClasses
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export default getLegendClasses
|