@cdc/chart 4.25.3 → 4.25.6
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 +46641 -42561
- package/index.html +130 -129
- package/package.json +22 -27
- package/src/CdcChartComponent.tsx +75 -35
- package/src/_stories/Chart.CI.stories.tsx +10 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
- package/src/_stories/Chart.stories.tsx +99 -86
- package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
- package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
- package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
- package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
- package/{examples/private/not-loading.json → src/_stories/_mock/pie_calculated_area.json} +152 -95
- package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
- package/src/components/AreaChart/components/AreaChart.jsx +33 -5
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -37
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +8 -8
- package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -36
- package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
- package/src/components/BarChart/components/context.tsx +20 -2
- package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
- package/src/components/BarChart/helpers/index.ts +5 -2
- package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
- package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
- package/src/components/BoxPlot/BoxPlot.tsx +2 -1
- package/src/components/Brush/BrushChart.tsx +73 -0
- package/src/components/Brush/BrushController..tsx +39 -0
- package/src/components/DeviationBar.jsx +2 -2
- package/src/components/EditorPanel/EditorPanel.tsx +232 -147
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +36 -36
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +52 -25
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +12 -4
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +5 -5
- package/src/components/ForestPlot/ForestPlot.tsx +2 -2
- package/src/components/HoverLine/HoverLine.tsx +74 -0
- package/src/components/Legend/Legend.Component.tsx +1 -1
- package/src/components/Legend/Legend.Suppression.tsx +59 -25
- package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
- package/src/components/Legend/helpers/index.ts +1 -1
- package/src/components/LineChart/LineChartProps.ts +3 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
- package/src/components/LineChart/helpers.ts +133 -56
- package/src/components/LineChart/index.tsx +106 -55
- package/src/components/LinearChart.tsx +178 -198
- package/src/components/PairedBarChart.jsx +3 -2
- package/src/components/PieChart/PieChart.tsx +127 -102
- package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
- package/src/components/Sparkline/components/SparkLine.tsx +80 -18
- package/src/data/initial-state.js +11 -6
- package/src/helpers/countNumOfTicks.ts +1 -1
- package/src/helpers/dataHelpers.ts +23 -2
- package/src/helpers/getNewRuntime.ts +35 -0
- package/src/helpers/getPiePercent.ts +22 -0
- package/src/helpers/getTransformedData.ts +22 -0
- package/src/helpers/sizeHelpers.ts +1 -1
- package/src/helpers/tests/getNewRuntime.test.ts +82 -0
- package/src/helpers/tests/getPiePercent.test.ts +38 -0
- package/src/hooks/useMinMax.ts +21 -28
- package/src/hooks/useRightAxis.ts +5 -3
- package/src/hooks/useScales.ts +15 -6
- package/src/hooks/useTooltip.tsx +218 -203
- package/src/index.jsx +2 -2
- package/src/scss/main.scss +13 -6
- package/src/store/chart.actions.ts +2 -6
- package/src/store/chart.reducer.ts +23 -23
- package/src/types/ChartConfig.ts +11 -3
- package/src/types/ChartContext.ts +0 -2
- package/examples/private/DEV-8850-2.json +0 -493
- package/examples/private/DEV-9822.json +0 -574
- package/examples/private/DEV-9840.json +0 -553
- package/examples/private/DEV-9850-3.json +0 -461
- package/examples/private/chart.json +0 -1084
- package/examples/private/ci_formatted.json +0 -202
- package/examples/private/ci_issue.json +0 -3016
- package/examples/private/completed.json +0 -634
- package/examples/private/dem-data-long.csv +0 -20
- package/examples/private/dem-data-long.json +0 -36
- package/examples/private/demographic_data.csv +0 -157
- package/examples/private/demographic_data.json +0 -2654
- package/examples/private/demographic_dynamic.json +0 -443
- package/examples/private/demographic_standard.json +0 -560
- package/examples/private/ehdi.json +0 -29939
- package/examples/private/test.json +0 -493
- package/src/components/ZoomBrush.tsx +0 -251
|
@@ -113,7 +113,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
113
113
|
)}
|
|
114
114
|
</div>
|
|
115
115
|
)}
|
|
116
|
-
{(visualizationType === 'Bar' || visualizationType === 'Combo'
|
|
116
|
+
{(visualizationType === 'Bar' || visualizationType === 'Combo') && (
|
|
117
117
|
<Select
|
|
118
118
|
value={visualizationSubType || 'Regular'}
|
|
119
119
|
fieldName='visualizationSubType'
|
|
@@ -209,10 +209,8 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
209
209
|
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
210
210
|
</Tooltip.Target>
|
|
211
211
|
<Tooltip.Content>
|
|
212
|
-
<p>
|
|
213
|
-
|
|
214
|
-
</p>
|
|
215
|
-
<hr/>
|
|
212
|
+
<p>Recommended set to display for Section 508 compliance.</p>
|
|
213
|
+
<hr />
|
|
216
214
|
<p>
|
|
217
215
|
Selecting this option will <i> not </i> hide the display of "zero value", "suppressed data", or
|
|
218
216
|
"missing data" indicators on the chart (if applicable).
|
|
@@ -245,35 +243,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
245
243
|
label='Display "Zero Data" Label'
|
|
246
244
|
updateField={updateField}
|
|
247
245
|
/>
|
|
248
|
-
|
|
249
|
-
tooltip={
|
|
250
|
-
<Tooltip style={{ textTransform: 'none' }}>
|
|
251
|
-
<Tooltip.Target>
|
|
252
|
-
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
253
|
-
</Tooltip.Target>
|
|
254
|
-
<Tooltip.Content>
|
|
255
|
-
{config.visualizationSubType === 'stacked' && (
|
|
256
|
-
<p>
|
|
257
|
-
We do not recommend using stacked vertical/horizontal bar charts for missing data. If you choose
|
|
258
|
-
to proceed, selecting this option will display 'N/A' in the tooltip hover and data table (e.g.
|
|
259
|
-
nothing will display in chart).
|
|
260
|
-
</p>
|
|
261
|
-
)}
|
|
262
|
-
{config.visualizationSubType !== 'stacked' && (
|
|
263
|
-
<p>
|
|
264
|
-
Selecting this option will display 'N/A' on the Date/Category Axis, in the tooltip hover, and in
|
|
265
|
-
the data table to indicate missing or undefined data values.
|
|
266
|
-
</p>
|
|
267
|
-
)}
|
|
268
|
-
</Tooltip.Content>
|
|
269
|
-
</Tooltip>
|
|
270
|
-
}
|
|
271
|
-
value={config.general.showMissingDataLabel}
|
|
272
|
-
section='general'
|
|
273
|
-
fieldName='showMissingDataLabel'
|
|
274
|
-
label='Display "Missing Data" Label'
|
|
275
|
-
updateField={updateField}
|
|
276
|
-
/>
|
|
246
|
+
|
|
277
247
|
<CheckBox
|
|
278
248
|
display={config.visualizationType === 'Bar' || config.visualizationType === 'Combo'}
|
|
279
249
|
tooltip={
|
|
@@ -329,6 +299,36 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
329
299
|
</>
|
|
330
300
|
)}
|
|
331
301
|
|
|
302
|
+
<CheckBox
|
|
303
|
+
tooltip={
|
|
304
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
305
|
+
<Tooltip.Target>
|
|
306
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
307
|
+
</Tooltip.Target>
|
|
308
|
+
<Tooltip.Content>
|
|
309
|
+
{config.visualizationSubType === 'stacked' && (
|
|
310
|
+
<p>
|
|
311
|
+
We do not recommend using stacked vertical/horizontal bar charts for missing data. If you choose to
|
|
312
|
+
proceed, selecting this option will display 'N/A' in the tooltip hover and data table (e.g. nothing
|
|
313
|
+
will display in chart).
|
|
314
|
+
</p>
|
|
315
|
+
)}
|
|
316
|
+
{config.visualizationSubType !== 'stacked' && (
|
|
317
|
+
<p>
|
|
318
|
+
Selecting this option will display 'N/A' on the Date/Category Axis, in the tooltip hover, and in the
|
|
319
|
+
data table to indicate missing or undefined data values.
|
|
320
|
+
</p>
|
|
321
|
+
)}
|
|
322
|
+
</Tooltip.Content>
|
|
323
|
+
</Tooltip>
|
|
324
|
+
}
|
|
325
|
+
value={config.general.showMissingDataLabel}
|
|
326
|
+
section='general'
|
|
327
|
+
fieldName='showMissingDataLabel'
|
|
328
|
+
label='Display "Missing Data" Label'
|
|
329
|
+
updateField={updateField}
|
|
330
|
+
/>
|
|
331
|
+
|
|
332
332
|
{visualizationType === 'Pie' && (
|
|
333
333
|
<Select fieldName='pieType' label='Pie Chart Type' updateField={updateField} options={['Regular', 'Donut']} />
|
|
334
334
|
)}
|
|
@@ -435,9 +435,9 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
435
435
|
{visSupportsFootnotes() && (
|
|
436
436
|
<TextField
|
|
437
437
|
type='textarea'
|
|
438
|
-
value={config.
|
|
438
|
+
value={config.legacyFootnotes}
|
|
439
439
|
updateField={updateField}
|
|
440
|
-
fieldName='
|
|
440
|
+
fieldName='legacyFootnotes'
|
|
441
441
|
label='Footnotes'
|
|
442
442
|
tooltip={
|
|
443
443
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../../../ConfigContext'
|
|
3
3
|
|
|
4
4
|
// Core
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
AccordionItemPanel,
|
|
18
18
|
AccordionItemButton
|
|
19
19
|
} from 'react-accessible-accordion'
|
|
20
|
-
import { Draggable } from '@hello-pangea/dnd'
|
|
20
|
+
import { Draggable, DragDropContext, Droppable } from '@hello-pangea/dnd'
|
|
21
21
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
22
22
|
|
|
23
23
|
const SeriesContext = React.createContext({})
|
|
@@ -609,13 +609,25 @@ const SeriesButtonRemove = props => {
|
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
const SeriesItem = props => {
|
|
612
|
-
const { config } = useContext(ConfigContext)
|
|
612
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
613
613
|
const { updateSeries, getColumns } = useContext(SeriesContext)
|
|
614
614
|
const { series, getItemStyle, sortableItemStyles, chartsWithOptions, index: i } = props
|
|
615
|
+
const legendOrderOptions: { label: string; value: string }[] = [
|
|
616
|
+
{ label: 'Order By Data Column', value: 'dataColumn' },
|
|
617
|
+
{
|
|
618
|
+
label: 'Ascending Alphanumeric',
|
|
619
|
+
value: 'asc'
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
label: 'Descending Alphanumeric',
|
|
623
|
+
value: 'desc'
|
|
624
|
+
}
|
|
625
|
+
]
|
|
615
626
|
const showDynamicCategory =
|
|
616
627
|
['Bar', 'Line'].includes(config.visualizationType) &&
|
|
617
628
|
config.visualizationSubType !== 'Stacked' &&
|
|
618
629
|
!config.series.find(s => s.dynamicCategory && s.dataKey !== series.dataKey)
|
|
630
|
+
const SELECT = '- Select -'
|
|
619
631
|
return (
|
|
620
632
|
<Draggable key={series.dataKey} draggableId={`draggableFilter-${series.dataKey}`} index={i}>
|
|
621
633
|
{(provided, snapshot) => (
|
|
@@ -646,28 +658,43 @@ const SeriesItem = props => {
|
|
|
646
658
|
<AccordionItemPanel>
|
|
647
659
|
<Series.Input.Name series={series} index={i} />
|
|
648
660
|
{showDynamicCategory && (
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
<Tooltip
|
|
660
|
-
<
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
<
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
661
|
+
<>
|
|
662
|
+
<Select
|
|
663
|
+
label='Dynamic Category'
|
|
664
|
+
value={series.dynamicCategory}
|
|
665
|
+
options={[SELECT, ...getColumns().filter(col => series.dataKey !== col)]}
|
|
666
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
667
|
+
if (value === SELECT) value = ''
|
|
668
|
+
updateSeries(i, value, 'dynamicCategory')
|
|
669
|
+
}}
|
|
670
|
+
tooltip={
|
|
671
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
672
|
+
<Tooltip.Target>
|
|
673
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
674
|
+
</Tooltip.Target>
|
|
675
|
+
<Tooltip.Content>
|
|
676
|
+
<p>
|
|
677
|
+
This field is Optional. If you have a dynamic data series you can select the category
|
|
678
|
+
field here. You can only add one dynamic category per visualization.
|
|
679
|
+
</p>
|
|
680
|
+
</Tooltip.Content>
|
|
681
|
+
</Tooltip>
|
|
682
|
+
}
|
|
683
|
+
/>
|
|
684
|
+
|
|
685
|
+
<>
|
|
686
|
+
<Select
|
|
687
|
+
value={config.legend.order}
|
|
688
|
+
options={legendOrderOptions}
|
|
689
|
+
section='legend'
|
|
690
|
+
fieldName='order'
|
|
691
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
692
|
+
updateConfig({ ...config, legend: { ...config.legend, order: value } })
|
|
693
|
+
}}
|
|
694
|
+
label='Dynamic Series Order'
|
|
695
|
+
/>
|
|
696
|
+
</>
|
|
697
|
+
</>
|
|
671
698
|
)}
|
|
672
699
|
<Series.Input.Weight series={series} index={i} />
|
|
673
700
|
<Series.Dropdown.SeriesType series={series} index={i} />
|
|
@@ -22,6 +22,8 @@ import { useEditorPermissions } from '../../useEditorPermissions.js'
|
|
|
22
22
|
import { useEditorPanelContext } from '../../EditorPanelContext.js'
|
|
23
23
|
import ConfigContext from '../../../../ConfigContext.js'
|
|
24
24
|
import { PanelProps } from '../PanelProps'
|
|
25
|
+
import { LineChartConfig } from '../../../../types/ChartConfig'
|
|
26
|
+
import './panelVisual.styles.css'
|
|
25
27
|
|
|
26
28
|
const PanelVisual: FC<PanelProps> = props => {
|
|
27
29
|
const { config, updateConfig, colorPalettes, twoColorPalette } = useContext<ChartContext>(ConfigContext)
|
|
@@ -44,7 +46,7 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
44
46
|
const { twoColorPalettes, sequential, nonSequential, accessibleColors } = useColorPalette(config, updateConfig)
|
|
45
47
|
|
|
46
48
|
const updateColor = (property, _value) => {
|
|
47
|
-
console.
|
|
49
|
+
console.error('value', _value)
|
|
48
50
|
if (property === 'storyNodeFontColor') {
|
|
49
51
|
updateConfig({
|
|
50
52
|
...config,
|
|
@@ -147,9 +149,7 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
147
149
|
/>
|
|
148
150
|
</Tooltip.Target>
|
|
149
151
|
<Tooltip.Content>
|
|
150
|
-
<p>
|
|
151
|
-
Recommended set to display for Section 508 compliance.
|
|
152
|
-
</p>
|
|
152
|
+
<p>Recommended set to display for Section 508 compliance.</p>
|
|
153
153
|
</Tooltip.Content>
|
|
154
154
|
</Tooltip>
|
|
155
155
|
}
|
|
@@ -222,6 +222,14 @@ const PanelVisual: FC<PanelProps> = props => {
|
|
|
222
222
|
updateField={updateField}
|
|
223
223
|
options={['Same as Line', 'Lighter than Line']}
|
|
224
224
|
/>
|
|
225
|
+
<CheckBox
|
|
226
|
+
value={!(config as LineChartConfig).isolatedDotsSameSize}
|
|
227
|
+
fieldName='isolatedDotsSameSize'
|
|
228
|
+
label='Accentuate isolated data points'
|
|
229
|
+
updateField={(section, subsection, fieldname, value) =>
|
|
230
|
+
updateField(section, subsection, fieldname, !value)
|
|
231
|
+
}
|
|
232
|
+
/>
|
|
225
233
|
</>
|
|
226
234
|
)}
|
|
227
235
|
{/* eslint-disable */}
|
|
@@ -197,7 +197,7 @@ export const useEditorPermissions = () => {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
const visSupportsTooltipLines = () => {
|
|
200
|
-
const enabledCharts = ['Combo', 'Forecasting', 'Area Chart', 'Line', 'Bar']
|
|
200
|
+
const enabledCharts = ['Combo', 'Forecasting', 'Area Chart', 'Line', 'Bar', 'Scatter Plot']
|
|
201
201
|
if (enabledCharts.includes(visualizationType)) return true
|
|
202
202
|
return false
|
|
203
203
|
}
|
|
@@ -357,10 +357,10 @@ export const useEditorPermissions = () => {
|
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
const visSupportsReactTooltip = () => {
|
|
360
|
-
if (config.yAxis.type === 'categorical') return true
|
|
360
|
+
if (config.yAxis.type === 'categorical' && config.tooltips.singleSeries) return true
|
|
361
361
|
if (
|
|
362
|
-
['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar'].includes(visualizationType)
|
|
363
|
-
|
|
362
|
+
['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar', 'Bar'].includes(visualizationType) &&
|
|
363
|
+
config.tooltips.singleSeries
|
|
364
364
|
) {
|
|
365
365
|
return true
|
|
366
366
|
}
|
|
@@ -384,7 +384,7 @@ export const useEditorPermissions = () => {
|
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
const visSupportsYPadding = () => {
|
|
387
|
-
return !config.
|
|
387
|
+
return !config.yAxis.inlineLabel || !config.yAxis.inlineLabel?.includes(' ')
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
const visHasSingleSeriesTooltip = () => {
|
|
@@ -14,7 +14,7 @@ import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
|
|
|
14
14
|
|
|
15
15
|
// cdc
|
|
16
16
|
import ConfigContext from '../../ConfigContext'
|
|
17
|
-
import {
|
|
17
|
+
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
18
18
|
|
|
19
19
|
const ForestPlot = ({
|
|
20
20
|
xScale,
|
|
@@ -254,7 +254,7 @@ const ForestPlot = ({
|
|
|
254
254
|
<LinePath
|
|
255
255
|
data={regressionPoints}
|
|
256
256
|
x={d => d.x}
|
|
257
|
-
y={d => d.y -
|
|
257
|
+
y={d => d.y - APP_FONT_SIZE / 2}
|
|
258
258
|
stroke='black'
|
|
259
259
|
strokeWidth={2}
|
|
260
260
|
fill={'black'}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../../ConfigContext'
|
|
3
|
+
import { Group } from '@visx/group'
|
|
4
|
+
import { Line } from '@visx/shape'
|
|
5
|
+
|
|
6
|
+
type HoverLineProps = {
|
|
7
|
+
tooltipData?: any // same as @visx/tooltip TooltipData
|
|
8
|
+
xMax?: number
|
|
9
|
+
yMax?: number
|
|
10
|
+
point: { x: number; y: number }
|
|
11
|
+
orientation: 'horizontal' | 'vertical'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const HoverLine: React.FC<HoverLineProps> = ({ tooltipData, xMax, yMax, point, orientation }) => {
|
|
15
|
+
const { config } = useContext(ConfigContext)
|
|
16
|
+
const { verticalHoverLine, horizontalHoverLine } = config.visual
|
|
17
|
+
const { visualizationType } = config
|
|
18
|
+
const isScatterPlot = visualizationType === 'Scatter Plot'
|
|
19
|
+
|
|
20
|
+
const isVertical = orientation === 'vertical'
|
|
21
|
+
const isHorizontal = orientation === 'horizontal'
|
|
22
|
+
const showVerticalHoverLine = verticalHoverLine || (verticalHoverLine && isScatterPlot)
|
|
23
|
+
const showHorizontalHoverLine = horizontalHoverLine || (horizontalHoverLine && isScatterPlot)
|
|
24
|
+
|
|
25
|
+
const getX = () => {
|
|
26
|
+
if (point.x > xMax + Number(config.yAxis.size)) return xMax
|
|
27
|
+
if (point.x < config.yAxis.size) return config.yAxis.size
|
|
28
|
+
return point.x
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const getY = () => {
|
|
32
|
+
if (point.y > yMax) return yMax
|
|
33
|
+
return point.y
|
|
34
|
+
}
|
|
35
|
+
if (isVertical) {
|
|
36
|
+
return (
|
|
37
|
+
showVerticalHoverLine && (
|
|
38
|
+
<Group key={`tooltipLine-vertical${point.y}${point.x}`} className='vertical-tooltip-line'>
|
|
39
|
+
<Line
|
|
40
|
+
from={{ x: getX(), y: 0 }}
|
|
41
|
+
to={{ x: getX(), y: yMax }}
|
|
42
|
+
stroke={'black'}
|
|
43
|
+
strokeWidth={1}
|
|
44
|
+
pointerEvents='none'
|
|
45
|
+
strokeDasharray='5,5'
|
|
46
|
+
className='vertical-tooltip-line'
|
|
47
|
+
/>
|
|
48
|
+
</Group>
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
if (isHorizontal) {
|
|
53
|
+
return (
|
|
54
|
+
showHorizontalHoverLine && (
|
|
55
|
+
<Group
|
|
56
|
+
key={`tooltipLine-horizontal${point.y}${point.x}`}
|
|
57
|
+
className='horizontal-tooltip-line'
|
|
58
|
+
left={config.yAxis.size ? config.yAxis.size : 0}
|
|
59
|
+
>
|
|
60
|
+
<Line
|
|
61
|
+
from={{ x: 0, y: getY() }}
|
|
62
|
+
to={{ x: xMax, y: getY() }}
|
|
63
|
+
stroke={'black'}
|
|
64
|
+
strokeWidth={1}
|
|
65
|
+
pointerEvents='none'
|
|
66
|
+
strokeDasharray='5,5'
|
|
67
|
+
className='horizontal-tooltip-line'
|
|
68
|
+
/>
|
|
69
|
+
</Group>
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export default HoverLine
|
|
@@ -56,7 +56,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
56
56
|
const { series } = runtime
|
|
57
57
|
|
|
58
58
|
const seriesWithData = getSeriesWithData(config)
|
|
59
|
-
const dontFilterLegendItems = !series.length || legend.unified
|
|
59
|
+
const dontFilterLegendItems = !series.length || legend.unified || !seriesWithData.length
|
|
60
60
|
|
|
61
61
|
const isLegendBottom =
|
|
62
62
|
legend?.position === 'bottom' ||
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
2
|
import { ChartConfig } from '../../types/ChartConfig'
|
|
3
|
-
import
|
|
4
|
-
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
3
|
+
import RichTooltip from '@cdc/core/components/RichTooltip/RichTooltip'
|
|
5
4
|
interface LegendProps {
|
|
6
5
|
config: ChartConfig
|
|
7
6
|
isLegendBottom: boolean
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) => {
|
|
11
|
-
const { preliminaryData, visualizationType, visualizationSubType, legend } = config
|
|
12
|
-
|
|
10
|
+
const { preliminaryData, visualizationType, visualizationSubType, legend, data } = config
|
|
11
|
+
const showPiePercent = config.dataFormat.showPiePercent && config.visualizationType === 'Pie'
|
|
13
12
|
const hasOpenCircleEffects = () =>
|
|
14
13
|
preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style !== 'Filled Circles') &&
|
|
15
14
|
['Line', 'Combo'].includes(visualizationType)
|
|
@@ -35,10 +34,6 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
|
|
|
35
34
|
</div>
|
|
36
35
|
)
|
|
37
36
|
)
|
|
38
|
-
const handleLinkClick = event => {
|
|
39
|
-
// prevent defintion link to change URl
|
|
40
|
-
event.preventDefault()
|
|
41
|
-
}
|
|
42
37
|
|
|
43
38
|
const renderSuppressedItems = () => {
|
|
44
39
|
const getStyle = displayGray => {
|
|
@@ -100,10 +95,31 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
|
|
|
100
95
|
const getLegendContainerClass = () =>
|
|
101
96
|
legend.singleRow && isLegendBottom ? 'legend-container__inner bottom single-row' : ''
|
|
102
97
|
|
|
98
|
+
const sumSeries = (data, seriesKey) => {
|
|
99
|
+
if (!Array.isArray(data)) return 0
|
|
100
|
+
|
|
101
|
+
return data.reduce((total, row) => {
|
|
102
|
+
const raw = row[seriesKey]
|
|
103
|
+
const num = parseFloat(raw)
|
|
104
|
+
return total + (Number.isFinite(num) ? num : 0)
|
|
105
|
+
}, 0)
|
|
106
|
+
}
|
|
107
|
+
const total = sumSeries(data, config.runtime.yAxis.dataKey)
|
|
108
|
+
|
|
103
109
|
const shouldShowSuppressedInfo = () =>
|
|
104
110
|
!config.legend.hideSuppressionLink &&
|
|
105
111
|
config.visualizationSubType !== 'stacked' &&
|
|
106
112
|
preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol))
|
|
113
|
+
// controls Pie chart Legend for calculated Area
|
|
114
|
+
|
|
115
|
+
const renderCalculatedAreaItems = () => {
|
|
116
|
+
return (
|
|
117
|
+
<div key={'pie-asterisk'} className='legend-preliminary'>
|
|
118
|
+
<span className={'Asterisk'}>{'**'}</span>
|
|
119
|
+
<p>{'Calculated Area'}</p>
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
107
123
|
|
|
108
124
|
return (
|
|
109
125
|
<React.Fragment>
|
|
@@ -113,6 +129,12 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
|
|
|
113
129
|
<div className={getLegendContainerClass()}>{renderEffectItems()}</div>
|
|
114
130
|
</React.Fragment>
|
|
115
131
|
)}
|
|
132
|
+
{showPiePercent && total < 100 && (
|
|
133
|
+
<React.Fragment>
|
|
134
|
+
<hr />
|
|
135
|
+
<div className={getLegendContainerClass()}>{renderCalculatedAreaItems()}</div>
|
|
136
|
+
</React.Fragment>
|
|
137
|
+
)}
|
|
116
138
|
|
|
117
139
|
{shouldShowSuppressedLabels() && (
|
|
118
140
|
<React.Fragment>
|
|
@@ -121,27 +143,39 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
|
|
|
121
143
|
</React.Fragment>
|
|
122
144
|
)}
|
|
123
145
|
{shouldShowSuppressedInfo() && (
|
|
124
|
-
<div className='legend-container__outer
|
|
125
|
-
<Icon alt='info-icon' display='info' />
|
|
146
|
+
<div className='legend-container__outer link-container'>
|
|
126
147
|
<p>
|
|
127
148
|
This chart contains
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
suppressed data
|
|
135
|
-
|
|
149
|
+
<RichTooltip
|
|
150
|
+
tooltipContent={`Data is
|
|
151
|
+
suppressed to maintain statistical reliability.
|
|
152
|
+
This occurs when the number of respondents or
|
|
153
|
+
reported values does not meet the minimum
|
|
154
|
+
reporting threshold.`}
|
|
155
|
+
linkText='suppressed data'
|
|
156
|
+
href={null}
|
|
157
|
+
tooltipOpacity={config.tooltips.opacity}
|
|
158
|
+
/>
|
|
159
|
+
</p>
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
{showPiePercent && (total < 100 || total > 100) && (
|
|
163
|
+
<div className='legend-container__outer link-container'>
|
|
164
|
+
<p>
|
|
165
|
+
{total < 100 ? '** This graph contains a' : 'The sum of percentages in this graph is larger than 100 '}
|
|
166
|
+
<RichTooltip
|
|
167
|
+
tooltipContent={
|
|
168
|
+
total < 100
|
|
169
|
+
? 'Calculated Areas are used to supplement the pie chart when the sum of the values in the data is less than 100%.'
|
|
170
|
+
: 'Calculated Areas are disabled when the total exceeds 100%.'
|
|
171
|
+
}
|
|
172
|
+
linkText={total < 100 ? 'calculated area' : ''}
|
|
173
|
+
href={null}
|
|
174
|
+
tooltipOpacity={config.tooltips.opacity}
|
|
175
|
+
/>
|
|
136
176
|
</p>
|
|
137
177
|
</div>
|
|
138
178
|
)}
|
|
139
|
-
|
|
140
|
-
<ReactTooltip // prettier-ignore
|
|
141
|
-
id='my-tooltip'
|
|
142
|
-
variant='light'
|
|
143
|
-
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black', maxWidth: '100%' }}
|
|
144
|
-
/>
|
|
145
179
|
</React.Fragment>
|
|
146
180
|
)
|
|
147
181
|
}
|
|
@@ -17,8 +17,36 @@ export const createFormatLabels =
|
|
|
17
17
|
})
|
|
18
18
|
: labels
|
|
19
19
|
const reverseLabels = labels => {
|
|
20
|
+
if (config.series.some(series => series.dynamicCategory)) {
|
|
21
|
+
return orderDynamicLabels(labels)
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
return config.legend.reverseLabelOrder ? sortVertical(labels).reverse() : sortVertical(labels)
|
|
21
25
|
}
|
|
26
|
+
|
|
27
|
+
const orderDynamicLabels = labels => {
|
|
28
|
+
// Handle different ordering configurations
|
|
29
|
+
switch (config.legend.order) {
|
|
30
|
+
case 'dataColumn':
|
|
31
|
+
return labels
|
|
32
|
+
case 'asc':
|
|
33
|
+
case 'desc':
|
|
34
|
+
return labels.sort((a, b) => {
|
|
35
|
+
const valA = a.datum || a.text
|
|
36
|
+
const valB = b.datum || b.text
|
|
37
|
+
const numA = parseFloat(valA)
|
|
38
|
+
const numB = parseFloat(valB)
|
|
39
|
+
if (!isNaN(numA) && !isNaN(numB)) {
|
|
40
|
+
return config.legend.order === 'asc' ? numA - numB : numB - numA
|
|
41
|
+
} else {
|
|
42
|
+
return config.legend.order === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
default:
|
|
47
|
+
return labels // Default case to handle any unexpected config.legend.order values
|
|
48
|
+
}
|
|
49
|
+
}
|
|
22
50
|
const colorCode = config.legend?.colorCode
|
|
23
51
|
if (visualizationType === 'Deviation Bar') {
|
|
24
52
|
const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
|
|
@@ -36,7 +36,7 @@ export const getMarginBottom = (isLegendBottom, config) => {
|
|
|
36
36
|
|
|
37
37
|
if (isLegendTop) marginBottom = 27
|
|
38
38
|
|
|
39
|
-
if (isLegendTop && config.
|
|
39
|
+
if (isLegendTop && config.yAxis?.inlineLabel) marginBottom += 9
|
|
40
40
|
|
|
41
41
|
if (isLegendBottom) marginBottom += 9
|
|
42
42
|
|
|
@@ -29,9 +29,11 @@ export interface StyleProps {
|
|
|
29
29
|
handleLineType: Function
|
|
30
30
|
lineType: string
|
|
31
31
|
preliminaryData: PreliminaryDataItem[]
|
|
32
|
-
seriesKey:
|
|
32
|
+
seriesKey: string
|
|
33
33
|
stroke: string
|
|
34
34
|
strokeWidth: number
|
|
35
|
+
dynamicCategory: string
|
|
36
|
+
originalSeriesKey: string
|
|
35
37
|
}
|
|
36
38
|
export interface Style {
|
|
37
39
|
stroke: string
|