1+ /* eslint-disable no-use-before-define */
12import { applyCanvasTransform } from '../../shared'
23import { Box , asserts } from '../graph'
34import { Display } from '../graph/display'
45import { Event } from '../native/event'
56import type { DefaultEventDefinition } from '../native/event'
67import { log } from '../native/log'
8+ import { Matrix2D } from '../native/matrix'
79import { Render } from './render'
810
911import type { RenderViewportOptions } from './render'
@@ -19,33 +21,141 @@ export interface DrawGraphIntoCanvasOptions {
1921// First cleanup canvas
2022export function drawGraphIntoCanvas (
2123 graph : Display ,
22- opts : DrawGraphIntoCanvasOptions
24+ opts : DrawGraphIntoCanvasOptions ,
25+ visibleSet ?: Set < number >
2326) {
2427 const { ctx, dpr } = opts
25- ctx . save ( )
28+ if ( asserts . isGraph ( graph ) && visibleSet && ! visibleSet . has ( graph . id ) ) {
29+ return
30+ }
31+
2632 if ( asserts . isBox ( graph ) ) {
2733 const elements = graph . elements
28- const cap = elements . length
29-
30- for ( let i = 0 ; i < cap ; i ++ ) {
34+ for ( let i = 0 ; i < elements . length ; i ++ ) {
3135 const element = elements [ i ]
32- drawGraphIntoCanvas ( element , opts )
36+ if ( asserts . isGraph ( element ) && visibleSet && ! visibleSet . has ( element . id ) ) {
37+ continue
38+ }
39+ drawGraphIntoCanvas ( element , opts , visibleSet )
3340 }
41+ return
3442 }
3543 if ( asserts . isGraph ( graph ) ) {
44+ ctx . save ( )
3645 const matrix = graph . matrix . create ( { a : 1 , b : 0 , c : 0 , d : 1 , e : 0 , f : 0 } )
3746 matrix . transform ( graph . x , graph . y , graph . scaleX , graph . scaleY , graph . rotation , graph . skewX , graph . skewY )
38-
3947 applyCanvasTransform ( ctx , matrix , dpr )
4048 graph . render ( ctx )
49+ ctx . restore ( )
50+ }
51+ }
52+
53+ type BBox = { x : number , y : number , width : number , height : number }
54+
55+ function bboxIntersect ( a : BBox , b : BBox ) {
56+ return ! (
57+ a . x + a . width < b . x ||
58+ a . x > b . x + b . width ||
59+ a . y + a . height < b . y ||
60+ a . y > b . y + b . height
61+ )
62+ }
63+
64+ class QuadTree < T > {
65+ boundary : BBox
66+ capacity : number
67+ objects : Array < { bbox : BBox , obj : T } >
68+ divided : boolean
69+ northeast ?: QuadTree < T >
70+ northwest ?: QuadTree < T >
71+ southeast ?: QuadTree < T >
72+ southwest ?: QuadTree < T >
73+ constructor ( boundary : BBox , capacity : number = 8 ) {
74+ this . boundary = boundary
75+ this . capacity = capacity
76+ this . objects = [ ]
77+ this . divided = false
78+ }
79+ insert ( bbox : BBox , obj : T ) : boolean {
80+ if ( ! bboxIntersect ( this . boundary , bbox ) ) {
81+ return false
82+ }
83+ if ( this . objects . length < this . capacity ) {
84+ this . objects . push ( { bbox, obj } )
85+ return true
86+ }
87+ if ( ! this . divided ) {
88+ this . subdivide ( )
89+ }
90+ return (
91+ this . northeast ! . insert ( bbox , obj ) ||
92+ this . northwest ! . insert ( bbox , obj ) ||
93+ this . southeast ! . insert ( bbox , obj ) ||
94+ this . southwest ! . insert ( bbox , obj )
95+ )
96+ }
97+ subdivide ( ) {
98+ const { x, y, width, height } = this . boundary
99+ const hw = width / 2
100+ const hh = height / 2
101+ this . northeast = new QuadTree ( { x : x + hw , y, width : hw , height : hh } , this . capacity )
102+ this . northwest = new QuadTree ( { x, y, width : hw , height : hh } , this . capacity )
103+ this . southeast = new QuadTree ( { x : x + hw , y : y + hh , width : hw , height : hh } , this . capacity )
104+ this . southwest = new QuadTree ( { x, y : y + hh , width : hw , height : hh } , this . capacity )
105+ this . divided = true
106+ }
107+ query ( range : BBox , found : T [ ] = [ ] ) : T [ ] {
108+ if ( ! bboxIntersect ( this . boundary , range ) ) { return found }
109+ for ( const { bbox, obj } of this . objects ) {
110+ if ( bboxIntersect ( bbox , range ) ) { found . push ( obj ) }
111+ }
112+ if ( this . divided ) {
113+ this . northeast ! . query ( range , found )
114+ this . northwest ! . query ( range , found )
115+ this . southeast ! . query ( range , found )
116+ this . southwest ! . query ( range , found )
117+ }
118+ return found
41119 }
42- ctx . restore ( )
120+ }
121+
122+ function collectBoundingGraphics ( graph : Display , parentMatrix ?: Matrix2D ) {
123+ const results : Array < { bbox : BBox , obj : Display } > = [ ]
124+ const matrix = parentMatrix ?? new Matrix2D ( )
125+ if ( asserts . isGraph ( graph ) ) {
126+ const x = graph . x ?? 0
127+ const y = graph . y ?? 0
128+ const width = graph . width ?? 0
129+ const height = graph . height ?? 0
130+
131+ const p1 = matrix . transformPoint ( x , y )
132+ const p2 = matrix . transformPoint ( x + width , y + height )
133+
134+ const bbox = < BBox > {
135+ x : Math . min ( p1 . x , p2 . x ) ,
136+ y : Math . min ( p1 . y , p2 . y ) ,
137+ width : Math . abs ( p2 . x - p1 . x ) ,
138+ height : Math . abs ( p2 . y - p1 . y )
139+ }
140+ results . push ( { bbox, obj : graph } )
141+ }
142+ if ( asserts . isBox ( graph ) ) {
143+ const elements = graph . elements
144+ const cap = elements . length
145+
146+ for ( let i = 0 ; i < cap ; i ++ ) {
147+ const element = elements [ i ]
148+ results . push ( ...collectBoundingGraphics ( element , matrix ) )
149+ }
150+ }
151+ return results
43152}
44153
45154export class Schedule < D extends DefaultEventDefinition = DefaultEventDefinition > extends Box {
46155 render : Render
47156 to : HTMLElement
48157 event : Event < D >
158+ quadTree ?: QuadTree < Display >
49159 constructor ( to : ApplyTo , renderOptions : Partial < RenderViewportOptions > = { } ) {
50160 super ( )
51161 this . to = typeof to === 'string' ? document . querySelector ( to ) ! : to
@@ -60,13 +170,27 @@ export class Schedule<D extends DefaultEventDefinition = DefaultEventDefinition>
60170
61171 update ( ) {
62172 this . render . clear ( this . render . options . width , this . render . options . height )
63- this . execute ( this . render , this )
173+ const all = collectBoundingGraphics ( this )
174+ const viewport = { x : 0 , y : 0 , width : this . render . options . width , height : this . render . options . height }
175+ this . quadTree = new QuadTree < Display > ( viewport )
176+ const cap = all . length
177+ for ( let i = 0 ; i < cap ; i ++ ) {
178+ const { bbox, obj } = all [ i ]
179+ this . quadTree . insert ( bbox , obj )
180+ }
181+ let visibleSet = undefined
182+ if ( this . quadTree ) {
183+ const visible = this . quadTree . query ( viewport )
184+ visibleSet = new Set ( visible . map ( ( g ) => g . id ) )
185+ }
186+ this . execute ( this . render , this , visibleSet )
187+
64188 const matrix = this . matrix . create ( { a : 1 , b : 0 , c : 0 , d : 1 , e : 0 , f : 0 } )
65189 applyCanvasTransform ( this . render . ctx , matrix , this . render . options . devicePixelRatio )
66190 }
67191
68192 // execute all graph elements
69- execute ( render : Render , graph : Display = this ) {
70- drawGraphIntoCanvas ( graph , { c : render . canvas , ctx : render . ctx , dpr : render . options . devicePixelRatio } )
193+ execute ( render : Render , graph : Display = this , visibleSet ?: Set < number > ) {
194+ drawGraphIntoCanvas ( graph , { c : render . canvas , ctx : render . ctx , dpr : render . options . devicePixelRatio } , visibleSet )
71195 }
72196}
0 commit comments