Skip to main content

Bleed

What is bleed?

Some XY Components need extra space inside the container for their marks to fully fit: half of the point diameter for Scatter, half of the bar width for bar charts at the edges of the domain, and so on. This space is called bleed. Before rendering, XY Container asks every component how much bleed it needs, combines the values (taking the maximum for each side), and shrinks the scale ranges accordingly.

That's why the first and the last points of the scatter chart below are shifted inward from the container's edges instead of being cut in half:

Loading...

Overriding the calculated bleed

The bleed property of XY Container lets you override the calculated values. It accepts either a Spacing object:

type Spacing = { top?: number; bottom?: number; left?: number; right?: number }

or a function that receives the container's components and returns a Spacing object (each component's own bleed is available via its bleed getter):

bleed: (components: XYComponentCore<Datum>[]) => Spacing

The sides you provide replace the calculated values, the sides left undefined fall back to them, so pass 0 explicitly to remove the bleed on a side. Here is the same scatter chart with the bleed set to zero on all sides — the edge points now get clipped:

Loading...

Reading the calculated bleed

The container reports the bleed it ended up using (calculated or configured) through the onRenderComplete callback:

onRenderComplete?: (
svgNode: SVGSVGElement,
margin: Spacing,
bleed: Spacing,
containerWidth: number,
containerHeight: number,
componentWidth: number,
componentHeight: number,
) => void;

Synchronizing bleed across charts

Bleed becomes important when you place several charts one below another and want their X values to line up. Even if the charts share the same xDomain, each one calculates its own bleed, so the same X value can land on a different horizontal position in every chart.

To align them, you only need to synchronize the horizontal bleed: capture the left and right values of the chart with the largest marks (or the per-side maximum across all charts) from onRenderComplete, and pass them to the other charts via the bleed property. Leave top and bottom out — they fall back to each chart's own calculated values. Toggle the checkbox below to see the effect — watch how the X axes of the two charts align:

import { useCallback, useState } from 'react'
import { VisXYContainer, VisScatter, VisLine, VisAxis } from '@unovis/react'

function SyncedCharts ({ data }) {
const [horizontalBleed, setHorizontalBleed] = useState()
const onRenderComplete = useCallback((svg, margin, b) => {
setHorizontalBleed(prev => (prev?.left === b.left && prev?.right === b.right)
? prev
: { left: b.left, right: b.right })
}, [])

// Synchronize the horizontal bleed only, the vertical values fall back to the calculated ones
return (<>
<VisXYContainer data={data} onRenderComplete={onRenderComplete}>
<VisScatter x={d => d.x} y={d => d.y} size={25}/>
<VisAxis type='x'/>
</VisXYContainer>
<VisXYContainer data={data} bleed={horizontalBleed}>
<VisLine x={d => d.x} y={d => d.y}/>
<VisAxis type='x'/>
</VisXYContainer>
</>)
}
tip

For the charts to align perfectly, their margin values need to match too. If the Y axes of your charts have tick labels of different widths, the automatically calculated margins will differ — set autoMargin to false and provide the same explicit margin to all the charts.

note

onRenderComplete reports the bleed the container actually used, so when you override bleed it reports your values on the sides you provided and the calculated ones on the sides you left out. If you need the components' calculated bleed for a side you're overriding, use the function form of bleed and read the bleed getter of the components you're interested in.