@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.
Files changed (57) hide show
  1. package/dist/cdcchart.js +47386 -36618
  2. package/examples/chart-regression-1.json +378 -0
  3. package/examples/chart-regression-2.json +2360 -0
  4. package/examples/feature/filters/url-filter.json +1076 -0
  5. package/examples/feature/line/line-chart.json +2 -1
  6. package/examples/feature/regions/index.json +50 -4
  7. package/examples/feature/sankey/sankey-example-data.json +1364 -0
  8. package/examples/feature/sankey/sankey_chart_data.csv +20 -0
  9. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +306 -19
  10. package/examples/sparkline.json +868 -0
  11. package/index.html +128 -123
  12. package/package.json +4 -2
  13. package/src/CdcChart.tsx +40 -22
  14. package/src/_stories/ChartEditor.stories.tsx +14 -3
  15. package/src/_stories/_mock/url_filter.json +1076 -0
  16. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +2 -1
  17. package/src/components/AreaChart/components/AreaChart.jsx +2 -1
  18. package/src/components/BarChart/components/BarChart.Horizontal.tsx +39 -49
  19. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +36 -56
  20. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +32 -39
  21. package/src/components/BarChart/components/BarChart.Vertical.tsx +40 -55
  22. package/src/components/BoxPlot/BoxPlot.jsx +2 -1
  23. package/src/components/DeviationBar.jsx +3 -3
  24. package/src/components/EditorPanel/EditorPanel.tsx +167 -15
  25. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +1 -1
  26. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +108 -0
  27. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +48 -4
  28. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +41 -0
  29. package/src/components/EditorPanel/components/Panels/index.tsx +9 -7
  30. package/src/components/EditorPanel/components/panels.scss +11 -0
  31. package/src/components/EditorPanel/useEditorPermissions.js +40 -14
  32. package/src/components/Legend/Legend.Component.tsx +23 -15
  33. package/src/components/Legend/Legend.tsx +4 -4
  34. package/src/components/LineChart/LineChartProps.ts +1 -0
  35. package/src/components/LineChart/helpers.ts +2 -2
  36. package/src/components/LineChart/index.tsx +7 -7
  37. package/src/components/LinearChart.jsx +9 -30
  38. package/src/components/PairedBarChart.jsx +6 -10
  39. package/src/components/PieChart/PieChart.tsx +3 -3
  40. package/src/components/Regions/components/Regions.tsx +120 -78
  41. package/src/components/Sankey/index.tsx +434 -0
  42. package/src/components/Sankey/sankey.scss +153 -0
  43. package/src/components/Sankey/types/index.ts +16 -0
  44. package/src/components/ScatterPlot/ScatterPlot.jsx +1 -0
  45. package/src/components/Sparkline/{SparkLine.jsx → components/SparkLine.tsx} +14 -30
  46. package/src/components/Sparkline/index.scss +3 -0
  47. package/src/components/Sparkline/index.tsx +1 -1
  48. package/src/components/ZoomBrush.tsx +2 -1
  49. package/src/data/initial-state.js +46 -2
  50. package/src/helpers/computeMarginBottom.ts +2 -1
  51. package/src/helpers/tests/computeMarginBottom.test.ts +2 -1
  52. package/src/hooks/useBarChart.js +5 -2
  53. package/src/hooks/useScales.ts +15 -18
  54. package/src/hooks/useTooltip.tsx +9 -8
  55. package/src/scss/main.scss +8 -29
  56. package/src/types/ChartConfig.ts +32 -14
  57. 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 { config, updateConfig, transformedData: data, loading, colorPalettes, twoColorPalette, unfilteredData, excludedData, isDashboard, setParentConfig, missingRequiredSections, isDebug, setFilteredData, lineOptions, rawData } = useContext<ChartContext>(ConfigContext)
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.sortDates && !updatedConfig.xAxis.padding) {
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
- {config.visualizationType !== 'Pie' && config.visualizationType !== 'Forest Plot' && (
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
- {config.visualizationType === 'Line' && <PreliminaryData config={config} updateConfig={updateConfig} data={data} />}
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
- <Select value={config.xAxis.type} section='xAxis' fieldName='type' label='Data Type' updateField={updateField} options={config.visualizationType !== 'Scatter Plot' ? ['categorical', 'date'] : ['categorical', 'continuous', 'date']} />
1629
- {(config.visualizationType === 'Bar' || config.visualizationType === 'Line' || config.visualizationType === 'Combo' || config.visualizationType === 'Area Chart') && config.xAxis.type === 'date' && config.orientation !== 'horizontal' && (
1630
- <CheckBox value={config.xAxis.sortDates} section='xAxis' fieldName='sortDates' label='Force Date Scale (Sort Dates)' updateField={updateField} />
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.type === 'date' && (
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={updateField} options={['highlight', 'isolate']} />
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>The date needs to be in the original format of the data. Not the displayed format of the data.</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
- let changeSeriesName = (i, value) => {
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.js'
2
- import Series from './Panel.Series.js'
3
- import Regions from './Panel.Regions.js'
4
- import General from './Panel.General.js'
5
- import BoxPlot from './Panel.BoxPlot.js'
6
- import Visual from './Panel.Visual.js'
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
@@ -70,3 +70,14 @@
70
70
  }
71
71
  }
72
72
  }
73
+
74
+ .sankey__color-input {
75
+ display: flex;
76
+ align-items: center;
77
+ margin: 10px auto;
78
+ label {
79
+ align-items: center;
80
+ padding-left: 5px;
81
+ margin-top: 0 !important;
82
+ }
83
+ }