@cdc/chart 4.24.2 → 4.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +47386 -36618
- package/examples/chart-regression-1.json +378 -0
- package/examples/chart-regression-2.json +2360 -0
- package/examples/feature/filters/url-filter.json +1076 -0
- package/examples/feature/line/line-chart.json +2 -1
- package/examples/feature/regions/index.json +50 -4
- package/examples/feature/sankey/sankey-example-data.json +1364 -0
- package/examples/feature/sankey/sankey_chart_data.csv +20 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +306 -19
- package/examples/sparkline.json +868 -0
- package/index.html +128 -123
- package/package.json +4 -2
- package/src/CdcChart.tsx +40 -22
- package/src/_stories/ChartEditor.stories.tsx +14 -3
- package/src/_stories/_mock/url_filter.json +1076 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +2 -1
- package/src/components/AreaChart/components/AreaChart.jsx +2 -1
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +39 -49
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +36 -56
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +32 -39
- package/src/components/BarChart/components/BarChart.Vertical.tsx +40 -55
- package/src/components/BoxPlot/BoxPlot.jsx +2 -1
- package/src/components/DeviationBar.jsx +3 -3
- package/src/components/EditorPanel/EditorPanel.tsx +167 -15
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +108 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +48 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +41 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +9 -7
- package/src/components/EditorPanel/components/panels.scss +11 -0
- package/src/components/EditorPanel/useEditorPermissions.js +40 -14
- package/src/components/Legend/Legend.Component.tsx +23 -15
- package/src/components/Legend/Legend.tsx +4 -4
- package/src/components/LineChart/LineChartProps.ts +1 -0
- package/src/components/LineChart/helpers.ts +2 -2
- package/src/components/LineChart/index.tsx +7 -7
- package/src/components/LinearChart.jsx +9 -30
- package/src/components/PairedBarChart.jsx +6 -10
- package/src/components/PieChart/PieChart.tsx +3 -3
- package/src/components/Regions/components/Regions.tsx +120 -78
- package/src/components/Sankey/index.tsx +434 -0
- package/src/components/Sankey/sankey.scss +153 -0
- package/src/components/Sankey/types/index.ts +16 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +1 -0
- package/src/components/Sparkline/{SparkLine.jsx → components/SparkLine.tsx} +14 -30
- package/src/components/Sparkline/index.scss +3 -0
- package/src/components/Sparkline/index.tsx +1 -1
- package/src/components/ZoomBrush.tsx +2 -1
- package/src/data/initial-state.js +46 -2
- package/src/helpers/computeMarginBottom.ts +2 -1
- package/src/helpers/tests/computeMarginBottom.test.ts +2 -1
- package/src/hooks/useBarChart.js +5 -2
- package/src/hooks/useScales.ts +15 -18
- package/src/hooks/useTooltip.tsx +9 -8
- package/src/scss/main.scss +8 -29
- package/src/types/ChartConfig.ts +32 -14
- package/src/types/ChartContext.ts +7 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, memo, useContext } from 'react'
|
|
2
2
|
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
|
3
|
-
|
|
3
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
4
4
|
import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
|
|
5
5
|
|
|
6
6
|
// @cdc/core
|
|
@@ -182,7 +182,7 @@ const PreliminaryData = memo(({ config, updateConfig, data }) => {
|
|
|
182
182
|
Remove
|
|
183
183
|
</button>
|
|
184
184
|
<Select value={type} initial='Select' fieldName='type' label='Type' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getTypeOptions()} />
|
|
185
|
-
<Select value={seriesKey} initial='Select' fieldName='seriesKey' label='ASSOCIATE TO SERIES' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={config.runtime?.seriesKeys} />
|
|
185
|
+
<Select value={seriesKey} initial='Select' fieldName='seriesKey' label='ASSOCIATE TO SERIES' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys} />
|
|
186
186
|
<Select value={column} initial='Select' fieldName='column' label='COLUMN WITH CONFIGURATION VALUE' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getColumnOptions()} />
|
|
187
187
|
<TextField value={value} fieldName='value' label='VALUE TO TRIGGER' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
|
|
188
188
|
<Select value={style} initial='Select' fieldName='style' label='Style' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getStyleOptions()} />
|
|
@@ -193,14 +193,34 @@ const PreliminaryData = memo(({ config, updateConfig, data }) => {
|
|
|
193
193
|
})}
|
|
194
194
|
|
|
195
195
|
<button type='button' onClick={addColumn} className='btn full-width'>
|
|
196
|
-
{config.visualizationType === 'Line' ? 'Add Special Line' : config.visualizationType === 'Bar' ? ' Add Special Bar' : 'Add Special Line/Bar'}
|
|
196
|
+
{config.visualizationType === 'Line' || config.visualizationType === 'Combo' ? 'Add Special Line' : config.visualizationType === 'Bar' ? ' Add Special Bar' : 'Add Special Line/Bar'}
|
|
197
197
|
</button>
|
|
198
198
|
</>
|
|
199
199
|
)
|
|
200
200
|
})
|
|
201
201
|
|
|
202
202
|
const EditorPanel = () => {
|
|
203
|
-
const {
|
|
203
|
+
const {
|
|
204
|
+
config,
|
|
205
|
+
updateConfig,
|
|
206
|
+
tableData,
|
|
207
|
+
transformedData: data,
|
|
208
|
+
loading,
|
|
209
|
+
colorScale,
|
|
210
|
+
colorPalettes,
|
|
211
|
+
twoColorPalette,
|
|
212
|
+
unfilteredData,
|
|
213
|
+
excludedData,
|
|
214
|
+
isDashboard,
|
|
215
|
+
setParentConfig,
|
|
216
|
+
missingRequiredSections,
|
|
217
|
+
isDebug,
|
|
218
|
+
setFilteredData,
|
|
219
|
+
lineOptions,
|
|
220
|
+
rawData,
|
|
221
|
+
highlight,
|
|
222
|
+
highlightReset
|
|
223
|
+
} = useContext<ChartContext>(ConfigContext)
|
|
204
224
|
|
|
205
225
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, unfilteredData)
|
|
206
226
|
|
|
@@ -220,8 +240,10 @@ const EditorPanel = () => {
|
|
|
220
240
|
visHasAnchors,
|
|
221
241
|
visHasBarBorders,
|
|
222
242
|
visHasDataCutoff,
|
|
243
|
+
visHasSelectableLegendValues,
|
|
223
244
|
visCanAnimate,
|
|
224
245
|
visHasLegend,
|
|
246
|
+
visHasLegendAxisAlign,
|
|
225
247
|
visHasBrushChart,
|
|
226
248
|
visSupportsDateCategoryAxis,
|
|
227
249
|
visSupportsValueAxisMin,
|
|
@@ -234,6 +256,7 @@ const EditorPanel = () => {
|
|
|
234
256
|
visSupportsDateCategoryAxisPadding,
|
|
235
257
|
visSupportsRegions,
|
|
236
258
|
visSupportsFilters,
|
|
259
|
+
visSupportsPreliminaryData,
|
|
237
260
|
visSupportsValueAxisGridLines,
|
|
238
261
|
visSupportsValueAxisLine,
|
|
239
262
|
visSupportsValueAxisTicks,
|
|
@@ -329,7 +352,7 @@ const EditorPanel = () => {
|
|
|
329
352
|
if (updatedConfig.visualizationType === 'Combo') {
|
|
330
353
|
updatedConfig.orientation = 'vertical'
|
|
331
354
|
}
|
|
332
|
-
if (updatedConfig.xAxis
|
|
355
|
+
if (isDateScale(updatedConfig.xAxis) && !updatedConfig.xAxis.padding) {
|
|
333
356
|
updatedConfig.xAxis.padding = 6
|
|
334
357
|
}
|
|
335
358
|
}
|
|
@@ -1003,6 +1026,28 @@ const EditorPanel = () => {
|
|
|
1003
1026
|
columns: updatedColumns
|
|
1004
1027
|
})
|
|
1005
1028
|
}
|
|
1029
|
+
|
|
1030
|
+
const colorCodeByCategory = config.visualizationType === 'Bar' && config.visualizationSubType === 'regular' && config.runtime.seriesKeys.length === 1
|
|
1031
|
+
const getLegendColumns = () => {
|
|
1032
|
+
const colorCodeData = data.map(d => d[config.legend.colorCode])
|
|
1033
|
+
return colorCodeByCategory ? colorCodeData : getColumns(false).filter(d => d !== config.xAxis.dataKey)
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const updateSeriesIsolateValues = updatedValues => {
|
|
1037
|
+
updateConfig({ ...config, legend: { ...config.legend, seriesHighlight: updatedValues } })
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const updateBehavior = (section, fieldName, newValue) => {
|
|
1041
|
+
const sectionValue = { ...config[section], [fieldName]: newValue }
|
|
1042
|
+
const updatedConfig = { ...config, [section]: sectionValue }
|
|
1043
|
+
|
|
1044
|
+
if (newValue === 'highlight' && config.legend.seriesHighlight?.length) {
|
|
1045
|
+
updatedConfig.legend.seriesHighlight.length = 0
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
updateConfig(updatedConfig)
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1006
1051
|
const editorContextValues = {
|
|
1007
1052
|
addNewExclusion,
|
|
1008
1053
|
data,
|
|
@@ -1042,7 +1087,8 @@ const EditorPanel = () => {
|
|
|
1042
1087
|
<Accordion allowZeroExpanded={true}>
|
|
1043
1088
|
<Panels.General name='General' />
|
|
1044
1089
|
<Panels.ForestPlot name='Forest Plot Settings' />
|
|
1045
|
-
|
|
1090
|
+
<Panels.Sankey name='Sankey' />
|
|
1091
|
+
{config.visualizationType !== 'Pie' && config.visualizationType !== 'Forest Plot' && config.visualizationType !== 'Sankey' && (
|
|
1046
1092
|
<AccordionItem>
|
|
1047
1093
|
<AccordionItemHeading>
|
|
1048
1094
|
<AccordionItemButton>Data Series {(!config.series || config.series.length === 0 || (config.visualizationType === 'Paired Bar' && config.series.length < 2)) && <WarningImage width='25' className='warning-icon' />}</AccordionItemButton>
|
|
@@ -1102,7 +1148,7 @@ const EditorPanel = () => {
|
|
|
1102
1148
|
)}
|
|
1103
1149
|
{visSupportsRankByValue() && config.series && config.series.length === 1 && <Select fieldName='visualizationType' label='Rank by Value' initial='Select' onChange={e => sortSeries(e.target.value)} options={['asc', 'desc']} />}
|
|
1104
1150
|
{/* {visHasDataSuppression() && <DataSuppression config={config} updateConfig={updateConfig} data={data} />} */}
|
|
1105
|
-
{
|
|
1151
|
+
{visSupportsPreliminaryData() && <PreliminaryData config={config} updateConfig={updateConfig} data={data} />}
|
|
1106
1152
|
</AccordionItemPanel>
|
|
1107
1153
|
</AccordionItem>
|
|
1108
1154
|
)}
|
|
@@ -1146,7 +1192,6 @@ const EditorPanel = () => {
|
|
|
1146
1192
|
<CheckBox value={config.isLegendValue} fieldName='isLegendValue' label='Use Legend Value in Hover' updateField={updateField} />
|
|
1147
1193
|
)}
|
|
1148
1194
|
<TextField value={config.yAxis.numTicks} placeholder='Auto' type='number' section='yAxis' fieldName='numTicks' label='Number of ticks' className='number-narrow' updateField={updateField} />
|
|
1149
|
-
{config.visualizationType === 'Paired Bar' && <TextField value={config.yAxis.tickRotation || 0} type='number' min={0} section='yAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
|
|
1150
1195
|
<TextField
|
|
1151
1196
|
value={config.yAxis.size}
|
|
1152
1197
|
type='number'
|
|
@@ -1167,7 +1212,7 @@ const EditorPanel = () => {
|
|
|
1167
1212
|
}
|
|
1168
1213
|
/>
|
|
1169
1214
|
{config.orientation === 'horizontal' && config.visualizationType !== 'Paired Bar' && <CheckBox value={config.isResponsiveTicks} fieldName='isResponsiveTicks' label='Use Responsive Ticks' updateField={updateField} />}
|
|
1170
|
-
{(config.orientation === 'vertical' || !config.isResponsiveTicks) && <TextField value={config.yAxis.tickRotation} type='number' min={0} section='yAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
|
|
1215
|
+
{(config.orientation === 'vertical' || !config.isResponsiveTicks) && <TextField value={config.yAxis.tickRotation || 0} type='number' min={0} section='yAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
|
|
1171
1216
|
{config.isResponsiveTicks && config.orientation === 'horizontal' && config.visualizationType !== 'Paired Bar' && (
|
|
1172
1217
|
<TextField
|
|
1173
1218
|
value={config.xAxis.maxTickRotation}
|
|
@@ -1625,10 +1670,35 @@ const EditorPanel = () => {
|
|
|
1625
1670
|
<>
|
|
1626
1671
|
{config.visualizationType !== 'Forest Plot' && (
|
|
1627
1672
|
<>
|
|
1628
|
-
<
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1673
|
+
<label>
|
|
1674
|
+
<span className='edit-label'>
|
|
1675
|
+
Data Scaling Type
|
|
1676
|
+
<Tooltip style={{ textTransform: 'none', display: 'inline-block' }}>
|
|
1677
|
+
<Tooltip.Target>
|
|
1678
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
1679
|
+
</Tooltip.Target>
|
|
1680
|
+
<Tooltip.Content>Linear scales are employed for quantitative data, while time scales are used for time-series data.</Tooltip.Content>
|
|
1681
|
+
</Tooltip>
|
|
1682
|
+
</span>
|
|
1683
|
+
<select
|
|
1684
|
+
value={config.xAxis.type}
|
|
1685
|
+
onChange={e =>
|
|
1686
|
+
updateConfig({
|
|
1687
|
+
...config,
|
|
1688
|
+
xAxis: {
|
|
1689
|
+
...config.xAxis,
|
|
1690
|
+
type: e.target.value
|
|
1691
|
+
}
|
|
1692
|
+
})
|
|
1693
|
+
}
|
|
1694
|
+
>
|
|
1695
|
+
<option value='categorical'>Categorical (Linear Scale)</option>
|
|
1696
|
+
<option value='date'>Date (Linear Scale)</option>
|
|
1697
|
+
<option value='date-time'>Date (Date Time Scale)</option>
|
|
1698
|
+
{config.visualizationType === 'Scatter Plot' && <option value={'continuous'}>Continuous</option>}
|
|
1699
|
+
</select>
|
|
1700
|
+
</label>
|
|
1701
|
+
|
|
1632
1702
|
{visSupportsDateCategoryAxisPadding() && (
|
|
1633
1703
|
<TextField
|
|
1634
1704
|
value={config.xAxis.padding}
|
|
@@ -1761,7 +1831,7 @@ const EditorPanel = () => {
|
|
|
1761
1831
|
</>
|
|
1762
1832
|
)}
|
|
1763
1833
|
|
|
1764
|
-
{config.xAxis
|
|
1834
|
+
{isDateScale(config.xAxis) && (
|
|
1765
1835
|
<>
|
|
1766
1836
|
<p style={{ padding: '1.5em 0 0.5em', fontSize: '.9rem', lineHeight: '1rem' }}>
|
|
1767
1837
|
Format how charts should parse and display your dates using{' '}
|
|
@@ -2584,8 +2654,79 @@ const EditorPanel = () => {
|
|
|
2584
2654
|
{config.visualizationType === 'Bar' && config.visualizationSubType === 'regular' && config.runtime.seriesKeys.length === 1 && (
|
|
2585
2655
|
<Select value={config.legend.colorCode} section='legend' fieldName='colorCode' label='Color code by category' initial='Select' updateField={updateField} options={getDataValueOptions(data)} />
|
|
2586
2656
|
)}
|
|
2587
|
-
<Select value={config.legend.behavior} section='legend' fieldName='behavior' label='Legend Behavior (When clicked)' updateField={
|
|
2657
|
+
<Select value={config.legend.behavior} section='legend' fieldName='behavior' label='Legend Behavior (When clicked)' updateField={(...[section, , fieldName, value]) => updateBehavior(section, fieldName, value)} options={['highlight', 'isolate']} />
|
|
2658
|
+
{visHasLegendAxisAlign() && <CheckBox value={config.legend.axisAlign} fieldName='axisAlign' section='legend' label='Align to Axis on Isolate' updateField={updateField} />}
|
|
2659
|
+
|
|
2588
2660
|
{config.legend.behavior === 'highlight' && config.tooltips.singleSeries && <CheckBox value={config.legend.highlightOnHover} section='legend' fieldName='highlightOnHover' label='HIGHLIGHT DATA SERIES ON HOVER' updateField={updateField} />}
|
|
2661
|
+
|
|
2662
|
+
{/* start: isolated values */}
|
|
2663
|
+
{visHasSelectableLegendValues && config.legend.behavior === 'isolate' && !colorCodeByCategory && (
|
|
2664
|
+
<fieldset className='primary-fieldset edit-block' key={'additional-highlight-values'}>
|
|
2665
|
+
<label>
|
|
2666
|
+
<span className='edit-label'>
|
|
2667
|
+
Isolate Data Series
|
|
2668
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
2669
|
+
<Tooltip.Target>
|
|
2670
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
2671
|
+
</Tooltip.Target>
|
|
2672
|
+
<Tooltip.Content>
|
|
2673
|
+
<p>You can choose data series that are shown on load. Others will be added when the user clicks on them in the legend.</p>
|
|
2674
|
+
</Tooltip.Content>
|
|
2675
|
+
</Tooltip>
|
|
2676
|
+
</span>
|
|
2677
|
+
</label>
|
|
2678
|
+
{config.legend.seriesHighlight &&
|
|
2679
|
+
config.legend.seriesHighlight.map((val, i) => (
|
|
2680
|
+
<fieldset className='edit-block' key={`${val}-${i}`}>
|
|
2681
|
+
<button
|
|
2682
|
+
className='remove-column'
|
|
2683
|
+
onClick={event => {
|
|
2684
|
+
event.preventDefault()
|
|
2685
|
+
const updatedSeriesHighlight = [...config.legend.seriesHighlight]
|
|
2686
|
+
updatedSeriesHighlight.splice(i, 1)
|
|
2687
|
+
updateField('legend', null, 'seriesHighlight', updatedSeriesHighlight)
|
|
2688
|
+
if (!updatedSeriesHighlight.length) {
|
|
2689
|
+
highlightReset()
|
|
2690
|
+
}
|
|
2691
|
+
}}
|
|
2692
|
+
>
|
|
2693
|
+
Remove
|
|
2694
|
+
</button>
|
|
2695
|
+
<Select
|
|
2696
|
+
value={config.legend.seriesHighlight[i]}
|
|
2697
|
+
fieldName='seriesHighlight'
|
|
2698
|
+
label='Isolate Value'
|
|
2699
|
+
onChange={e => {
|
|
2700
|
+
const updatedSeriesHighlight = [...config.legend.seriesHighlight]
|
|
2701
|
+
if (!updatedSeriesHighlight.includes(e.target.value)) {
|
|
2702
|
+
updatedSeriesHighlight[i] = e.target.value
|
|
2703
|
+
updateSeriesIsolateValues([...updatedSeriesHighlight])
|
|
2704
|
+
}
|
|
2705
|
+
}}
|
|
2706
|
+
options={getLegendColumns()}
|
|
2707
|
+
/>
|
|
2708
|
+
</fieldset>
|
|
2709
|
+
))}
|
|
2710
|
+
<button
|
|
2711
|
+
className={'btn full-width'}
|
|
2712
|
+
onClick={event => {
|
|
2713
|
+
event.preventDefault()
|
|
2714
|
+
const legendColumns = getLegendColumns()
|
|
2715
|
+
const updatedSeriesHighlight = [...config.legend.seriesHighlight]
|
|
2716
|
+
const seriesLength = updatedSeriesHighlight.length
|
|
2717
|
+
if (seriesLength < legendColumns.length) {
|
|
2718
|
+
const [newSeriesHighlight] = legendColumns.filter(d => !updatedSeriesHighlight.includes(d))
|
|
2719
|
+
updatedSeriesHighlight.push(newSeriesHighlight)
|
|
2720
|
+
updateSeriesIsolateValues([...updatedSeriesHighlight])
|
|
2721
|
+
}
|
|
2722
|
+
}}
|
|
2723
|
+
>
|
|
2724
|
+
Add Isolate Value
|
|
2725
|
+
</button>
|
|
2726
|
+
</fieldset>
|
|
2727
|
+
)}
|
|
2728
|
+
{/* end: isolated values */}
|
|
2729
|
+
|
|
2589
2730
|
<TextField value={config.legend.label} section='legend' fieldName='label' label='Title' updateField={updateField} />
|
|
2590
2731
|
<Select value={config.legend.position} section='legend' fieldName='position' label='Position' updateField={updateField} options={['right', 'left', 'bottom']} />
|
|
2591
2732
|
{config.legend.position === 'bottom' && (
|
|
@@ -2702,6 +2843,17 @@ const EditorPanel = () => {
|
|
|
2702
2843
|
/>
|
|
2703
2844
|
</label>
|
|
2704
2845
|
|
|
2846
|
+
<label>
|
|
2847
|
+
<span className='edit-label column-heading'>Default Value Set By Query String Parameter</span>
|
|
2848
|
+
<input
|
|
2849
|
+
type='text'
|
|
2850
|
+
value={filter.setByQueryParameter}
|
|
2851
|
+
onChange={e => {
|
|
2852
|
+
updateFilterProp('setByQueryParameter', index, e.target.value)
|
|
2853
|
+
}}
|
|
2854
|
+
/>
|
|
2855
|
+
</label>
|
|
2856
|
+
|
|
2705
2857
|
<label>
|
|
2706
2858
|
<span className='edit-filterOrder column-heading'>Filter Order</span>
|
|
2707
2859
|
<select value={filter.order ? filter.order : 'asc'} onChange={e => updateFilterProp('order', index, e.target.value)}>
|
|
@@ -104,7 +104,7 @@ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; up
|
|
|
104
104
|
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
105
105
|
</Tooltip.Target>
|
|
106
106
|
<Tooltip.Content>
|
|
107
|
-
<p>
|
|
107
|
+
<p>When using categorical (linear scale) match the data set value. When using date (linear / date time scale) match the x-axis value.</p>
|
|
108
108
|
</Tooltip.Content>
|
|
109
109
|
</Tooltip>
|
|
110
110
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../../../../ConfigContext'
|
|
3
|
+
import { CheckBox, TextField } from '@cdc/core/components/EditorPanel/Inputs'
|
|
4
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
5
|
+
|
|
6
|
+
import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
|
|
7
|
+
import EditorPanelContext, { type EditorPanelContext as EPContext } from '../../EditorPanelContext'
|
|
8
|
+
|
|
9
|
+
const SankeySettings = () => {
|
|
10
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
11
|
+
const data = config.data?.[0]
|
|
12
|
+
const { updateField } = useContext<EPContext>(EditorPanelContext)
|
|
13
|
+
|
|
14
|
+
if (config.visualizationType !== 'Sankey') return
|
|
15
|
+
|
|
16
|
+
const updateStoryNode = (fieldName, value, i) => {
|
|
17
|
+
let storyNodes = []
|
|
18
|
+
|
|
19
|
+
if (data?.storyNodeText) {
|
|
20
|
+
storyNodes = [...data?.storyNodeText]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
storyNodes[i][fieldName] = value
|
|
24
|
+
console.log('story nodes', storyNodes[i])
|
|
25
|
+
updateConfig({
|
|
26
|
+
...config,
|
|
27
|
+
sankey: {
|
|
28
|
+
...config.sankey,
|
|
29
|
+
data: {
|
|
30
|
+
...config.sankey.data,
|
|
31
|
+
storyNodeText: storyNodes
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const addStoryNode = () => {
|
|
38
|
+
const newData = data
|
|
39
|
+
|
|
40
|
+
newData.storyNodeText.push({
|
|
41
|
+
StoryNode: '',
|
|
42
|
+
segmentTextBefore: '',
|
|
43
|
+
segmentTextAfter: ''
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
updateConfig({
|
|
47
|
+
...config,
|
|
48
|
+
sankey: {
|
|
49
|
+
...config.sankey,
|
|
50
|
+
data: [{ ...newData }]
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const removeStoryNode = index => {
|
|
56
|
+
const newData = data
|
|
57
|
+
newData.storyNodeText.splice(index, 1)
|
|
58
|
+
|
|
59
|
+
updateConfig({ ...config, sankey: { ...config.sankey, data: { ...newData } } })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<AccordionItem>
|
|
64
|
+
<AccordionItemHeading>
|
|
65
|
+
<AccordionItemButton>Sankey Settings</AccordionItemButton>
|
|
66
|
+
</AccordionItemHeading>
|
|
67
|
+
<AccordionItemPanel>
|
|
68
|
+
{data?.storyNodeText &&
|
|
69
|
+
data?.storyNodeText.map(({ StoryNode, segmentTextBefore, segmentTextAfter }, i) => (
|
|
70
|
+
<div key={i} style={{ border: '1px solid black', margin: '15px auto', padding: '15px', borderRadius: '10px' }}>
|
|
71
|
+
<label>
|
|
72
|
+
Story Node Text
|
|
73
|
+
<input type='text' value={StoryNode} fieldName='StoryNode' label='StoryNode' onChange={e => updateStoryNode('StoryNode', e.target.value, i)} />
|
|
74
|
+
</label>
|
|
75
|
+
<label>
|
|
76
|
+
Story Text Before
|
|
77
|
+
<input type='text' value={segmentTextBefore} fieldName='segmentTextBefore' label='Segment Text Before' onChange={e => updateStoryNode('segmentTextBefore', e.target.value, i)} />
|
|
78
|
+
</label>
|
|
79
|
+
<label>
|
|
80
|
+
Story Text After
|
|
81
|
+
<input type='text' value={segmentTextAfter} fieldName='segmentTextAfter' label='Segment Text After' onChange={e => updateStoryNode('segmentTextAfter', e.target.value, i)} />
|
|
82
|
+
</label>
|
|
83
|
+
<Button onClick={e => removeStoryNode(i)} className='btn' style={{ background: 'tomato' }}>
|
|
84
|
+
Remove Story Node
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
88
|
+
{`Total Story Nodes: ${data?.storyNodeText?.length}`}
|
|
89
|
+
{data?.storyNodeText?.length < 3 && (
|
|
90
|
+
<button
|
|
91
|
+
type='button'
|
|
92
|
+
className='btn full-width'
|
|
93
|
+
onClick={e => {
|
|
94
|
+
e.preventDefault()
|
|
95
|
+
addStoryNode()
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
Add StoryNode
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
<CheckBox value={config.enableTooltips} fieldName='enableTooltips' label='Enable Tooltips' updateField={updateField} />
|
|
103
|
+
</AccordionItemPanel>
|
|
104
|
+
</AccordionItem>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default SankeySettings
|
|
@@ -424,14 +424,56 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
424
424
|
)
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
const SeriesInputWeight = props => {
|
|
428
|
+
const { series, index: i } = props
|
|
429
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
430
|
+
const adjustableWeightSeriesTypes = ['Line', 'Combo', 'dashed-sm', 'dashed-md', 'dashed-lg']
|
|
431
|
+
|
|
432
|
+
if (!adjustableWeightSeriesTypes.includes(series.type)) return
|
|
433
|
+
|
|
434
|
+
const changeSeriesWeight = (i, value, min, max) => {
|
|
435
|
+
let series = [...config.series]
|
|
436
|
+
let seriesLabelsCopy = { ...config.runtime.seriesLabels }
|
|
437
|
+
series[i].weight = !value ? value : Math.max(Number(min), Math.min(Number(max), Number(value)))
|
|
438
|
+
seriesLabelsCopy[series[i].dataKey] = series[i].weight ? series[i].weight : series[i].dataKey
|
|
439
|
+
|
|
440
|
+
const newConfig = {
|
|
441
|
+
...config,
|
|
442
|
+
series,
|
|
443
|
+
runtime: {
|
|
444
|
+
...config.runtime,
|
|
445
|
+
seriesLabels: seriesLabelsCopy
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
updateConfig(newConfig)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return (
|
|
453
|
+
<>
|
|
454
|
+
<label htmlFor='series-weight'>Line Weight</label>
|
|
455
|
+
<input
|
|
456
|
+
type='number'
|
|
457
|
+
key={`series-weight-${i}`}
|
|
458
|
+
value={series.weight ? series.weight : ''}
|
|
459
|
+
min="1"
|
|
460
|
+
max="9"
|
|
461
|
+
onChange={event => {
|
|
462
|
+
changeSeriesWeight(i, event.target.value, event.target.min, event.target.max)
|
|
463
|
+
}}
|
|
464
|
+
/>
|
|
465
|
+
</>
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
427
469
|
const SeriesInputName = props => {
|
|
428
470
|
const { series, index: i } = props
|
|
429
471
|
const { config, updateConfig } = useContext(ConfigContext)
|
|
430
|
-
const adjustableNameSeriesTypes = ['Bar', 'Line', 'Area Chart', 'Combo', 'Deviation', 'Paired', 'Scatter', 'dashed-sm', 'dashed-md', 'dashed-lg']
|
|
472
|
+
const adjustableNameSeriesTypes = ['Bar', 'Line', 'Area Chart', 'Combo', 'Deviation Bar', 'Paired Bar', 'Scatter Plot', 'dashed-sm', 'dashed-md', 'dashed-lg']
|
|
431
473
|
|
|
432
474
|
if (!adjustableNameSeriesTypes.includes(series.type)) return
|
|
433
475
|
|
|
434
|
-
|
|
476
|
+
const changeSeriesName = (i, value) => {
|
|
435
477
|
let series = [...config.series]
|
|
436
478
|
let seriesLabelsCopy = { ...config.runtime.seriesLabels }
|
|
437
479
|
series[i].name = value
|
|
@@ -468,7 +510,7 @@ const SeriesDisplayInTooltip = props => {
|
|
|
468
510
|
const { series, index } = props
|
|
469
511
|
const { config, updateConfig } = useContext(ConfigContext)
|
|
470
512
|
|
|
471
|
-
if(['Paired Bar', 'Scatter Plot', 'Deviation Bar'].includes(config.visualizationType)) return
|
|
513
|
+
if (['Paired Bar', 'Scatter Plot', 'Deviation Bar'].includes(config.visualizationType)) return
|
|
472
514
|
|
|
473
515
|
const toggleTooltip = seriesIndex => {
|
|
474
516
|
let copiedSeries = [...config.series]
|
|
@@ -569,6 +611,7 @@ const SeriesItem = props => {
|
|
|
569
611
|
{chartsWithOptions.includes(config.visualizationType) && (
|
|
570
612
|
<AccordionItemPanel>
|
|
571
613
|
<Series.Input.Name series={series} index={i} />
|
|
614
|
+
<Series.Input.Weight series={series} index={i} />
|
|
572
615
|
<Series.Dropdown.SeriesType series={series} index={i} />
|
|
573
616
|
<Series.Dropdown.AxisPosition series={series} index={i} />
|
|
574
617
|
<Series.Dropdown.LineType series={series} index={i} />
|
|
@@ -604,7 +647,8 @@ const Series = {
|
|
|
604
647
|
ForecastingColor: SeriesDropdownForecastColor
|
|
605
648
|
},
|
|
606
649
|
Input: {
|
|
607
|
-
Name: SeriesInputName
|
|
650
|
+
Name: SeriesInputName,
|
|
651
|
+
Weight: SeriesInputWeight
|
|
608
652
|
},
|
|
609
653
|
Checkbox: {
|
|
610
654
|
DisplayInTooltip: SeriesDisplayInTooltip
|
|
@@ -25,6 +25,31 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
25
25
|
const { visHasBarBorders, visCanAnimate, visSupportsNonSequentialPallete, headerColors, visSupportsTooltipOpacity, visSupportsTooltipLines, visSupportsBarSpace, visSupportsBarThickness, visHasDataCutoff, visSupportsSequentialPallete, visSupportsReverseColorPalette } = useEditorPermissions()
|
|
26
26
|
const { twoColorPalettes, sequential, nonSequential } = useColorPalette(config, updateConfig)
|
|
27
27
|
|
|
28
|
+
const updateColor = (property, _value) => {
|
|
29
|
+
console.log('value', _value)
|
|
30
|
+
if (property === 'storyNodeFontColor') {
|
|
31
|
+
updateConfig({
|
|
32
|
+
...config,
|
|
33
|
+
sankey: {
|
|
34
|
+
...config.sankey,
|
|
35
|
+
storyNodeFontColor: _value
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
return
|
|
39
|
+
} else {
|
|
40
|
+
updateConfig({
|
|
41
|
+
...config,
|
|
42
|
+
sankey: {
|
|
43
|
+
...config.sankey,
|
|
44
|
+
[property]: {
|
|
45
|
+
...config.sankey[property],
|
|
46
|
+
default: _value
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
return (
|
|
29
54
|
<AccordionItem>
|
|
30
55
|
<AccordionItemHeading>
|
|
@@ -175,6 +200,22 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
175
200
|
)}
|
|
176
201
|
</>
|
|
177
202
|
)}
|
|
203
|
+
{config.visualizationType === 'Sankey' && (
|
|
204
|
+
<>
|
|
205
|
+
<span className='sankey__color-input'>
|
|
206
|
+
<input type='color' value={config.sankey.nodeColor.default} id='storyNodeColor' name='storyNodeColor' onChange={e => updateColor('nodeColor', e.target.value)} />
|
|
207
|
+
<label htmlFor='storyNodeColor'>Story Node Color</label>
|
|
208
|
+
</span>
|
|
209
|
+
<span className='sankey__color-input'>
|
|
210
|
+
<input type='color' value={config.sankey.storyNodeFontColor || 'red'} id='storyNodeFontColor' name='storyNodeFontColor' onChange={e => updateColor('storyNodeFontColor', e.target.value)} />
|
|
211
|
+
<label htmlFor='storyNodeFontColor'>Story Node Font Color</label>
|
|
212
|
+
</span>
|
|
213
|
+
<span className='sankey__color-input'>
|
|
214
|
+
<input type='color' value={config.sankey.linkColor.default} id='linkColor' name='linkColor' onChange={e => updateColor('linkColor', e.target.value)} />
|
|
215
|
+
<label htmlFor='linkColor'>Link Color</label>
|
|
216
|
+
</span>
|
|
217
|
+
</>
|
|
218
|
+
)}
|
|
178
219
|
{(config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar') && (
|
|
179
220
|
<>
|
|
180
221
|
<InputToggle section='twoColor' fieldName='isPaletteReversed' size='small' label='Use selected palette in reverse order' updateField={updateField} value={config.twoColor.isPaletteReversed} />
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import ForestPlotSettings from './Panel.ForestPlotSettings
|
|
2
|
-
import Series from './Panel.Series
|
|
3
|
-
import Regions from './Panel.Regions
|
|
4
|
-
import General from './Panel.General
|
|
5
|
-
import BoxPlot from './Panel.BoxPlot
|
|
6
|
-
import Visual from './Panel.Visual
|
|
1
|
+
import ForestPlotSettings from './Panel.ForestPlotSettings'
|
|
2
|
+
import Series from './Panel.Series'
|
|
3
|
+
import Regions from './Panel.Regions'
|
|
4
|
+
import General from './Panel.General'
|
|
5
|
+
import BoxPlot from './Panel.BoxPlot'
|
|
6
|
+
import Visual from './Panel.Visual'
|
|
7
|
+
import Sankey from './Panel.Sankey'
|
|
7
8
|
|
|
8
9
|
const Panels = {
|
|
9
10
|
ForestPlot: ForestPlotSettings,
|
|
@@ -11,7 +12,8 @@ const Panels = {
|
|
|
11
12
|
Regions,
|
|
12
13
|
General,
|
|
13
14
|
BoxPlot,
|
|
14
|
-
Visual
|
|
15
|
+
Visual,
|
|
16
|
+
Sankey
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export default Panels
|