@cdc/chart 4.25.5-1 → 4.25.6-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/LICENSE +201 -0
- package/dist/cdcchart.js +32053 -27935
- package/index.html +130 -130
- package/package.json +2 -2
- package/src/CdcChartComponent.tsx +66 -26
- package/src/_stories/Chart.stories.tsx +99 -93
- package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
- package/src/_stories/_mock/pie_calculated_area.json +417 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -13
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +3 -14
- package/src/components/BarChart/components/BarChart.Vertical.tsx +2 -8
- package/src/components/Brush/BrushChart.tsx +73 -0
- package/src/components/Brush/BrushController..tsx +39 -0
- package/src/components/DeviationBar.jsx +0 -1
- package/src/components/EditorPanel/EditorPanel.tsx +246 -156
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +2 -1
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +7 -4
- package/src/components/HoverLine/HoverLine.tsx +74 -0
- package/src/components/Legend/Legend.Suppression.tsx +47 -3
- package/src/components/Legend/helpers/index.ts +1 -1
- package/src/components/LineChart/helpers.ts +7 -7
- package/src/components/LineChart/index.tsx +3 -6
- package/src/components/LinearChart.tsx +108 -72
- package/src/components/PieChart/PieChart.tsx +58 -13
- package/src/data/initial-state.js +8 -5
- package/src/helpers/countNumOfTicks.ts +4 -19
- package/src/helpers/getNewRuntime.ts +35 -0
- package/src/helpers/getPiePercent.ts +22 -0
- package/src/helpers/getTransformedData.ts +22 -0
- package/src/helpers/tests/getNewRuntime.test.ts +82 -0
- package/src/helpers/tests/getPiePercent.test.ts +38 -0
- package/src/hooks/useRightAxis.ts +1 -1
- package/src/hooks/useScales.ts +8 -3
- package/src/hooks/useTooltip.tsx +24 -10
- package/src/scss/main.scss +8 -4
- package/src/store/chart.actions.ts +2 -6
- package/src/store/chart.reducer.ts +23 -23
- package/src/types/ChartConfig.ts +7 -4
- package/src/types/ChartContext.ts +0 -2
- package/src/components/ZoomBrush.tsx +0 -251
|
@@ -11,8 +11,7 @@ import _ from 'lodash'
|
|
|
11
11
|
|
|
12
12
|
// CDC Components
|
|
13
13
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
14
|
-
import
|
|
15
|
-
import { AreaChart, AreaChartStacked } from './AreaChart'
|
|
14
|
+
import { AreaChartStacked } from './AreaChart'
|
|
16
15
|
import BarChart from './BarChart'
|
|
17
16
|
import ConfigContext from '../ConfigContext'
|
|
18
17
|
import BoxPlot from './BoxPlot'
|
|
@@ -43,6 +42,8 @@ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
|
43
42
|
import Annotation from './Annotations'
|
|
44
43
|
import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
|
|
45
44
|
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
45
|
+
import HoverLine from './HoverLine/HoverLine'
|
|
46
|
+
import BrushChart from './BrushChart'
|
|
46
47
|
|
|
47
48
|
type LinearChartProps = {
|
|
48
49
|
parentWidth: number
|
|
@@ -59,10 +60,23 @@ const AXIS_LABEL_FONT_SIZE = 18
|
|
|
59
60
|
const AXIS_LABEL_FONT_SIZE_SMALL = 14
|
|
60
61
|
const TICK_LABEL_MARGIN_RIGHT = 4.5
|
|
61
62
|
|
|
63
|
+
type TooltipData = {
|
|
64
|
+
dataXPosition?: number
|
|
65
|
+
dataYPosition?: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type UseTooltipReturn<T = TooltipData> = {
|
|
69
|
+
tooltipData: T | null
|
|
70
|
+
showTooltip: (tooltipData: T) => void
|
|
71
|
+
hideTooltip: () => void
|
|
72
|
+
tooltipOpen: boolean
|
|
73
|
+
tooltipLeft: number | null
|
|
74
|
+
tooltipTop: number | null
|
|
75
|
+
}
|
|
76
|
+
|
|
62
77
|
const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
|
|
63
78
|
// prettier-ignore
|
|
64
79
|
const {
|
|
65
|
-
brushConfig,
|
|
66
80
|
colorScale,
|
|
67
81
|
config,
|
|
68
82
|
convertLineToBarGraph,
|
|
@@ -79,8 +93,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
79
93
|
parentRef,
|
|
80
94
|
tableData,
|
|
81
95
|
transformedData: data,
|
|
82
|
-
updateConfig,
|
|
83
96
|
seriesHighlight,
|
|
97
|
+
brushConfig
|
|
84
98
|
} = useContext(ConfigContext)
|
|
85
99
|
|
|
86
100
|
// CONFIG
|
|
@@ -99,14 +113,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
99
113
|
dataFormat,
|
|
100
114
|
debugSvg
|
|
101
115
|
} = config
|
|
102
|
-
const {
|
|
103
|
-
const { labelsAboveGridlines, hideAxis } = config.yAxis
|
|
116
|
+
const { labelsAboveGridlines, hideAxis, inlineLabel } = config.yAxis
|
|
104
117
|
|
|
105
118
|
// HOOKS % STATES
|
|
106
119
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
|
|
107
120
|
const { visSupportsReactTooltip } = useEditorPermissions()
|
|
108
121
|
const { hasTopAxis } = getTopAxis(config)
|
|
109
122
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
123
|
+
const [showHoverLine, setShowHoverLine] = useState(false)
|
|
110
124
|
const [point, setPoint] = useState({ x: 0, y: 0 })
|
|
111
125
|
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
112
126
|
const [yAxisAutoPadding, setYAxisAutoPadding] = useState(0)
|
|
@@ -121,6 +135,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
121
135
|
const xAxisTitleRef = useRef(null)
|
|
122
136
|
const lastMaxValue = useRef(maxValue)
|
|
123
137
|
const gridLineRefs = useRef([])
|
|
138
|
+
const tooltipRef = useRef(null)
|
|
124
139
|
|
|
125
140
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
126
141
|
freezeOnceVisible: false
|
|
@@ -132,8 +147,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
132
147
|
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
133
148
|
const isForestPlot = visualizationType === 'Forest Plot'
|
|
134
149
|
const isDateTime = config.xAxis.type === 'date-time'
|
|
135
|
-
const
|
|
136
|
-
const labelsOverflow =
|
|
150
|
+
const inlineLabelHasNoSpace = !inlineLabel?.includes(' ')
|
|
151
|
+
const labelsOverflow = inlineLabel && !inlineLabelHasNoSpace
|
|
137
152
|
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
138
153
|
const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
|
|
139
154
|
const tickLabelFontSize = isMobileHeightViewport(currentViewport) ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
|
|
@@ -218,7 +233,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
218
233
|
yMax
|
|
219
234
|
}
|
|
220
235
|
const { min, max, leftMax, rightMax } = useMinMax(properties)
|
|
221
|
-
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data
|
|
236
|
+
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data })
|
|
222
237
|
const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleAnnotation } = useScales({
|
|
223
238
|
...properties,
|
|
224
239
|
min,
|
|
@@ -238,7 +253,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
238
253
|
const handleNumTicks = isForestPlot ? config.data.length : yTickCount
|
|
239
254
|
|
|
240
255
|
// Tooltip Helpers
|
|
241
|
-
|
|
256
|
+
|
|
257
|
+
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } =
|
|
258
|
+
useTooltip<UseTooltipReturn<TooltipData>>()
|
|
242
259
|
|
|
243
260
|
// prettier-ignore
|
|
244
261
|
const {
|
|
@@ -247,11 +264,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
247
264
|
handleTooltipMouseOff,
|
|
248
265
|
TooltipListItem,
|
|
249
266
|
} = useCoveTooltip({
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
267
|
+
xScale,
|
|
268
|
+
yScale,
|
|
269
|
+
seriesScale,
|
|
270
|
+
showTooltip,
|
|
271
|
+
hideTooltip
|
|
255
272
|
})
|
|
256
273
|
// get the number of months between the first and last date
|
|
257
274
|
const { dataKey } = runtime.xAxis
|
|
@@ -271,7 +288,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
271
288
|
if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
|
|
272
289
|
if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
|
|
273
290
|
if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
|
|
274
|
-
if (orientation === 'vertical' && max - min < 3)
|
|
291
|
+
if (orientation === 'vertical' && max - min < 3 && !config.dataFormat?.roundTo)
|
|
275
292
|
return formatNumber(tick, 'left', shouldAbbreviate, false, false, '1', { index, length: ticks.length })
|
|
276
293
|
if (orientation === 'vertical') {
|
|
277
294
|
// TODO suggestion: pass all options as object key/values to allow for more flexibility
|
|
@@ -375,7 +392,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
375
392
|
if (!suffixEl) return setSuffixWidth(0)
|
|
376
393
|
const suffixElWidth = suffixEl.getBBox().width
|
|
377
394
|
setSuffixWidth(suffixElWidth)
|
|
378
|
-
}, [config.dataFormat.suffix,
|
|
395
|
+
}, [config.dataFormat.suffix, inlineLabel])
|
|
379
396
|
|
|
380
397
|
// forest plot x-axis label positioning
|
|
381
398
|
useEffect(() => {
|
|
@@ -401,11 +418,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
401
418
|
const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
|
|
402
419
|
|
|
403
420
|
// Heights to add
|
|
404
|
-
|
|
405
421
|
const brushHeight = brush?.active ? brush?.height + brush?.height : 0
|
|
422
|
+
const brushHeightWithMargin = config.brush?.active ? brushHeight + brushHeight : 0
|
|
406
423
|
const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
|
|
407
424
|
const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
|
|
408
|
-
const additionalHeight = axisBottomHeight +
|
|
425
|
+
const additionalHeight = axisBottomHeight + brushHeightWithMargin + forestRowsHeight + topLabelOnGridlineHeight
|
|
409
426
|
const newHeight = initialHeight + additionalHeight
|
|
410
427
|
if (!parentRef.current) return
|
|
411
428
|
|
|
@@ -471,6 +488,23 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
471
488
|
setYAxisAutoPadding(newPadding * 100 + 0.1)
|
|
472
489
|
}, [maxValue, labelsOverflow, yScale, handleNumTicks])
|
|
473
490
|
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (!tooltipOpen) return
|
|
493
|
+
if (!tooltipRef.current) return
|
|
494
|
+
|
|
495
|
+
const { dataXPosition } = tooltipData as { [key: string]: number }
|
|
496
|
+
|
|
497
|
+
if (!dataXPosition) return
|
|
498
|
+
|
|
499
|
+
const { width: tooltipWidth } = tooltipRef.current.node.getBoundingClientRect()
|
|
500
|
+
|
|
501
|
+
const rightSideRemainingSpace = parentWidth - dataXPosition
|
|
502
|
+
|
|
503
|
+
const rightSide = rightSideRemainingSpace <= tooltipWidth && dataXPosition > parentWidth / 2 - 10
|
|
504
|
+
const maxWidth = rightSide ? dataXPosition - 10 : parentWidth - (dataXPosition + 6)
|
|
505
|
+
tooltipRef.current.node.style.maxWidth = `${maxWidth}px`
|
|
506
|
+
}, [tooltipOpen, tooltipData])
|
|
507
|
+
|
|
474
508
|
// Render Functions
|
|
475
509
|
const generatePairedBarAxis = () => {
|
|
476
510
|
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
@@ -635,6 +669,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
635
669
|
role='img'
|
|
636
670
|
aria-label={handleChartAriaLabels(config)}
|
|
637
671
|
style={{ overflow: 'visible' }}
|
|
672
|
+
onMouseLeave={() => setShowHoverLine(false)}
|
|
673
|
+
onMouseEnter={() => setShowHoverLine(true)}
|
|
638
674
|
>
|
|
639
675
|
{!isDraggingAnnotation && <Bar width={parentWidth} height={initialHeight} fill={'transparent'}></Bar>}{' '}
|
|
640
676
|
{/* GRID LINES */}
|
|
@@ -771,6 +807,27 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
771
807
|
showTooltip={showTooltip}
|
|
772
808
|
/>
|
|
773
809
|
)}
|
|
810
|
+
{/* Line chart */}
|
|
811
|
+
{/* TODO: Make this just line or combo? */}
|
|
812
|
+
{!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
|
|
813
|
+
visualizationType
|
|
814
|
+
) &&
|
|
815
|
+
!convertLineToBarGraph && (
|
|
816
|
+
<>
|
|
817
|
+
<LineChart
|
|
818
|
+
xScale={xScale}
|
|
819
|
+
yScale={yScale}
|
|
820
|
+
getXAxisData={getXAxisData}
|
|
821
|
+
getYAxisData={getYAxisData}
|
|
822
|
+
xMax={xMax}
|
|
823
|
+
yMax={yMax}
|
|
824
|
+
seriesStyle={config.runtime.series}
|
|
825
|
+
tooltipData={tooltipData}
|
|
826
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
827
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
828
|
+
/>
|
|
829
|
+
</>
|
|
830
|
+
)}
|
|
774
831
|
{(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
|
|
775
832
|
<Forecasting
|
|
776
833
|
showTooltip={showTooltip}
|
|
@@ -778,13 +835,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
778
835
|
xScale={xScale}
|
|
779
836
|
yScale={yScale}
|
|
780
837
|
width={xMax}
|
|
781
|
-
le
|
|
782
838
|
height={yMax}
|
|
783
839
|
xScaleNoPadding={xScaleNoPadding}
|
|
784
840
|
chartRef={svgRef}
|
|
785
841
|
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
786
842
|
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
787
|
-
isBrush={false}
|
|
788
843
|
/>
|
|
789
844
|
)}
|
|
790
845
|
{visualizationType === 'Forest Plot' && (
|
|
@@ -855,7 +910,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
855
910
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
856
911
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
857
912
|
className='anchor-y'
|
|
858
|
-
from={{ x: 0 + padding, y: position - middleOffset}}
|
|
913
|
+
from={{ x: 0 + padding, y: position - middleOffset }}
|
|
859
914
|
to={{ x: width - config.yAxis.rightAxisSize, y: position - middleOffset }}
|
|
860
915
|
/>
|
|
861
916
|
)
|
|
@@ -887,14 +942,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
887
942
|
return (
|
|
888
943
|
// prettier-ignore
|
|
889
944
|
<Line
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
945
|
+
key={`xAxis-${anchor.value}--${index}`}
|
|
946
|
+
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
947
|
+
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
948
|
+
fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
949
|
+
className='anchor-x'
|
|
950
|
+
from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
|
|
951
|
+
to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
|
|
952
|
+
/>
|
|
898
953
|
)
|
|
899
954
|
})}
|
|
900
955
|
{/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
|
|
@@ -921,35 +976,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
921
976
|
{config.chartMessage.noData}
|
|
922
977
|
</Text>
|
|
923
978
|
)}
|
|
924
|
-
{
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
>
|
|
930
|
-
<Line
|
|
931
|
-
from={{ x: 0, y: point.y }}
|
|
932
|
-
to={{ x: xMax, y: point.y }}
|
|
933
|
-
stroke={'black'}
|
|
934
|
-
strokeWidth={1}
|
|
935
|
-
pointerEvents='none'
|
|
936
|
-
strokeDasharray='5,5'
|
|
937
|
-
className='horizontal-tooltip-line'
|
|
938
|
-
/>
|
|
939
|
-
</Group>
|
|
940
|
-
)}
|
|
941
|
-
{config.visual.verticalHoverLine && tooltipData && (
|
|
942
|
-
<Group key={`tooltipLine-vertical${point.y}${point.x}`} className='vertical-tooltip-line'>
|
|
943
|
-
<Line
|
|
944
|
-
from={{ x: point.x, y: 0 }}
|
|
945
|
-
to={{ x: point.x, y: yMax }}
|
|
946
|
-
stroke={'black'}
|
|
947
|
-
strokeWidth={1}
|
|
948
|
-
pointerEvents='none'
|
|
949
|
-
strokeDasharray='5,5'
|
|
950
|
-
className='vertical-tooltip-line'
|
|
951
|
-
/>
|
|
952
|
-
</Group>
|
|
979
|
+
{showHoverLine && (
|
|
980
|
+
<>
|
|
981
|
+
<HoverLine xMax={xMax} yMax={yMax} point={point} tooltipData={tooltipData} orientation='horizontal' />
|
|
982
|
+
<HoverLine xMax={xMax} yMax={yMax} point={point} tooltipData={tooltipData} orientation='vertical' />
|
|
983
|
+
</>
|
|
953
984
|
)}
|
|
954
985
|
<Group left={Number(config.runtime.yAxis.size)}>
|
|
955
986
|
<Annotation.Draggable
|
|
@@ -1021,15 +1052,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1021
1052
|
|
|
1022
1053
|
// Vertical value/suffix vars
|
|
1023
1054
|
const lastTick = props.ticks.length - 1 === i
|
|
1024
|
-
const
|
|
1055
|
+
const useInlineLabel = lastTick && inlineLabel
|
|
1056
|
+
const hideTopTick = lastTick && inlineLabel && !inlineLabelHasNoSpace
|
|
1025
1057
|
const valueOnLinePadding = hideAxis ? -8 : -12
|
|
1026
1058
|
const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT
|
|
1027
1059
|
const labelYPadding = labelsAboveGridlines ? 4 : 0
|
|
1028
1060
|
const labelX = tick.to.x - labelXPadding
|
|
1029
1061
|
const labelY = tick.to.y - labelYPadding
|
|
1030
1062
|
const labelVerticalAnchor = labelsAboveGridlines ? 'end' : 'middle'
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1063
|
+
const combineDomInlineLabelWithValue = inlineLabel && labelsAboveGridlines && lastTick
|
|
1064
|
+
const formattedValue = useInlineLabel
|
|
1065
|
+
? tick.formattedValue.replace(config.dataFormat.suffix, '')
|
|
1066
|
+
: tick.formattedValue
|
|
1033
1067
|
|
|
1034
1068
|
return (
|
|
1035
1069
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
@@ -1148,11 +1182,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1148
1182
|
visualizationType !== 'Bump Chart' &&
|
|
1149
1183
|
!config.yAxis.hideLabel && (
|
|
1150
1184
|
<>
|
|
1151
|
-
{/*
|
|
1152
|
-
{/*
|
|
1153
|
-
{/* SPECIAL ONE CHAR CASE: a one character
|
|
1185
|
+
{/* INLINE LABEL BEHAVIOR: Dom suffix for 'inlineLabel' behavior */}
|
|
1186
|
+
{/* inline label is shown alone and is allowed to 'overflow' to the right */}
|
|
1187
|
+
{/* SPECIAL ONE CHAR CASE: a one character inlineLabel does not overflow */}
|
|
1154
1188
|
{/* IF VALUES ON LINE: suffix is combined with value to avoid having to calculate varying (now left-aligned) value widths */}
|
|
1155
|
-
{
|
|
1189
|
+
{inlineLabel && lastTick && !labelsAboveGridlines && (
|
|
1156
1190
|
<BlurStrokeText
|
|
1157
1191
|
innerRef={suffixRef}
|
|
1158
1192
|
display={isLogarithmicAxis ? showTicks : 'block'}
|
|
@@ -1161,7 +1195,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1161
1195
|
y={labelY}
|
|
1162
1196
|
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1163
1197
|
verticalAnchor={labelVerticalAnchor}
|
|
1164
|
-
textAnchor={
|
|
1198
|
+
textAnchor={inlineLabelHasNoSpace ? 'end' : 'start'}
|
|
1165
1199
|
fill={config.yAxis.tickLabelColor}
|
|
1166
1200
|
stroke={'#fff'}
|
|
1167
1201
|
paintOrder={'stroke'} // keeps stroke under fill
|
|
@@ -1169,7 +1203,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1169
1203
|
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1170
1204
|
fontSize={tickLabelFontSize}
|
|
1171
1205
|
>
|
|
1172
|
-
{
|
|
1206
|
+
{inlineLabel}
|
|
1173
1207
|
</BlurStrokeText>
|
|
1174
1208
|
)}
|
|
1175
1209
|
|
|
@@ -1178,7 +1212,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1178
1212
|
innerRef={el => lastTick && (topYLabelRef.current = el)}
|
|
1179
1213
|
display={isLogarithmicAxis ? showTicks : 'block'}
|
|
1180
1214
|
dx={isLogarithmicAxis ? -6 : 0}
|
|
1181
|
-
x={
|
|
1215
|
+
x={inlineLabelHasNoSpace ? labelX - suffixWidth : labelX}
|
|
1182
1216
|
y={labelY + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
1183
1217
|
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1184
1218
|
verticalAnchor={config.runtime.horizontal ? 'start' : labelVerticalAnchor}
|
|
@@ -1191,7 +1225,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1191
1225
|
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1192
1226
|
fontSize={tickLabelFontSize}
|
|
1193
1227
|
>
|
|
1194
|
-
{`${
|
|
1228
|
+
{`${formattedValue}${combineDomInlineLabelWithValue ? inlineLabel : ''}`}
|
|
1195
1229
|
</BlurStrokeText>
|
|
1196
1230
|
</>
|
|
1197
1231
|
)}
|
|
@@ -1495,13 +1529,15 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1495
1529
|
showTooltip &&
|
|
1496
1530
|
!tooltipData?.data?.some(subArray => subArray.some(item => item === undefined)) &&
|
|
1497
1531
|
tooltipData.dataYPosition &&
|
|
1498
|
-
tooltipData.dataXPosition &&
|
|
1532
|
+
tooltipData.dataXPosition &&
|
|
1533
|
+
!config.tooltips.singleSeries && (
|
|
1499
1534
|
<>
|
|
1500
1535
|
<style>{`.tooltip {background-color: rgba(255,255,255, ${
|
|
1501
1536
|
config.tooltips.opacity / 100
|
|
1502
1537
|
}) !important;`}</style>
|
|
1503
1538
|
<style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
|
|
1504
1539
|
<TooltipWithBounds
|
|
1540
|
+
ref={tooltipRef}
|
|
1505
1541
|
key={Math.random()}
|
|
1506
1542
|
className={'tooltip cdc-open-viz-module'}
|
|
1507
1543
|
left={tooltipLeft}
|
|
@@ -1525,7 +1561,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1525
1561
|
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
|
|
1526
1562
|
/>
|
|
1527
1563
|
)}
|
|
1528
|
-
{
|
|
1564
|
+
{!isDraggingAnnotation && (
|
|
1529
1565
|
<ReactTooltip
|
|
1530
1566
|
id={`cdc-open-viz-tooltip-${runtime.uniqueId}`}
|
|
1531
1567
|
variant='light'
|
|
@@ -46,28 +46,51 @@ const PieChart = props => {
|
|
|
46
46
|
const pivotColumns = Object.values(config.columns).filter(column => column.showInViz)
|
|
47
47
|
const dataNeedsPivot = pivotColumns.length > 0
|
|
48
48
|
const pivotKey = dataNeedsPivot ? 'pivotColumn' : undefined
|
|
49
|
+
const showPercentage = config.dataFormat.showPiePercent
|
|
50
|
+
const labelForCalcArea = 'Calculated Area'
|
|
51
|
+
|
|
49
52
|
const _data = useMemo(() => {
|
|
53
|
+
let baseData = []
|
|
54
|
+
|
|
50
55
|
if (dataNeedsPivot) {
|
|
51
|
-
let newData = []
|
|
52
56
|
const primaryColumn = config.yAxis.dataKey
|
|
53
57
|
const additionalColumns = pivotColumns.map(column => column.name)
|
|
54
58
|
const allColumns = [primaryColumn, ...additionalColumns]
|
|
55
59
|
const columnToUpdate = config.xAxis.dataKey
|
|
60
|
+
|
|
56
61
|
data.forEach(d => {
|
|
57
62
|
allColumns.forEach(col => {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
[pivotKey]:
|
|
63
|
+
const val = d[col]
|
|
64
|
+
if (val) {
|
|
65
|
+
baseData.push({
|
|
66
|
+
[pivotKey]: val,
|
|
62
67
|
[columnToUpdate]: `${d[columnToUpdate]} - ${col}`
|
|
63
68
|
})
|
|
64
69
|
}
|
|
65
70
|
})
|
|
66
71
|
})
|
|
67
|
-
|
|
72
|
+
} else {
|
|
73
|
+
baseData = [...data]
|
|
68
74
|
}
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
|
|
76
|
+
// === ADD "OTHER" IF PERCENT MODE IS ENABLED ===
|
|
77
|
+
if (showPercentage) {
|
|
78
|
+
const total = baseData.reduce((sum, d) => {
|
|
79
|
+
const val = parseFloat(d[config.runtime.yAxis.dataKey])
|
|
80
|
+
return sum + (isNaN(val) ? 0 : val)
|
|
81
|
+
}, 0)
|
|
82
|
+
|
|
83
|
+
if (total < 100) {
|
|
84
|
+
const remaining = 100 - total
|
|
85
|
+
baseData.push({
|
|
86
|
+
[config.runtime.xAxis.dataKey]: labelForCalcArea,
|
|
87
|
+
[config.runtime.yAxis.dataKey]: remaining
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return baseData
|
|
93
|
+
}, [data, dataNeedsPivot, showPercentage, config])
|
|
71
94
|
|
|
72
95
|
const _colorScale = useMemo(() => {
|
|
73
96
|
if (dataNeedsPivot) {
|
|
@@ -78,15 +101,31 @@ const PieChart = props => {
|
|
|
78
101
|
const numberOfKeys = Object.entries(keys).length
|
|
79
102
|
let palette = config.customColors || colorPalettes[config.palette]
|
|
80
103
|
palette = palette.slice(0, numberOfKeys)
|
|
81
|
-
|
|
82
104
|
return scaleOrdinal({
|
|
83
105
|
domain: Object.keys(keys),
|
|
84
106
|
range: palette,
|
|
85
107
|
unknown: null
|
|
86
108
|
})
|
|
87
109
|
}
|
|
110
|
+
|
|
111
|
+
if (showPercentage) {
|
|
112
|
+
const keys = {}
|
|
113
|
+
_data.forEach(d => {
|
|
114
|
+
keys[d[config.xAxis.dataKey]] = true
|
|
115
|
+
})
|
|
116
|
+
// take out Calculated Area so it falls back to `unknown`
|
|
117
|
+
const domainKeys = Object.keys(keys).filter(k => k !== labelForCalcArea)
|
|
118
|
+
|
|
119
|
+
const basePalette = (config.customColors || colorPalettes[config.palette]).slice(0, domainKeys.length)
|
|
120
|
+
const COOL_GRAY_90 = getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-10').trim()
|
|
121
|
+
return scaleOrdinal({
|
|
122
|
+
domain: domainKeys,
|
|
123
|
+
range: basePalette,
|
|
124
|
+
unknown: COOL_GRAY_90
|
|
125
|
+
})
|
|
126
|
+
}
|
|
88
127
|
return colorScale
|
|
89
|
-
}, [
|
|
128
|
+
}, [_data, dataNeedsPivot, colorScale])
|
|
90
129
|
|
|
91
130
|
const triggerRef = useRef()
|
|
92
131
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
@@ -130,9 +169,15 @@ const PieChart = props => {
|
|
|
130
169
|
const roundTo = Number(config.dataFormat.roundTo) || 0
|
|
131
170
|
// Calculate the percentage of the full circle (360 degrees)
|
|
132
171
|
const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
|
|
172
|
+
const valueFromData = parseFloat(arc.data[config.runtime.yAxis.dataKey])
|
|
173
|
+
const percentageToDisplay = showPercentage ? valueFromData : (degrees / 360) * 100
|
|
174
|
+
|
|
175
|
+
let roundedPercentage = percentageToDisplay.toFixed(roundTo) + '%'
|
|
176
|
+
// add missing pie part
|
|
177
|
+
if (arc.data[config.xAxis.dataKey] === labelForCalcArea && config.dataFormat.showPiePercent) {
|
|
178
|
+
roundedPercentage = '**'
|
|
179
|
+
}
|
|
133
180
|
|
|
134
|
-
const percentageOfCircle = (degrees / 360) * 100
|
|
135
|
-
const roundedPercentage = percentageOfCircle.toFixed(roundTo) + '%'
|
|
136
181
|
return (
|
|
137
182
|
<Group key={key} className={`slice-${key}`}>
|
|
138
183
|
{/* ── the slice */}
|
|
@@ -238,7 +283,7 @@ const PieChart = props => {
|
|
|
238
283
|
<AnimatedPie
|
|
239
284
|
{...pie}
|
|
240
285
|
getKey={d => d.data[config.runtime.xAxis.dataKey]}
|
|
241
|
-
|
|
286
|
+
colorScale={_colorScale}
|
|
242
287
|
onHover={handleTooltipMouseOver}
|
|
243
288
|
onLeave={handleTooltipMouseOff}
|
|
244
289
|
/>
|
|
@@ -89,6 +89,11 @@ export default {
|
|
|
89
89
|
topAxis: {
|
|
90
90
|
hasLine: false
|
|
91
91
|
},
|
|
92
|
+
brush: {
|
|
93
|
+
isActive: false,
|
|
94
|
+
isBrushing: false,
|
|
95
|
+
data: []
|
|
96
|
+
},
|
|
92
97
|
isLegendValue: false,
|
|
93
98
|
barThickness: 0.35,
|
|
94
99
|
barHeight: 25,
|
|
@@ -120,7 +125,8 @@ export default {
|
|
|
120
125
|
maxTickRotation: 0,
|
|
121
126
|
padding: 5,
|
|
122
127
|
showYearsOnce: false,
|
|
123
|
-
sortByRecentDate: false
|
|
128
|
+
sortByRecentDate: false,
|
|
129
|
+
brushActive: false
|
|
124
130
|
},
|
|
125
131
|
table: {
|
|
126
132
|
label: 'Data Table',
|
|
@@ -176,10 +182,7 @@ export default {
|
|
|
176
182
|
position: 'right',
|
|
177
183
|
orderedValues: []
|
|
178
184
|
},
|
|
179
|
-
|
|
180
|
-
height: 45,
|
|
181
|
-
active: false
|
|
182
|
-
},
|
|
185
|
+
|
|
183
186
|
exclusions: {
|
|
184
187
|
active: false,
|
|
185
188
|
keys: []
|
|
@@ -15,17 +15,8 @@ export const countNumOfTicks = ({ axis, max, runtime, currentViewport, isHorizon
|
|
|
15
15
|
? undefined
|
|
16
16
|
: !isHorizontal && numTicks && numTicks
|
|
17
17
|
// to fix edge case of small numbers with decimals
|
|
18
|
-
if (tickCount === undefined
|
|
19
|
-
//
|
|
20
|
-
if (Number(max) <= 3) {
|
|
21
|
-
tickCount = 2
|
|
22
|
-
} else {
|
|
23
|
-
tickCount = 4 // same default as standalone components
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (Number(tickCount) > Number(max) && !isHorizontal) {
|
|
27
|
-
// cap it and round it so its an integer
|
|
28
|
-
tickCount = Math.max(2, Number(min) < 0 ? Math.round(max) * 2 : Math.round(max))
|
|
18
|
+
if (tickCount === undefined) {
|
|
19
|
+
tickCount = 4 // same default as standalone components
|
|
29
20
|
}
|
|
30
21
|
}
|
|
31
22
|
|
|
@@ -38,14 +29,8 @@ export const countNumOfTicks = ({ axis, max, runtime, currentViewport, isHorizon
|
|
|
38
29
|
: !isHorizontal && !numTicks
|
|
39
30
|
? undefined
|
|
40
31
|
: !isHorizontal && numTicks && numTicks
|
|
41
|
-
if (isHorizontal && tickCount === undefined
|
|
42
|
-
//
|
|
43
|
-
// - check for small numbers situation
|
|
44
|
-
if (max <= 3) {
|
|
45
|
-
tickCount = 2
|
|
46
|
-
} else {
|
|
47
|
-
tickCount = 4 // same default as standalone components
|
|
48
|
-
}
|
|
32
|
+
if (isHorizontal && tickCount === undefined) {
|
|
33
|
+
tickCount = 4 // same default as standalone components
|
|
49
34
|
}
|
|
50
35
|
|
|
51
36
|
if (config.visualizationType === 'Forest Plot') {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
export const getNewRuntime = (visualizationConfig, newFilteredData) => {
|
|
4
|
+
const runtime = _.cloneDeep(visualizationConfig.runtime) || {}
|
|
5
|
+
runtime.series = []
|
|
6
|
+
runtime.seriesLabels = {}
|
|
7
|
+
runtime.seriesLabelsAll = []
|
|
8
|
+
const { filters, columns, dynamicSeriesType, dynamicSeriesLineType, xAxis } = visualizationConfig
|
|
9
|
+
if (newFilteredData?.length) {
|
|
10
|
+
const columnNames = Object.keys(newFilteredData[0])
|
|
11
|
+
columnNames.forEach(colName => {
|
|
12
|
+
const isNotXAxis = xAxis.dataKey !== colName
|
|
13
|
+
const isNotFiltered = !filters || !filters?.find(filter => filter.columnName === colName)
|
|
14
|
+
const noColConfiguration = !columns || Object.keys(columns).indexOf(colName) === -1
|
|
15
|
+
if (isNotXAxis && isNotFiltered && noColConfiguration) {
|
|
16
|
+
runtime.series.push({
|
|
17
|
+
dataKey: colName,
|
|
18
|
+
type: dynamicSeriesType,
|
|
19
|
+
lineType: dynamicSeriesLineType,
|
|
20
|
+
tooltip: true
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
runtime.seriesKeys = runtime.series
|
|
27
|
+
? runtime.series.map(series => {
|
|
28
|
+
runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
|
|
29
|
+
runtime.seriesLabelsAll.push(series.name || series.dataKey)
|
|
30
|
+
return series.dataKey
|
|
31
|
+
})
|
|
32
|
+
: []
|
|
33
|
+
|
|
34
|
+
return runtime
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
export const getPiePercent = (data: Record<string, any>[], seriesKey: string): Record<string, any>[] => {
|
|
4
|
+
// getonly the numeric values for each seriesKey
|
|
5
|
+
const numericValues = data.map(row => _.toNumber(row[seriesKey])).filter(v => !Number.isNaN(v))
|
|
6
|
+
|
|
7
|
+
const total = numericValues.reduce((sum, v) => sum + v, 0)
|
|
8
|
+
|
|
9
|
+
return data.map(row => {
|
|
10
|
+
const raw = _.toNumber(row[seriesKey])
|
|
11
|
+
if (Number.isNaN(raw)) {
|
|
12
|
+
// skip non-numeric / undefined
|
|
13
|
+
return row
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pct = total === 0 ? 0 : (raw / total) * 100
|
|
17
|
+
return {
|
|
18
|
+
...row,
|
|
19
|
+
[seriesKey]: pct
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type DataRow = Record<string, any>
|
|
2
|
+
|
|
3
|
+
export const getTransformedData = ({
|
|
4
|
+
brushData,
|
|
5
|
+
filteredData,
|
|
6
|
+
excludedData,
|
|
7
|
+
clean
|
|
8
|
+
}: {
|
|
9
|
+
brushData: DataRow[]
|
|
10
|
+
filteredData: DataRow[]
|
|
11
|
+
excludedData: DataRow[]
|
|
12
|
+
clean: (data: DataRow[]) => DataRow[]
|
|
13
|
+
}): DataRow[] => {
|
|
14
|
+
const data =
|
|
15
|
+
Array.isArray(brushData) && brushData.length > 0
|
|
16
|
+
? brushData
|
|
17
|
+
: Array.isArray(filteredData) && filteredData.length > 0
|
|
18
|
+
? filteredData
|
|
19
|
+
: excludedData
|
|
20
|
+
|
|
21
|
+
return clean(data)
|
|
22
|
+
}
|