@@ -131,55 +131,57 @@ export function renderChartSVG(
131131 svg . appendChild ( defs ) ;
132132
133133 // Prime mark-renderer module-level state so mark sub-renderers can resolve
134- // animation + gradient fills without signature changes.
134+ // animation + gradient fills without signature changes. try/finally guarantees
135+ // the reset fires even if any downstream renderer throws, so the next render
136+ // starts with a clean slate.
135137 setMarkRenderState ( { animation, gradientMap } ) ;
138+ try {
139+ // Render layers in order (back to front)
140+ // Axes render outside clip (labels extend beyond chart area)
141+ renderAxes ( svg , layout ) ;
142+
143+ // Marks are clipped to chart area so area fills don't cover chrome
144+ const clippedGroup = createSVGElement ( 'g' ) ;
145+ clippedGroup . setAttribute ( 'clip-path' , `url(#${ clipId } )` ) ;
146+ renderMarks ( clippedGroup , layout ) ;
147+
148+ // Add transparent overlay rect for line/area charts to enable voronoi tooltip lookup.
149+ // Only added when there are line or area marks with dataPoints, and no explicit
150+ // PointMark objects (which use per-element event handling instead).
151+ const hasLineOrAreaWithDataPoints = layout . marks . some (
152+ ( m ) => ( m . type === 'line' || m . type === 'area' ) && m . dataPoints && m . dataPoints . length > 0 ,
153+ ) ;
154+ const hasPointMarks = layout . marks . some ( ( m ) => m . type === 'point' ) ;
155+ if ( hasLineOrAreaWithDataPoints && ! hasPointMarks ) {
156+ const overlay = createSVGElement ( 'rect' ) ;
157+ setAttrs ( overlay , {
158+ x : layout . area . x ,
159+ y : layout . area . y ,
160+ width : layout . area . width ,
161+ height : layout . area . height ,
162+ fill : 'transparent' ,
163+ } ) ;
164+ overlay . setAttribute ( 'class' , 'oc-voronoi-overlay' ) ;
165+ overlay . setAttribute ( 'data-voronoi-overlay' , 'true' ) ;
166+ clippedGroup . appendChild ( overlay ) ;
167+ }
136168
137- // Render layers in order (back to front)
138- // Axes render outside clip (labels extend beyond chart area)
139- renderAxes ( svg , layout ) ;
140-
141- // Marks are clipped to chart area so area fills don't cover chrome
142- const clippedGroup = createSVGElement ( 'g' ) ;
143- clippedGroup . setAttribute ( 'clip-path' , `url(#${ clipId } )` ) ;
144- renderMarks ( clippedGroup , layout ) ;
145-
146- // Add transparent overlay rect for line/area charts to enable voronoi tooltip lookup.
147- // Only added when there are line or area marks with dataPoints, and no explicit
148- // PointMark objects (which use per-element event handling instead).
149- const hasLineOrAreaWithDataPoints = layout . marks . some (
150- ( m ) => ( m . type === 'line' || m . type === 'area' ) && m . dataPoints && m . dataPoints . length > 0 ,
151- ) ;
152- const hasPointMarks = layout . marks . some ( ( m ) => m . type === 'point' ) ;
153- if ( hasLineOrAreaWithDataPoints && ! hasPointMarks ) {
154- const overlay = createSVGElement ( 'rect' ) ;
155- setAttrs ( overlay , {
156- x : layout . area . x ,
157- y : layout . area . y ,
158- width : layout . area . width ,
159- height : layout . area . height ,
160- fill : 'transparent' ,
161- } ) ;
162- overlay . setAttribute ( 'class' , 'oc-voronoi-overlay' ) ;
163- overlay . setAttribute ( 'data-voronoi-overlay' , 'true' ) ;
164- clippedGroup . appendChild ( overlay ) ;
165- }
166-
167- svg . appendChild ( clippedGroup ) ;
169+ svg . appendChild ( clippedGroup ) ;
168170
169- renderAnnotations ( svg , layout ) ;
170- renderLegend ( svg , layout . legend ) ;
171+ renderAnnotations ( svg , layout ) ;
172+ renderLegend ( svg , layout . legend ) ;
171173
172- // Chrome renders on top so titles are never obscured by chart elements
173- renderChrome ( svg , layout ) ;
174+ // Chrome renders on top so titles are never obscured by chart elements
175+ renderChrome ( svg , layout ) ;
174176
175- // Brand renders as a footer item, right-aligned on the source/footer row
176- if ( layout . watermark ) {
177- renderBrand ( svg , layout ) ;
177+ // Brand renders as a footer item, right-aligned on the source/footer row
178+ if ( layout . watermark ) {
179+ renderBrand ( svg , layout ) ;
180+ }
181+ } finally {
182+ resetMarkRenderState ( ) ;
178183 }
179184
180- // Reset module-level state after rendering
181- resetMarkRenderState ( ) ;
182-
183185 container . appendChild ( svg ) ;
184186 return svg ;
185187}
0 commit comments