@cdc/chart 4.24.12-2 → 4.25.1
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 +79411 -78816
- package/examples/feature/boxplot/boxplot.json +2 -157
- package/examples/feature/boxplot/testing.csv +23 -38
- package/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json +394 -30
- package/examples/private/not-loading.json +360 -0
- package/index.html +7 -14
- package/package.json +2 -2
- package/src/CdcChart.tsx +92 -1512
- package/src/CdcChartComponent.tsx +1105 -0
- package/src/_stories/Chart.Anchors.stories.tsx +1 -1
- package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
- package/src/_stories/Chart.DynamicSeries.stories.tsx +1 -1
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
- package/src/_stories/Chart.ScatterPlot.stories.tsx +19 -0
- package/src/_stories/Chart.tooltip.stories.tsx +1 -2
- package/src/_stories/ChartAnnotation.stories.tsx +1 -1
- package/src/_stories/ChartAxisLabels.stories.tsx +1 -1
- package/src/_stories/ChartAxisTitles.stories.tsx +1 -1
- package/src/_stories/ChartEditor.stories.tsx +1 -1
- package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
- package/src/_stories/ChartLine.Symbols.stories.tsx +18 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +1 -1
- package/src/_stories/_mock/line_chart_symbols.json +437 -0
- package/src/_stories/_mock/scatterplot-image-download.json +1244 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +3 -11
- package/src/components/Annotations/components/AnnotationDropdown.tsx +3 -3
- package/src/components/Axis/Categorical.Axis.tsx +3 -4
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +14 -5
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +10 -4
- package/src/components/BoxPlot/BoxPlot.tsx +34 -32
- package/src/components/BoxPlot/helpers/index.ts +108 -18
- package/src/components/DeviationBar.jsx +2 -6
- package/src/components/EditorPanel/EditorPanel.tsx +62 -6
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +4 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +44 -7
- package/src/components/ForestPlot/ForestPlot.tsx +176 -26
- package/src/components/Legend/Legend.Component.tsx +29 -38
- package/src/components/Legend/Legend.Suppression.tsx +3 -5
- package/src/components/Legend/Legend.tsx +2 -2
- package/src/components/Legend/LegendLine.Shape.tsx +51 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +29 -26
- package/src/components/Legend/helpers/getLegendClasses.ts +20 -38
- package/src/components/Legend/helpers/index.ts +14 -7
- package/src/components/Legend/tests/getLegendClasses.test.ts +3 -20
- package/src/components/LineChart/components/LineChart.Circle.tsx +90 -88
- package/src/components/LineChart/index.tsx +4 -0
- package/src/components/LinearChart.tsx +54 -29
- package/src/components/PairedBarChart.jsx +2 -9
- package/src/components/ZoomBrush.tsx +5 -7
- package/src/data/initial-state.js +6 -3
- package/src/helpers/getBoxPlotConfig.ts +68 -0
- package/src/helpers/getColorScale.ts +28 -0
- package/src/helpers/getComboChartConfig.ts +42 -0
- package/src/helpers/getExcludedData.ts +37 -0
- package/src/helpers/getTopAxis.ts +7 -0
- package/src/hooks/useBarChart.ts +28 -9
- package/src/hooks/{useHighlightedBars.js → useHighlightedBars.ts} +2 -1
- package/src/hooks/useIntersectionObserver.ts +37 -0
- package/src/hooks/useMinMax.ts +4 -0
- package/src/hooks/useReduceData.ts +1 -1
- package/src/hooks/useTooltip.tsx +9 -1
- package/src/index.jsx +1 -0
- package/src/scss/DataTable.scss +0 -5
- package/src/scss/main.scss +30 -115
- package/src/types/ChartConfig.ts +6 -3
- package/src/types/ChartContext.ts +1 -3
- package/src/helpers/getQuartiles.ts +0 -27
- package/src/hooks/useColorScale.ts +0 -50
- package/src/hooks/useIntersectionObserver.jsx +0 -29
- package/src/hooks/useTopAxis.js +0 -6
|
@@ -136,15 +136,23 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
136
136
|
/>
|
|
137
137
|
</fieldset>
|
|
138
138
|
)}
|
|
139
|
-
<Select
|
|
140
|
-
value={config.fontSize}
|
|
141
|
-
fieldName='fontSize'
|
|
142
|
-
label='Font Size'
|
|
143
|
-
updateField={updateField}
|
|
144
|
-
options={['small', 'medium', 'large']}
|
|
145
|
-
/>
|
|
146
139
|
{visHasBarBorders() && (
|
|
147
140
|
<Select
|
|
141
|
+
tooltip={
|
|
142
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
143
|
+
<Tooltip.Target>
|
|
144
|
+
<Icon
|
|
145
|
+
display='question'
|
|
146
|
+
style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
|
|
147
|
+
/>
|
|
148
|
+
</Tooltip.Target>
|
|
149
|
+
<Tooltip.Content>
|
|
150
|
+
<p>
|
|
151
|
+
Recommended set to display for Section 508 compliance.
|
|
152
|
+
</p>
|
|
153
|
+
</Tooltip.Content>
|
|
154
|
+
</Tooltip>
|
|
155
|
+
}
|
|
148
156
|
value={config.barHasBorder}
|
|
149
157
|
fieldName='barHasBorder'
|
|
150
158
|
label='Bar Borders'
|
|
@@ -171,6 +179,35 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
171
179
|
config.visualizationType === 'Combo') ||
|
|
172
180
|
config.visualizationType === 'Line') && (
|
|
173
181
|
<>
|
|
182
|
+
<Select
|
|
183
|
+
tooltip={
|
|
184
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
185
|
+
<Tooltip.Target>
|
|
186
|
+
<Icon
|
|
187
|
+
display='question'
|
|
188
|
+
style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
|
|
189
|
+
/>
|
|
190
|
+
</Tooltip.Target>
|
|
191
|
+
<Tooltip.Content>
|
|
192
|
+
<p>
|
|
193
|
+
Shapes will appear in the following order: circle, square, triangle, diamond, and inverted
|
|
194
|
+
triangle. Use with a maximum of 5 data points.
|
|
195
|
+
</p>
|
|
196
|
+
</Tooltip.Content>
|
|
197
|
+
</Tooltip>
|
|
198
|
+
}
|
|
199
|
+
value={config.visual.lineDatapointSymbol}
|
|
200
|
+
section='visual'
|
|
201
|
+
fieldName='lineDatapointSymbol'
|
|
202
|
+
label='Line Datapoint Symbols'
|
|
203
|
+
updateField={updateField}
|
|
204
|
+
options={['none', 'standard']}
|
|
205
|
+
/>
|
|
206
|
+
{config.series.length > config.visual.maximumShapeAmount &&
|
|
207
|
+
config.visual.lineDatapointSymbol === 'standard' && (
|
|
208
|
+
<small className='text-danger'>Standard only supports up to 7 data points</small>
|
|
209
|
+
)}
|
|
210
|
+
|
|
174
211
|
<Select
|
|
175
212
|
value={config.lineDatapointStyle}
|
|
176
213
|
fieldName='lineDatapointStyle'
|
|
@@ -14,9 +14,18 @@ import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
|
|
|
14
14
|
|
|
15
15
|
// cdc
|
|
16
16
|
import ConfigContext from '../../ConfigContext'
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
const ForestPlot = ({
|
|
17
|
+
import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
|
|
18
|
+
|
|
19
|
+
const ForestPlot = ({
|
|
20
|
+
xScale,
|
|
21
|
+
yScale,
|
|
22
|
+
config,
|
|
23
|
+
height,
|
|
24
|
+
width,
|
|
25
|
+
handleTooltipMouseOff,
|
|
26
|
+
handleTooltipMouseOver,
|
|
27
|
+
forestPlotRightLabelRef
|
|
28
|
+
}: ForestPlotProps) => {
|
|
20
29
|
const { rawData: data, updateConfig } = useContext<ChartContext>(ConfigContext)
|
|
21
30
|
const { forestPlot } = config as ChartConfig
|
|
22
31
|
const labelPosition = config.xAxis.tickWidthMax + 10
|
|
@@ -33,7 +42,10 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
33
42
|
const colsToCheck = 10
|
|
34
43
|
for (let i = 0; i < colsToCheck; i++) {
|
|
35
44
|
defaultColumns.forEach(col => {
|
|
36
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
config.forestPlot[col] &&
|
|
47
|
+
config.forestPlot[col] !== newConfig.columns[config.forestPlot[`additionalColumn${i}`]]?.name
|
|
48
|
+
) {
|
|
37
49
|
delete newConfig.columns[`additionalColumn${i}`] // Remove old value if found to prevent duplicates
|
|
38
50
|
newConfig.columns[config.forestPlot[col]] = {}
|
|
39
51
|
newConfig.columns[config.forestPlot[col]].dataKey = newConfig.forestPlot[col]
|
|
@@ -83,9 +95,15 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
83
95
|
const regressionPoints = pooledData
|
|
84
96
|
? [
|
|
85
97
|
{ x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) },
|
|
86
|
-
{
|
|
98
|
+
{
|
|
99
|
+
x: xScale(pooledData[config.forestPlot.estimateField]),
|
|
100
|
+
y: height - forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight)
|
|
101
|
+
},
|
|
87
102
|
{ x: xScale(pooledData[config.forestPlot.upper]), y: height - Number(config.forestPlot.rowHeight) },
|
|
88
|
-
{
|
|
103
|
+
{
|
|
104
|
+
x: xScale(pooledData[config.forestPlot.estimateField]),
|
|
105
|
+
y: height + forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight)
|
|
106
|
+
},
|
|
89
107
|
{ x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) }
|
|
90
108
|
]
|
|
91
109
|
: []
|
|
@@ -114,16 +132,37 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
114
132
|
<>
|
|
115
133
|
<Group width={width}>
|
|
116
134
|
{forestPlot.title && (
|
|
117
|
-
<Text
|
|
135
|
+
<Text
|
|
136
|
+
className={`forest-plot--title`}
|
|
137
|
+
x={forestPlot.type === 'Linear' ? xScale(0) : xScale(1)}
|
|
138
|
+
y={0}
|
|
139
|
+
textAnchor='middle'
|
|
140
|
+
verticalAnchor='start'
|
|
141
|
+
fill={'black'}
|
|
142
|
+
>
|
|
118
143
|
{forestPlot.title}
|
|
119
144
|
</Text>
|
|
120
145
|
)}
|
|
121
146
|
|
|
122
147
|
{/* Line of no effect on Continuous Scale. */}
|
|
123
|
-
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Linear' &&
|
|
148
|
+
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Linear' && (
|
|
149
|
+
<Line
|
|
150
|
+
from={{ x: xScale(0), y: 0 + topMarginOffset }}
|
|
151
|
+
to={{ x: xScale(0), y: height }}
|
|
152
|
+
className='forestplot__line-of-no-effect'
|
|
153
|
+
stroke={forestPlot.regression.baseLineColor || 'black'}
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
124
156
|
|
|
125
157
|
{/* Line of no effect on Logarithmic Scale. */}
|
|
126
|
-
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Logarithmic' &&
|
|
158
|
+
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Logarithmic' && (
|
|
159
|
+
<Line
|
|
160
|
+
from={{ x: xScale(1), y: 0 + topMarginOffset }}
|
|
161
|
+
to={{ x: xScale(1), y: height }}
|
|
162
|
+
className='forestplot__line-of-no-effect'
|
|
163
|
+
stroke={forestPlot.regression.baseLineColor || 'black'}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
127
166
|
|
|
128
167
|
{data.map((d, i) => {
|
|
129
168
|
// calculate both square and circle size based on radius.min and radius.max
|
|
@@ -133,7 +172,8 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
133
172
|
})
|
|
134
173
|
|
|
135
174
|
// glyph settings
|
|
136
|
-
const rectSize =
|
|
175
|
+
const rectSize =
|
|
176
|
+
forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4
|
|
137
177
|
const shapeColor = forestPlot.colors.shape ? forestPlot.colors.shape : 'black'
|
|
138
178
|
const lineColor = forestPlot.colors.line ? forestPlot.colors.line : 'black'
|
|
139
179
|
|
|
@@ -167,41 +207,124 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
167
207
|
/>
|
|
168
208
|
|
|
169
209
|
{/* main line */}
|
|
170
|
-
<line
|
|
210
|
+
<line
|
|
211
|
+
stroke={lineColor}
|
|
212
|
+
className={`line-${d[config.yAxis.dataKey]}`}
|
|
213
|
+
key={i}
|
|
214
|
+
x1={xScale(d[forestPlot.lower])}
|
|
215
|
+
x2={xScale(d[forestPlot.upper])}
|
|
216
|
+
y1={yScale(i)}
|
|
217
|
+
y2={yScale(i)}
|
|
218
|
+
/>
|
|
171
219
|
{forestPlot.shape === 'circle' && (
|
|
172
|
-
<Circle
|
|
220
|
+
<Circle
|
|
221
|
+
className='forest-plot--circle'
|
|
222
|
+
cx={xScale(Number(d[forestPlot.estimateField]))}
|
|
223
|
+
cy={yScale(i)}
|
|
224
|
+
r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4}
|
|
225
|
+
fill={shapeColor}
|
|
226
|
+
style={{ opacity: 1, filter: 'unset' }}
|
|
227
|
+
/>
|
|
228
|
+
)}
|
|
229
|
+
{forestPlot.shape === 'square' && (
|
|
230
|
+
<rect
|
|
231
|
+
className='forest-plot--square'
|
|
232
|
+
x={xScale(Number(d[forestPlot.estimateField]))}
|
|
233
|
+
y={yScale(i) - rectSize / 2}
|
|
234
|
+
width={rectSize}
|
|
235
|
+
height={rectSize}
|
|
236
|
+
fill={shapeColor}
|
|
237
|
+
style={{ opacity: 1, filter: 'unset' }}
|
|
238
|
+
/>
|
|
173
239
|
)}
|
|
174
|
-
{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' }} />}
|
|
175
240
|
{forestPlot.shape === 'text' && (
|
|
176
|
-
<Text
|
|
241
|
+
<Text
|
|
242
|
+
className='forest-plot--text'
|
|
243
|
+
x={xScale(Number(d[forestPlot.estimateField]))}
|
|
244
|
+
y={yScale(i)}
|
|
245
|
+
textAnchor='middle'
|
|
246
|
+
verticalAnchor='middle'
|
|
247
|
+
fill={shapeColor}
|
|
248
|
+
>
|
|
177
249
|
{d[forestPlot.estimateField]}
|
|
178
250
|
</Text>
|
|
179
251
|
)}
|
|
180
252
|
</Group>
|
|
181
253
|
) : (
|
|
182
|
-
<LinePath
|
|
254
|
+
<LinePath
|
|
255
|
+
data={regressionPoints}
|
|
256
|
+
x={d => d.x}
|
|
257
|
+
y={d => d.y - appFontSize / 2}
|
|
258
|
+
stroke='black'
|
|
259
|
+
strokeWidth={2}
|
|
260
|
+
fill={'black'}
|
|
261
|
+
curve={curveLinearClosed}
|
|
262
|
+
/>
|
|
183
263
|
)
|
|
184
264
|
})}
|
|
185
265
|
|
|
186
266
|
{/* regression diamond */}
|
|
187
|
-
{regressionPoints && forestPlot.regression.showDiamond &&
|
|
267
|
+
{regressionPoints && forestPlot.regression.showDiamond && (
|
|
268
|
+
<LinePath
|
|
269
|
+
data={regressionPoints}
|
|
270
|
+
x={d => d.x}
|
|
271
|
+
y={d => d.y}
|
|
272
|
+
stroke='black'
|
|
273
|
+
strokeWidth={2}
|
|
274
|
+
fill={forestPlot.regression.baseLineColor}
|
|
275
|
+
curve={curveLinearClosed}
|
|
276
|
+
/>
|
|
277
|
+
)}
|
|
188
278
|
{/* regression text */}
|
|
189
279
|
{forestPlot.regression.description && (
|
|
190
|
-
<Text
|
|
280
|
+
<Text
|
|
281
|
+
x={0 - Number(config.xAxis.size)}
|
|
282
|
+
width={width}
|
|
283
|
+
y={height - config.forestPlot.rowHeight - Number(forestPlot.rowHeight) / 3}
|
|
284
|
+
verticalAnchor='start'
|
|
285
|
+
textAnchor='start'
|
|
286
|
+
style={{ fontWeight: 'bold', fontSize: 12 }}
|
|
287
|
+
>
|
|
191
288
|
{forestPlot.regression.description}
|
|
192
289
|
</Text>
|
|
193
290
|
)}
|
|
194
291
|
|
|
195
|
-
<Bar
|
|
292
|
+
<Bar
|
|
293
|
+
key='forest-plot-tooltip-area'
|
|
294
|
+
className='forest-plot-tooltip-area'
|
|
295
|
+
width={width}
|
|
296
|
+
height={height}
|
|
297
|
+
fill={false ? 'red' : 'transparent'}
|
|
298
|
+
fillOpacity={0.5}
|
|
299
|
+
onMouseMove={e => handleTooltipMouseOver(e, data)}
|
|
300
|
+
onMouseOut={handleTooltipMouseOff}
|
|
301
|
+
/>
|
|
196
302
|
</Group>
|
|
197
|
-
<Line
|
|
198
|
-
|
|
303
|
+
<Line
|
|
304
|
+
from={topLine[0]}
|
|
305
|
+
to={topLine[1]}
|
|
306
|
+
style={{ stroke: 'black', strokeWidth: 2 }}
|
|
307
|
+
className='forestplot__top-line'
|
|
308
|
+
/>
|
|
309
|
+
<Line
|
|
310
|
+
from={bottomLine[0]}
|
|
311
|
+
to={bottomLine[1]}
|
|
312
|
+
style={{ stroke: 'black', strokeWidth: 2 }}
|
|
313
|
+
className='forestplot__bottom-line'
|
|
314
|
+
/>
|
|
199
315
|
|
|
200
316
|
{/* column data */}
|
|
201
317
|
{columnsOnChart.map(column => {
|
|
202
318
|
return data.map((d, i) => {
|
|
203
319
|
return (
|
|
204
|
-
<Text
|
|
320
|
+
<Text
|
|
321
|
+
className={`${d[column.name]}`}
|
|
322
|
+
x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint}
|
|
323
|
+
y={yScale(i)}
|
|
324
|
+
textAnchor={column.forestPlotAlignRight ? 'end' : 'start'}
|
|
325
|
+
verticalAnchor='middle'
|
|
326
|
+
fill={'black'}
|
|
327
|
+
>
|
|
205
328
|
{d[column.name]}
|
|
206
329
|
</Text>
|
|
207
330
|
)
|
|
@@ -212,7 +335,14 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
212
335
|
{!forestPlot.hideDateCategoryCol &&
|
|
213
336
|
data.map((d, i) => {
|
|
214
337
|
return (
|
|
215
|
-
<Text
|
|
338
|
+
<Text
|
|
339
|
+
className={`${d[config.xAxis.dataKey]}`}
|
|
340
|
+
x={0}
|
|
341
|
+
y={yScale(i)}
|
|
342
|
+
textAnchor={'start'}
|
|
343
|
+
verticalAnchor='middle'
|
|
344
|
+
fill={'black'}
|
|
345
|
+
>
|
|
216
346
|
{d[config.xAxis.dataKey]}
|
|
217
347
|
</Text>
|
|
218
348
|
)
|
|
@@ -220,7 +350,7 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
220
350
|
|
|
221
351
|
{/* X Axis Datakey Header */}
|
|
222
352
|
{!forestPlot.hideDateCategoryCol && config.xAxis.dataKey && (
|
|
223
|
-
<Text className={config.xAxis.dataKey} x={0} y={0} textAnchor={'start'} verticalAnchor='start'
|
|
353
|
+
<Text className={config.xAxis.dataKey} x={0} y={0} textAnchor={'start'} verticalAnchor='start' fill={'black'}>
|
|
224
354
|
{config.xAxis.dataKey}
|
|
225
355
|
</Text>
|
|
226
356
|
)}
|
|
@@ -228,7 +358,14 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
228
358
|
{/* column headers */}
|
|
229
359
|
{columnsOnChart.map(column => {
|
|
230
360
|
return (
|
|
231
|
-
<Text
|
|
361
|
+
<Text
|
|
362
|
+
className={`${column.label}`}
|
|
363
|
+
x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint}
|
|
364
|
+
y={0}
|
|
365
|
+
textAnchor={column.forestPlotAlignRight ? 'end' : 'start'}
|
|
366
|
+
verticalAnchor='start'
|
|
367
|
+
fill={'black'}
|
|
368
|
+
>
|
|
232
369
|
{column.label}
|
|
233
370
|
</Text>
|
|
234
371
|
)
|
|
@@ -236,13 +373,26 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
|
|
|
236
373
|
|
|
237
374
|
{/* left bottom label */}
|
|
238
375
|
{forestPlot.leftLabel && (
|
|
239
|
-
<Text
|
|
376
|
+
<Text
|
|
377
|
+
className='forest-plot__left-label'
|
|
378
|
+
x={forestPlot.type === 'Linear' ? xScale(0) - 25 : xScale(1) - 25}
|
|
379
|
+
y={height + labelPosition}
|
|
380
|
+
textAnchor='end'
|
|
381
|
+
verticalAnchor='start'
|
|
382
|
+
>
|
|
240
383
|
{forestPlot.leftLabel}
|
|
241
384
|
</Text>
|
|
242
385
|
)}
|
|
243
386
|
|
|
244
387
|
{forestPlot.rightLabel && (
|
|
245
|
-
<Text
|
|
388
|
+
<Text
|
|
389
|
+
innerRef={forestPlotRightLabelRef}
|
|
390
|
+
className='forest-plot__right-label'
|
|
391
|
+
x={forestPlot.type === 'Linear' ? xScale(0) + 25 : xScale(1) + 25}
|
|
392
|
+
y={height + labelPosition}
|
|
393
|
+
textAnchor='start'
|
|
394
|
+
verticalAnchor='start'
|
|
395
|
+
>
|
|
246
396
|
{forestPlot.rightLabel}
|
|
247
397
|
</Text>
|
|
248
398
|
)}
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import parse from 'html-react-parser'
|
|
2
|
+
import React from 'react'
|
|
2
3
|
import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
|
|
3
4
|
import LegendShape from '@cdc/core/components/LegendShape'
|
|
4
5
|
import Button from '@cdc/core/components/elements/Button'
|
|
5
6
|
import { getLegendClasses } from './helpers/getLegendClasses'
|
|
6
7
|
import { useHighlightedBars } from '../../hooks/useHighlightedBars'
|
|
7
|
-
import { handleLineType } from '../../helpers/handleLineType'
|
|
8
|
-
|
|
9
8
|
import { getMarginTop, getGradientConfig, getMarginBottom } from './helpers/index'
|
|
10
|
-
import { Line } from '@visx/shape'
|
|
11
9
|
import { Label } from '../../types/Label'
|
|
12
10
|
import { ChartConfig, ViewportSize } from '../../types/ChartConfig'
|
|
13
11
|
import { ColorScale } from '../../types/ChartContext'
|
|
14
|
-
import { forwardRef
|
|
12
|
+
import { forwardRef } from 'react'
|
|
15
13
|
import LegendSuppression from './Legend.Suppression'
|
|
16
14
|
import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
|
|
17
15
|
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
18
16
|
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
17
|
+
import LegendLineShape from './LegendLine.Shape'
|
|
19
18
|
|
|
20
|
-
const LEGEND_PADDING =
|
|
19
|
+
const LEGEND_PADDING = 36
|
|
21
20
|
|
|
22
21
|
export interface LegendProps {
|
|
23
22
|
colorScale: ColorScale
|
|
@@ -25,7 +24,7 @@ export interface LegendProps {
|
|
|
25
24
|
currentViewport: ViewportSize
|
|
26
25
|
formatLabels: (labels: Label[]) => Label[]
|
|
27
26
|
highlight: Function
|
|
28
|
-
|
|
27
|
+
handleShowAll: Function
|
|
29
28
|
ref: React.Ref<() => void>
|
|
30
29
|
seriesHighlight: string[]
|
|
31
30
|
skipId: string
|
|
@@ -40,7 +39,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
40
39
|
colorScale,
|
|
41
40
|
seriesHighlight,
|
|
42
41
|
highlight,
|
|
43
|
-
|
|
42
|
+
handleShowAll,
|
|
44
43
|
currentViewport,
|
|
45
44
|
formatLabels,
|
|
46
45
|
skipId = 'legend',
|
|
@@ -51,14 +50,13 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
51
50
|
const { innerClasses, containerClasses } = getLegendClasses(config)
|
|
52
51
|
const { runtime, legend } = config
|
|
53
52
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
legend?.position === 'bottom' || (isLegendWrapViewport(currentViewport) && !legend.hide)
|
|
53
|
+
const isLegendBottom =
|
|
54
|
+
legend?.position === 'bottom' ||
|
|
55
|
+
(isLegendWrapViewport(currentViewport) && !legend.hide && legend?.position !== 'top')
|
|
58
56
|
|
|
59
57
|
const legendClasses = {
|
|
60
|
-
marginBottom: getMarginBottom(
|
|
61
|
-
marginTop: getMarginTop(
|
|
58
|
+
marginBottom: getMarginBottom(isLegendBottom, config),
|
|
59
|
+
marginTop: getMarginTop(isLegendBottom, config)
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
@@ -74,8 +72,12 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
74
72
|
aria-label='legend'
|
|
75
73
|
tabIndex={0}
|
|
76
74
|
>
|
|
77
|
-
{legend.label
|
|
78
|
-
|
|
75
|
+
{(legend.label || legend.description) && (
|
|
76
|
+
<div className={legend.description ? 'mb-3' : 'mb-2'}>
|
|
77
|
+
{legend.label && <h3 className='fw-bold'>{parse(legend.label)}</h3>}
|
|
78
|
+
{legend.description && <p className='mt-2'>{parse(legend.description)}</p>}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
79
81
|
<LegendGradient
|
|
80
82
|
config={config}
|
|
81
83
|
{...getGradientConfig(config, formatLabels, colorScale)}
|
|
@@ -106,8 +108,10 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
|
|
109
|
-
if (seriesHighlight.length
|
|
110
|
-
|
|
111
|
+
if (seriesHighlight.length) {
|
|
112
|
+
if (!seriesHighlight.includes(itemName)) {
|
|
113
|
+
className.push('inactive')
|
|
114
|
+
} else className.push('highlighted')
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
if (config.legend.style === 'gradient') {
|
|
@@ -133,15 +137,9 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
133
137
|
>
|
|
134
138
|
<>
|
|
135
139
|
{config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
|
|
136
|
-
<
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
to={{ x: 40, y: 10 }}
|
|
140
|
-
stroke={label.value}
|
|
141
|
-
strokeWidth={2}
|
|
142
|
-
strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')}
|
|
143
|
-
/>
|
|
144
|
-
</svg>
|
|
140
|
+
<React.Fragment>
|
|
141
|
+
<LegendLineShape index={i} label={label} config={config} />
|
|
142
|
+
</React.Fragment>
|
|
145
143
|
) : (
|
|
146
144
|
<>
|
|
147
145
|
<LegendShape
|
|
@@ -151,8 +149,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
151
149
|
</>
|
|
152
150
|
)}
|
|
153
151
|
</>
|
|
154
|
-
|
|
155
|
-
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
152
|
+
<LegendLabel align='left' className='m-0'>
|
|
156
153
|
{label.text}
|
|
157
154
|
</LegendLabel>
|
|
158
155
|
</LegendItem>
|
|
@@ -191,26 +188,20 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
191
188
|
fill='transparent'
|
|
192
189
|
borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`}
|
|
193
190
|
/>{' '}
|
|
194
|
-
<LegendLabel align='left'
|
|
195
|
-
{bar.legendLabel ? bar.legendLabel : bar.value}
|
|
196
|
-
</LegendLabel>
|
|
191
|
+
<LegendLabel align='left'>{bar.legendLabel ? bar.legendLabel : bar.value}</LegendLabel>
|
|
197
192
|
</LegendItem>
|
|
198
193
|
)
|
|
199
194
|
})}
|
|
200
195
|
</div>
|
|
201
196
|
|
|
202
|
-
<LegendSuppression
|
|
203
|
-
config={config}
|
|
204
|
-
isBottomOrSmallViewport={isBottomOrSmallViewport}
|
|
205
|
-
setHasSuppression={setHasSuppression}
|
|
206
|
-
/>
|
|
197
|
+
<LegendSuppression config={config} isLegendBottom={isLegendBottom} />
|
|
207
198
|
</>
|
|
208
199
|
)
|
|
209
200
|
}}
|
|
210
201
|
</LegendOrdinal>
|
|
211
202
|
{seriesHighlight.length > 0 && (
|
|
212
|
-
<Button onClick={labels =>
|
|
213
|
-
|
|
203
|
+
<Button onClick={labels => handleShowAll(labels)} style={{ marginTop: '1rem' }}>
|
|
204
|
+
Show All
|
|
214
205
|
</Button>
|
|
215
206
|
)}
|
|
216
207
|
</aside>
|
|
@@ -4,10 +4,10 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
4
4
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
5
5
|
interface LegendProps {
|
|
6
6
|
config: ChartConfig
|
|
7
|
-
|
|
7
|
+
isLegendBottom: boolean
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const LegendSuppression: React.FC<LegendProps> = ({ config,
|
|
10
|
+
const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) => {
|
|
11
11
|
const { preliminaryData, visualizationType, visualizationSubType, legend } = config
|
|
12
12
|
|
|
13
13
|
const hasOpenCircleEffects = () =>
|
|
@@ -98,15 +98,13 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewp
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
const getLegendContainerClass = () =>
|
|
101
|
-
legend.singleRow &&
|
|
101
|
+
legend.singleRow && isLegendBottom ? 'legend-container__inner bottom single-row' : ''
|
|
102
102
|
|
|
103
103
|
const shouldShowSuppressedInfo = () =>
|
|
104
104
|
!config.legend.hideSuppressionLink &&
|
|
105
105
|
config.visualizationSubType !== 'stacked' &&
|
|
106
106
|
preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol))
|
|
107
107
|
|
|
108
|
-
setHasSuppression(shouldShowSuppressedInfo())
|
|
109
|
-
|
|
110
108
|
return (
|
|
111
109
|
<React.Fragment>
|
|
112
110
|
{hasOpenCircleEffects() && (
|
|
@@ -13,7 +13,7 @@ const Legend = forwardRef((props, ref) => {
|
|
|
13
13
|
seriesHighlight,
|
|
14
14
|
highlight,
|
|
15
15
|
tableData,
|
|
16
|
-
|
|
16
|
+
handleShowAll,
|
|
17
17
|
transformedData: data,
|
|
18
18
|
currentViewport,
|
|
19
19
|
dimensions,
|
|
@@ -35,7 +35,7 @@ const Legend = forwardRef((props, ref) => {
|
|
|
35
35
|
colorScale={colorScale}
|
|
36
36
|
seriesHighlight={seriesHighlight}
|
|
37
37
|
highlight={highlight}
|
|
38
|
-
|
|
38
|
+
handleShowAll={handleShowAll}
|
|
39
39
|
currentViewport={currentViewport}
|
|
40
40
|
formatLabels={createLegendLabels}
|
|
41
41
|
/>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { handleLineType } from '../../helpers/handleLineType'
|
|
3
|
+
import { Line } from '@visx/shape'
|
|
4
|
+
import { GlyphDiamond, GlyphCircle, GlyphSquare, GlyphTriangle, GlyphCross, Glyph as CustomGlyph } from '@visx/glyph'
|
|
5
|
+
import { Text } from '@visx/text'
|
|
6
|
+
|
|
7
|
+
// Define glyph shapes
|
|
8
|
+
const Glyphs = [
|
|
9
|
+
GlyphCircle,
|
|
10
|
+
GlyphSquare,
|
|
11
|
+
GlyphTriangle,
|
|
12
|
+
GlyphDiamond,
|
|
13
|
+
GlyphTriangle,
|
|
14
|
+
GlyphCross,
|
|
15
|
+
({ fill }: { fill: string }) => (
|
|
16
|
+
<CustomGlyph>
|
|
17
|
+
{/* Render Filled Pentagon */}
|
|
18
|
+
<Text fill={fill} fontSize={14} textAnchor='middle' verticalAnchor='middle'>
|
|
19
|
+
⬟
|
|
20
|
+
</Text>
|
|
21
|
+
</CustomGlyph>
|
|
22
|
+
)
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const LegendLineShape = props => {
|
|
26
|
+
const { config, label, index } = props
|
|
27
|
+
const isReversedTriangle = index === 4
|
|
28
|
+
|
|
29
|
+
const Shape =
|
|
30
|
+
Glyphs[config.visual.lineDatapointSymbol === 'standard' && index < config.visual.maximumShapeAmount ? index : 0]
|
|
31
|
+
const transform = `translate(${15}, ${3}) ${isReversedTriangle ? 'rotate(180)' : ''}`
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<svg width={30} height={10} style={{ overflow: 'visible' }} className='me-2'>
|
|
35
|
+
{/* Render line */}
|
|
36
|
+
<Line
|
|
37
|
+
from={{ x: 0, y: 3 }}
|
|
38
|
+
to={{ x: 30, y: 3 }}
|
|
39
|
+
stroke={label.value}
|
|
40
|
+
strokeWidth={2}
|
|
41
|
+
strokeDasharray={handleLineType(config.series[index]?.type || '')}
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
<g display={config.legend.hasShape ? 'block' : 'none'} transform={transform}>
|
|
45
|
+
<Shape fillOpacity={1} fill={label.value} />
|
|
46
|
+
</g>
|
|
47
|
+
</svg>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default LegendLineShape
|