@@ -16,6 +16,7 @@ const wrtc = require('wrtc');
1616const { MemoryView} = require ( '@nvidia/cuda' ) ;
1717const { Float32Buffer} = require ( '@nvidia/cuda' ) ;
1818const { GraphCOO} = require ( '@rapidsai/cugraph' ) ;
19+ const { Series, Int32} = require ( '@rapidsai/cudf' ) ;
1920
2021const { loadNodes, loadEdges} = require ( './loader' ) ;
2122const { RenderCluster} = require ( '../../render/cluster' ) ;
@@ -54,12 +55,32 @@ function graphSSRClients(fastify) {
5455
5556 clients [ stream . id ] = {
5657 video : source ,
57- state : { } ,
58+ state : {
59+ pickingMode : 'click' , // 'click', 'boxSelect'
60+ selectedInfo : { } ,
61+ boxSelectCoordinates : { rectdata : [ { polygon : [ [ ] ] , show : false } ] , startPos : null } ,
62+ clearSelections : false
63+ } ,
5864 event : { } ,
5965 props : { width, height, layout} ,
6066 graph : await loadGraph ( graphId ) ,
6167 frame : shmCreate ( width * height * 3 / 2 ) ,
68+ peer : peer ,
6269 } ;
70+ if ( clients [ stream . id ] . graph . dataframes [ 0 ] ) {
71+ const res = getPaginatedRows ( clients [ stream . id ] . graph . dataframes [ 0 ] ) ;
72+ peer . send ( JSON . stringify ( {
73+ type : 'data' ,
74+ data : { nodes : { data : res , length : clients [ stream . id ] . graph . dataframes [ 0 ] . numRows } }
75+ } ) ) ;
76+ }
77+ if ( clients [ stream . id ] . graph . dataframes [ 1 ] ) {
78+ const res = getPaginatedRows ( clients [ stream . id ] . graph . dataframes [ 1 ] ) ;
79+ peer . send ( JSON . stringify ( {
80+ type : 'data' ,
81+ data : { edges : { data : res , length : clients [ stream . id ] . graph . dataframes [ 1 ] . numRows } }
82+ } ) ) ;
83+ }
6384
6485 stream . addTrack ( source . createTrack ( ) ) ;
6586 peer . streams . push ( stream ) ;
@@ -79,6 +100,18 @@ function graphSSRClients(fastify) {
79100 clients [ stream . id ] . event [ data . type ] = data ;
80101 break ;
81102 }
103+ case 'pickingMode' : {
104+ clients [ stream . id ] . state . pickingMode = data ;
105+ break ;
106+ }
107+ case 'clearSelections' : {
108+ clients [ stream . id ] . state . clearSelections = JSON . parse ( data ) ;
109+ break ;
110+ }
111+ case 'layout' : {
112+ clients [ stream . id ] . props . layout = JSON . parse ( data ) ;
113+ break ;
114+ }
82115 }
83116 }
84117 }
@@ -95,22 +128,24 @@ function graphSSRClients(fastify) {
95128 }
96129
97130 async function loadGraph ( id ) {
131+ let dataframes = [ ] ;
132+
98133 if ( ! ( id in graphs ) ) {
99134 const asDeviceMemory = ( buf ) => new ( buf [ Symbol . species ] ) ( buf ) ;
100- const [ nodes , edges ] = await Promise . all ( [ loadNodes ( id ) , loadEdges ( id ) ] ) ;
101- const src = edges . get ( 'src' ) ;
102- const dst = edges . get ( 'dst' ) ;
135+ dataframes = await Promise . all ( [ loadNodes ( id ) , loadEdges ( id ) ] ) ;
136+ const src = dataframes [ 1 ] . get ( 'src' ) ;
137+ const dst = dataframes [ 1 ] . get ( 'dst' ) ;
103138 graphs [ id ] = {
104139 refCount : 0 ,
105140 nodes : {
106- nodeRadius : asDeviceMemory ( nodes . get ( 'size' ) . data ) ,
107- nodeFillColors : asDeviceMemory ( nodes . get ( 'color' ) . data ) ,
108- nodeElementIndices : asDeviceMemory ( nodes . get ( 'id' ) . data ) ,
141+ nodeRadius : asDeviceMemory ( dataframes [ 0 ] . get ( 'size' ) . data ) ,
142+ nodeFillColors : asDeviceMemory ( dataframes [ 0 ] . get ( 'color' ) . data ) ,
143+ nodeElementIndices : asDeviceMemory ( dataframes [ 0 ] . get ( 'id' ) . data ) ,
109144 } ,
110145 edges : {
111- edgeList : asDeviceMemory ( edges . get ( 'edge' ) . data ) ,
112- edgeColors : asDeviceMemory ( edges . get ( 'color' ) . data ) ,
113- edgeBundles : asDeviceMemory ( edges . get ( 'bundle' ) . data ) ,
146+ edgeList : asDeviceMemory ( dataframes [ 1 ] . get ( 'edge' ) . data ) ,
147+ edgeColors : asDeviceMemory ( dataframes [ 1 ] . get ( 'color' ) . data ) ,
148+ edgeBundles : asDeviceMemory ( dataframes [ 1 ] . get ( 'bundle' ) . data ) ,
114149 } ,
115150 graph : new GraphCOO ( src . _col , dst . _col , { directedEdges : true } ) ,
116151 } ;
@@ -124,24 +159,25 @@ function graphSSRClients(fastify) {
124159 ) ) ;
125160
126161 return {
127- gravity : 1 .0,
162+ gravity : 0 .0,
128163 linLogMode : false ,
129164 scalingRatio : 5.0 ,
130165 barnesHutTheta : 0.0 ,
131166 jitterTolerance : 0.05 ,
132167 strongGravityMode : false ,
133168 outboundAttraction : false ,
134169 graph : graphs [ id ] . graph ,
135- edges : {
136- ...graphs [ id ] . edges ,
137- length : graphs [ id ] . graph . numEdges ( ) ,
138- } ,
139170 nodes : {
140171 ...graphs [ id ] . nodes ,
141172 length : graphs [ id ] . graph . numNodes ( ) ,
142173 nodeXPositions : pos . subarray ( 0 , pos . length / 2 ) ,
143174 nodeYPositions : pos . subarray ( pos . length / 2 ) ,
144175 } ,
176+ edges : {
177+ ...graphs [ id ] . edges ,
178+ length : graphs [ id ] . graph . numEdges ( ) ,
179+ } ,
180+ dataframes : dataframes
145181 } ;
146182 }
147183}
@@ -152,8 +188,17 @@ function layoutAndRenderGraphs(clients) {
152188 return ( ) => {
153189 for ( const id in clients ) {
154190 const client = clients [ id ] ;
191+ const sendToClient =
192+ ( [ nodes , edges ] ) => {
193+ client . peer . send ( JSON . stringify (
194+ { type : 'data' , data : { nodes : { data : getPaginatedRows ( nodes ) , length : nodes . numRows } } } ) ) ;
195+ client . peer . send ( JSON . stringify (
196+ { type : 'data' , data : { edges : { data : getPaginatedRows ( edges ) , length : edges . numRows } } } ) ) ;
197+ }
155198
156- if ( client . isRendering ) { continue ; }
199+ if ( client . isRendering ) {
200+ continue ;
201+ }
157202
158203 const state = { ...client . state } ;
159204 const props = { ...client . props } ;
@@ -170,55 +215,89 @@ function layoutAndRenderGraphs(clients) {
170215 'mouseup' ,
171216 'mouseleave' ,
172217 'wheel' ,
173- 'beforeunload'
218+ 'beforeunload' ,
219+ 'shiftKey' ,
220+ 'dragStart' ,
221+ 'dragOver'
174222 ] . map ( ( x ) => client . event [ x ] )
175223 . filter ( Boolean ) ;
176224
177225 if ( event . length === 0 && ! props . layout ) { continue ; }
178226 if ( event . length !== 0 ) { client . event = Object . create ( null ) ; }
179- if ( props . layout ) { client . graph = forceAtlas2 ( client . graph ) ; }
227+ if ( props . layout == true ) { client . graph = forceAtlas2 ( client . graph ) ; }
180228
181229 const {
182230 width = client . props . width ?? 800 ,
183231 height = client . props . height ?? 600 ,
184232 } = client . state ;
185233
234+ state . window = { width : width , height : height , ...client . state . window } ;
235+
186236 if ( client . frame ?. byteLength !== ( width * height * 3 / 2 ) ) {
187237 shmDetach ( client . frame . key , true ) ;
188238 client . frame = shmCreate ( width * height * 3 / 2 ) ;
189239 }
190-
191240 client . isRendering = true ;
192241
193- renderer . render ( id ,
194- {
195- state,
196- props,
197- event,
198- frame : client . frame . key ,
199- graph : {
200- ...client . graph ,
201- graph : undefined ,
202- edges : getIpcHandles ( client . graph . edges ) ,
203- nodes : getIpcHandles ( client . graph . nodes ) ,
204- } ,
205- } ,
206- ( error , result ) => {
207- client . isRendering = false ;
208- if ( id in clients ) {
209- if ( error ) { throw error ; }
210- result ?. state && Object . assign ( client . state , result . state ) ;
211- // console.log(state?.deck?.props?.initialViewState);
212- // if (state?.deck?.props) {
213- // client.props.initialViewState = state.deck.props.initialViewState;
214- // }
215- client . video . onFrame ( { ...result . frame , data : client . frame . buffer } ) ;
216- }
217- } ) ;
242+ renderer . render (
243+ id ,
244+ {
245+ state,
246+ props,
247+ event,
248+ frame : client . frame . key ,
249+ graph : {
250+ ...client . graph ,
251+ graph : undefined ,
252+ edges : getIpcHandles ( client . graph . edges ) ,
253+ nodes : getIpcHandles ( client . graph . nodes ) ,
254+ } ,
255+ } ,
256+ ( error , result ) => {
257+ client . isRendering = false ;
258+ if ( id in clients ) {
259+ if ( error ) { throw error ; }
260+ if ( client . state . clearSelections == true ) {
261+ // clear selection is called once
262+ result . state . clearSelections = false ;
263+
264+ // reset selected state
265+ result . state . selectedInfo . selectedNodes = [ ] ;
266+ result . state . selectedInfo . selectedEdges = [ ] ;
267+ result . state . selectedInfo . selectedCoordinates = { } ;
268+ result . state . boxSelectCoordinates . rectdata = [ { polygon : [ [ ] ] , show : false } ] ;
269+
270+ // send to client
271+ if ( client . graph . dataframes ) { sendToClient ( client . graph . dataframes ) ; }
272+ } else if ( JSON . stringify ( client . state . selectedInfo . selectedCoordinates ) !==
273+ JSON . stringify ( result . state . selectedInfo . selectedCoordinates ) ) {
274+ // selections updated
275+ const nodes =
276+ Series . new ( { type : new Int32 , data : result . state . selectedInfo . selectedNodes } ) ;
277+ const edges =
278+ Series . new ( { type : new Int32 , data : result . state . selectedInfo . selectedEdges } ) ;
279+ if ( client . graph . dataframes ) {
280+ sendToClient ( [
281+ client . graph . dataframes [ 0 ] . gather ( nodes ) ,
282+ client . graph . dataframes [ 1 ] . gather ( edges )
283+ ] ) ;
284+ }
285+ }
286+ // copy result state to client's current state
287+ result ?. state && Object . assign ( client . state , result . state ) ;
288+
289+ client . video . onFrame ( { ...result . frame , data : client . frame . buffer } ) ;
290+ }
291+ } ) ;
218292 }
219293 }
220294}
221295
296+ function getPaginatedRows ( df , page = 1 , rowsPerPage = 400 ) {
297+ if ( ! df ) { return { } ; }
298+ return df . head ( page * rowsPerPage ) . tail ( rowsPerPage ) . toArrow ( ) . toArray ( ) ;
299+ }
300+
222301function forceAtlas2 ( { graph, nodes, edges, ...params } ) {
223302 graph . forceAtlas2 ( { ...params , positions : nodes . nodeXPositions . buffer } ) ;
224303 return {
0 commit comments