[xy-chart] Responsiveness is broken in some cases?
I have responsiveness broken in my app, probably due to flex and grid positioning it somehow breaks it. But I'll let you decide whom to blame.
Click to expand:
import React, { useEffect, useMemo } from 'react'
import {
lightTheme as lt,
darkTheme as dt,
Grid,
AreaSeries,
Axis,
XYChart,
Tooltip,
buildChartTheme,
} from '@visx/xychart'
import { ThemeConfig } from '@visx/xychart/lib/theme/buildChartTheme'
import { curveMonotoneX } from '@visx/curve'
import { LinearGradient } from '@visx/gradient'
import { Box, useColorMode, Stack, useBreakpointValue } from '@chakra-ui/react'
import { ParentSize } from '@visx/responsive'
import { css } from '@emotion/css'
import { MetricHistoryPoint } from '__generated__/graphql-types.generated'
import { formatMoney } from 'packlets/utils'
const dateScaleConfig = { type: 'band', paddingInner: 0.9 } as const
const moneyScaleConfig = { type: 'linear' } as const
const darkTheme = buildChartTheme(({
...dt,
colors: ['#109CF1'],
} as unknown) as ThemeConfig)
const lightTheme = buildChartTheme(({
...lt,
colors: ['#109CF1'],
} as unknown) as ThemeConfig)
const lineStyle = css`
stroke-width: 3;
`
export const DashboardGraph = ({ data }: { data: MetricHistoryPoint[] }) => {
const config = useMemo(
() => ({
x: dateScaleConfig,
y: moneyScaleConfig,
}),
[],
)
const { colorMode } = useColorMode()
const theme = colorMode === 'light' ? lightTheme : darkTheme
useEffect(() => {
setTimeout(() => {
window.dispatchEvent(new CustomEvent('scroll'))
}, 300)
})
const xTicks = useBreakpointValue({ base: 3, xl: 5 })
return (
<ParentSize>
{({ width }) => (
<XYChart
theme={theme}
xScale={config.x}
yScale={config.y}
height={300}
width={width - 16}
>
<LinearGradient
id="area-gradient"
from="#109CF1"
to="#109CF1"
toOpacity={0.1}
/>
<Grid
rows
columns={false}
strokeDasharray="10, 5"
// @ts-expect-error
strokeOpacity={0.3}
/>
<AreaSeries
dataKey="Cash in"
data={data}
fillOpacity={0.4}
xAccessor={(it) => it.date}
yAccessor={(it) => it.value}
fill="url(#area-gradient)"
stroke="url(#area-gradient)"
lineProps={{
className: lineStyle,
}}
curve={curveMonotoneX}
/>
<Axis
orientation="bottom"
strokeWidth={0}
numTicks={xTicks}
hideAxisLine
hideTicks
/>
<Axis orientation="left" numTicks={5} hideAxisLine hideTicks />
<Tooltip<{ date: string; value: number }>
showVerticalCrosshair
snapTooltipToDatumX
snapTooltipToDatumY
verticalCrosshairStyle={{
strokeOpacity: 0.4,
}}
showSeriesGlyphs
glyphStyle={{
r: 4,
fill: colorMode === 'light' ? 'white' : 'black',
strokeWidth: 1.5,
stroke: '#109CF1',
}}
renderTooltip={({ tooltipData }) => (
<Stack borderRadius={4}>
<Box>{tooltipData?.nearestDatum?.datum.date}</Box>
<Box>{formatMoney(tooltipData?.nearestDatum?.datum.value ?? 0)}</Box>
</Stack>
)}
/>
</XYChart>
)}
</ParentSize>
)
}
In order to have my graphs to resize I have to deduct padding of the div that holds XYChart (I guess it's div -> XY Chart generated div -> actual SVGs) So basically padding of the block that holds component above inside.
Here is how it looks when I add into XYChart width prop width - 16

And here when I don't add that or remove <ParentSize/> completely. I'm getting horizontal scrolling after page refresh and no resize. Note that the 2 graphs above are in regular display: flex, and 2x2 graphs at the bottom are in display: grid.

Are you facing problems with the longer tickLabels for AnimatedAxis in smaller screen sizes ?
Code above says it all :)
If you set your padding to 0, and want your chart to have 100% width, this happens too.
I saw this problem using react-use-measure too, here: https://github.com/pmndrs/react-use-measure/issues/43
Seems like this is an issue in react-use-measure then? Will leave this open to track progress on the link @pedroapfilho provided (thanks for digging in there!)
@williaster having display: grid; or display: flex; anywhere up the parent chain set for the ParentSize component will break responsive behavior on shrink (smaller than initial render width).
One would be able to reproduce by adding:
body {
display: grid | flex;
}
to the sandbox-styles.css inside the xychart demo
Preventable by rendering all of your chart stuff inside ScaleSVG component, but xychart simply uses svg.
(I've found it out by making my own "xychart")
Seems like the problem is caused by the automatic minimum size of flex item (I guess this also applies to grid items)
The "solution" to get the chart to resize when its parent shrinks is set min-width: 0 on the chart parent element.
Also, from the linked stack overflow response:
If you're dealing with flex items on multiple levels of the HTML structure, it may be necessary to override the default min-width: auto / min-height: auto on items at higher levels.
In my case I had to set min-width: 0 in some parent flex items as well but it did the trick.
I still can't figure out the issue on my end. Can someone assist please? I'm basically using an XYChart but not the ParentSize component as my assumption is that the XYChart comes responsiveness by default compared to the regular Bar or Line components
Here's the below code
export const BarGraph = ({
bottomNumTicks,
data,
hasCurrencyInTooltip = true,
height,
margin,
numTicks,
tooltipSubtext,
width,
xAccessor,
xValueFormat,
yAccessor,
yValueFormat,
}: IGraphProps) => {
const defaultMargin = { top: 0, right: 0, bottom: 40, left: 80 };
return (
<XYChart
height={height}
width={width}
xScale={{ type: "band", padding: handleBarGraphPadding(data) }}
yScale={{ type: "linear" }}
margin={margin ? margin : defaultMargin}
>
<Grid
columns={false}
numTicks={numTicks}
lineStyle={{
stroke: colors.black[25],
strokeWidth: 1,
}}
/>
{xAccessor && (
<Axis
hideAxisLine
hideTicks
orientation="bottom"
numTicks={bottomNumTicks}
tickFormat={xValueFormat}
tickLength={16}
tickLabelProps={{
fontSize: 10,
lineHeight: 14,
fill: colors.black[300],
fontFamily: "CircularXXWeb-Regular",
}}
/>
)}
{yAccessor && (
<Axis
hideAxisLine
hideTicks
orientation="left"
numTicks={numTicks}
tickFormat={yValueFormat}
tickLabelProps={{
fontSize: 10,
lineHeight: 14,
fill: colors.black[300],
fontFamily: "CircularXXWeb-Regular",
}}
/>
)}
<AnimatedBarSeries
data={data}
dataKey="primary"
xAccessor={xAccessor}
yAccessor={yAccessor}
radius={4}
radiusAll
colorAccessor={() => colors.blue[500]}
/>
<Tooltip
snapTooltipToDatumX
snapTooltipToDatumY
style={tooltipStyles}
renderTooltip={({ tooltipData }) => {
return (
<div>
{Object.entries(tooltipData.datumByKey).map(lineDataArray => {
const [key, value] = lineDataArray;
return (
<div className={sharedTooltipClasses("top")} key={key}>
{hasCurrencyInTooltip ? (
<span className="font-medium">
AED{" "}
{convertToLocaleStringHelper(
yAccessor(value.datum as any),
)}
</span>
) : (
<span className="font-medium">
{yAccessor(value.datum as any)}
</span>
)}
{tooltipSubtext && (
<span className="text-black-200">
{tooltipSubtext(value.datum)}
</span>
)}
</div>
);
})}
</div>
);
}}
/>
</XYChart>
);
};
https://github.com/airbnb/visx/assets/86906789/83b9050c-87a0-4da4-82a8-5217a1c36124
okay so looks like flex is an issue. So I switched to grid and that did the trick
Closing for now as this isn't a visx-related bug.