@cdc/chart 4.25.6 → 4.25.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.
- package/dist/cdcchart.js +53500 -32825
- package/package.json +3 -2
- package/src/CdcChart.tsx +9 -2
- package/src/CdcChartComponent.tsx +30 -12
- package/src/_stories/Chart.BoxPlot.stories.tsx +35 -0
- package/src/_stories/Chart.stories.tsx +0 -7
- package/src/_stories/Chart.tooltip.stories.tsx +35 -275
- package/src/_stories/_mock/bar-chart-suppressed.json +2 -80
- package/src/_stories/_mock/boxplot_multiseries.json +252 -166
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +4 -8
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +45 -7
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
- package/src/components/BarChart/components/BarChart.Vertical.tsx +36 -4
- package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +131 -0
- package/src/components/BoxPlot/{BoxPlot.tsx → BoxPlot.Vertical.tsx} +4 -4
- package/src/components/BoxPlot/helpers/index.ts +32 -12
- package/src/components/Brush/BrushChart.tsx +65 -10
- package/src/components/Brush/BrushController.tsx +71 -0
- package/src/components/Brush/types.tsx +8 -0
- package/src/components/BrushChart.tsx +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +19 -14
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -34
- package/src/components/Forecasting/{Forecasting.jsx → Forecasting.tsx} +32 -12
- package/src/components/Legend/Legend.Component.tsx +16 -1
- package/src/components/Legend/Legend.tsx +3 -1
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +1 -0
- package/src/components/Legend/helpers/index.ts +2 -2
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +27 -26
- package/src/components/LineChart/helpers.ts +7 -7
- package/src/components/LinearChart.tsx +130 -75
- package/src/data/initial-state.js +12 -15
- package/src/helpers/countNumOfTicks.ts +4 -19
- package/src/helpers/filterAndShiftLinearDateTicks.ts +58 -0
- package/src/helpers/getBridgedData.ts +13 -0
- package/src/helpers/tests/getBridgedData.test.ts +64 -0
- package/src/hooks/useScales.ts +42 -42
- package/src/hooks/useTooltip.tsx +3 -2
- package/src/index.jsx +6 -1
- package/src/scss/main.scss +2 -4
- package/src/store/chart.actions.ts +2 -2
- package/src/store/chart.reducer.ts +4 -12
- package/src/types/ChartConfig.ts +1 -6
- package/src/components/BoxPlot/index.tsx +0 -3
- package/src/components/Brush/BrushController..tsx +0 -39
|
@@ -14,7 +14,8 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
|
14
14
|
import { AreaChartStacked } from './AreaChart'
|
|
15
15
|
import BarChart from './BarChart'
|
|
16
16
|
import ConfigContext from '../ConfigContext'
|
|
17
|
-
import
|
|
17
|
+
import BoxPlotVertical from './BoxPlot/BoxPlot.Vertical'
|
|
18
|
+
import BoxPlotHorizontal from './BoxPlot/BoxPlot.Horizontal'
|
|
18
19
|
import ScatterPlot from './ScatterPlot'
|
|
19
20
|
import DeviationBar from './DeviationBar'
|
|
20
21
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
@@ -25,17 +26,20 @@ import PairedBarChart from './PairedBarChart'
|
|
|
25
26
|
import useIntersectionObserver from '../hooks/useIntersectionObserver'
|
|
26
27
|
import Regions from './Regions'
|
|
27
28
|
import CategoricalYAxis from './Axis/Categorical.Axis'
|
|
29
|
+
import BrushChart from './Brush/BrushController'
|
|
28
30
|
|
|
29
31
|
// Helpers
|
|
30
|
-
import { isLegendWrapViewport,
|
|
32
|
+
import { isLegendWrapViewport, isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
31
33
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
32
34
|
import { calcInitialHeight, handleAutoPaddingRight } from '../helpers/sizeHelpers'
|
|
35
|
+
import { filterAndShiftLinearDateTicks } from '../helpers/filterAndShiftLinearDateTicks'
|
|
33
36
|
|
|
34
37
|
// Hooks
|
|
35
38
|
import useMinMax from '../hooks/useMinMax'
|
|
36
39
|
import useReduceData from '../hooks/useReduceData'
|
|
37
40
|
import useRightAxis from '../hooks/useRightAxis'
|
|
38
|
-
import useScales, { getTickValues
|
|
41
|
+
import useScales, { getTickValues } from '../hooks/useScales'
|
|
42
|
+
|
|
39
43
|
import getTopAxis from '../helpers/getTopAxis'
|
|
40
44
|
import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
41
45
|
import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
@@ -43,7 +47,6 @@ import Annotation from './Annotations'
|
|
|
43
47
|
import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
|
|
44
48
|
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
45
49
|
import HoverLine from './HoverLine/HoverLine'
|
|
46
|
-
import BrushChart from './BrushChart'
|
|
47
50
|
|
|
48
51
|
type LinearChartProps = {
|
|
49
52
|
parentWidth: number
|
|
@@ -94,7 +97,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
94
97
|
tableData,
|
|
95
98
|
transformedData: data,
|
|
96
99
|
seriesHighlight,
|
|
97
|
-
|
|
100
|
+
|
|
98
101
|
} = useContext(ConfigContext)
|
|
99
102
|
|
|
100
103
|
// CONFIG
|
|
@@ -151,8 +154,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
151
154
|
const labelsOverflow = inlineLabel && !inlineLabelHasNoSpace
|
|
152
155
|
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
153
156
|
const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
|
|
154
|
-
const tickLabelFontSize =
|
|
155
|
-
const axisLabelFontSize =
|
|
157
|
+
const tickLabelFontSize = isMobileFontViewport(currentViewport) ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
|
|
158
|
+
const axisLabelFontSize = isMobileFontViewport(currentViewport) ? AXIS_LABEL_FONT_SIZE_SMALL : AXIS_LABEL_FONT_SIZE
|
|
156
159
|
const GET_TEXT_WIDTH_FONT = `normal ${tickLabelFontSize}px Nunito, sans-serif`
|
|
157
160
|
|
|
158
161
|
// zero if not forest plot
|
|
@@ -208,10 +211,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
208
211
|
? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
|
|
209
212
|
: d[config.runtime.originalXAxis.dataKey]
|
|
210
213
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
211
|
-
const xAxisDataMapped =
|
|
212
|
-
config.brush.active && brushConfig.data?.length
|
|
213
|
-
? brushConfig.data.map(d => getXAxisData(d))
|
|
214
|
-
: data.map(d => getXAxisData(d))
|
|
214
|
+
const xAxisDataMapped = data.map(d => getXAxisData(d))
|
|
215
215
|
const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
|
|
216
216
|
const properties = {
|
|
217
217
|
data,
|
|
@@ -288,7 +288,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
288
288
|
if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
|
|
289
289
|
if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
|
|
290
290
|
if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
|
|
291
|
-
if (orientation === 'vertical' && max - min < 3)
|
|
291
|
+
if (orientation === 'vertical' && max - min < 3 && !config.dataFormat?.roundTo)
|
|
292
292
|
return formatNumber(tick, 'left', shouldAbbreviate, false, false, '1', { index, length: ticks.length })
|
|
293
293
|
if (orientation === 'vertical') {
|
|
294
294
|
// TODO suggestion: pass all options as object key/values to allow for more flexibility
|
|
@@ -418,8 +418,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
418
418
|
const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
|
|
419
419
|
|
|
420
420
|
// Heights to add
|
|
421
|
-
|
|
422
|
-
const
|
|
421
|
+
|
|
422
|
+
const brushHeight = 25
|
|
423
|
+
const brushHeightWithMargin = config.xAxis.brushActive ? brushHeight + brushHeight : 0
|
|
423
424
|
const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
|
|
424
425
|
const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
|
|
425
426
|
const additionalHeight = axisBottomHeight + brushHeightWithMargin + forestRowsHeight + topLabelOnGridlineHeight
|
|
@@ -447,7 +448,15 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
447
448
|
const legendIsLeftOrRight =
|
|
448
449
|
legend?.position !== 'top' && legend?.position !== 'bottom' && !isLegendWrapViewport(currentViewport)
|
|
449
450
|
legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
|
|
450
|
-
}, [
|
|
451
|
+
}, [
|
|
452
|
+
axisBottomRef.current,
|
|
453
|
+
config,
|
|
454
|
+
bottomLabelStart,
|
|
455
|
+
config.xAxis.brushActive,
|
|
456
|
+
currentViewport,
|
|
457
|
+
topYLabelRef.current,
|
|
458
|
+
initialHeight
|
|
459
|
+
])
|
|
451
460
|
|
|
452
461
|
useEffect(() => {
|
|
453
462
|
if (lastMaxValue.current === maxValue) return
|
|
@@ -663,8 +672,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
663
672
|
onMouseMove={onMouseMove}
|
|
664
673
|
width={parentWidth + config.yAxis.rightAxisSize}
|
|
665
674
|
height={isNoDataAvailable ? 1 : parentHeight}
|
|
666
|
-
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
667
|
-
|
|
675
|
+
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
676
|
+
debugSvg && 'debug'
|
|
677
|
+
} ${isDraggingAnnotation && 'dragging-annotation'}`}
|
|
668
678
|
role='img'
|
|
669
679
|
aria-label={handleChartAriaLabels(config)}
|
|
670
680
|
style={{ overflow: 'visible' }}
|
|
@@ -744,8 +754,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
744
754
|
showTooltip={showTooltip}
|
|
745
755
|
/>
|
|
746
756
|
)}
|
|
747
|
-
{visualizationType === 'Box Plot' && (
|
|
748
|
-
<
|
|
757
|
+
{visualizationType === 'Box Plot' && config.orientation === 'vertical' && (
|
|
758
|
+
<BoxPlotVertical
|
|
759
|
+
seriesScale={seriesScale}
|
|
760
|
+
xMax={xMax}
|
|
761
|
+
yMax={yMax}
|
|
762
|
+
min={min}
|
|
763
|
+
max={max}
|
|
764
|
+
xScale={xScale}
|
|
765
|
+
yScale={yScale}
|
|
766
|
+
/>
|
|
767
|
+
)}
|
|
768
|
+
{visualizationType === 'Box Plot' && config.orientation === 'horizontal' && (
|
|
769
|
+
<BoxPlotHorizontal
|
|
749
770
|
seriesScale={seriesScale}
|
|
750
771
|
xMax={xMax}
|
|
751
772
|
yMax={yMax}
|
|
@@ -757,20 +778,20 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
757
778
|
)}
|
|
758
779
|
{((visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') ||
|
|
759
780
|
visualizationType === 'Combo') && (
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
781
|
+
<AreaChartStacked
|
|
782
|
+
xScale={xScale}
|
|
783
|
+
yScale={yScale}
|
|
784
|
+
yMax={yMax}
|
|
785
|
+
xMax={xMax}
|
|
786
|
+
chartRef={svgRef}
|
|
787
|
+
width={xMax}
|
|
788
|
+
height={yMax}
|
|
789
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
790
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
791
|
+
tooltipData={tooltipData}
|
|
792
|
+
showTooltip={showTooltip}
|
|
793
|
+
/>
|
|
794
|
+
)}
|
|
774
795
|
{(visualizationType === 'Bar' || visualizationType === 'Combo' || convertLineToBarGraph) && (
|
|
775
796
|
<BarChart
|
|
776
797
|
xScale={xScale}
|
|
@@ -863,7 +884,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
863
884
|
/>
|
|
864
885
|
)}
|
|
865
886
|
{/*Brush chart */}
|
|
866
|
-
{config.
|
|
887
|
+
{config.xAxis.brushActive && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
|
|
867
888
|
{/* Line chart */}
|
|
868
889
|
{/* TODO: Make this just line or combo? */}
|
|
869
890
|
{!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
|
|
@@ -1018,22 +1039,31 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1018
1039
|
to={
|
|
1019
1040
|
runtime.horizontal
|
|
1020
1041
|
? {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1042
|
+
x: 0,
|
|
1043
|
+
y:
|
|
1044
|
+
config.visualizationType === 'Forest Plot' ? parentHeight : Number(heights.horizontal)
|
|
1045
|
+
}
|
|
1025
1046
|
: props.axisToPoint
|
|
1026
1047
|
}
|
|
1027
1048
|
stroke='#000'
|
|
1028
1049
|
/>
|
|
1029
1050
|
)}
|
|
1030
|
-
{yScale.domain()[0] < 0 && (
|
|
1051
|
+
{orientation === 'vertical' && yScale.domain()[0] < 0 && (
|
|
1052
|
+
// draw from the Left of the chart …
|
|
1031
1053
|
<Line
|
|
1032
1054
|
from={{ x: props.axisFromPoint.x, y: yScale(0) }}
|
|
1033
1055
|
to={{ x: xMax, y: yScale(0) }}
|
|
1034
1056
|
stroke='#333'
|
|
1035
1057
|
/>
|
|
1036
1058
|
)}
|
|
1059
|
+
{orientation === 'horizontal' && xScale.domain()[0] < 0 && (
|
|
1060
|
+
<Line
|
|
1061
|
+
// draw from the top of the char
|
|
1062
|
+
from={{ x: xScale(0), y: 0 }}
|
|
1063
|
+
to={{ x: xScale(0), y: yMax }}
|
|
1064
|
+
stroke='#333'
|
|
1065
|
+
/>
|
|
1066
|
+
)}
|
|
1037
1067
|
{visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && (
|
|
1038
1068
|
<Line
|
|
1039
1069
|
from={{ x: xScale(0), y: 0 }}
|
|
@@ -1061,8 +1091,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1061
1091
|
const labelVerticalAnchor = labelsAboveGridlines ? 'end' : 'middle'
|
|
1062
1092
|
const combineDomInlineLabelWithValue = inlineLabel && labelsAboveGridlines && lastTick
|
|
1063
1093
|
const formattedValue = useInlineLabel
|
|
1064
|
-
? tick
|
|
1065
|
-
: tick
|
|
1094
|
+
? String(tick?.formattedValue || '').replace(config.dataFormat.suffix, '')
|
|
1095
|
+
: tick?.formattedValue
|
|
1066
1096
|
|
|
1067
1097
|
return (
|
|
1068
1098
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
@@ -1078,16 +1108,36 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1078
1108
|
)}
|
|
1079
1109
|
|
|
1080
1110
|
{orientation === 'horizontal' &&
|
|
1111
|
+
visualizationType === 'Box Plot' &&
|
|
1112
|
+
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1113
|
+
!config.yAxis.hideLabel && (
|
|
1114
|
+
<Text
|
|
1115
|
+
x={tick.to.x}
|
|
1116
|
+
y={yScale(tick.value) + yScale.bandwidth() / 2}
|
|
1117
|
+
transform={`rotate(${
|
|
1118
|
+
config.orientation === 'horizontal' ? config.runtime.yAxis.tickRotation || 0 : 0
|
|
1119
|
+
}, ${tick.to.x}, ${tick.to.y})`}
|
|
1120
|
+
verticalAnchor={'middle'}
|
|
1121
|
+
textAnchor={'end'}
|
|
1122
|
+
fontSize={tickLabelFontSize}
|
|
1123
|
+
>
|
|
1124
|
+
{tick.formattedValue}
|
|
1125
|
+
</Text>
|
|
1126
|
+
)}
|
|
1127
|
+
|
|
1128
|
+
{orientation === 'horizontal' &&
|
|
1129
|
+
visualizationType !== 'Box Plot' &&
|
|
1081
1130
|
visualizationSubType !== 'stacked' &&
|
|
1082
1131
|
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1083
1132
|
!config.yAxis.hideLabel && (
|
|
1084
1133
|
<Text
|
|
1085
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1134
|
+
transform={`translate(${tick.to.x - 5}, ${
|
|
1135
|
+
config.isLollipopChart
|
|
1086
1136
|
? tick.to.y - minY
|
|
1087
1137
|
: tick.to.y -
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1138
|
+
minY +
|
|
1139
|
+
(Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
|
|
1140
|
+
}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
|
|
1091
1141
|
verticalAnchor={'start'}
|
|
1092
1142
|
textAnchor={'end'}
|
|
1093
1143
|
fontSize={tickLabelFontSize}
|
|
@@ -1101,8 +1151,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1101
1151
|
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1102
1152
|
!config.yAxis.hideLabel && (
|
|
1103
1153
|
<Text
|
|
1104
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1105
|
-
|
|
1154
|
+
transform={`translate(${tick.to.x - 5}, ${
|
|
1155
|
+
tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
|
|
1156
|
+
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1106
1157
|
verticalAnchor={'start'}
|
|
1107
1158
|
textAnchor={'end'}
|
|
1108
1159
|
fontSize={tickLabelFontSize}
|
|
@@ -1115,8 +1166,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1115
1166
|
visualizationType === 'Paired Bar' &&
|
|
1116
1167
|
!config.yAxis.hideLabel && (
|
|
1117
1168
|
<Text
|
|
1118
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1119
|
-
|
|
1169
|
+
transform={`translate(${tick.to.x - 5}, ${
|
|
1170
|
+
tick.to.y - minY + Number(config.barHeight) / 2
|
|
1171
|
+
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1120
1172
|
textAnchor={'end'}
|
|
1121
1173
|
verticalAnchor='middle'
|
|
1122
1174
|
fontSize={tickLabelFontSize}
|
|
@@ -1128,10 +1180,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1128
1180
|
visualizationType === 'Deviation Bar' &&
|
|
1129
1181
|
!config.yAxis.hideLabel && (
|
|
1130
1182
|
<Text
|
|
1131
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1183
|
+
transform={`translate(${tick.to.x - 5}, ${
|
|
1184
|
+
config.isLollipopChart
|
|
1132
1185
|
? tick.to.y - minY + 2
|
|
1133
1186
|
: tick.to.y - minY + Number(config.barHeight) / 2
|
|
1134
|
-
|
|
1187
|
+
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1135
1188
|
textAnchor={'end'}
|
|
1136
1189
|
verticalAnchor='middle'
|
|
1137
1190
|
fontSize={tickLabelFontSize}
|
|
@@ -1162,14 +1215,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1162
1215
|
seriesHighlight.includes(
|
|
1163
1216
|
config.runtime.seriesLabelsAll[tick.formattedValue - 1]
|
|
1164
1217
|
)) && (
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1218
|
+
<rect
|
|
1219
|
+
x={0 - Number(config.yAxis.size)}
|
|
1220
|
+
y={tick.to.y - 8 + (config.runtime.horizontal ? horizontalTickOffset : 7)}
|
|
1221
|
+
width={Number(config.yAxis.size) + xScale(xScale.domain()[0])}
|
|
1222
|
+
height='2'
|
|
1223
|
+
fill={colorScale(config.runtime.seriesLabelsAll[tick.formattedValue - 1])}
|
|
1224
|
+
/>
|
|
1225
|
+
)}
|
|
1173
1226
|
</>
|
|
1174
1227
|
)}
|
|
1175
1228
|
{orientation === 'vertical' &&
|
|
@@ -1312,8 +1365,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1312
1365
|
className='y-label'
|
|
1313
1366
|
textAnchor='middle'
|
|
1314
1367
|
verticalAnchor='start'
|
|
1315
|
-
transform={`translate(${
|
|
1316
|
-
|
|
1368
|
+
transform={`translate(${
|
|
1369
|
+
config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
|
|
1370
|
+
}, ${axisCenter}) rotate(-90)`}
|
|
1317
1371
|
fontWeight='bold'
|
|
1318
1372
|
fill={config.yAxis.rightAxisLabelColor}
|
|
1319
1373
|
fontSize={axisLabelFontSize}
|
|
@@ -1345,8 +1399,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1345
1399
|
runtime.horizontal && config.visualizationType !== 'Forest Plot'
|
|
1346
1400
|
? Number(heights.horizontal) + Number(config.xAxis.axisPadding)
|
|
1347
1401
|
: config.visualizationType === 'Forest Plot'
|
|
1348
|
-
|
|
1349
|
-
|
|
1402
|
+
? yMax + Number(config.xAxis.axisPadding)
|
|
1403
|
+
: yMax
|
|
1350
1404
|
}
|
|
1351
1405
|
left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
|
|
1352
1406
|
label={config[section].label}
|
|
@@ -1359,8 +1413,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1359
1413
|
config.runtime.xAxis.manual
|
|
1360
1414
|
? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
|
|
1361
1415
|
: config.runtime.xAxis.type === 'date'
|
|
1362
|
-
|
|
1363
|
-
|
|
1416
|
+
? xAxisDataMapped
|
|
1417
|
+
: undefined
|
|
1364
1418
|
}
|
|
1365
1419
|
>
|
|
1366
1420
|
{props => {
|
|
@@ -1386,14 +1440,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1386
1440
|
// filter out every [distanceBetweenTicks] tick starting from the end, so the last tick is always labeled
|
|
1387
1441
|
const filteredTicks = useDateSpanMonths
|
|
1388
1442
|
? [...props.ticks]
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1443
|
+
.reverse()
|
|
1444
|
+
.filter((_, i) => i % distanceBetweenTicks === 0)
|
|
1445
|
+
.reverse()
|
|
1446
|
+
.map((tick, i, arr) => ({
|
|
1447
|
+
...tick,
|
|
1448
|
+
// reformat in case showYearsOnce, since first month of year may have changed
|
|
1449
|
+
formattedValue: handleBottomTickFormatting(tick.value, i, arr)
|
|
1450
|
+
}))
|
|
1397
1451
|
: props.ticks
|
|
1398
1452
|
|
|
1399
1453
|
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
@@ -1526,8 +1580,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1526
1580
|
tooltipData.dataXPosition &&
|
|
1527
1581
|
!config.tooltips.singleSeries && (
|
|
1528
1582
|
<>
|
|
1529
|
-
<style>{`.tooltip {background-color: rgba(255,255,255, ${
|
|
1530
|
-
|
|
1583
|
+
<style>{`.tooltip {background-color: rgba(255,255,255, ${
|
|
1584
|
+
config.tooltips.opacity / 100
|
|
1585
|
+
}) !important;`}</style>
|
|
1531
1586
|
<style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
|
|
1532
1587
|
<TooltipWithBounds
|
|
1533
1588
|
ref={tooltipRef}
|
|
@@ -44,16 +44,16 @@ export default {
|
|
|
44
44
|
enablePadding: false,
|
|
45
45
|
min: '',
|
|
46
46
|
max: '',
|
|
47
|
-
labelColor: '#
|
|
48
|
-
tickLabelColor: '#
|
|
49
|
-
tickColor: '#
|
|
47
|
+
labelColor: '#1c1d1f',
|
|
48
|
+
tickLabelColor: '#1c1d1f',
|
|
49
|
+
tickColor: '#1c1d1f',
|
|
50
50
|
rightHideAxis: false,
|
|
51
51
|
rightAxisSize: 0,
|
|
52
52
|
rightLabel: '',
|
|
53
53
|
rightLabelOffsetSize: 0,
|
|
54
|
-
rightAxisLabelColor: '#
|
|
55
|
-
rightAxisTickLabelColor: '#
|
|
56
|
-
rightAxisTickColor: '#
|
|
54
|
+
rightAxisLabelColor: '#1c1d1f',
|
|
55
|
+
rightAxisTickLabelColor: '#1c1d1f',
|
|
56
|
+
rightAxisTickColor: '#1c1d1f',
|
|
57
57
|
numTicks: '',
|
|
58
58
|
axisPadding: 0,
|
|
59
59
|
scalePadding: 10,
|
|
@@ -89,11 +89,7 @@ export default {
|
|
|
89
89
|
topAxis: {
|
|
90
90
|
hasLine: false
|
|
91
91
|
},
|
|
92
|
-
|
|
93
|
-
isActive: false,
|
|
94
|
-
isBrushing: false,
|
|
95
|
-
data: []
|
|
96
|
-
},
|
|
92
|
+
|
|
97
93
|
isLegendValue: false,
|
|
98
94
|
barThickness: 0.35,
|
|
99
95
|
barHeight: 25,
|
|
@@ -115,9 +111,9 @@ export default {
|
|
|
115
111
|
tickRotation: 0,
|
|
116
112
|
min: '',
|
|
117
113
|
max: '',
|
|
118
|
-
labelColor: '#
|
|
119
|
-
tickLabelColor: '#
|
|
120
|
-
tickColor: '#
|
|
114
|
+
labelColor: '#1c1d1f',
|
|
115
|
+
tickLabelColor: '#1c1d1f',
|
|
116
|
+
tickColor: '#1c1d1f',
|
|
121
117
|
numTicks: '',
|
|
122
118
|
labelOffset: 0,
|
|
123
119
|
axisPadding: 200,
|
|
@@ -142,7 +138,8 @@ export default {
|
|
|
142
138
|
showVertical: true,
|
|
143
139
|
dateDisplayFormat: '',
|
|
144
140
|
showMissingDataLabel: true,
|
|
145
|
-
showSuppressedSymbol: true
|
|
141
|
+
showSuppressedSymbol: true,
|
|
142
|
+
collapsible: true
|
|
146
143
|
},
|
|
147
144
|
orientation: 'vertical',
|
|
148
145
|
color: 'pinkpurple',
|
|
@@ -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,58 @@
|
|
|
1
|
+
// Ensure that the last tick is shown for charts with a "Date (Linear Scale)" scale
|
|
2
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
3
|
+
import { getTicks } from '@visx/scale'
|
|
4
|
+
export const filterAndShiftLinearDateTicks = (
|
|
5
|
+
config: ChartConfig,
|
|
6
|
+
axisProps: { scale: any; numTicks: number; ticks: { value: any; formattedValue?: string }[] },
|
|
7
|
+
xAxisDataMapped: any[],
|
|
8
|
+
formatDate: (value: any, index: number, all: any[]) => string
|
|
9
|
+
) => {
|
|
10
|
+
// Guard #1: must have a scale & ticks array
|
|
11
|
+
if (!axisProps?.scale || !Array.isArray(axisProps.ticks)) {
|
|
12
|
+
return []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Guard #2: if no domain data, just format what we have
|
|
16
|
+
if (!Array.isArray(xAxisDataMapped) || xAxisDataMapped.length === 0) {
|
|
17
|
+
axisProps.ticks.forEach((t, i) => {
|
|
18
|
+
t.formattedValue = formatDate(t.value, i, axisProps.ticks)
|
|
19
|
+
})
|
|
20
|
+
return axisProps.ticks
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// get our filtered tick *values*
|
|
24
|
+
const filteredTickValues = getTicks(axisProps.scale, axisProps.numTicks) || []
|
|
25
|
+
|
|
26
|
+
let ticks = axisProps.ticks
|
|
27
|
+
|
|
28
|
+
if (filteredTickValues.length > 0 && filteredTickValues.length < xAxisDataMapped.length) {
|
|
29
|
+
const lastFiltered = filteredTickValues[filteredTickValues.length - 1]
|
|
30
|
+
const lastIdx = xAxisDataMapped.indexOf(lastFiltered)
|
|
31
|
+
|
|
32
|
+
let shift = 0
|
|
33
|
+
if (lastIdx >= 0 && lastIdx < xAxisDataMapped.length - 1) {
|
|
34
|
+
shift = config.xAxis.sortByRecentDate
|
|
35
|
+
? -xAxisDataMapped.indexOf(filteredTickValues[0])
|
|
36
|
+
: xAxisDataMapped.length - 1 - lastIdx
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ticks = filteredTickValues.map(value => {
|
|
40
|
+
const baseIndex = axisProps.ticks.findIndex(t => t.value === value)
|
|
41
|
+
const targetIndex = baseIndex + shift
|
|
42
|
+
|
|
43
|
+
// Guard #3: if shifting would go out of bounds, clamp
|
|
44
|
+
const safeIndex = Math.max(0, Math.min(axisProps.ticks.length - 1, targetIndex))
|
|
45
|
+
return axisProps.ticks[safeIndex]
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Finally, format all ticks
|
|
50
|
+
ticks.forEach((tick, i) => {
|
|
51
|
+
// Guard #4: only format if we actually have a value
|
|
52
|
+
if (tick && tick.value != null) {
|
|
53
|
+
tick.formattedValue = formatDate(tick.value, i, ticks)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return ticks
|
|
58
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const getBridgedData = (stageKey: string, stageColumn: string, data: Record<string, any>[]) => {
|
|
2
|
+
const allStages: string[] = Array.from(new Set(data.map(d => d?.[stageColumn]).filter(Boolean)))
|
|
3
|
+
|
|
4
|
+
const stageIndex: number = allStages.indexOf(stageKey)
|
|
5
|
+
if (stageIndex === -1) return []
|
|
6
|
+
const currentStage = data.filter(d => d?.[stageColumn] === stageKey)
|
|
7
|
+
const nextKey = allStages[stageIndex + 1]
|
|
8
|
+
if (nextKey) {
|
|
9
|
+
const nextSlice = data.filter(d => d[stageColumn] === nextKey)
|
|
10
|
+
return [...currentStage, nextSlice[0]]
|
|
11
|
+
}
|
|
12
|
+
return currentStage
|
|
13
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { getBridgedData } from '../getBridgedData'
|
|
3
|
+
|
|
4
|
+
describe('getBridgedData', () => {
|
|
5
|
+
const sampleData = [
|
|
6
|
+
{ Month: 'Jan', value: 1 },
|
|
7
|
+
{ Month: 'Jan', value: 2 },
|
|
8
|
+
{ Month: 'Feb', value: 3 },
|
|
9
|
+
{ Month: 'Feb', value: 4 },
|
|
10
|
+
{ Month: 'Mar', value: 5 }
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
it('returns correct slice for stageKey with next stage', () => {
|
|
14
|
+
const result = getBridgedData('Jan', 'Month', sampleData)
|
|
15
|
+
|
|
16
|
+
expect(result).toEqual([
|
|
17
|
+
{ Month: 'Jan', value: 1 },
|
|
18
|
+
{ Month: 'Jan', value: 2 },
|
|
19
|
+
{ Month: 'Feb', value: 3 } // only first item from next stage
|
|
20
|
+
])
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns correct slice for stageKey with no next stage', () => {
|
|
24
|
+
const result = getBridgedData('Mar', 'Month', sampleData)
|
|
25
|
+
|
|
26
|
+
expect(result).toEqual([{ Month: 'Mar', value: 5 }])
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('returns empty if stageKey not found', () => {
|
|
30
|
+
const result = getBridgedData('Dec', 'Month', sampleData)
|
|
31
|
+
|
|
32
|
+
expect(result).toEqual([])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('handles unordered input data and preserves stage order by first appearance', () => {
|
|
36
|
+
const unorderedData = [
|
|
37
|
+
{ step: 'B', val: 1 },
|
|
38
|
+
{ step: 'A', val: 2 },
|
|
39
|
+
{ step: 'C', val: 3 },
|
|
40
|
+
{ step: 'A', val: 4 },
|
|
41
|
+
{ step: 'B', val: 5 }
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
const result = getBridgedData('A', 'step', unorderedData)
|
|
45
|
+
|
|
46
|
+
expect(result).toEqual([
|
|
47
|
+
{ step: 'A', val: 2 },
|
|
48
|
+
{ step: 'A', val: 4 },
|
|
49
|
+
{ step: 'C', val: 3 }
|
|
50
|
+
])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('returns only items matching current stage if next stage has no data', () => {
|
|
54
|
+
const dataWithEmptyStage = [
|
|
55
|
+
{ stage: '1', x: 10 },
|
|
56
|
+
{ stage: '2', x: 20 },
|
|
57
|
+
{ stage: '3', x: 30 }
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
const result = getBridgedData('3', 'stage', dataWithEmptyStage)
|
|
61
|
+
|
|
62
|
+
expect(result).toEqual([{ stage: '3', x: 30 }])
|
|
63
|
+
})
|
|
64
|
+
})
|