@cdc/chart 4.23.7 → 4.23.8

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 (38) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +27964 -26942
  3. package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
  4. package/examples/feature/__data__/city-temperature.json +2198 -0
  5. package/examples/feature/area/area-chart-category.json +45 -45
  6. package/examples/feature/area/area-chart-date-apple.json +10376 -0
  7. package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
  8. package/examples/feature/area/area-chart-date.json +111 -3
  9. package/examples/feature/forest-plot/broken.json +700 -0
  10. package/examples/feature/forest-plot/data.csv +24 -0
  11. package/examples/feature/forest-plot/forest-plot.json +717 -0
  12. package/examples/feature/pie/planet-pie-example-config.json +1 -1
  13. package/examples/private/confidence_interval_test.json +248 -0
  14. package/examples/private/tooltip-issue.json +45275 -0
  15. package/index.html +13 -11
  16. package/package.json +4 -3
  17. package/src/CdcChart.jsx +24 -14
  18. package/src/components/AreaChart.jsx +84 -59
  19. package/src/components/BarChart.Horizontal.jsx +251 -0
  20. package/src/components/BarChart.StackedHorizontal.jsx +118 -0
  21. package/src/components/BarChart.StackedVertical.jsx +93 -0
  22. package/src/components/BarChart.Vertical.jsx +204 -0
  23. package/src/components/BarChart.jsx +14 -674
  24. package/src/components/BarChartType.jsx +15 -0
  25. package/src/components/BrushHandle.jsx +17 -0
  26. package/src/components/DataTable.jsx +63 -21
  27. package/src/components/EditorPanel.jsx +351 -303
  28. package/src/components/ForestPlot.jsx +191 -0
  29. package/src/components/ForestPlotSettings.jsx +508 -0
  30. package/src/components/LineChart.jsx +2 -2
  31. package/src/components/LinearChart.jsx +115 -310
  32. package/src/data/initial-state.js +43 -0
  33. package/src/hooks/useBarChart.js +186 -0
  34. package/src/hooks/useEditorPermissions.js +218 -0
  35. package/src/hooks/useMinMax.js +15 -3
  36. package/src/hooks/useScales.js +45 -2
  37. package/src/hooks/useTooltip.jsx +407 -0
  38. package/src/scss/main.scss +7 -0
