[Dashboards] [Performance] Bar chart re renders fix (#17162)
before - https://github.com/user-attachments/assets/c9b4a60a-d4bd-43d5-8621-54777a326171 after - https://github.com/user-attachments/assets/74358810-067e-44f7-82f1-f4dcd7956956
This commit is contained in:
+5
-7
@@ -1,7 +1,8 @@
|
||||
import { LEGEND_HIGHLIGHT_DIMMED_OPACITY } from '@/page-layout/widgets/graph/constants/LegendHighlightDimmedOpacity.constant';
|
||||
import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants';
|
||||
import { graphWidgetHoveredSliceIndexComponentState } from '@/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetHoveredSliceIndexComponentState';
|
||||
import { graphWidgetIsSliceHoveredComponentFamilySelector } from '@/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetIsSliceHoveredComponentFamilySelector';
|
||||
import { graphWidgetHighlightedLegendIdComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHighlightedLegendIdComponentState';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { type BarDatum, type BarItemProps } from '@nivo/bar';
|
||||
import { animated, to } from '@react-spring/web';
|
||||
@@ -62,18 +63,15 @@ export const CustomBarItem = <D extends BarDatum>({
|
||||
graphWidgetHighlightedLegendIdComponentState,
|
||||
);
|
||||
|
||||
const hoveredSliceIndex = useRecoilComponentValue(
|
||||
graphWidgetHoveredSliceIndexComponentState,
|
||||
const isSliceHovered = useRecoilComponentFamilyValue(
|
||||
graphWidgetIsSliceHoveredComponentFamilySelector,
|
||||
String(barData.indexValue),
|
||||
);
|
||||
|
||||
const isDimmed =
|
||||
isDefined(highlightedLegendId) &&
|
||||
String(highlightedLegendId) !== String(barData.id);
|
||||
|
||||
const isSliceHovered =
|
||||
isDefined(hoveredSliceIndex) &&
|
||||
String(barData.indexValue) === hoveredSliceIndex;
|
||||
|
||||
const isNegativeValue = useMemo(
|
||||
() => isNumber(barData.value) && barData.value < 0,
|
||||
[barData.value],
|
||||
|
||||
+49
-61
@@ -3,14 +3,11 @@ import { type BarChartSlice } from '@/page-layout/widgets/graph/graphWidgetBarCh
|
||||
import { computeSliceHighlightPosition } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeSliceHighlightPosition';
|
||||
import { computeSlicesFromBars } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeSlicesFromBars';
|
||||
import { findSliceAtPosition } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/findSliceAtPosition';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { type BarDatum, type ComputedBarDatum } from '@nivo/bar';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { useMemo, type MouseEvent } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useCallback, useMemo, type MouseEvent } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { BarChartLayout } from '~/generated/graphql';
|
||||
|
||||
@@ -44,13 +41,8 @@ export const CustomSliceHoverLayer = ({
|
||||
onSliceLeave,
|
||||
}: CustomSliceHoverLayerProps) => {
|
||||
const theme = useTheme();
|
||||
const hoveredSliceIndex = useRecoilComponentValue(
|
||||
graphWidgetHoveredSliceIndexComponentState,
|
||||
);
|
||||
const hoveredSliceIndexState = useRecoilComponentCallbackState(
|
||||
graphWidgetHoveredSliceIndexComponentState,
|
||||
);
|
||||
const setHoveredSliceIndex = useSetRecoilComponentState(
|
||||
|
||||
const hoveredSliceIndexValue = useRecoilComponentValue(
|
||||
graphWidgetHoveredSliceIndexComponentState,
|
||||
);
|
||||
|
||||
@@ -61,43 +53,36 @@ export const CustomSliceHoverLayer = ({
|
||||
[bars, isVerticalLayout],
|
||||
);
|
||||
|
||||
const handleMouseMove = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(event: MouseEvent<SVGRectElement>) => {
|
||||
const sliceData = findSliceAtPosition({
|
||||
event,
|
||||
slices,
|
||||
marginLeft,
|
||||
marginTop,
|
||||
isVerticalLayout,
|
||||
});
|
||||
const currentHoveredSliceIndex = snapshot
|
||||
.getLoadable(hoveredSliceIndexState)
|
||||
.getValue();
|
||||
const handleMouseMove = useCallback(
|
||||
(event: MouseEvent<SVGRectElement>) => {
|
||||
const sliceData = findSliceAtPosition({
|
||||
event,
|
||||
slices,
|
||||
marginLeft,
|
||||
marginTop,
|
||||
isVerticalLayout,
|
||||
});
|
||||
|
||||
if (!isDefined(sliceData)) {
|
||||
if (isDefined(currentHoveredSliceIndex)) {
|
||||
setHoveredSliceIndex(null);
|
||||
onSliceHover(null);
|
||||
}
|
||||
return;
|
||||
if (!isDefined(sliceData)) {
|
||||
if (isDefined(hoveredSliceIndexValue)) {
|
||||
onSliceHover(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (sliceData.slice.indexValue === currentHoveredSliceIndex) {
|
||||
return;
|
||||
}
|
||||
if (sliceData.slice.indexValue === hoveredSliceIndexValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
setHoveredSliceIndex(sliceData.slice.indexValue);
|
||||
onSliceHover(sliceData);
|
||||
},
|
||||
onSliceHover(sliceData);
|
||||
},
|
||||
[
|
||||
hoveredSliceIndexState,
|
||||
setHoveredSliceIndex,
|
||||
onSliceHover,
|
||||
slices,
|
||||
marginLeft,
|
||||
marginTop,
|
||||
isVerticalLayout,
|
||||
hoveredSliceIndexValue,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -126,11 +111,11 @@ export const CustomSliceHoverLayer = ({
|
||||
};
|
||||
|
||||
const hoveredSlice = useMemo(() => {
|
||||
if (!isDefined(hoveredSliceIndex)) {
|
||||
if (!isDefined(hoveredSliceIndexValue)) {
|
||||
return null;
|
||||
}
|
||||
return slices.find((slice) => slice.indexValue === hoveredSliceIndex);
|
||||
}, [slices, hoveredSliceIndex]);
|
||||
return slices.find((slice) => slice.indexValue === hoveredSliceIndexValue);
|
||||
}, [slices, hoveredSliceIndexValue]);
|
||||
|
||||
const highlightPosition = computeSliceHighlightPosition({
|
||||
sliceCenter: hoveredSlice?.sliceCenter ?? null,
|
||||
@@ -139,31 +124,34 @@ export const CustomSliceHoverLayer = ({
|
||||
innerHeight,
|
||||
});
|
||||
|
||||
const { opacity } = useSpring({
|
||||
opacity: isDefined(hoveredSlice) ? 1 : 0,
|
||||
config: {
|
||||
tension: 300,
|
||||
friction: 30,
|
||||
},
|
||||
});
|
||||
|
||||
if (bars.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<g>
|
||||
<animated.g
|
||||
transform={`translate(${highlightPosition.x}, ${highlightPosition.y})`}
|
||||
opacity={opacity}
|
||||
>
|
||||
<rect
|
||||
width={highlightPosition.width}
|
||||
height={highlightPosition.height}
|
||||
fill={theme.background.transparent.medium}
|
||||
style={{ pointerEvents: 'none' }}
|
||||
/>
|
||||
</animated.g>
|
||||
<AnimatePresence>
|
||||
{isDefined(hoveredSlice) && (
|
||||
<motion.g
|
||||
key="highlight"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
duration: theme.animation.duration.fast,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
transform={`translate(${highlightPosition.x}, ${highlightPosition.y})`}
|
||||
>
|
||||
<rect
|
||||
width={highlightPosition.width}
|
||||
height={highlightPosition.height}
|
||||
fill={theme.background.transparent.medium}
|
||||
style={{ pointerEvents: 'none' }}
|
||||
/>
|
||||
</motion.g>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<rect
|
||||
x={0}
|
||||
|
||||
+11
-2
@@ -107,6 +107,15 @@ export const GraphWidgetBarChart = ({
|
||||
const [chartHeight, setChartHeight] = useState<number>(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const debouncedSetChartDimensions = useDebouncedCallback(
|
||||
(width: number, height: number) => {
|
||||
setChartWidth(width);
|
||||
setChartHeight(height);
|
||||
},
|
||||
|
||||
300,
|
||||
);
|
||||
|
||||
const setActiveBarTooltip = useSetRecoilComponentState(
|
||||
graphWidgetBarTooltipComponentState,
|
||||
);
|
||||
@@ -193,6 +202,7 @@ export const GraphWidgetBarChart = ({
|
||||
) => {
|
||||
if (isDefined(sliceData)) {
|
||||
debouncedHideTooltip.cancel();
|
||||
setHoveredSliceIndex(sliceData.slice.indexValue);
|
||||
setActiveBarTooltip({
|
||||
slice: sliceData.slice,
|
||||
offsetLeft: sliceData.offsetLeft,
|
||||
@@ -305,8 +315,7 @@ export const GraphWidgetBarChart = ({
|
||||
<NodeDimensionEffect
|
||||
elementRef={containerRef}
|
||||
onDimensionChange={({ width, height }) => {
|
||||
setChartWidth(width);
|
||||
setChartHeight(height);
|
||||
debouncedSetChartDimensions(width, height);
|
||||
}}
|
||||
/>
|
||||
<ResponsiveBar
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { graphWidgetHoveredSliceIndexComponentState } from '@/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetHoveredSliceIndexComponentState';
|
||||
import { WidgetComponentInstanceContext } from '@/page-layout/widgets/states/contexts/WidgetComponentInstanceContext';
|
||||
import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const graphWidgetIsSliceHoveredComponentFamilySelector =
|
||||
createComponentFamilySelector<boolean, string>({
|
||||
key: 'graphWidgetIsSliceHoveredComponentFamilySelector',
|
||||
componentInstanceContext: WidgetComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId, familyKey }: { instanceId: string; familyKey: string }) =>
|
||||
({ get }) => {
|
||||
const hoveredSliceIndex = get(
|
||||
graphWidgetHoveredSliceIndexComponentState.atomFamily({ instanceId }),
|
||||
);
|
||||
|
||||
return isDefined(hoveredSliceIndex) && hoveredSliceIndex === familyKey;
|
||||
},
|
||||
});
|
||||
+8
-5
@@ -85,7 +85,7 @@ export const GraphWidgetLineChart = ({
|
||||
id,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
omitNullValues: _omitNullValues = false,
|
||||
omitNullValues = false,
|
||||
displayType,
|
||||
groupMode,
|
||||
colorMode,
|
||||
@@ -101,6 +101,11 @@ export const GraphWidgetLineChart = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [chartWidth, setChartWidth] = useState(0);
|
||||
|
||||
const debouncedSetChartWidth = useDebouncedCallback(
|
||||
(width: number) => setChartWidth(width),
|
||||
300,
|
||||
);
|
||||
|
||||
const formatOptions: GraphValueFormatOptions = {
|
||||
displayType,
|
||||
decimals,
|
||||
@@ -195,7 +200,7 @@ export const GraphWidgetLineChart = ({
|
||||
formatValue={(value) => formatGraphValue(value, formatOptions)}
|
||||
offset={theme.spacingMultiplicator * 2}
|
||||
groupMode={groupMode}
|
||||
omitNullValues={_omitNullValues}
|
||||
omitNullValues={omitNullValues}
|
||||
enablePointLabel={enablePointLabel}
|
||||
/>
|
||||
);
|
||||
@@ -293,9 +298,7 @@ export const GraphWidgetLineChart = ({
|
||||
>
|
||||
<NodeDimensionEffect
|
||||
elementRef={containerRef}
|
||||
onDimensionChange={({ width }) => {
|
||||
setChartWidth(width);
|
||||
}}
|
||||
onDimensionChange={({ width }) => debouncedSetChartWidth(width)}
|
||||
/>
|
||||
<ResponsiveLine
|
||||
data={nivoData}
|
||||
|
||||
Reference in New Issue
Block a user