@@ -27,6 +27,34 @@ export type RenderBundle = {
2727 div : string
2828}
2929
30+ export interface DocumentChanged {
31+ event : "jsevent"
32+ kind : string
33+ }
34+
35+ export interface ModelChanged extends DocumentChanged {
36+ event : "jsevent"
37+ kind : "ModelChanged"
38+ id : string
39+ new : unknown
40+ attr : string
41+ old : unknown
42+ hint : unknown
43+ }
44+
45+ export interface MessageSent extends DocumentChanged {
46+ event : "jsevent"
47+ kind : "MessageSent"
48+ msg_data : {
49+ event_name : string ,
50+ event_values : {
51+ model : { id : string } ,
52+ [ other : string ] : any
53+ }
54+ }
55+ msg_type : string
56+ }
57+
3058export class BokehModel extends DOMWidgetModel {
3159 defaults ( ) {
3260 return {
@@ -40,6 +68,7 @@ export class BokehModel extends DOMWidgetModel {
4068 _view_module : name ,
4169 _view_module_version : version_range ,
4270
71+ combine_events : false ,
4372 render_bundle : { } ,
4473 }
4574 }
@@ -53,17 +82,46 @@ export class BokehView extends DOMWidgetView {
5382 private _document : Document | null
5483 private _receiver : Receiver
5584 private _blocked : boolean
85+ private _msgs : any [ ]
86+ private _idle : boolean
87+ private _combine : boolean
5688
5789 constructor ( options ?: any ) {
5890 super ( options )
5991 this . _document = null
6092 this . _blocked = false
93+ this . _idle = true
94+ this . _combine = true
95+ this . _msgs = [ ]
6196 const { Receiver} = bk_require ( "protocol/receiver" )
6297 this . _receiver = new Receiver ( )
6398 this . model . on ( "change:render_bundle" , ( ) => this . render ( ) )
99+ if ( ( window as any ) . Jupyter != null && ( window as any ) . Jupyter . notebook != null ) {
100+ // Handle classic Jupyter notebook
101+ const events = ( window as any ) . require ( 'base/js/events' )
102+ events . on ( 'kernel_idle.Kernel' , ( ) => this . _process_msg ( ) )
103+ } else if ( ( this . model . widget_manager as any ) . context != null ) {
104+ // Handle JupyterLab
105+ ( this . model . widget_manager as any ) . context . sessionContext . statusChanged . connect ( ( _ : any , status : any ) => {
106+ if ( status === "idle" )
107+ this . _process_msg ( )
108+ } )
109+ } else {
110+ if ( this . model . get ( "combine_events" ) )
111+ console . warn ( "BokehView cannot combine events because Kernel idle status cannot be determined." )
112+ this . _combine = false
113+ }
64114 this . listenTo ( this . model , "msg:custom" , ( content , buffers ) => this . _consume_patch ( content , buffers ) )
65115 }
66116
117+ protected _process_msg ( ) : void {
118+ if ( this . _msgs . length == 0 ) {
119+ this . _idle = true
120+ return
121+ }
122+ this . send ( this . _msgs . shift ( ) )
123+ }
124+
67125 render ( ) : void {
68126 const bundle = JSON . parse ( this . model . get ( "render_bundle" ) )
69127 const { docs_json, render_items, div} = bundle as RenderBundle
@@ -82,10 +140,77 @@ export class BokehView extends DOMWidgetView {
82140 this . _document . on_change ( ( event : any ) => this . _change_event ( event ) )
83141 }
84142
143+ _combine_events ( new_msg : ( ModelChanged | MessageSent ) ) : ( ModelChanged | MessageSent ) [ ] {
144+ const new_msgs = [ ]
145+ for ( const msg of this . _msgs ) {
146+ if ( new_msg . kind != msg . kind )
147+ new_msgs . push ( msg )
148+ else if ( msg . kind == "ModelChanged" && new_msg . kind == "ModelChanged" ) {
149+ if ( msg . id != new_msg . id || msg . attr != new_msg . attr )
150+ new_msgs . push ( msg )
151+ } else if ( msg . kind == "MessageSent" && new_msg . kind == "MessageSent" ) {
152+ if (
153+ msg . msg_data . event_values . model . id != new_msg . msg_data . event_values . model . id ||
154+ msg . msg_data . event_name != new_msg . msg_data . event_name
155+ )
156+ new_msgs . push ( msg )
157+ }
158+ }
159+ new_msgs . push ( new_msg )
160+ return new_msgs
161+ }
162+
163+ _send ( msg : ( ModelChanged | MessageSent ) ) : void {
164+ if ( ! this . _idle && this . _combine && this . model . get ( "combine_events" ) )
165+ // Queue event and drop previous events on same model attribute
166+ this . _msgs = this . _combine_events ( msg )
167+ else {
168+ this . _idle = false
169+ this . send ( msg )
170+ }
171+ }
172+
85173 protected _change_event ( event : DocumentChangedEvent ) : void {
86- const { ModelChangedEvent} = bk_require ( "document/events" )
87- if ( ! this . _blocked && event instanceof ModelChangedEvent )
88- this . send ( { event : "jsevent" , id : event . model . id , new : event . new_ , attr : event . attr , old : event . old } )
174+ if ( this . _blocked ) { return }
175+ const { ModelChangedEvent, MessageSentEvent} = bk_require ( "document/events" )
176+ if ( event instanceof ModelChangedEvent ) {
177+ const js_msg : ModelChanged = {
178+ event : "jsevent" ,
179+ kind : "ModelChanged" ,
180+ id : event . model . id ,
181+ attr : event . attr ,
182+ new : event . new_ ,
183+ old : event . old ,
184+ hint : null ,
185+ }
186+ if ( event . hint != null ) {
187+ if ( event . hint . patches != null ) {
188+ js_msg [ "hint" ] = {
189+ column_source : event . hint . column_source ,
190+ patches : event . hint . patches ,
191+ }
192+ } else if ( event . hint . data != null ) {
193+ js_msg [ "hint" ] = {
194+ column_source : event . hint . column_source ,
195+ data : event . hint . data ,
196+ rollover : event . hint . rollover ,
197+ }
198+ }
199+ }
200+ this . _send ( js_msg )
201+ } else if ( ( event instanceof MessageSentEvent ) && ( event . msg_type == "bokeh_event" ) ) {
202+ const msg_data = { ...event . msg_data }
203+ const event_values = { ...msg_data . event_values }
204+ event_values [ "model" ] = { id : event_values . model . id }
205+ msg_data [ "event_values" ] = event_values
206+ const js_msg : MessageSent = {
207+ event : "jsevent" ,
208+ kind : "MessageSent" ,
209+ msg_type : event . msg_type ,
210+ msg_data : msg_data ,
211+ }
212+ this . _send ( js_msg )
213+ }
89214 }
90215
91216 protected _consume_patch ( content : { msg : "patch" , payload ?: Fragment } , buffers : DataView [ ] ) : void {
0 commit comments