@@ -0,0 +1,191 @@
1
+ import React, { useContext, useEffect } from 'react'
2
+
3
+ // visx
4
+ import { Group } from '@visx/group'
5
+ import { Line, Bar, Circle, LinePath } from '@visx/shape'
6
+ import { GlyphDiamond } from '@visx/glyph'
7
+ import { Text } from '@visx/text'
8
+ import { scaleLinear } from '@visx/scale'
9
+ import { curveLinearClosed } from '@visx/curve'
10
+
11
+ // cdc
12
+ import ConfigContext from '../ConfigContext'
13
+ import { getFontSize } from '@cdc/core/helpers/cove/number'
14
+
15
+ const ForestPlot = props => {
16
+ const { transformedData: data, updateConfig, dimensions, rawData } = useContext(ConfigContext)
17
+ const { xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver, maxWidth, maxHeight } = props
18
+ const { forestPlot, runtime, dataFormat } = config
19
+ const [screenWidth, screenHeight] = dimensions
20
+
21
+ // Requirements for forest plot
22
+ // - force legend to be hidden for this chart type
23
+ // - reset the date category axis to zero
24
+ useEffect(() => {
25
+ if (!config.legend.hide) {
26
+ updateConfig({
27
+ ...config,
28
+ legend: {
29
+ ...config.legend,
30
+ hide: true
31
+ },
32
+ xAxis: {
33
+ ...config.xAxis,
34
+ size: 0
35
+ }
36
+ })
37
+ }
38
+ }, [])
39
+
40
+ const diamondHeight = 5
41
+
42
+ // diamond path
43
+ const regressionPoints = [
44
+ { x: xScale(forestPlot.regression.lower), y: height - Number(config.forestPlot.rowHeight) },
45
+ { x: xScale(forestPlot.regression.estimateField), y: height - diamondHeight - Number(config.forestPlot.rowHeight) },
46
+ { x: xScale(forestPlot.regression.upper), y: height - Number(config.forestPlot.rowHeight) },
47
+ { x: xScale(forestPlot.regression.estimateField), y: height + diamondHeight - Number(config.forestPlot.rowHeight) },
48
+ { x: xScale(forestPlot.regression.lower), y: height - Number(config.forestPlot.rowHeight) }
49
+ ]
50
+
51
+ const topMarginOffset = config.forestPlot.rowHeight
52
+
53
+ const topLine = [
54
+ { x: 0, y: topMarginOffset },
55
+ { x: width, y: topMarginOffset }
56
+ ]
57
+
58
+ const bottomLine = [
59
+ { x: 0, y: height },
60
+ { x: width, y: height }
61
+ ]
62
+
63
+ const columnsOnChart = Object.entries(config.columns)
64
+ .map(entry => entry[1])
65
+ .filter(entry => entry.forestPlot === true)
66
+
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
+ return (
72
+ <>
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'}>
76
+ {forestPlot.title}
77
+ </Text>
78
+ )}
79
+ {forestPlot.regression.showBaseLine && <Line from={{ x: xScale(forestPlot.regression.estimateField), y: 0 + topMarginOffset }} to={{ x: xScale(forestPlot.regression.estimateField), y: height }} className='forestplot__baseline' stroke={forestPlot.regression.baseLineColor || 'black'} />}
80
+ {forestPlot.showZeroLine && <Line from={{ x: xScale(0), y: 0 + topMarginOffset }} to={{ x: xScale(0), y: height }} className='forestplot__zero-line' stroke='gray' strokeDasharray={'5 5'} />}
81
+
82
+ {data.map((d, i) => {
83
+ // calculate both square and circle size based on radius.min and radius.max
84
+ const scaleRadius = scaleLinear({
85
+ domain: xScale.domain(),
86
+ range: [forestPlot.radius.min, forestPlot.radius.max]
87
+ })
88
+
89
+ // glyph settings
90
+ const diamondSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) * 5 : 4
91
+ const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) : 4
92
+ const shapeColor = forestPlot.colors.shape ? forestPlot.colors.shape : 'black'
93
+ const lineColor = forestPlot.colors.line ? forestPlot.colors.line : 'black'
94
+
95
+ // ci size
96
+ const ciEndSize = 4
97
+
98
+ return (
99
+ <Group>
100
+ {/* Confidence Interval Paths */}
101
+ <path
102
+ stroke={lineColor}
103
+ strokeWidth={1}
104
+ className='lower-ci'
105
+ d={`
106
+ M${xScale(d[forestPlot.lower])} ${yScale(i) - Number(ciEndSize)}
107
+ L${xScale(d[forestPlot.lower])} ${yScale(i) + Number(ciEndSize)}
108
+ `}
109
+ />
110
+
111
+ <path
112
+ stroke={lineColor}
113
+ strokeWidth={1}
114
+ className='upper-ci'
115
+ d={`
116
+ M${xScale(d[forestPlot.upper])} ${yScale(i) - Number(ciEndSize)}
117
+ L${xScale(d[forestPlot.upper])} ${yScale(i) + Number(ciEndSize)}
118
+ `}
119
+ />
120
+
121
+ {/* main line */}
122
+ <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
+ {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.estimateField]) : 4} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />
125
+ )}
126
+ {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
+ {forestPlot.shape === 'text' && (
129
+ <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
+ {d[forestPlot.estimateField]}
131
+ </Text>
132
+ )}
133
+ </Group>
134
+ )
135
+ })}
136
+
137
+ {/* regression diamond */}
138
+ {regressionPoints && forestPlot.regression.showDiamond && <LinePath data={regressionPoints} x={d => d.x} y={d => d.y} stroke='black' strokeWidth={2} fill={forestPlot.regression.baseLineColor} curve={curveLinearClosed} />}
139
+ {/* regression text */}
140
+ {forestPlot.regression.description && (
141
+ <Text x={0 - Number(config.xAxis.size)} width={width} y={height - config.forestPlot.rowHeight - Number(forestPlot.rowHeight) / 3} verticalAnchor='start' textAnchor='start' style={{ fontWeight: 'bold', fontSize: 12 }}>
142
+ {forestPlot.regression.description}
143
+ </Text>
144
+ )}
145
+
146
+ <Bar key='forest-plot-tooltip-area' className='forest-plot-tooltip-area' width={width} height={height} fill={false ? 'red' : 'transparent'} fillOpacity={0.5} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} />
147
+ </Group>
148
+ <Line from={topLine[0]} to={topLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__top-line' />
149
+ <Line from={bottomLine[0]} to={bottomLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__bottom-line' />
150
+
151
+ {/* column data */}
152
+ {columnsOnChart.map(column => {
153
+ return rawData.map((d, i) => {
154
+ return (
155
+ <Text className={`${d[column.name]}`} x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint} y={yScale(i)} textAnchor={column.forestPlotAlignRight ? 'end' : 'start'} verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={'black'}>
156
+ {d[column.name]}
157
+ </Text>
158
+ )
159
+ })
160
+ })}
161
+
162
+ {/* X Axis DataKey Cols*/}
163
+ {!forestPlot.hideDateCategoryCol &&
164
+ data.map((d, i) => {
165
+ return (
166
+ <Text className={`${d[config.xAxis.dataKey]}`} x={0} y={yScale(i)} textAnchor={'start'} verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={'black'}>
167
+ {d[config.xAxis.dataKey]}
168
+ </Text>
169
+ )
170
+ })}
171
+
172
+ {/* X Axis Datakey Header */}
173
+ {!forestPlot.hideDateCategoryCol && config.xAxis.dataKey && (
174
+ <Text className={config.xAxis.dataKey} x={0} y={0} textAnchor={'start'} verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
175
+ {config.xAxis.dataKey}
176
+ </Text>
177
+ )}
178
+
179
+ {/* column headers */}
180
+ {columnsOnChart.map(column => {
181
+ return (
182
+ <Text className={`${column.label}`} x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint} y={0} textAnchor={column.forestPlotAlignRight ? 'end' : 'start'} verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
183
+ {column.label}
184
+ </Text>
185
+ )
186
+ })}
187
+ </>
188
+ )
189
+ }
190
+
191
+ export default ForestPlot
@@ -0,0 +1,508 @@
1
+ import React, { useContext, memo, useState, useEffect } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+ import { useDebounce } from 'use-debounce'
4
+ import WarningImage from '../images/warning.svg'
5
+
6
+ import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
7
+
8
+ const Select = memo(({ label, value, options, fieldName, section = null, subsection = null, required = false, tooltip, updateField, initial: initialValue, ...attributes }) => {
9
+ let optionsJsx = options.map((optionName, index) => (
10
+ <option value={optionName} key={index}>
11
+ {optionName}
12
+ </option>
13
+ ))
14
+
15
+ if (initialValue) {
16
+ optionsJsx.unshift(
17
+ <option value='' key='initial'>
18
+ {initialValue}
19
+ </option>
20
+ )
21
+ }
22
+
23
+ return (
24
+ <label>
25
+ <span className='edit-label'>
26
+ {label}
27
+ {tooltip}
28
+ </span>
29
+ <select
30
+ className={required && !value ? 'warning' : ''}
31
+ name={fieldName}
32
+ value={value}
33
+ onChange={event => {
34
+ updateField(section, subsection, fieldName, event.target.value)
35
+ }}
36
+ {...attributes}
37
+ >
38
+ {optionsJsx}
39
+ </select>
40
+ </label>
41
+ )
42
+ })
43
+
44
+ const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
45
+ <label className='checkbox column-heading'>
46
+ <input
47
+ type='checkbox'
48
+ name={fieldName}
49
+ checked={value}
50
+ onChange={e => {
51
+ updateField(section, subsection, fieldName, !value)
52
+ }}
53
+ {...attributes}
54
+ />
55
+ <span className='edit-label'>
56
+ {label}
57
+ {tooltip}
58
+ </span>
59
+ </label>
60
+ ))
61
+
62
+ /* eslint-disable react-hooks/rules-of-hooks */
63
+ const TextField = memo(({ label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', i = null, min = null, ...attributes }) => {
64
+ const [value, setValue] = useState(stateValue)
65
+
66
+ const [debouncedValue] = useDebounce(value, 500)
67
+
68
+ useEffect(() => {
69
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
70
+ updateField(section, subsection, fieldName, debouncedValue, i)
71
+ }
72
+ }, [debouncedValue]) // eslint-disable-line
73
+
74
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
75
+
76
+ const onChange = e => {
77
+ if ('number' !== type || min === null) {
78
+ setValue(e.target.value)
79
+ } else {
80
+ if (!e.target.value || min <= parseFloat(e.target.value)) {
81
+ setValue(e.target.value)
82
+ } else {
83
+ setValue(min.toString())
84
+ }
85
+ }
86
+ }
87
+
88
+ let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
89
+
90
+ if ('textarea' === type) {
91
+ formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
92
+ }
93
+
94
+ if ('number' === type) {
95
+ formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
96
+ }
97
+
98
+ if ('date' === type) {
99
+ formElement = <input type='date' name={name} onChange={onChange} {...attributes} value={value} />
100
+ }
101
+
102
+ return (
103
+ <label>
104
+ <span className='edit-label column-heading'>
105
+ {label}
106
+ {tooltip}
107
+ </span>
108
+ {formElement}
109
+ </label>
110
+ )
111
+ })
112
+
113
+ const ForestPlotSettings = () => {
114
+ const { config, rawData: unfilteredData, updateConfig, isDebug } = useContext(ConfigContext)
115
+
116
+ const enforceRestrictions = updatedConfig => {
117
+ if (updatedConfig.orientation === 'horizontal') {
118
+ updatedConfig.labels = false
119
+ }
120
+ if (updatedConfig.table.show === undefined) {
121
+ updatedConfig.table.show = !isDashboard
122
+ }
123
+ // DEV-3293 - Force combo to always be vertical
124
+ if (updatedConfig.visualizationType === 'Combo') {
125
+ updatedConfig.orientation = 'vertical'
126
+ }
127
+ }
128
+
129
+ const getColumns = (filter = true) => {
130
+ let columns = {}
131
+ unfilteredData.forEach(row => {
132
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
133
+ })
134
+
135
+ if (filter) {
136
+ Object.keys(columns).forEach(key => {
137
+ if (
138
+ (config.series && config.series.filter(series => series.dataKey === key).length > 0) ||
139
+ (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))
140
+ /*
141
+ TODO: Resolve errors when config keys exist, but have no value
142
+ Proposal: (((confidenceUpper && confidenceLower) || confidenceUpper || confidenceLower) && Object.keys(config.confidenceKeys).includes(key))
143
+ */
144
+ ) {
145
+ delete columns[key]
146
+ }
147
+ })
148
+ }
149
+
150
+ return Object.keys(columns)
151
+ }
152
+
153
+ const updateField = (section, subsection, fieldName, newValue) => {
154
+ if (section === 'boxplot' && subsection === 'legend') {
155
+ updateConfig({
156
+ ...config,
157
+ [section]: {
158
+ ...config[section],
159
+ [subsection]: {
160
+ ...config.boxplot[subsection],
161
+ [fieldName]: newValue
162
+ }
163
+ }
164
+ })
165
+ return
166
+ }
167
+
168
+ if (section === 'boxplot' && subsection === 'labels') {
169
+ updateConfig({
170
+ ...config,
171
+ [section]: {
172
+ ...config[section],
173
+ [subsection]: {
174
+ ...config.boxplot[subsection],
175
+ [fieldName]: newValue
176
+ }
177
+ }
178
+ })
179
+ return
180
+ }
181
+
182
+ if (section === 'forestPlot' && subsection) {
183
+ updateConfig({
184
+ ...config,
185
+ [section]: {
186
+ ...config[section],
187
+ [subsection]: {
188
+ ...config.forestPlot[subsection],
189
+ [fieldName]: newValue
190
+ }
191
+ }
192
+ })
193
+ return
194
+ }
195
+
196
+ if (section === 'columns' && subsection !== '' && fieldName !== '') {
197
+ updateConfig({
198
+ ...config,
199
+ [section]: {
200
+ ...config[section],
201
+ [subsection]: {
202
+ ...config[section][subsection],
203
+ [fieldName]: newValue
204
+ }
205
+ }
206
+ })
207
+ return
208
+ }
209
+ if (null === section && null === subsection) {
210
+ let updatedConfig = { ...config, [fieldName]: newValue }
211
+ enforceRestrictions(updatedConfig)
212
+ updateConfig(updatedConfig)
213
+ return
214
+ }
215
+
216
+ const isArray = Array.isArray(config[section])
217
+
218
+ let sectionValue = isArray ? [...config[section], newValue] : { ...config[section], [fieldName]: newValue }
219
+
220
+ if (null !== subsection) {
221
+ if (isArray) {
222
+ sectionValue = [...config[section]]
223
+ sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
224
+ } else if (typeof newValue === 'string') {
225
+ sectionValue[subsection] = newValue
226
+ } else {
227
+ sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
228
+ }
229
+ }
230
+
231
+ let updatedConfig = { ...config, [section]: sectionValue }
232
+
233
+ enforceRestrictions(updatedConfig)
234
+
235
+ updateConfig(updatedConfig)
236
+ }
237
+
238
+ return (
239
+ <AccordionItem>
240
+ <AccordionItemHeading>
241
+ <AccordionItemButton>
242
+ Forest Plot Settings
243
+ {(!config.forestPlot.estimateField || !config.forestPlot.upper || !config.forestPlot.lower) && <WarningImage width='25' className='warning-icon' />}
244
+ </AccordionItemButton>
245
+ </AccordionItemHeading>
246
+ <AccordionItemPanel>
247
+ <TextField type='text' value={config.forestPlot?.title || ''} updateField={updateField} section='forestPlot' fieldName='title' label='Plot Title' />
248
+
249
+ {/* width in center */}
250
+ {/* <label>
251
+ <span className='edit-label column-heading'>Forest Plot Width (%)</span>
252
+ <input
253
+ type='number'
254
+ min={0}
255
+ max={100}
256
+ value={config.forestPlot.width || ''}
257
+ onChange={e => {
258
+ updateConfig({
259
+ ...config,
260
+ forestPlot: {
261
+ ...config.forestPlot,
262
+ width: e.target.value
263
+ }
264
+ })
265
+ }}
266
+ />
267
+ </label> */}
268
+
269
+ <label>
270
+ <span className='edit-label column-heading'>Chart Offset Left (%)</span>
271
+ <input
272
+ type='number'
273
+ min={0}
274
+ max={100}
275
+ value={config.forestPlot.leftWidthOffset || 0}
276
+ onChange={e => {
277
+ updateConfig({
278
+ ...config,
279
+ forestPlot: {
280
+ ...config.forestPlot,
281
+ leftWidthOffset: e.target.value
282
+ }
283
+ })
284
+ }}
285
+ />
286
+ </label>
287
+
288
+ <label>
289
+ <span className='edit-label column-heading'>Chart Offset Left Mobile(%)</span>
290
+ <input
291
+ type='number'
292
+ min={0}
293
+ max={100}
294
+ value={config.forestPlot.leftWidthOffsetMobile || 0}
295
+ onChange={e => {
296
+ updateConfig({
297
+ ...config,
298
+ forestPlot: {
299
+ ...config.forestPlot,
300
+ leftWidthOffsetMobile: e.target.value
301
+ }
302
+ })
303
+ }}
304
+ />
305
+ </label>
306
+
307
+ <label>
308
+ <span className='edit-label column-heading'>Chart Offset Right (%)</span>
309
+ <input
310
+ type='number'
311
+ min={0}
312
+ max={100}
313
+ value={config.forestPlot.rightWidthOffset || 0}
314
+ onChange={e => {
315
+ updateConfig({
316
+ ...config,
317
+ forestPlot: {
318
+ ...config.forestPlot,
319
+ rightWidthOffset: e.target.value
320
+ }
321
+ })
322
+ }}
323
+ />
324
+ </label>
325
+
326
+ <label>
327
+ <span className='edit-label column-heading'>Chart Offset Right Mobile(%)</span>
328
+ <input
329
+ type='number'
330
+ min={0}
331
+ max={100}
332
+ value={config.forestPlot.rightWidthOffsetMobile || 0}
333
+ onChange={e => {
334
+ updateConfig({
335
+ ...config,
336
+ forestPlot: {
337
+ ...config.forestPlot,
338
+ rightWidthOffsetMobile: e.target.value
339
+ }
340
+ })
341
+ }}
342
+ />
343
+ </label>
344
+
345
+ <Select
346
+ value={config.forestPlot.estimateField}
347
+ label='Point Estimate Column'
348
+ initial={'Select'}
349
+ required={true}
350
+ onChange={e => {
351
+ if (e.target.value !== '' && e.target.value !== 'Select') {
352
+ updateConfig({
353
+ ...config,
354
+ forestPlot: {
355
+ ...config.forestPlot,
356
+ estimateField: e.target.value
357
+ }
358
+ })
359
+ }
360
+ e.target.value = ''
361
+ }}
362
+ options={getColumns(false)}
363
+ />
364
+
365
+ <Select
366
+ value={config.forestPlot.lower}
367
+ label='Lower CI Column'
368
+ required={true}
369
+ initial={'Select'}
370
+ onChange={e => {
371
+ if (e.target.value !== '' && e.target.value !== 'Select') {
372
+ updateConfig({
373
+ ...config,
374
+ forestPlot: {
375
+ ...config.forestPlot,
376
+ lower: e.target.value
377
+ }
378
+ })
379
+ }
380
+ e.target.value = ''
381
+ }}
382
+ options={getColumns(false)}
383
+ />
384
+ <Select
385
+ value={config.forestPlot.upper}
386
+ label='Upper CI Column'
387
+ initial={'Select'}
388
+ required={true}
389
+ onChange={e => {
390
+ if (e.target.value !== '' && e.target.value !== 'Select') {
391
+ updateConfig({
392
+ ...config,
393
+ forestPlot: {
394
+ ...config.forestPlot,
395
+ upper: e.target.value
396
+ }
397
+ })
398
+ }
399
+ e.target.value = ''
400
+ }}
401
+ options={getColumns(false)}
402
+ />
403
+
404
+ <CheckBox value={config.forestPlot.showZeroLine} section='forestPlot' fieldName='showZeroLine' label='Show Line on Zero' updateField={updateField} />
405
+ <Select
406
+ value={config.forestPlot.shape}
407
+ label='Point Estimate Shape'
408
+ initial={'Select'}
409
+ onChange={e => {
410
+ if (e.target.value !== '' && e.target.value !== 'Select') {
411
+ updateConfig({
412
+ ...config,
413
+ forestPlot: {
414
+ ...config.forestPlot,
415
+ shape: e.target.value
416
+ }
417
+ })
418
+ }
419
+ e.target.value = ''
420
+ }}
421
+ options={['text', 'circle', 'square', 'diamond']}
422
+ />
423
+ <Select
424
+ value={config.forestPlot.radius.scalingColumn}
425
+ label='Scale Radius Column'
426
+ initial={'Select'}
427
+ onChange={e => {
428
+ if (e.target.value !== '' && e.target.value !== 'Select') {
429
+ updateConfig({
430
+ ...config,
431
+ forestPlot: {
432
+ ...config.forestPlot,
433
+ radius: {
434
+ ...config.forestPlot.radius,
435
+ scalingColumn: e.target.value
436
+ }
437
+ }
438
+ })
439
+ }
440
+ e.target.value = ''
441
+ }}
442
+ options={getColumns(false)}
443
+ />
444
+ <label>
445
+ <span className='edit-label column-heading'>Radius Minimum Size</span>
446
+ <input
447
+ min={1}
448
+ max={5}
449
+ value={config.forestPlot.radius.min}
450
+ onChange={e => {
451
+ updateConfig({
452
+ ...config,
453
+ forestPlot: {
454
+ ...config.forestPlot,
455
+ radius: {
456
+ ...config.forestPlot.radius,
457
+ min: Number(e.target.value)
458
+ }
459
+ }
460
+ })
461
+ }}
462
+ type='number'
463
+ label='Radius Minimum'
464
+ placeholder=' 1'
465
+ />
466
+ </label>
467
+ <label>
468
+ <span className='edit-label column-heading'>Radius Maximum Size</span>
469
+ <input
470
+ min={5}
471
+ max={10}
472
+ value={config.forestPlot.radius.max}
473
+ onChange={e => {
474
+ updateConfig({
475
+ ...config,
476
+ forestPlot: {
477
+ ...config.forestPlot,
478
+ radius: {
479
+ ...config.forestPlot.radius,
480
+ max: Number(e.target.value)
481
+ }
482
+ }
483
+ })
484
+ }}
485
+ type='number'
486
+ label='Radius Minimum'
487
+ placeholder=' 1'
488
+ />
489
+ </label>
490
+ <TextField type='number' min={20} max={45} value={config.forestPlot.rowHeight ? config.forestPlot.rowHeight : 10} updateField={updateField} section='forestPlot' fieldName='rowHeight' label='Row Height' placeholder='10' />
491
+ <br />
492
+ <hr />
493
+ <br />
494
+ <h4>Add Regression Line</h4>
495
+ <TextField type='number' value={config.forestPlot?.regression?.upper || ''} updateField={updateField} section='forestPlot' subsection='regression' fieldName='upper' label='Upper Value' />
496
+ <TextField type='number' value={config.forestPlot?.regression?.lower || ''} updateField={updateField} section='forestPlot' subsection='regression' fieldName='lower' label='Lower Value' />
497
+ <TextField type='number' value={config.forestPlot?.regression?.estimateField || ''} updateField={updateField} section='forestPlot' subsection='regression' fieldName='estimateField' label='Estimate Value' />
498
+ <TextField type='text' value={config.forestPlot?.regression?.baseLineColor || 'black'} updateField={updateField} section='forestPlot' subsection='regression' fieldName='baseLineColor' label='Base Color' />
499
+ <CheckBox value={config.forestPlot?.regression?.showBaseLine || false} section='forestPlot' subsection='regression' fieldName='showBaseLine' label='Show base line' updateField={updateField} />
500
+ <CheckBox value={config.forestPlot?.regression?.showDiamond || false} section='forestPlot' subsection='regression' fieldName='showDiamond' label='Show Diamond' updateField={updateField} />
501
+ <CheckBox value={config.forestPlot?.hideDateCategoryCol || false} section='forestPlot' fieldName='hideDateCategoryCol' label='Hide Date Category Column' updateField={updateField} />
502
+ <TextField type='text' value={config.forestPlot?.regression?.description || ''} updateField={updateField} section='forestPlot' subsection='regression' fieldName='description' label='Description' />
503
+ </AccordionItemPanel>
504
+ </AccordionItem>
505
+ )
506
+ }
507
+
508
+ export default ForestPlotSettings