Version
5.5.0
Link to Minimal Reproduction
https://wplong11.github.io/echarts-candlestick-clip-bug/
Source
Steps to Reproduce
- Open the reproduction link above — it shows two vertically stacked grids (Grid 0: line, Grid 1: candlestick), with a y-axis
dataZoom (start: 30, end: 70) on Grid 1 and filterMode: 'none'.
- Observe that candlestick wicks from Grid 1 bleed upward into Grid 0's area, even though both series have
clip: true.
- Click "Toggle filterMode" to switch to
filterMode: 'empty' — the bleed disappears immediately.
- Click "Toggle y-axis zoom" to reset the y-axis zoom to full range — no bleed either.
Current Behavior
Candlestick wicks extend beyond Grid 1's boundary and render into Grid 0's area. Setting clip: true on the candlestick series has no effect in normal (non-large) rendering mode.
This happens because CandlestickView.render() unconditionally calls group.removeClipPath() (CandlestickView.ts:52), and then relies solely on a per-item isNormalBoxClipped() check (CandlestickView.ts:276-286). That check uses clipArea.contain() against each of the candlestick's ends points — if any point is inside the grid, the entire candlestick is rendered without pixel-level clipping. So a candle whose body is inside the grid but whose wick extends beyond passes the check and draws outside the grid boundary.
By contrast, LineView and BarView both prevent rendering outside the grid in normal mode, albeit via different mechanisms:
- LineView.ts:744, 775 — applies
group.setClipPath() (canvas-level pixel clipping)
- BarView.ts:222-273 — modifies bar layout geometry via
clip[coord.type]() to truncate shapes at the grid boundary (no canvas clip in normal mode, but the layout modification achieves the same visual result)
Candlestick does neither: it removes the canvas clip path and relies solely on the all-or-nothing per-item visibility check, making it the only standard series type where clip: true does not actually prevent rendering outside the grid.
Expected Behavior
Candlestick wicks and bodies should be clipped at the grid boundary when clip: true is set, consistent with how line and bar series behave. In the reproduction, Grid 1's candles should never render pixels outside Grid 1's area.
Environment
- OS: macOS Sequoia 15.5
- Browser: Chrome 137
- Framework: N/A (vanilla HTML, also reproduced in React Native WebView)
Any additional comments?
Root cause summary
| What |
File (v5.5.0) |
Line |
| Normal mode removes clip path |
CandlestickView.ts |
52 |
| Per-item contain check (show-or-hide, not pixel clip) |
CandlestickView.ts |
276-286 |
Large mode does apply setClipPath |
CandlestickView.ts |
174-179 |
| Grid clip rect helper |
createClipPathFromCoordSys.ts |
32-105 |
Workaround
Setting dataZoom.filterMode: 'empty' instead of 'none' on the y-axis works around the issue. In 'empty' mode, AxisProxy converts out-of-range data to NaN (AxisProxy.ts:333-338), which causes the data.hasValue() check to skip those items entirely. However, this changes data semantics and may not always be acceptable.
Suggested fix
Apply group.setClipPath(createClipPathFromCoordSys(...)) in candlestick normal mode when clip: true, similar to how LineView applies canvas-level clipping. The per-item isNormalBoxClipped check could be kept as an optimization to avoid rendering items that are entirely outside the clip area, but the canvas-level clip path is needed to handle partial overflow (body inside, wick outside).
Related issues
Version
5.5.0
Link to Minimal Reproduction
https://wplong11.github.io/echarts-candlestick-clip-bug/
Source
Steps to Reproduce
dataZoom(start: 30, end: 70) on Grid 1 andfilterMode: 'none'.clip: true.filterMode: 'empty'— the bleed disappears immediately.Current Behavior
Candlestick wicks extend beyond Grid 1's boundary and render into Grid 0's area. Setting
clip: trueon the candlestick series has no effect in normal (non-large) rendering mode.This happens because
CandlestickView.render()unconditionally callsgroup.removeClipPath()(CandlestickView.ts:52), and then relies solely on a per-itemisNormalBoxClipped()check (CandlestickView.ts:276-286). That check usesclipArea.contain()against each of the candlestick'sendspoints — if any point is inside the grid, the entire candlestick is rendered without pixel-level clipping. So a candle whose body is inside the grid but whose wick extends beyond passes the check and draws outside the grid boundary.By contrast,
LineViewandBarViewboth prevent rendering outside the grid in normal mode, albeit via different mechanisms:group.setClipPath()(canvas-level pixel clipping)clip[coord.type]()to truncate shapes at the grid boundary (no canvas clip in normal mode, but the layout modification achieves the same visual result)Candlestick does neither: it removes the canvas clip path and relies solely on the all-or-nothing per-item visibility check, making it the only standard series type where
clip: truedoes not actually prevent rendering outside the grid.Expected Behavior
Candlestick wicks and bodies should be clipped at the grid boundary when
clip: trueis set, consistent with how line and bar series behave. In the reproduction, Grid 1's candles should never render pixels outside Grid 1's area.Environment
Any additional comments?
Root cause summary
CandlestickView.tsCandlestickView.tssetClipPathCandlestickView.tscreateClipPathFromCoordSys.tsWorkaround
Setting
dataZoom.filterMode: 'empty'instead of'none'on the y-axis works around the issue. In'empty'mode,AxisProxyconverts out-of-range data to NaN (AxisProxy.ts:333-338), which causes thedata.hasValue()check to skip those items entirely. However, this changes data semantics and may not always be acceptable.Suggested fix
Apply
group.setClipPath(createClipPathFromCoordSys(...))in candlestick normal mode whenclip: true, similar to howLineViewapplies canvas-level clipping. The per-itemisNormalBoxClippedcheck could be kept as an optimization to avoid rendering items that are entirely outside the clip area, but the canvas-level clip path is needed to handle partial overflow (body inside, wick outside).Related issues
dataZoom.filterMode='none'(v4, closed via feat(clip): candlestick add clip option #11529 which added the per-item check but not canvas clip)filterMode: 'none'clipoption tracking (candlestick not listed)