Conversation
DefaultCanvasLineRender was the only canvas graphic renderer that never resolved or invoked render contributions: its constructor took no IContributionProvider, never called this.init(), and neither drawSegmentItem() nor drawLinearLineHighPerformance() called beforeRenderStep()/afterRenderStep(). As a result there was no LineRenderContribution symbol at all, and no way to extend how a line is painted, while all the other renderers (rect/arc/area/symbol/path/polygon/ text/image/circle/star/richtext) support it. This wires line up the same way rect does: - add ILineRenderContribution type and LineRenderContribution symbol - DefaultCanvasLineRender now takes an IContributionProvider and calls init() - bindLineRenderModule creates the contribution provider and calls bindContributionProvider - both draw paths invoke beforeRenderStep()/afterRenderStep() - drawSegmentItem() now receives drawContext so contributions get the same arguments they do on other graphics No behaviour change when no contribution is registered.
xile611
left a comment
There was a problem hiding this comment.
@g1f9 The line extension point makes sense, but this revision is not ready to merge. It introduces core compilation errors, misses the incremental renderer wiring, changes clipping behavior even without a custom contribution, and does not expose the active segment's attributes to contributions. Details and reproduction results are in the four inline comments.
Validation against head 838ab47f and base 69d82c11:
tsc --noEmit --incremental false --composite false --pretty falseinpackages/vrender-corepasses on the base and reports five errors on the PR: four TS2345 errors at line-render.ts:123/154/212/243, plus TS2554 at runtime-installer.ts:172.- With ts-jest type diagnostics disabled specifically to inspect runtime behavior, 24 existing targeted registry/contribution tests pass. Nine additional review probes produce four passes (normal linear, curved, clipRange, and segmented hook invocation) and five failures covering visibility arguments, clip-path preservation, segment attributes, incremental provider wiring, and incremental hook invocation.
- Comparing the same clipping input with the base renderer confirms the regression: the base strokes the polyline; this revision strokes the rectangular clip path instead.
- Both build workflows currently fail; Unit test CI stops during the build before running tests.
Please address these issues, add regression coverage, and get CI green before merging. A small draw/pick benchmark would also help assess the added work on the high-performance line path: BaseRender.init() adds two built-in clip contributions, so the no-custom-contribution case does not iterate over empty lists.
| offsetY, | ||
| !!fill, | ||
| !!stroke, | ||
| fillOpacity, |
There was a problem hiding this comment.
[P1] Pass computed visibility flags, not opacity values
The seventh/eighth hook arguments are fVisible and sVisible booleans, but these calls pass numeric fillOpacity/strokeOpacity. All four new calls fail TypeScript compilation (lines 123, 154, 212, and 243). This is also a semantic problem: a line with fill: false and strokeOpacity: 0.5 delivers [doFill, doStroke, fVisible, sVisible] = [false, true, 1, 0.5], rather than [false, true, false, true]. Please propagate the computed fill/stroke eligibility and visibility values, accounting for fill presence, overall opacity, and line width; merely coercing the opacity values to booleans would not fix the contract.
| numberType: number = LINE_NUMBER_TYPE; | ||
| declare z: number; | ||
|
|
||
| constructor(protected readonly graphicRenderContributions: IContributionProvider<ILineRenderContribution>) { |
There was a problem hiding this comment.
[P1] Update the incremental renderer factory for this required dependency
DefaultIncrementalCanvasLineRender inherits this constructor, but configureRuntimeApplicationForApp() still calls new DefaultIncrementalCanvasLineRender() at src/entries/runtime-installer.ts:172. The PR therefore adds TS2554 and leaves the incremental renderer without the registered line contributions at runtime. Please wire the LineRenderContribution provider through that factory, as is already done for area. There is a second incremental-path gap to cover: even with a provider supplied, drawIncreaseSegment() never invokes either hook (verified with a contribution spy while an actual stroke occurs). Making the constructor parameter optional alone would hide the missing wiring rather than complete it.
|
|
||
| const { x: originX = 0, x: originY = 0 } = line.attribute; | ||
|
|
||
| this.beforeRenderStep( |
There was a problem hiding this comment.
[P2] Preserve the line path when enabling the built-in clipping contribution
BaseRender.init() always adds the before/after clip contributions, even when builtinContributions and the provider are empty. Consequently this new call activates DefaultBaseClipRenderBeforeContribution after the line path has already been constructed. For a line with clipConfig: { shape: 'rect' }, that contribution calls beginPath() and builds the clip rectangle; the subsequent context.stroke() then strokes the rectangle instead of the line. A recording-context comparison on the same input yields moveTo, lineTo, lineTo on the base and rect, closePath on this revision. Please preserve/reconstruct the graphic path across clipping and add a no-custom-contribution regression test. The same ordering needs checking in drawSegmentItem().
| !!stroke, | ||
| fillOpacity, | ||
| strokeOpacity, | ||
| defaultAttribute as Required<ILineGraphicAttribute>, |
There was a problem hiding this comment.
[P2] Expose the active segment attributes to per-segment contributions
For a segmented line, attribute is the current segment while defaultAttribute is [lineAttribute, line.attribute]. The hook receives only the whole line and that array cast to a single required attribute object; the current segment is omitted. A style-dependent extra pass cannot reliably determine which segment's stroke/lineWidth/opacity it should use. In a reproduction with a black parent stroke and red/blue segment overrides, resolving styles from the hook inputs yields black for both passes. Please provide the active segment attributes explicitly to both hooks (the existing area renderer passes { attribute } in the final argument), and test differing segment styles. The type assertion alone does not make the array conform to the advertised attribute-object contract.
…nderer DefaultIncrementalCanvasLineRender extends DefaultCanvasLineRender and declares no constructor of its own, so the provider parameter added in the previous commit turned the zero-argument call in runtime-installer.ts into a type error. Pass the provider the same way the sibling incremental area renderer right below it already does. This also closes a behaviour gap: without it the incremental line renderer would never resolve any LineRenderContribution. Also add line to the renderer table in runtime-renderer-contributions.test.ts. That table covers every other canvas renderer and had no line entry only because line had no contribution support until now; the existing assertions now verify that a runtime-bound LineRenderContribution reaches the renderer.
Pass computed visibility instead of opacity (P1)
BaseRender.valid() already returns { doFill, doStroke, fVisible, sVisible }
and drawShape() already called it and threw the result away — there was even
a commented-out destructuring left in the file. Thread that result into both
drawSegmentItem() and drawLinearLineHighPerformance() and hand it to the
hooks, so a line with fill:false and strokeOpacity:0.5 now delivers
[false, true, false, true] instead of [false, true, 1, 0.5]. This also clears
the four TS2345 errors.
Preserve the graphic path across the built-in clip contribution (P2)
BaseRender.init() always installs the before/after clip contributions, so
invoking the hooks activates DefaultBaseClipRenderBeforeContribution even
when no custom contribution is registered. That contribution calls
beginPath() to build the clip shape, which discarded the line path that had
just been constructed, and the following stroke() painted the clip rectangle.
Path construction now lives in a local buildPath() that is replayed after the
hook when clipConfig is present. Note this makes clipConfig actually take
effect on lines; before this PR the line renderer never ran the hooks, so the
attribute was silently ignored. Say the word if you would rather keep strict
parity with the old behaviour and gate it.
Expose the active segment attributes (P2)
Both hooks now receive { attribute } as the trailing argument, matching what
the area renderer already does, so a per-segment contribution can resolve the
stroke/lineWidth/opacity of the segment being drawn rather than only the
parent line.
Complete the incremental wiring (P1)
drawIncreaseSegment() now invokes beforeRenderStep()/afterRenderStep() with
the visibility flags drawShape() had already computed, and applies the same
clip-path handling. Together with the provider passed through
runtime-installer.ts this closes both halves of the incremental gap.
Coverage
__tests__/unit/render/line-render-contribution.test.ts adds five cases: the
unchanged no-contribution drawing sequence, the clipConfig path regression,
the visibility argument contract, per-segment attributes, and the incremental
renderer wiring. Each was verified to fail against the unfixed code.
__tests__/perf/line-render-contribution-performance.test.ts follows the
existing opt-in perf convention (VRENDER_LINE_RENDER_PERF=1) and measures the
hook overhead on the high-performance path by comparing against a subclass
with both hooks stubbed out, interleaving the two variants because measuring
them in sequence charges JIT warmup to whichever runs first.
|
Thanks for the very detailed review — every one of the four points reproduced. Pushed [P1] Visibility flags — [P1] Incremental wiring — both halves are covered. [P2] Clip path — reproduced exactly as described. Path construction now lives in a local
[P2] Segment attributes — both hooks now receive VerificationRun locally on this branch:
Benchmark
200-point line, 2000 draws per round, 9 rounds, minimum per variant, three separate runs: Two caveats on reading that number. The context is a no-op stub, so real canvas work is excluded and the percentage is an upper bound relative to real rendering. And the two variants have to be interleaved: measuring them in sequence charges JIT warmup to whichever runs first, which in my first attempt produced a negative overhead. Happy to take another pass if any of this is not what you had in mind. |
xile611
left a comment
There was a problem hiding this comment.
Re-reviewed head 40be3619 against base 69d82c11. The previous compilation errors, incremental provider/hook wiring, visibility argument types, segment-attribute propagation, and painted-path preservation are now fixed.
One P2 remains: activating the built-in clip contribution also changes line picking. An unfilled line now reports a hit in empty space inside the clip rectangle. This reproduces with both Canvas and Math line pickers and no custom contribution; see the inline comment.
Validation:
- Core
tsc --noEmit --incremental false --composite false --pretty false: passed. - Six targeted suites, including the PR's new tests, runtime contribution tests, and review probes: 47 tests passed; two new regression checks failed, one for each picker. The same picking input returns false with the base renderer and true with this head. Curved, partial clipRange, segmented, closed, and incremental painted-path preservation checks passed.
- Unit test CI is green. The automatic Bug Server run fails at upload with an invalid-token response; it provides no screenshot-regression result for this head.
Please fix the clip/picker interaction and add coverage for empty space inside the clip as well as the actual visible stroke before merging.
| fillCb, | ||
| strokeCb, |
There was a problem hiding this comment.
[P2] Keep the clip path from becoming the line's hit region
Forwarding the picker callbacks into this newly activated hook makes DefaultBaseClipRenderBeforeContribution call fillCb(..., true) on the clip path, even when the line has fill: false. BaseLinePicker then latches picked = true for any point inside that path; the Math picker's PickerBase additionally marks the clip test final. Rebuilding the line path afterward cannot undo that result, so hovering/clicking empty space selects the line.
Reproduced without custom contributions using points [{x:0,y:0},{x:50,y:30},{x:100,y:0}], stroke: 'red', fill: false, lineWidth: 1, pickStrokeBuffer: 0, and clipConfig: {shape: 'rect'}. Calling either DefaultCanvasLinePicker.contains or DefaultMathLinePicker.contains at {x:50,y:5} with EmptyContext2d returns true on this head, versus false with the base renderer. The point is well away from the stroke. The same callback forwarding is present in drawSegmentItem().
Please make clipping constrain the actual line hit test instead of supplying a filled hit region, and add picker regressions alongside the painted-path tests.
🤔 This is a ...
🔗 Related issue link
None — happy to open one if preferred.
💡 Background and solution
DefaultCanvasLineRenderis the only canvas graphic renderer that never resolves or invokes render contributions:IContributionProviderand never callsthis.init()drawSegmentItem()nordrawLinearLineHighPerformance()callsbeforeRenderStep()/afterRenderStep()LineRenderContributionsymbol at allEvery other canvas renderer supports it —
rect,arc,area,symbol,path,polygon,text,image,circle,star,richtextall call the hooks (seebase-render.ts).linelooks like an oversight rather than a decision: it already extendsBaseRender, so the hook methods are inherited, just never called.Why this matters: render contributions are the supported way to draw extra passes around a graphic (
DefaultBaseBackgroundRenderContribution, the texture contributions and the interactive contribution all use it). Today anything built on that extension point silently skips line marks, so line/radar series can't participate in effects that every other mark type can.This PR wires
lineup exactly the wayrectis wired:ILineRenderContributiontype and theLineRenderContributionsymbolDefaultCanvasLineRendernow accepts anIContributionProviderand callsinit()bindLineRenderModulecreates the contribution provider and callsbindContributionProviderbeforeRenderStep()/afterRenderStep()drawSegmentItem()now receivesdrawContextso contributions get the same arguments they get on other graphicsNo behaviour change when no contribution is registered — with an empty
builtinContributionsthe added calls iterate over an empty list.📝 Changelog
☑️ Self Check before Merge
🧪 Verification
Verified against
@visactor/vrender-core@1.1.10by applying the equivalent change to the published bundle and registering a contribution onLineRenderContributionin a real VChart page:linegraphics never reached any contribution (0 invocations across every chart type)linegraphics (268 invocations in one render pass) and its extra draw pass is visible on screen, including line marks and pie leader lines