victory
victory copied to clipboard
Performance issues on Android with events
Describe the bug
Every time onActivated
event of VictoryVoronoiContainer
is fired - all children components of VictoryChart
are being rerendered.
Victory version "victory-core": "^36.5.0", "victory-native": "^36.5.0",
Code Sandbox link
type ChartValueItemFragment = {
value: number | null;
valueLow: number | null;
valueHigh: number | null;
date: any | null;
timestamp: number | null;
};
type TransformedCompareValueItem = Omit<ComparisonChartData, 'date'> & {
date: Date;
};
type TransformedChartValueItem = Omit<ChartValueItemFragment, 'date'> & {
date: Date;
};
type Point = {
_x: number;
_y: number;
};
type Props = {
xDomain: [Date, Date];
yDomain: [number, number];
comparedData: TransformedCompareValueItem[];
selectedData: TransformedChartValueItem[];
setActivePoint: (point: Point | null) => void; // setState from parent component
setComparePoint: (point: Point | null) => void; // setState from parent component
activePoint: Point | null;
};
function formatLocalDate(
data: string | Date,
formatMask: string,
): string {
const date = new Date(data);
const offsetInHours = date.getTimezoneOffset();
return dayjs(data).subtract(offsetInHours, 'minute').format(formatMask);
}
export function formatBigNumber(num: number, digits: number): string {
const lookup = [
{ value: 1, symbol: '' },
{ value: 1e3, symbol: 'k' },
{ value: 1e6, symbol: 'M' },
{ value: 1e9, symbol: 'G' },
{ value: 1e12, symbol: 'T' },
{ value: 1e15, symbol: 'P' },
{ value: 1e18, symbol: 'E' },
];
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
const item = lookup
.slice()
.reverse()
.find(function (item) {
return num >= item.value;
});
return item
? (num / item.value).toFixed(digits).replace(rx, '$1') + item.symbol
: '0';
}
export const ExampleVictoryChart = React.memo(
({
xDomain,
yDomain,
setActivePoint,
setComparePoint,
comparedData,
selectedData,
activePoint,
}: Props) => {
const chartColor = colors.green;
const comparedChartColor = colors.gray;
const comparedChartStyle = {
data: {
stroke: comparedChartColor,
strokeWidth: 3,
},
};
const verticalLineStyle = {
data: {
strokeDasharray: '6,6',
},
};
const chartStyle = {
data: {
stroke: chartColor,
strokeWidth: 3,
},
};
return (
<VictoryChart
padding={{
top: 0,
right: 0,
bottom: 50,
left: 0,
}}
theme={VictoryTheme.material}
domain={{
x:
xDomain[0].getTime() === xDomain[1].getTime()
? [
dayjs(xDomain[0]).subtract(5, 'months').toDate(),
new Date(xDomain[0]),
]
: xDomain,
y: yDomain,
}}
containerComponent={
<VictoryVoronoiContainer
onActivated={(points) => {
const mainPoint = points.find(
(point) => point.childName === 'all',
);
setActivePoint(mainPoint || null);
const comparePoint = points.find(
(point) => point.childName === 'compare',
);
setComparePoint(comparePoint || null);
}}
voronoiBlacklist={['line']}
voronoiDimension="x"
/>
}>
<VictoryGroup color={comparedChartColor}>
<VictoryLine
name="compare"
data={comparedData}
y={(datum: TransformedCompareValueItem) => datum.index}
x={(datum: ChartValueItemFragment) => new Date(datum.date)}
style={comparedChartStyle}
/>
</VictoryGroup>
<VictoryAxis
fixLabelOverlap
domain={xDomain}
standalone={false}
tickCount={4}
tickFormat={(t) => formatLocalDate(t, 'MMM YYYY')}
theme={VictoryTheme.material}
style={{
grid: {
strokeWidth: 0,
},
axis: {
stroke: colors.silverGray,
strokeDasharray: '2, 4',
strokeWidth: 1,
},
}}
/>
<VictoryAxis
tickFormat={(t: number) => formatBigNumber(t, 1)}
dependentAxis
fixLabelOverlap
domain={yDomain}
standalone={false}
theme={VictoryTheme.material}
style={{
grid: {
stroke: colors.silverGray,
strokeDasharray: '2, 4',
strokeWidth: 1,
},
axis: {
strokeWidth: 0,
},
}}
/>
<VictoryGroup color={chartColor}>
<VictoryLine
data={selectedData}
name="all"
y={(datum: ChartValueItemFragment) => datum.value as number}
x={(datum: ChartValueItemFragment) => new Date(datum.date)}
style={chartStyle}
/>
</VictoryGroup>
<VictoryLine
x={() => activePoint?._x || 0}
name="line"
style={verticalLineStyle}
/>
</VictoryChart>
);
},
);
To Reproduce Steps to reproduce the behavior:
- To reproduce you can mock data, try run this code and observe
Expected behavior I expect at least 40-50 fps, not 0-2
Screenshots
🎥Demo: https://user-images.githubusercontent.com/65399389/192005279-b24c899e-61bc-456b-8369-305e45b109f3.mp4
Desktop (please complete the following information):
- OS: -
- Browser -
- Version -
Smartphone (please complete the following information):
- Device: Samsung Galaxy A53
- OS: Android
- Browser -
- Version: 12
Additional context Please feel free to make comments about code, I am not very familiar about how victory-native works. maybe my code breaks some good practices of victory native. I just need help as soon as possible.
Hi @scottrippey, there seems to be some reports on performance issues on android. Oldest I could found was reported in February of 2022. Is there a plan for prioritizing this? (Sry if tagging the wrong person)
Hello, we just switched from highcharts to victory and our android users report heavy performance regressions even crashing the app :/ we have about 120 data points on a victory line within a voronoi conainer custom labels etc.. Is somebody by any chance working on or planning to work on this topic? Heres some pseudo example of our setup:
const Crosshair = React.memo(({ x, y }) => (
<G>
<Circle cx={x} cy={y} r="4" fill="#ffffff" />
<Circle cx={x} cy={y} r="3" fill={config.color.TEXT_RGBA} />
<Line x1={0} y1={y} x2={500} y2={y} stroke={config.color.LIGHTGREY} strokeDasharray={1} />
<Line x1={x} y1={0} x2={x} y2={500} stroke={config.color.LIGHTGREY} strokeDasharray={1} />
</G>
))
const CrosshairLabel = React.memo(props => <VictoryLabel
{...props}
lineHeight={[ 1, 1.5 ]}
backgroundPadding={10}
backgroundStyle={{
stroke: config.color.PRIMARY,
fill: config.color.WHITE,
fillOpacity: .8,
}}
style={[
{ fontSize: 12 },
{ fontSize: 14, fontWeight: 'bold' },
]}
/>)
const CrosshairWithLabel = props => {
const { activePoints, datum } = props
const isPointActive = !!(activePoints.some(activePoint => moment(activePoint.x).isSame(moment(datum.x))))
if (!isPointActive) return null
/**
* Calculate the offset from the label to the crosshair to prevent the user's thumb covering the label
*/
const getYOffset = ({ height, y, flyoutHeight, flyoutPadding }) => {
const DEFAULT_OFFSET = -15
const thumbOffset = DEFAULT_OFFSET * 2
const isInLowerHalf = y > height / 2
const heightOfTooltip = flyoutPadding.top + (flyoutHeight) + flyoutPadding.bottom
const overlapsWithTop = y - heightOfTooltip + DEFAULT_OFFSET < 0
return isInLowerHalf ? thumbOffset
: overlapsWithTop ? heightOfTooltip
: DEFAULT_OFFSET
}
/**
* Calculates an offset so the tooltip does not move out of visible chart area
* Usually this is happening by providing the prop `constrainToVisibleArea` to the <VictoryTooltip />
* but there is a bug that this does not work for victory-native
*
* Related ticket: https://github.com/FormidableLabs/victory/issues/2593
*/
const getXOffset = ({ flyoutPadding, flyoutWidth, width, x }) => {
const widthOfTooltip = flyoutPadding.left + flyoutWidth + flyoutPadding.right
const halfWidthOfTooltip = widthOfTooltip / 2
const overlapsWithYAxis = x + widthOfTooltip > width
return overlapsWithYAxis ? halfWidthOfTooltip * -1 : 0
}
const centerOffset = {
y: getYOffset,
x: getXOffset,
}
return (
<VictoryTooltip
animate={false}
{...props}
active
renderInPortal={false}
cornerRadius={0}
constrainToVisibleArea
flyoutComponent={<Crosshair {...props} />}
labelComponent={<CrosshairLabel />}
centerOffset={centerOffset }
/>
)
}
// Main Chart:
const chart = <VictoryChart
scale={scale}
theme={myTheme}
domainPadding={{ y: 10 }}
width={chartWidth}
padding={{ top: 30, bottom: 50, right: PADDING_RIGHT }}
containerComponent={
<VictoryVoronoiContainer
voronoiDimension="x"
voronoiBlacklist={[ 'current-price' ]}
onActivated={setActivePoints}
/>
}
>
<VictoryAxis {...xAxisConfig} />
<VictoryAxis {...yAxisConfig} tickLabelComponent={<YAxisTickLabel currentPrice={lastPoint.y} />} />
<VictoryLine
data={data}
labels={getLabels}
labelComponent={<CrosshairWithLabel data={data} activePoints={activePoints} />}
/>
<VictoryLine
animate={false}
name="current-price"
data={currentPriceData}
style={{ data: { strokeDasharray: 2 }}}
/>
</VictoryChart>
We are focusing our efforts on a rebuild of Victory specific to native in https://commerce.nearform.com/open-source/victory-native/