@cdc/chart 4.23.10 → 4.23.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +30989 -29057
- package/examples/feature/bar/example-bar-chart.json +1 -46
- package/examples/feature/bar/lollipop.json +156 -0
- package/examples/feature/combo/planet-combo-example-config.json +99 -9
- package/examples/feature/dev-4261.json +399 -0
- package/examples/feature/forest-plot/{broken.json → linear.json} +55 -50
- package/examples/feature/forest-plot/logarithmic.json +306 -0
- package/examples/feature/line/line-points.json +340 -0
- package/examples/feature/regions/index.json +462 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
- package/examples/sparkline-multilple.json +846 -0
- package/index.html +10 -6
- package/package.json +3 -3
- package/src/CdcChart.tsx +75 -63
- package/src/_stories/Chart.stories.tsx +188 -0
- package/src/_stories/Chart.tooltip.stories.tsx +305 -0
- package/src/_stories/ChartBrush.stories.tsx +19 -0
- package/src/_stories/ChartSuppress.stories.tsx +19 -0
- package/src/_stories/_mock/brush_mock.json +393 -0
- package/src/_stories/_mock/suppress_mock.json +911 -0
- package/src/components/AreaChart.Stacked.jsx +4 -5
- package/src/components/AreaChart.jsx +6 -35
- package/src/components/{BarChart.Horizontal.jsx → BarChart.Horizontal.tsx} +106 -29
- package/src/components/{BarChart.StackedHorizontal.jsx → BarChart.StackedHorizontal.tsx} +22 -17
- package/src/components/{BarChart.StackedVertical.jsx → BarChart.StackedVertical.tsx} +22 -26
- package/src/components/{BarChart.Vertical.jsx → BarChart.Vertical.tsx} +175 -31
- package/src/components/BarChart.jsx +1 -1
- package/src/components/DeviationBar.jsx +4 -3
- package/src/components/EditorPanel.jsx +176 -50
- package/src/components/ForestPlot/ForestPlotProps.ts +11 -0
- package/src/components/ForestPlot/Readme.md +0 -0
- package/src/components/ForestPlot/index.scss +1 -0
- package/src/components/{ForestPlot.jsx → ForestPlot/index.tsx} +51 -31
- package/src/components/ForestPlotSettings.jsx +162 -113
- package/src/components/Legend.jsx +36 -3
- package/src/components/{LineChart.Circle.tsx → LineChart/LineChart.Circle.tsx} +26 -29
- package/src/components/LineChart/LineChartProps.ts +17 -0
- package/src/components/LineChart/index.scss +1 -0
- package/src/components/{LineChart.tsx → LineChart/index.tsx} +64 -35
- package/src/components/LinearChart.jsx +120 -60
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/Series.jsx +22 -3
- package/src/components/ZoomBrush.tsx +168 -0
- package/src/data/initial-state.js +27 -12
- package/src/hooks/useBarChart.js +71 -7
- package/src/hooks/useColorScale.ts +50 -0
- package/src/hooks/useEditorPermissions.js +22 -4
- package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
- package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
- package/src/hooks/{useScales.js → useScales.ts} +64 -17
- package/src/hooks/useTooltip.jsx +68 -41
- package/src/scss/main.scss +3 -35
- package/src/types/ChartConfig.ts +43 -0
- package/src/types/ChartContext.ts +38 -0
- package/src/types/ChartProps.ts +7 -0
- package/src/types/ForestPlot.ts +60 -0
|
@@ -26,6 +26,7 @@ import ConfigContext from '../ConfigContext'
|
|
|
26
26
|
import useReduceData from '../hooks/useReduceData'
|
|
27
27
|
import useRightAxis from '../hooks/useRightAxis'
|
|
28
28
|
import WarningImage from '../images/warning.svg'
|
|
29
|
+
import useMinMax from './../hooks/useMinMax'
|
|
29
30
|
|
|
30
31
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
31
32
|
const TextField = memo(({ label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', i = null, min = null, ...attributes }) => {
|
|
@@ -132,6 +133,81 @@ const Select = memo(({ label, value, options, fieldName, section = null, subsect
|
|
|
132
133
|
)
|
|
133
134
|
})
|
|
134
135
|
|
|
136
|
+
const DataSuppression = memo(({ config, updateConfig, data }) => {
|
|
137
|
+
const getColumnOptions = () => {
|
|
138
|
+
const keys = new Set()
|
|
139
|
+
data.forEach(d => {
|
|
140
|
+
Object.keys(d).forEach(key => {
|
|
141
|
+
keys.add(key)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
return [...keys]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const getIconOptions = () => {
|
|
148
|
+
return ['star']
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let removeColumn = i => {
|
|
152
|
+
let suppressedData = []
|
|
153
|
+
|
|
154
|
+
if (config.suppressedData) {
|
|
155
|
+
suppressedData = [...config.suppressedData]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
suppressedData.splice(i, 1)
|
|
159
|
+
|
|
160
|
+
updateConfig({ ...config, suppressedData })
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let addColumn = () => {
|
|
164
|
+
let suppressedData = config.suppressedData ? [...config.suppressedData] : []
|
|
165
|
+
suppressedData.push({ label: '', column: '', value: '', icon: '' })
|
|
166
|
+
updateConfig({ ...config, suppressedData })
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let update = (fieldName, value, i) => {
|
|
170
|
+
let suppressedData = []
|
|
171
|
+
|
|
172
|
+
if (config.suppressedData) {
|
|
173
|
+
suppressedData = [...config.suppressedData]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
suppressedData[i][fieldName] = value
|
|
177
|
+
updateConfig({ ...config, suppressedData })
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<>
|
|
182
|
+
{config.suppressedData &&
|
|
183
|
+
config.suppressedData.map(({ label, column, value, icon }, i) => {
|
|
184
|
+
return (
|
|
185
|
+
<div key={`suppressed-${i}`} className='edit-block'>
|
|
186
|
+
<button
|
|
187
|
+
type='button'
|
|
188
|
+
className='remove-column'
|
|
189
|
+
onClick={event => {
|
|
190
|
+
event.preventDefault()
|
|
191
|
+
removeColumn(i)
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
Remove
|
|
195
|
+
</button>
|
|
196
|
+
<Select value={column} initial='Select' fieldName='column' label='Column' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getColumnOptions()} />
|
|
197
|
+
<TextField value={value} fieldName='value' label='Value' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
|
|
198
|
+
<Select value={icon} initial='Select' fieldName='icon' label='Icon' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getIconOptions()} />
|
|
199
|
+
<TextField value={label} fieldName='label' label='Label' placeholder='suppressed' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
|
|
200
|
+
</div>
|
|
201
|
+
)
|
|
202
|
+
})}
|
|
203
|
+
|
|
204
|
+
<button type='button' onClick={addColumn} className='btn full-width'>
|
|
205
|
+
Add Suppression Class
|
|
206
|
+
</button>
|
|
207
|
+
</>
|
|
208
|
+
)
|
|
209
|
+
})
|
|
210
|
+
|
|
135
211
|
const Regions = memo(({ config, updateConfig }) => {
|
|
136
212
|
let regionUpdate = (fieldName, value, i) => {
|
|
137
213
|
let regions = []
|
|
@@ -192,7 +268,22 @@ const Regions = memo(({ config, updateConfig }) => {
|
|
|
192
268
|
<TextField value={background} label='Background' fieldName='background' updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
|
|
193
269
|
</div>
|
|
194
270
|
<div className='two-col-inputs'>
|
|
195
|
-
<TextField
|
|
271
|
+
<TextField
|
|
272
|
+
value={from}
|
|
273
|
+
label='From Value'
|
|
274
|
+
fieldName='from'
|
|
275
|
+
updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
|
|
276
|
+
tooltip={
|
|
277
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
278
|
+
<Tooltip.Target>
|
|
279
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
280
|
+
</Tooltip.Target>
|
|
281
|
+
<Tooltip.Content>
|
|
282
|
+
<p>The date needs to be in the original format of the data. Not the displayed format of the data.</p>
|
|
283
|
+
</Tooltip.Content>
|
|
284
|
+
</Tooltip>
|
|
285
|
+
}
|
|
286
|
+
/>
|
|
196
287
|
<TextField value={to} label='To Value' fieldName='to' updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
|
|
197
288
|
</div>
|
|
198
289
|
</div>
|
|
@@ -219,6 +310,9 @@ const EditorPanel = () => {
|
|
|
219
310
|
|
|
220
311
|
const { twoColorPalettes, sequential, nonSequential } = useColorPalette(config, updateConfig)
|
|
221
312
|
|
|
313
|
+
const properties = { data, config }
|
|
314
|
+
const { leftMax, rightMax } = useMinMax(properties)
|
|
315
|
+
|
|
222
316
|
const {
|
|
223
317
|
enabledChartTypes,
|
|
224
318
|
headerColors,
|
|
@@ -233,6 +327,7 @@ const EditorPanel = () => {
|
|
|
233
327
|
visHasDataCutoff,
|
|
234
328
|
visCanAnimate,
|
|
235
329
|
visHasLegend,
|
|
330
|
+
visHasBrushChart,
|
|
236
331
|
visSupportsDateCategoryAxisLabel,
|
|
237
332
|
visSupportsDateCategoryAxisLine,
|
|
238
333
|
visSupportsDateCategoryAxisTicks,
|
|
@@ -254,7 +349,8 @@ const EditorPanel = () => {
|
|
|
254
349
|
visSupportsTooltipOpacity,
|
|
255
350
|
visSupportsRankByValue,
|
|
256
351
|
visSupportsResponsiveTicks,
|
|
257
|
-
visSupportsDateCategoryHeight
|
|
352
|
+
visSupportsDateCategoryHeight,
|
|
353
|
+
visHasDataSuppression
|
|
258
354
|
} = useEditorPermissions()
|
|
259
355
|
|
|
260
356
|
// argument acts as props
|
|
@@ -280,11 +376,6 @@ const EditorPanel = () => {
|
|
|
280
376
|
...config,
|
|
281
377
|
series: newSeries
|
|
282
378
|
})
|
|
283
|
-
|
|
284
|
-
// disable brush if categorical - or - for now if not Area Chart
|
|
285
|
-
if (config.xAxis.type === 'categorical' || config.visualizationType !== 'Area Chart') {
|
|
286
|
-
config.showChartBrush = false
|
|
287
|
-
}
|
|
288
379
|
}, [config.visualizationType]) // eslint-disable-line
|
|
289
380
|
|
|
290
381
|
// Scatter Plots default date/category axis is 'continuous'
|
|
@@ -300,6 +391,12 @@ const EditorPanel = () => {
|
|
|
300
391
|
}
|
|
301
392
|
}, [])
|
|
302
393
|
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
if (config.visualizationType !== 'Bar') {
|
|
396
|
+
updateConfig({ ...config, tooltips: { ...config.tooltips, singleSeries: false } })
|
|
397
|
+
}
|
|
398
|
+
}, [config.visualizationType])
|
|
399
|
+
|
|
303
400
|
const { hasRightAxis } = useRightAxis({ config: config, yMax: config.yAxis.size, data: config.data, updateConfig })
|
|
304
401
|
|
|
305
402
|
const getItemStyle = (isDragging, draggableStyle) => ({
|
|
@@ -745,23 +842,45 @@ const EditorPanel = () => {
|
|
|
745
842
|
}
|
|
746
843
|
|
|
747
844
|
const section = config.orientation === 'horizontal' ? 'xAxis' : 'yAxis'
|
|
748
|
-
const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '' })
|
|
845
|
+
const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '', rightMaxMessage: '', minMsgRight: '' })
|
|
749
846
|
|
|
750
847
|
const validateMaxValue = () => {
|
|
751
848
|
const enteredValue = config[section].max
|
|
849
|
+
const enteredRightMax = config[section].rightMax
|
|
850
|
+
|
|
752
851
|
let message = ''
|
|
852
|
+
let rightMaxMessage = ''
|
|
853
|
+
|
|
854
|
+
if (config.visualizationType !== 'Combo') {
|
|
855
|
+
switch (true) {
|
|
856
|
+
case enteredValue && parseFloat(enteredValue) < parseFloat(maxValue) && existPositiveValue:
|
|
857
|
+
message = 'Max value must be more than ' + maxValue
|
|
858
|
+
break
|
|
859
|
+
case enteredValue && parseFloat(enteredValue) < 0 && !existPositiveValue:
|
|
860
|
+
message = 'Value must be more than or equal to 0'
|
|
861
|
+
break
|
|
862
|
+
default:
|
|
863
|
+
message = ''
|
|
864
|
+
}
|
|
865
|
+
}
|
|
753
866
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
867
|
+
if (config.visualizationType === 'Combo') {
|
|
868
|
+
switch (true) {
|
|
869
|
+
case enteredValue && parseFloat(enteredValue) < leftMax:
|
|
870
|
+
message = 'Max value must be more than ' + leftMax
|
|
871
|
+
break
|
|
872
|
+
case enteredRightMax && parseFloat(enteredRightMax) < rightMax:
|
|
873
|
+
rightMaxMessage = 'Max value must be more than ' + rightMax
|
|
874
|
+
break
|
|
875
|
+
case enteredValue && parseFloat(enteredValue) < 0 && !existPositiveValue:
|
|
876
|
+
message = 'Value must be more than or equal to 0'
|
|
877
|
+
break
|
|
878
|
+
default:
|
|
879
|
+
message = ''
|
|
880
|
+
}
|
|
763
881
|
}
|
|
764
|
-
|
|
882
|
+
|
|
883
|
+
setWarningMsg(prevMsg => ({ ...prevMsg, maxMsg: message, rightMaxMessage: rightMaxMessage }))
|
|
765
884
|
}
|
|
766
885
|
|
|
767
886
|
const validateMinValue = () => {
|
|
@@ -840,7 +959,7 @@ const EditorPanel = () => {
|
|
|
840
959
|
]
|
|
841
960
|
|
|
842
961
|
if (config.data && config.series) {
|
|
843
|
-
Object.keys(config.data[0]).map(colName => {
|
|
962
|
+
Object.keys(config.data?.[0] || []).map(colName => {
|
|
844
963
|
// OMIT ANY COLUMNS THAT ARE IN DATA SERIES!
|
|
845
964
|
const found = config?.series.some(series => series.dataKey === colName)
|
|
846
965
|
if (colName !== config.xAxis.dataKey && !found) {
|
|
@@ -1092,7 +1211,7 @@ const EditorPanel = () => {
|
|
|
1092
1211
|
/>
|
|
1093
1212
|
|
|
1094
1213
|
<TextField
|
|
1095
|
-
type='
|
|
1214
|
+
type='textarea'
|
|
1096
1215
|
value={config.description}
|
|
1097
1216
|
fieldName='description'
|
|
1098
1217
|
label='Subtext/Citation'
|
|
@@ -1184,7 +1303,6 @@ const EditorPanel = () => {
|
|
|
1184
1303
|
</Series.Wrapper>
|
|
1185
1304
|
)}
|
|
1186
1305
|
</>
|
|
1187
|
-
|
|
1188
1306
|
{config.series && config.series.length <= 1 && config.visualizationType === 'Bar' && (
|
|
1189
1307
|
<>
|
|
1190
1308
|
<span className='divider-heading'>Confidence Keys</span>
|
|
@@ -1192,8 +1310,8 @@ const EditorPanel = () => {
|
|
|
1192
1310
|
<Select value={config.confidenceKeys.lower || ''} section='confidenceKeys' fieldName='lower' label='Lower' updateField={updateField} initial='Select' options={getColumns()} />
|
|
1193
1311
|
</>
|
|
1194
1312
|
)}
|
|
1195
|
-
|
|
1196
1313
|
{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']} />}
|
|
1314
|
+
{/* {visHasDataSuppression() && <DataSuppression config={config} updateConfig={updateConfig} data={data} />} */}
|
|
1197
1315
|
</AccordionItemPanel>
|
|
1198
1316
|
</AccordionItem>
|
|
1199
1317
|
)}
|
|
@@ -1367,7 +1485,9 @@ const EditorPanel = () => {
|
|
|
1367
1485
|
{config.visualizationType !== 'Pie' && (
|
|
1368
1486
|
<>
|
|
1369
1487
|
<TextField value={config.yAxis.label} section='yAxis' fieldName='label' label='Label' updateField={updateField} />
|
|
1370
|
-
{config.runtime.seriesKeys && config.runtime.seriesKeys.length === 1 &&
|
|
1488
|
+
{config.runtime.seriesKeys && config.runtime.seriesKeys.length === 1 && !['Box Plot', 'Deviation Bar', 'Forest Plot'].includes(config.visualizationType) && (
|
|
1489
|
+
<CheckBox value={config.isLegendValue} fieldName='isLegendValue' label='Use Legend Value in Hover' updateField={updateField} />
|
|
1490
|
+
)}
|
|
1371
1491
|
<TextField value={config.yAxis.numTicks} placeholder='Auto' type='number' section='yAxis' fieldName='numTicks' label='Number of ticks' className='number-narrow' updateField={updateField} />
|
|
1372
1492
|
{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} />}
|
|
1373
1493
|
<TextField
|
|
@@ -1505,9 +1625,9 @@ const EditorPanel = () => {
|
|
|
1505
1625
|
<CheckBox value={config.yAxis.hideLabel} section='yAxis' fieldName='hideLabel' label='Hide Label' updateField={updateField} />
|
|
1506
1626
|
<CheckBox value={config.yAxis.hideTicks} section='yAxis' fieldName='hideTicks' label='Hide Ticks' updateField={updateField} />
|
|
1507
1627
|
|
|
1508
|
-
<TextField value={config.yAxis.max} section='yAxis' fieldName='max' type='number' label='max value' placeholder='Auto' updateField={updateField} />
|
|
1628
|
+
<TextField value={config.yAxis.max} section='yAxis' fieldName='max' type='number' label='left axis max value' placeholder='Auto' updateField={updateField} />
|
|
1509
1629
|
<span style={{ color: 'red', display: 'block' }}>{warningMsg.maxMsg}</span>
|
|
1510
|
-
<TextField value={config.yAxis.min} section='yAxis' fieldName='min' type='number' label='min value' placeholder='Auto' updateField={updateField} />
|
|
1630
|
+
<TextField value={config.yAxis.min} section='yAxis' fieldName='min' type='number' label='left axis min value' placeholder='Auto' updateField={updateField} />
|
|
1511
1631
|
<span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
|
|
1512
1632
|
</>
|
|
1513
1633
|
)
|
|
@@ -1826,6 +1946,11 @@ const EditorPanel = () => {
|
|
|
1826
1946
|
<CheckBox value={config.yAxis.rightHideAxis} section='yAxis' fieldName='rightHideAxis' label='Hide Axis' updateField={updateField} />
|
|
1827
1947
|
<CheckBox value={config.yAxis.rightHideLabel} section='yAxis' fieldName='rightHideLabel' label='Hide Label' updateField={updateField} />
|
|
1828
1948
|
<CheckBox value={config.yAxis.rightHideTicks} section='yAxis' fieldName='rightHideTicks' label='Hide Ticks' updateField={updateField} />
|
|
1949
|
+
|
|
1950
|
+
<TextField value={config.yAxis.max} section='yAxis' fieldName='rightMax' type='number' label='right axis max value' placeholder='Auto' updateField={updateField} />
|
|
1951
|
+
<span style={{ color: 'red', display: 'block' }}>{warningMsg.rightMaxMessage}</span>
|
|
1952
|
+
<TextField value={config.yAxis.min} section='yAxis' fieldName='rightMin' type='number' label='right axis min value' placeholder='Auto' updateField={updateField} />
|
|
1953
|
+
<span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
|
|
1829
1954
|
</AccordionItemPanel>
|
|
1830
1955
|
</AccordionItem>
|
|
1831
1956
|
)}
|
|
@@ -1984,6 +2109,7 @@ const EditorPanel = () => {
|
|
|
1984
2109
|
}
|
|
1985
2110
|
updateField={updateField}
|
|
1986
2111
|
/>
|
|
2112
|
+
{/* {visHasBrushChart && <CheckBox value={config.brush.active} section='brush' fieldName='active' label='Brush Slider ' updateField={updateField} />} */}
|
|
1987
2113
|
|
|
1988
2114
|
{config.exclusions.active && (
|
|
1989
2115
|
<>
|
|
@@ -2027,8 +2153,7 @@ const EditorPanel = () => {
|
|
|
2027
2153
|
|
|
2028
2154
|
{/* Hiding this for now, not interested in moving the axis lines away from chart comp. right now. */}
|
|
2029
2155
|
{/* <TextField value={config.xAxis.axisPadding} type='number' max={10} min={0} section='xAxis' fieldName='axisPadding' label={'Axis Padding'} className='number-narrow' updateField={updateField} /> */}
|
|
2030
|
-
|
|
2031
|
-
{config.xAxis.type === 'continuous' && (
|
|
2156
|
+
{(config.xAxis.type === 'continuous' || config.forestPlot.type === 'Logarithmic') && (
|
|
2032
2157
|
<>
|
|
2033
2158
|
<CheckBox value={config.dataFormat.bottomCommas} section='dataFormat' fieldName='bottomCommas' label='Add commas' updateField={updateField} />
|
|
2034
2159
|
<TextField value={config.dataFormat.bottomRoundTo} type='number' section='dataFormat' fieldName='bottomRoundTo' label='Round to decimal point' className='number-narrow' updateField={updateField} min={0} />
|
|
@@ -2428,7 +2553,7 @@ const EditorPanel = () => {
|
|
|
2428
2553
|
</AccordionItem>
|
|
2429
2554
|
)}{' '}
|
|
2430
2555
|
{/* Columns */}
|
|
2431
|
-
{config.visualizationType !== 'Box Plot' &&
|
|
2556
|
+
{config.visualizationType !== 'Box Plot' && (
|
|
2432
2557
|
<AccordionItem>
|
|
2433
2558
|
<AccordionItemHeading>
|
|
2434
2559
|
<AccordionItemButton>Columns</AccordionItemButton>
|
|
@@ -2491,32 +2616,33 @@ const EditorPanel = () => {
|
|
|
2491
2616
|
</label>
|
|
2492
2617
|
</li>
|
|
2493
2618
|
<li>
|
|
2494
|
-
|
|
2495
|
-
<
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2619
|
+
{config.table.showVertical && (
|
|
2620
|
+
<label className='checkbox'>
|
|
2621
|
+
<input
|
|
2622
|
+
type='checkbox'
|
|
2623
|
+
checked={config.columns[val].dataTable}
|
|
2624
|
+
onChange={event => {
|
|
2625
|
+
editColumn(val, 'dataTable', event.target.checked)
|
|
2626
|
+
}}
|
|
2627
|
+
/>
|
|
2628
|
+
<span className='edit-label'>Show in Data Table</span>
|
|
2629
|
+
</label>
|
|
2630
|
+
)}
|
|
2504
2631
|
</li>
|
|
2505
2632
|
{/* disable for now */}
|
|
2506
|
-
|
|
2633
|
+
|
|
2507
2634
|
<li>
|
|
2508
2635
|
<label className='checkbox'>
|
|
2509
2636
|
<input
|
|
2510
2637
|
type='checkbox'
|
|
2511
|
-
checked={config.columns[val].
|
|
2638
|
+
checked={config.columns[val].tooltips || false}
|
|
2512
2639
|
onChange={event => {
|
|
2513
|
-
|
|
2640
|
+
updateSeriesTooltip(val, event.target.checked)
|
|
2514
2641
|
}}
|
|
2515
2642
|
/>
|
|
2516
|
-
<span className='edit-label'>
|
|
2643
|
+
<span className='edit-label'>Show in tooltip</span>
|
|
2517
2644
|
</label>
|
|
2518
2645
|
</li>
|
|
2519
|
-
*/}
|
|
2520
2646
|
|
|
2521
2647
|
{config.visualizationType === 'Forest Plot' && (
|
|
2522
2648
|
<>
|
|
@@ -2684,22 +2810,18 @@ const EditorPanel = () => {
|
|
|
2684
2810
|
</Tooltip>
|
|
2685
2811
|
}
|
|
2686
2812
|
/>
|
|
2687
|
-
|
|
2688
2813
|
{/* {config.visualizationType === 'Box Plot' &&
|
|
2689
2814
|
<>
|
|
2690
2815
|
<CheckBox value={config.boxplot.legend.displayHowToReadText} fieldName='displayHowToReadText' section='boxplot' subsection='legend' label='Display How To Read Text' updateField={updateField} />
|
|
2691
2816
|
<TextField type='textarea' value={config.boxplot.legend.howToReadText} updateField={updateField} fieldName='howToReadText' section='boxplot' subsection='legend' label='How to read text' />
|
|
2692
2817
|
</>
|
|
2693
2818
|
} */}
|
|
2694
|
-
|
|
2695
|
-
{config.visualizationType !== 'Box Plot' && <CheckBox value={config.legend.showLegendValuesTooltip ? true : false} section='legend' fieldName='showLegendValuesTooltip' label='Show Legend Values in Tooltip' updateField={updateField} />}
|
|
2696
|
-
|
|
2697
2819
|
{config.visualizationType === 'Line' && <CheckBox value={config.legend.lineMode} section='legend' fieldName='lineMode' label='Show Lined Style Legend' updateField={updateField} />}
|
|
2698
|
-
|
|
2699
2820
|
{config.visualizationType === 'Bar' && config.visualizationSubType === 'regular' && config.runtime.seriesKeys.length === 1 && (
|
|
2700
2821
|
<Select value={config.legend.colorCode} section='legend' fieldName='colorCode' label='Color code by category' initial='Select' updateField={updateField} options={getDataValueOptions(data)} />
|
|
2701
2822
|
)}
|
|
2702
2823
|
<Select value={config.legend.behavior} section='legend' fieldName='behavior' label='Legend Behavior (When clicked)' updateField={updateField} options={['highlight', 'isolate']} />
|
|
2824
|
+
{config.legend.behavior === 'highlight' && config.tooltips.singleSeries && <CheckBox value={config.legend.highlightOnHover} section='legend' fieldName='highlightOnHover' label='HIGHLIGHT DATA SERIES ON HOVER' updateField={updateField} />}
|
|
2703
2825
|
<TextField value={config.legend.label} section='legend' fieldName='label' label='Title' updateField={updateField} />
|
|
2704
2826
|
<Select value={config.legend.position} section='legend' fieldName='position' label='Position' updateField={updateField} options={['right', 'left', 'bottom']} />
|
|
2705
2827
|
{config.legend.position === 'bottom' && (
|
|
@@ -2906,7 +3028,10 @@ const EditorPanel = () => {
|
|
|
2906
3028
|
{/*<CheckBox value={config.animateReplay} fieldName="animateReplay" label="Replay Animation When Filters Are Changed" updateField={updateField} />*/}
|
|
2907
3029
|
|
|
2908
3030
|
{((config.series?.some(series => series.type === 'Line' || series.type === 'dashed-lg' || series.type === 'dashed-sm' || series.type === 'dashed-md') && config.visualizationType === 'Combo') || config.visualizationType === 'Line') && (
|
|
2909
|
-
|
|
3031
|
+
<>
|
|
3032
|
+
<Select value={config.lineDatapointStyle} fieldName='lineDatapointStyle' label='Line Datapoint Style' updateField={updateField} options={['hidden', 'hover', 'always show']} />
|
|
3033
|
+
<Select value={config.lineDatapointColor} fieldName='lineDatapointColor' label='Line Datapoint Color' updateField={updateField} options={['Same as Line', 'Lighter than Line']} />
|
|
3034
|
+
</>
|
|
2910
3035
|
)}
|
|
2911
3036
|
|
|
2912
3037
|
{/* eslint-disable */}
|
|
@@ -3105,6 +3230,7 @@ const EditorPanel = () => {
|
|
|
3105
3230
|
/>
|
|
3106
3231
|
</label>
|
|
3107
3232
|
)}
|
|
3233
|
+
{config.visualizationType === 'Bar' && <CheckBox value={config.tooltips.singleSeries} fieldName='singleSeries' section='tooltips' label='SHOW HOVER FOR SINGLE DATA SERIES' updateField={updateField} />}
|
|
3108
3234
|
|
|
3109
3235
|
<label>
|
|
3110
3236
|
<span className='edit-label column-heading'>No Data Message</span>
|
|
@@ -3195,7 +3321,7 @@ const EditorPanel = () => {
|
|
|
3195
3321
|
section='table'
|
|
3196
3322
|
type='textarea'
|
|
3197
3323
|
fieldName='caption'
|
|
3198
|
-
label='
|
|
3324
|
+
label='Screen Reader Description'
|
|
3199
3325
|
placeholder=' Data table'
|
|
3200
3326
|
tooltip={
|
|
3201
3327
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export type ForestPlotProps = {
|
|
4
|
+
xScale: Function
|
|
5
|
+
yScale: Function
|
|
6
|
+
config: ChartConfig
|
|
7
|
+
height: number
|
|
8
|
+
width: number
|
|
9
|
+
handleTooltipMouseOff: Function
|
|
10
|
+
handleTooltipMouseOver: Function
|
|
11
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Forest Plot Styles...
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useContext, useEffect } from 'react'
|
|
2
2
|
|
|
3
3
|
// visx
|
|
4
4
|
import { Group } from '@visx/group'
|
|
5
5
|
import { Line, Bar, Circle, LinePath } from '@visx/shape'
|
|
6
|
-
import { GlyphDiamond } from '@visx/glyph'
|
|
7
6
|
import { Text } from '@visx/text'
|
|
8
7
|
import { scaleLinear } from '@visx/scale'
|
|
9
8
|
import { curveLinearClosed } from '@visx/curve'
|
|
10
9
|
|
|
10
|
+
// types
|
|
11
|
+
import { type ForestPlotProps } from '@cdc/chart/src/components/ForestPlot/ForestPlotProps'
|
|
12
|
+
import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
13
|
+
import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
|
|
14
|
+
|
|
11
15
|
// cdc
|
|
12
|
-
import ConfigContext from '
|
|
16
|
+
import ConfigContext from '../../ConfigContext'
|
|
13
17
|
import { getFontSize } from '@cdc/core/helpers/cove/number'
|
|
14
18
|
|
|
15
|
-
const ForestPlot = props => {
|
|
16
|
-
const {
|
|
17
|
-
const { xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver
|
|
18
|
-
const { forestPlot
|
|
19
|
-
const [screenWidth, screenHeight] = dimensions
|
|
19
|
+
const ForestPlot = (props: ForestPlotProps) => {
|
|
20
|
+
const { rawData: data, updateConfig } = useContext<ChartContext>(ConfigContext)
|
|
21
|
+
const { xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver } = props
|
|
22
|
+
const { forestPlot } = config as ChartConfig
|
|
20
23
|
|
|
21
24
|
// Requirements for forest plot
|
|
22
25
|
// - force legend to be hidden for this chart type
|
|
@@ -37,16 +40,17 @@ const ForestPlot = props => {
|
|
|
37
40
|
}
|
|
38
41
|
}, [])
|
|
39
42
|
|
|
40
|
-
const
|
|
43
|
+
const pooledData = config.data.find(d => d[config.xAxis.dataKey] === config.forestPlot.pooledResult.column)
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
const regressionPoints = pooledData
|
|
46
|
+
? [
|
|
47
|
+
{ x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) },
|
|
48
|
+
{ x: xScale(pooledData[config.forestPlot.estimateField]), y: height - forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
|
|
49
|
+
{ x: xScale(pooledData[config.forestPlot.upper]), y: height - Number(config.forestPlot.rowHeight) },
|
|
50
|
+
{ x: xScale(pooledData[config.forestPlot.estimateField]), y: height + forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
|
|
51
|
+
{ x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) }
|
|
52
|
+
]
|
|
53
|
+
: []
|
|
50
54
|
|
|
51
55
|
const topMarginOffset = config.forestPlot.rowHeight
|
|
52
56
|
|
|
@@ -64,38 +68,40 @@ const ForestPlot = props => {
|
|
|
64
68
|
.map(entry => entry[1])
|
|
65
69
|
.filter(entry => entry.forestPlot === true)
|
|
66
70
|
|
|
67
|
-
const rightOffset = forestPlot.rightWidthOffset !== 0 ? (Number(forestPlot.rightWidthOffset) / 100) * width : width
|
|
68
|
-
const leftOffset = forestPlot.leftWidthOffset !== 0 ? (Number(forestPlot.leftWidthOffset) / 100) * width : width
|
|
69
|
-
const chartWidth = width - rightOffset - leftOffset
|
|
70
|
-
|
|
71
71
|
return (
|
|
72
72
|
<>
|
|
73
73
|
<Group>
|
|
74
|
-
{forestPlot.title
|
|
75
|
-
<Text className={`forest-plot--title`} x={xScale(0)} y={0} textAnchor='middle' verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
|
|
74
|
+
{forestPlot.title && (
|
|
75
|
+
<Text className={`forest-plot--title`} x={forestPlot.type === 'Linear' ? xScale(0) : xScale(1)} y={0} textAnchor='middle' verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
|
|
76
76
|
{forestPlot.title}
|
|
77
77
|
</Text>
|
|
78
78
|
)}
|
|
79
|
-
|
|
80
|
-
{
|
|
79
|
+
|
|
80
|
+
{/* Line of no effect on Continuous Scale. */}
|
|
81
|
+
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Linear' && <Line from={{ x: xScale(0), y: 0 + topMarginOffset }} to={{ x: xScale(0), y: height }} className='forestplot__line-of-no-effect' stroke={forestPlot.regression.baseLineColor || 'black'} />}
|
|
82
|
+
|
|
83
|
+
{/* Line of no effect on Logarithmic Scale. */}
|
|
84
|
+
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Logarithmic' && <Line from={{ x: xScale(1), y: 0 + topMarginOffset }} to={{ x: xScale(1), y: height }} className='forestplot__line-of-no-effect' stroke={forestPlot.regression.baseLineColor || 'black'} />}
|
|
81
85
|
|
|
82
86
|
{data.map((d, i) => {
|
|
83
87
|
// calculate both square and circle size based on radius.min and radius.max
|
|
84
88
|
const scaleRadius = scaleLinear({
|
|
85
|
-
domain:
|
|
89
|
+
domain: data.map(d => d[forestPlot.radius.scalingColumn]),
|
|
86
90
|
range: [forestPlot.radius.min, forestPlot.radius.max]
|
|
87
91
|
})
|
|
88
92
|
|
|
89
93
|
// glyph settings
|
|
90
|
-
const
|
|
91
|
-
const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) : 4
|
|
94
|
+
const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4
|
|
92
95
|
const shapeColor = forestPlot.colors.shape ? forestPlot.colors.shape : 'black'
|
|
93
96
|
const lineColor = forestPlot.colors.line ? forestPlot.colors.line : 'black'
|
|
94
97
|
|
|
95
98
|
// ci size
|
|
96
99
|
const ciEndSize = 4
|
|
97
100
|
|
|
98
|
-
|
|
101
|
+
// Don't run calculations on the pooled column
|
|
102
|
+
const isTotalColumn = d[config.xAxis.dataKey] === forestPlot.pooledResult.column
|
|
103
|
+
|
|
104
|
+
return !isTotalColumn ? (
|
|
99
105
|
<Group>
|
|
100
106
|
{/* Confidence Interval Paths */}
|
|
101
107
|
<path
|
|
@@ -121,16 +127,17 @@ const ForestPlot = props => {
|
|
|
121
127
|
{/* main line */}
|
|
122
128
|
<line stroke={lineColor} className={`line-${d[config.yAxis.dataKey]}`} key={i} x1={xScale(d[forestPlot.lower])} x2={xScale(d[forestPlot.upper])} y1={yScale(i)} y2={yScale(i)} />
|
|
123
129
|
{forestPlot.shape === 'circle' && (
|
|
124
|
-
<Circle className='forest-plot--circle' cx={xScale(Number(d[forestPlot.estimateField]))} cy={yScale(i)} r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.
|
|
130
|
+
<Circle className='forest-plot--circle' cx={xScale(Number(d[forestPlot.estimateField]))} cy={yScale(i)} r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />
|
|
125
131
|
)}
|
|
126
132
|
{forestPlot.shape === 'square' && <rect className='forest-plot--square' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i) - rectSize / 2} width={rectSize} height={rectSize} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />}
|
|
127
|
-
{forestPlot.shape === 'diamond' && <GlyphDiamond className='forest-plot--diamond' size={diamondSize} top={yScale(i)} left={xScale(Number(d[forestPlot.estimateField]))} fill={shapeColor} />}
|
|
128
133
|
{forestPlot.shape === 'text' && (
|
|
129
134
|
<Text className='forest-plot--text' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i)} textAnchor='middle' verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={shapeColor}>
|
|
130
135
|
{d[forestPlot.estimateField]}
|
|
131
136
|
</Text>
|
|
132
137
|
)}
|
|
133
138
|
</Group>
|
|
139
|
+
) : (
|
|
140
|
+
<LinePath data={regressionPoints} x={d => d.x} y={d => d.y - getFontSize(config.fontSize) / 2} stroke='black' strokeWidth={2} fill={'black'} curve={curveLinearClosed} />
|
|
134
141
|
)
|
|
135
142
|
})}
|
|
136
143
|
|
|
@@ -184,6 +191,19 @@ const ForestPlot = props => {
|
|
|
184
191
|
</Text>
|
|
185
192
|
)
|
|
186
193
|
})}
|
|
194
|
+
|
|
195
|
+
{/* left bottom label */}
|
|
196
|
+
{forestPlot.leftLabel && (
|
|
197
|
+
<Text className='forest-plot__left-label' x={forestPlot.type === 'Linear' ? xScale(0) - 25 : xScale(1) - 25} y={height + 50} textAnchor='end'>
|
|
198
|
+
{forestPlot.leftLabel}
|
|
199
|
+
</Text>
|
|
200
|
+
)}
|
|
201
|
+
|
|
202
|
+
{forestPlot.rightLabel && (
|
|
203
|
+
<Text className='forest-plot__right-label' x={forestPlot.type === 'Linear' ? xScale(0) + 25 : xScale(1) + 25} y={height + 50} textAnchor='start'>
|
|
204
|
+
{forestPlot.rightLabel}
|
|
205
|
+
</Text>
|
|
206
|
+
)}
|
|
187
207
|
</>
|
|
188
208
|
)
|
|
189
209
|
}
|