diff --git a/release-notes.md b/release-notes.md index fcc2526..bf72848 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,11 +1,12 @@ # neomap release notes -## 0.5.0 (WIP) +## 0.5.0 - Support for clusters in map rendering (#50) - Add support for Neo4j point built-in type (#58) - Better support for Neo4j Desktop versions (#53) +- Support for Neo4j 4.x (#60) - Some code refactoring (#52 - #58) diff --git a/src/App.css b/src/App.css index 6a31b5e..c0265e9 100644 --- a/src/App.css +++ b/src/App.css @@ -116,4 +116,12 @@ h4 { padding: 4px; position: absolute; bottom: 3px; +} + +.leaflet-control-layers { + font-size: 1.25rem; +} + +.leaflet-control-layers section { + padding: 5px 10px; } \ No newline at end of file diff --git a/src/components/Map.js b/src/components/Map.js index 415fac6..ae3b0be 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -4,6 +4,7 @@ */ import React, {Component} from 'react' import {connect} from 'react-redux'; +import {RENDERING_CLUSTERS, RENDERING_HEATMAP, RENDERING_MARKERS, RENDERING_POLYLINE} from "./layers/Layer"; import L from 'leaflet'; import 'leaflet.heat'; import 'leaflet.markercluster'; @@ -30,6 +31,7 @@ class Map extends Component { this.leafletPolylineLayers = {}; this.leafletHeatmapLayers = {}; this.leafletClusterLayers = {}; + this.layerControl = null; } @@ -47,27 +49,27 @@ class Map extends Component { let bds = new L.LatLngBounds(layer.bounds); if (bds.isValid()) globalBounds.extend(bds); - if (layer.rendering === "markers") { + if (layer.rendering === RENDERING_MARKERS) { ukeyMarkerArray.push(layer.ukey); if (!this.leafletMarkerLayers[layer.ukey]) { this.leafletMarkerLayers[layer.ukey] = L.layerGroup().addTo(this.map); } this.updateMarkerLayer(layer.data, layer.color, layer.ukey); - } else if (layer.rendering === "polyline") { + } else if (layer.rendering === RENDERING_POLYLINE) { ukeyPolylineArray.push(layer.ukey); if (this.leafletPolylineLayers[layer.ukey]) { // todo find a way of updating the polyline layer instead of delete & recreate this.map.removeLayer(this.leafletPolylineLayers[layer.ukey]); } this.updatePolylineLayer(layer.data, layer.color, layer.ukey); - } else if (layer.rendering === "heatmap") { + } else if (layer.rendering === RENDERING_HEATMAP) { ukeyHeatmapArray.push(layer.ukey); if (this.leafletHeatmapLayers[layer.ukey]) { // todo find a way of updating the heat layer instead of delete & recreate this.map.removeLayer(this.leafletHeatmapLayers[layer.ukey]); } this.updateHeatmapLayer(layer.data, layer.radius, layer.ukey); - } else if (layer.rendering === "clusters") { + } else if (layer.rendering === RENDERING_CLUSTERS) { ukeyClusterArray.push(layer.ukey); if (!this.leafletClusterLayers[layer.ukey]) { this.leafletClusterLayers[layer.ukey] = L.markerClusterGroup(); @@ -97,7 +99,7 @@ class Map extends Component { delete this.leafletPolylineLayers[key]; return null; }); - let deletedHeatmapUkeyLayers = Object.keys(this.leafletHeatmapLayers).filter(function(key) { + let deletedHeatmapUkeyLayers = Object.keys(this.leafletHeatmapLayers).filter(function (key) { return !ukeyHeatmapArray.includes(key); }); deletedHeatmapUkeyLayers.map((key) => { @@ -105,7 +107,7 @@ class Map extends Component { delete this.leafletHeatmapLayers[key]; return null; }); - let deletedClusterUkeyLayers = Object.keys(this.leafletClusterLayers).filter(function(key) { + let deletedClusterUkeyLayers = Object.keys(this.leafletClusterLayers).filter(function (key) { return !ukeyClusterArray.includes(key); }); deletedClusterUkeyLayers.map((key) => { @@ -114,6 +116,35 @@ class Map extends Component { return null; }); this.map.flyToBounds(globalBounds); + this.updateLayerControl(); + } + + updateLayerControl() { + if (this.layerControl) { + this.layerControl.remove(this.map); + } + var overlayMaps = {}; + this.props.layers.map(l => { + switch (l.rendering) { + case RENDERING_MARKERS: + overlayMaps[l.name] = this.leafletMarkerLayers[l.ukey]; + break; + case RENDERING_HEATMAP: + overlayMaps[l.name] = this.leafletHeatmapLayers[l.ukey]; + break; + case RENDERING_CLUSTERS: + overlayMaps[l.name] = this.leafletClusterLayers[l.ukey]; + break; + case RENDERING_POLYLINE: + overlayMaps[l.name] = this.leafletPolylineLayers[l.ukey]; + break; + default: + break; + } + return null; + }); + this.layerControl = L.control.layers([], overlayMaps); + this.layerControl.addTo(this.map); } updateMarkerLayer(data, color, ukey) { @@ -158,14 +189,12 @@ class Map extends Component { let heatData = data.map((entry) => { return entry.pos.concat(1.0); }); - var mapLayer = L.heatLayer(heatData, { + this.leafletHeatmapLayers[ukey] = L.heatLayer(heatData, { radius: radius, minOpacity: 0.1, blur: 15, max: 10.0 }).addTo(this.map); - // this.leafletHeatmapLayers[ukey].setLatLngs(heatData); - // this.leafletHeatmapLayers[ukey].setConfig({ radius }); } @@ -192,7 +221,11 @@ class Map extends Component { render() { - return
; + return ( +
+ text that will be replaced by the map +
+ ); } } diff --git a/src/components/layers/Layer.js b/src/components/layers/Layer.js index 799c70c..0459757 100644 --- a/src/components/layers/Layer.js +++ b/src/components/layers/Layer.js @@ -14,7 +14,6 @@ import neo4jService from '../../services/neo4jService' import {addOrUpdateLayer, removeLayer} from "../../actions"; - import 'react-confirm-alert/src/react-confirm-alert.css'; // Import css // css needed for CypherEditor import "codemirror/lib/codemirror.css"; @@ -24,19 +23,17 @@ import "cypher-codemirror/dist/cypher-codemirror-syntax.css"; import ColorPicker from "../ColorPicker"; -// maximum number of points to show -const LIMIT = 10000; - // layer type: either from node labels or cypher const LAYER_TYPE_LATLON = "latlon"; const LAYER_TYPE_POINT = "point"; const LAYER_TYPE_CYPHER = "cypher"; const LAYER_TYPE_SPATIAL = "spatial"; -const RENDERING_MARKERS = "markers"; -const RENDERING_POLYLINE = "polyline"; -const RENDERING_HEATMAP = "heatmap"; -const RENDERING_CLUSTERS = "clusters"; +// TODO: move this into a separate configuration/constants file +export const RENDERING_MARKERS = "markers"; +export const RENDERING_POLYLINE = "polyline"; +export const RENDERING_HEATMAP = "heatmap"; +export const RENDERING_CLUSTERS = "clusters"; // default parameters for new layers @@ -53,7 +50,7 @@ const DEFAULT_LAYER = { data: [], bounds: [], color: {r: 0, g: 0, b: 255, a: 1}, - limit: LIMIT, + limit: null, rendering: RENDERING_MARKERS, radius: 30, cypher: "", @@ -168,12 +165,13 @@ class Layer extends Component { getSpatialQuery() { let query = `CALL spatial.layer('${this.state.spatialLayer.value}') YIELD node `; query += "WITH node "; - query += "MATCH (node)-[:RTREE_ROOT]-()-[:RTREE_CHILD]-()-[:RTREE_REFERENCE]-(n) "; + query += "MATCH (node)-[:RTREE_ROOT]-()-[:RTREE_CHILD*1..10]->()-[:RTREE_REFERENCE]-(n) "; query += "WHERE n.point.srid = 4326 "; query += "RETURN n.point.x as longitude, n.point.y as latitude "; if (this.state.tooltipProperty.value !== '') query += `, n.${this.state.tooltipProperty.value} as tooltip `; - query += `\nLIMIT ${this.state.limit}`; + if (this.state.limit) + query += `\nLIMIT ${this.state.limit}`; return query; }; @@ -210,7 +208,8 @@ class Layer extends Component { // TODO: is that really needed??? // limit the number of points to avoid browser crash... - query += `\nLIMIT ${this.state.limit}`; + if (this.state.limit) + query += `\nLIMIT ${this.state.limit}`; return query; };