The issue comes from the order in which CeTZ applies transformations and gradients.
When you call rotate(), the rectangle is indeed rotated first. But when CeTZ then computes the bounding box of that rotated shape to draw the gradient, it uses the projected width of the rotated rectangle — the distance it now spans horizontally or vertically in the canvas coordinates.
Because the shape is slanted, that projected width is larger than the real thickness of the rectangle. CeTZ then stretches the gradient to fill that larger width, using your defined color stops (for example 45 %, 50 %, 55 %).
Finally, the gradient (already stretched) is rotated along with the shape — so visually, it looks too wide and the bright center doesn’t align with the actual thickness anymore.
In short:
- The rectangle is rotated.
- CeTZ measures the new (larger) projected width of the rotated shape.
- The gradient is drawn across that full width.
- The gradient is rotated with the shape → the stops are now too far apart.
That’s why the diagonal shape’s gradient looks “washed out”: the gradient was sized for the larger bounding box, not for the true 0.5 cm thickness of the rotated rectangle.
Because CeTZ stretches the gradient over the projected width of the rotated shape, you need to compress the gradient stops so they match the true thickness of the rectangle after rotation.
To fix this, you can manually adjust the stop positions so that the gradient corresponds to the intended 0.5 cm thickness after rotation. The code below does this by scaling the stops. That way, the gradient’s bright band keeps its correct visual thickness, even after rotation:
#canvas(length: 1cm, {
import draw: *
let _fill = gradient.linear((gray, 0%), (gray, 0%), (luma(90%), 50%), (gray, 100%), angle: 90deg)
rect((0, 0), (4, 0.5), fill: _fill, stroke: none)
let L = 4
let d = 0.5
let θ = 45deg
// Adjust the gradient width to match the true thickness
let diagonal_length = L * calc.sqrt(2)
let r = d / diagonal_length * 100%
let center = 50%
let inner = center - r / 2
let outer = center + r / 2
rotate(z: θ)
let _fill = gradient.linear(
(gray, 0%),
(gray, inner),
(luma(90%), center),
(gray, outer),
(gray, 100%),
angle: θ,
)
rect((0, 0), (L, d), fill: _fill, stroke: none)
rotate(z: 45deg)
let _fill = gradient.linear((gray, 0%), (luma(90%), 50%), (gray, 100%), angle: 0deg)
rect((0, 0), (4, 0.5), fill: _fill, stroke: none)
})
This keeps the gradient visually aligned with the rotated shape — just like the horizontal and vertical ones.
I’m still not entirely sure, though, whether the calculation is perfect; it might need a small empirical tweak to match perception exactly.