@@ -23,40 +23,17 @@ class NeoMapReport extends NeoReport {
2323 this . state . width = this . props . clientWidth - 50 ; //-90 + props.width * 105 - xShift * 0.5;
2424 this . state . height = - 145 + this . props . height * 100 ;
2525
26- this . state . nodesAndPositions = [ ]
26+ this . state . nodesAndPositions = { }
27+ this . state . relationshipsAndPositions = { }
2728 if ( this . state . data ) {
28- this . state . data . forEach ( record => {
29- Object . values ( record ) . forEach ( v => {
30- if ( v . identity && v . properties && v . properties . latitude && v . properties . longitude ) {
31- let lat = parseFloat ( v . properties . latitude ) ;
32- let long = parseFloat ( v . properties . longitude ) ;
33- if ( ! isNaN ( lat ) && ! isNaN ( long ) ) {
34- this . state . nodesAndPositions . push ( { pos : [ lat , long ] , node : v } )
35- }
36- } else if ( v . identity && v . properties && v . properties . lat && v . properties . long ) {
37- let lat = parseFloat ( v . properties . lat ) ;
38- let long = parseFloat ( v . properties . long ) ;
39- if ( ! isNaN ( lat ) && ! isNaN ( long ) ) {
40- this . state . nodesAndPositions . push ( { pos : [ lat , long ] , node : v } )
41- }
42- } else if ( v . identity && v . properties ) {
43- Object . values ( v . properties ) . forEach ( p => {
44- // We found a property that holds a Neo4j point object
45- if ( p . srid && p . x && p . y ) {
46- if ( ! isNaN ( p . x ) && ! isNaN ( p . y ) ) {
47- this . state . nodesAndPositions . push ( { pos : [ p . y , p . x ] , node : v } )
48- }
49- }
50- } )
51- }
52- } )
53-
54- } )
29+ this . extractNodesFromAllRecords ( ) ;
30+ this . extractRelationshipsFromAllRecords ( ) ;
5531 }
5632
5733 // This is where math happens - we try to come up with the optimal zoom to fit all rendered nodes...
58- let longitudePositions = this . state . nodesAndPositions . map ( i => i . pos [ 0 ] + 180 ) ;
59- this . state . centerLongitude = longitudePositions . reduce ( ( a , b ) => a + b , 0 ) / this . state . nodesAndPositions . length ;
34+ let nodesAndPositionsValues = Object . values ( this . state . nodesAndPositions ) ;
35+ let longitudePositions = nodesAndPositionsValues . map ( i => i . pos [ 0 ] + 180 ) ;
36+ this . state . centerLongitude = longitudePositions . reduce ( ( a , b ) => a + b , 0 ) / nodesAndPositionsValues . length ;
6037 let maxLong = Math . max . apply ( null , longitudePositions ) ;
6138 if ( ( maxLong === this . state . centerLongitude ) ) {
6239 maxLong += 0.000000001 ;
@@ -67,8 +44,8 @@ class NeoMapReport extends NeoReport {
6744 let longZoomFit = Math . ceil ( Math . log2 ( 1.0 / longProjectedHeight ) ) ;
6845
6946
70- let latitudePositions = this . state . nodesAndPositions . map ( i => i . pos [ 1 ] + 90 ) ;
71- this . state . centerLatitude = latitudePositions . reduce ( ( a , b ) => a + b , 0 ) / this . state . nodesAndPositions . length ;
47+ let latitudePositions = nodesAndPositionsValues . map ( i => i . pos [ 1 ] + 90 ) ;
48+ this . state . centerLatitude = latitudePositions . reduce ( ( a , b ) => a + b , 0 ) / nodesAndPositionsValues . length ;
7249 let maxLat = Math . max . apply ( null , latitudePositions ) ;
7350 if ( ( maxLat === this . state . centerLatitude ) ) {
7451 maxLat += 0.000000001 ;
@@ -83,20 +60,147 @@ class NeoMapReport extends NeoReport {
8360
8461 }
8562
63+ extractRelationshipsFromAllRecords ( ) {
64+ this . state . data . forEach ( record => {
65+ Object . values ( record ) . forEach ( recordField => {
66+ // single relationship
67+ if ( recordField && recordField [ "type" ] && recordField [ "start" ] && recordField [ "end" ] && recordField [ "identity" ] && recordField [ "properties" ] ) {
68+ let start = recordField [ "start" ]
69+ let end = recordField [ "end" ]
70+ if ( start && end && this . state . nodesAndPositions [ start . low ] && this . state . nodesAndPositions [ end . low ] ) {
71+ this . state . relationshipsAndPositions [ recordField [ "identity" ] ] =
72+ {
73+ start : this . state . nodesAndPositions [ start . low ] . pos ,
74+ end : this . state . nodesAndPositions [ end . low ] . pos ,
75+ rel : recordField
76+ }
77+ }
78+ } else if ( recordField [ "start" ] && recordField [ "end" ] && recordField [ "segments" ] && recordField [ "length" ] ) {
79+ // paths
80+ recordField [ "segments" ] . forEach ( s => {
81+ let segment = s [ "relationship" ] ;
82+ if ( segment && segment [ "type" ] && segment [ "start" ] && segment [ "end" ] && segment [ "identity" ] && segment [ "properties" ] ) {
83+ let start = segment [ "start" ]
84+ let end = segment [ "end" ]
85+ if ( start && end && this . state . nodesAndPositions [ start . low ] && this . state . nodesAndPositions [ end . low ] ) {
86+ this . state . relationshipsAndPositions [ segment [ "identity" ] ] =
87+ {
88+ start : this . state . nodesAndPositions [ start . low ] . pos ,
89+ end : this . state . nodesAndPositions [ end . low ] . pos ,
90+ rel : segment
91+ }
92+ }
93+ }
94+ } )
95+ }
96+ // TODO - collections of relationships
97+ } )
98+ } )
99+ }
100+
101+ extractNodesFromAllRecords ( ) {
102+ let nodeLabels = { }
103+ this . state . data . forEach ( record => {
104+ Object . values ( record ) . forEach ( recordField => {
105+ if ( recordField && Array . isArray ( recordField ) ) {
106+ // Arrays (of nodes)
107+ recordField . forEach ( element => {
108+ let nodeIdandPos = this . extractGeocoordsFromNode ( element , nodeLabels ) ;
109+ if ( nodeIdandPos ) {
110+ this . state . nodesAndPositions [ nodeIdandPos [ 0 ] ] = nodeIdandPos [ 1 ] ;
111+ }
112+ } )
113+ } else if ( recordField [ "start" ] && recordField [ "end" ] && recordField [ "segments" ] && recordField [ "length" ] ) {
114+ // Paths
115+ let nodeIdandPos = this . extractGeocoordsFromNode ( recordField [ "start" ] , nodeLabels ) ;
116+ if ( nodeIdandPos ) {
117+ this . state . nodesAndPositions [ nodeIdandPos [ 0 ] ] = nodeIdandPos [ 1 ] ;
118+ }
119+ let nodeIdandPos2 = this . extractGeocoordsFromNode ( recordField [ "end" ] , nodeLabels ) ;
120+ if ( nodeIdandPos2 ) {
121+ this . state . nodesAndPositions [ nodeIdandPos2 [ 0 ] ] = nodeIdandPos2 [ 1 ] ;
122+ }
123+ } else {
124+ // Single nodes
125+ let nodeIdandPos = this . extractGeocoordsFromNode ( recordField , nodeLabels ) ;
126+ if ( nodeIdandPos ) {
127+ this . state . nodesAndPositions [ nodeIdandPos [ 0 ] ] = nodeIdandPos [ 1 ] ;
128+ }
129+ }
130+ } )
131+ } ) ;
132+
133+ this . state . nodeLabels = Object . keys ( nodeLabels )
134+ }
135+
136+ extractGeocoordsFromNode ( recordField , nodeLabels ) {
137+ /**
138+ * Extracts node geo-coordinates from a record.
139+ */
140+ if ( recordField && recordField . identity && recordField . properties && recordField . properties . latitude && recordField . properties . longitude ) {
141+ let lat = parseFloat ( recordField . properties . latitude ) ;
142+ let long = parseFloat ( recordField . properties . longitude ) ;
143+ if ( ! isNaN ( lat ) && ! isNaN ( long ) ) {
144+ recordField . labels . forEach ( l => nodeLabels [ l ] = null ) ;
145+ return [ recordField . identity . low , { pos : [ lat , long ] , node : recordField } ] ;
146+ }
147+ } else if ( recordField && recordField . identity && recordField . properties && recordField . properties . lat && recordField . properties . long ) {
148+ let lat = parseFloat ( recordField . properties . lat ) ;
149+ let long = parseFloat ( recordField . properties . long ) ;
150+ if ( ! isNaN ( lat ) && ! isNaN ( long ) ) {
151+ recordField . labels . forEach ( l => nodeLabels [ l ] = null ) ;
152+ return [ recordField . identity . low , { pos : [ lat , long ] , node : recordField } ] ;
153+ }
154+ } else if ( recordField && recordField . identity && recordField . properties ) {
155+ let result = null ;
156+ Object . values ( recordField . properties ) . forEach ( p => {
157+ // We found a property that holds a Neo4j point object
158+
159+ if ( p . srid != null && p . x != null && p . y != null ) {
160+ if ( ! isNaN ( p . x ) && ! isNaN ( p . y ) ) {
161+ recordField . labels . forEach ( l => nodeLabels [ l ] = null ) ;
162+ // TODO - this only returns the first point object it finds on a node...
163+ result = [ recordField . identity . low , { pos : [ p . y , p . x ] , node : recordField } ] ;
164+ }
165+ }
166+ } )
167+ return result ;
168+ }
169+ return null ;
170+ }
171+
172+ /**
173+ *
174+ */
175+ neoPropertyToString ( property ) {
176+ if ( property . srid ) {
177+ return "(lat:" + property . y + ", long:" + property . x + ")" ;
178+ }
179+ return property ;
180+ }
86181
87182 /**
88183 * Creates the leaflet visualization to render in the report.
89184 */
90185 createMapVisualization ( ) {
91186 let colors = [ "#588c7e" , "#f2e394" , "#f2ae72" , "#d96459" , "#5b9aa0" , "#d6d4e0" , "#b8a9c9" , "#622569" , "#ddd5af" , "#d9ad7c" , "#a2836e" , "#674d3c" , "grey" ]
92- let markers = ( this . state . nodesAndPositions ) ?
93- this . state . nodesAndPositions . map ( i =>
187+ let nodesAndPositionsValues = Object . values ( this . state . nodesAndPositions ) ;
188+ let relationshipsAndPositionsValues = Object . values ( this . state . relationshipsAndPositions ) ;
189+ let markers = nodesAndPositionsValues ?
190+ nodesAndPositionsValues . map ( i =>
94191 < Marker position = { i . pos }
95- icon = { < div style = { { color : colors [ 0 ] } } > < Icon className = "close" > place</ Icon > </ div > } >
96- < Popup > < h6 > { i . node . labels . map ( b => b + " " ) } </ h6 > < code > { Object . keys ( i . node . properties ) . map ( key =>
97- < pre > { key + ": " + i . node . properties [ key ] + "\n" } </ pre > ) } </ code > </ Popup >
192+ icon = { < div
193+ style = { { color : colors [ this . state . nodeLabels . indexOf ( i . node . labels [ i . node . labels . length - 1 ] ) % colors . length ] } } >
194+ < Icon className = "close" > place</ Icon > </ div > } >
195+ { this . createPopupFromNodeProperties ( i ) }
98196 </ Marker > ) : < div > </ div >
99- let lines = < div > </ div > // [<Polyline key={0} positions={[this.state.pos1, this.state.pos2]} color={colors[0]}/>];
197+ let lines = ( relationshipsAndPositionsValues ) ?
198+ relationshipsAndPositionsValues . map ( i =>
199+ < Polyline width = "5" key = { 0 } positions = { [ i . start , i . end ] } color = { "grey" } >
200+ { this . createPopupFromRelProperties ( i ) }
201+ </ Polyline >
202+ ) : < div > </ div >
203+
100204
101205 return < MapContainer key = { 0 } style = { { "width" : this . state . width + "px" , "height" : this . state . height + "px" } }
102206 center = {
@@ -117,6 +221,26 @@ class NeoMapReport extends NeoReport {
117221 </ MapContainer > ;
118222 }
119223
224+ createPopupFromRelProperties ( i ) {
225+ return < Popup className = { "leaflet-rel-popup" } >
226+ < h6 > < b > { i . rel . type } </ b > </ h6 >
227+ < code > { ( Object . keys ( i . rel . properties ) . length > 0 ) ?
228+ Object . keys ( i . rel . properties ) . map ( key =>
229+ < pre > { key + ": " + this . neoPropertyToString ( i . rel . properties [ key ] ) + "\n" } </ pre > )
230+ : "(no properties)" }
231+ </ code >
232+ </ Popup > ;
233+ }
234+
235+ createPopupFromNodeProperties ( i ) {
236+ return < Popup >
237+ < h6 > < b > { ( i . node . labels . length > 0 ) ? i . node . labels . map ( b => b + " " ) : "(No labels)" } </ b > </ h6 >
238+ < code > { Object . keys ( i . node . properties ) . map ( key =>
239+ < pre > { key + ": " + this . neoPropertyToString ( i . node . properties [ key ] ) + "\n" } </ pre > ) }
240+ </ code >
241+ </ Popup > ;
242+ }
243+
120244 render ( ) {
121245 let rendered = super . render ( ) ;
122246 if ( rendered ) {
0 commit comments