better readme, do scaling properly
implement pan/zoom on topo
R to reset pan/zoom
require meta to drag or select nodes
Change-Id: I15e20296e76d5cd8656b144b2d61a6923a5509ad
diff --git a/web/gui/src/main/webapp/d3Utils.js b/web/gui/src/main/webapp/d3Utils.js
index 51651fa..e647a37 100644
--- a/web/gui/src/main/webapp/d3Utils.js
+++ b/web/gui/src/main/webapp/d3Utils.js
@@ -23,7 +23,7 @@
(function (onos) {
'use strict';
- function createDragBehavior(force, selectCb, atDragEnd) {
+ function createDragBehavior(force, selectCb, atDragEnd, requireMeta) {
var draggedThreshold = d3.scale.linear()
.domain([0, 0.1])
.range([5, 20])
@@ -51,29 +51,39 @@
drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on('dragstart', function(d) {
- d.oldX = d.x;
- d.oldY = d.y;
- d.dragged = false;
- d.fixed |= 2;
+ if (requireMeta ^ !d3.event.sourceEvent.metaKey) {
+ d3.event.sourceEvent.stopPropagation();
+
+ d.oldX = d.x;
+ d.oldY = d.y;
+ d.dragged = false;
+ d.fixed |= 2;
+ d.dragStarted = true;
+ }
})
.on('drag', function(d) {
- d.px = d3.event.x;
- d.py = d3.event.y;
- if (dragged(d)) {
- if (!force.alpha()) {
- force.alpha(.025);
+ if (requireMeta ^ !d3.event.sourceEvent.metaKey) {
+ d.px = d3.event.x;
+ d.py = d3.event.y;
+ if (dragged(d)) {
+ if (!force.alpha()) {
+ force.alpha(.025);
+ }
}
}
})
.on('dragend', function(d) {
- if (!dragged(d)) {
- // consider this the same as a 'click' (selection of node)
- selectCb(d, this); // TODO: set 'this' context instead of param
- }
- d.fixed &= ~6;
+ if (d.dragStarted) {
+ d.dragStarted = false;
+ if (!dragged(d)) {
+ // consider this the same as a 'click' (selection of node)
+ selectCb(d, this); // TODO: set 'this' context instead of param
+ }
+ d.fixed &= ~6;
- // hook at the end of a drag gesture
- atDragEnd(d, this); // TODO: set 'this' context instead of param
+ // hook at the end of a drag gesture
+ atDragEnd(d, this); // TODO: set 'this' context instead of param
+ }
});
return drag;
diff --git a/web/gui/src/main/webapp/json/map/README.txt b/web/gui/src/main/webapp/json/map/README.txt
index 1f72bae..5cd2634 100644
--- a/web/gui/src/main/webapp/json/map/README.txt
+++ b/web/gui/src/main/webapp/json/map/README.txt
@@ -5,11 +5,57 @@
To generate continental US map:
-$ wget 'http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_1_states_provinces_lakes.zip'
-$ unzip ne_50m_admin_1_states_provinces_lakes.zip
-$ ogr2ogr -f GeoJSON -where "sr_adm0_a3 IN ('USA')" states.json ne_50m_admin_1_states_provinces_lakes.shp
+ $ wget 'http://www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_1_states_provinces_lakes.zip'
+ $ unzip ne_50m_admin_1_states_provinces_lakes.zip
+ $ ogr2ogr -f GeoJSON -where "sr_adm0_a3 IN ('USA')" states.json ne_50m_admin_1_states_provinces_lakes.shp
edit states.json to remove data for Hawaii and Alaska
-$ topojson states.json > topology.json
+ $ topojson states.json > topology.json
+
+
+The .shp file above is incomplete (USA and part of Candada.)
+So it may be that each region requires a bit of research to generate.
+Ideally a source for public domain shp files can be found that covers all geographic regions.
+
+
+For Canada:
+
+ # wget 'http://www12.statcan.gc.ca/census-recensement/2011/geo/bound-limit/files-fichiers/gpr_000b11a_e.zip'
+ # unzip gpr_000b11a_e.zip
+ # ogr2ogr -f "GeoJSON" -s_srs EPSG:21781 -t_srs EPSG:4326 canada.json gpr_000b11a_e.shp
+ # topojson --id-property CFSAUID -p name=PRNAME -p name canada.json > topology.json
+
+
+This produces a very large (5MB) file and draws very slowly in Chrome.
+So some additional processing is required to simplify the geometry. (It is not checked in.)
+
+Also, the specification of object structure within the geojson is unclear.
+In the US map the geojson structure is
+
+ json.objects.states
+
+but in the Canadian data it's
+
+ json.objects.canada
+
+
+Lastly, the projection that is used may be tailored to the region.
+The preferred projection for the US is "albers" and d3 provides a "albersUSA" which can be used to
+ project hawaii and alaska as well
+
+For Canada, apparantly a "Lambert" projection (called conicConformal in d3) is preferred
+
+see:
+ https://github.com/mbostock/d3/wiki/Geo-Projections
+ http://www.statcan.gc.ca/pub/92-195-x/2011001/other-autre/mapproj-projcarte/m-c-eng.htm
+
+
+Summary:
+- some additional work is required to fully generalize maps functionality.
+- it may be worthwhile for ON.LAB to provide the topo files for key regions since producing these
+ files is non-trivial
+
+
+
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index d87cd27..41fcd6b 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -128,6 +128,7 @@
L: cycleLabels,
P: togglePorts,
U: unpin,
+ R: resetZoomPan,
W: requestTraffic, // bag of selections
X: cancelTraffic,
@@ -168,6 +169,7 @@
// D3 selections
var svg,
+ zoomPanContainer,
bgImg,
topoG,
nodeG,
@@ -179,6 +181,9 @@
// the projection for the map background
var geoMapProjection;
+ // the zoom function
+ var zoom;
+
// ==============================
// For Debugging / Development
@@ -1358,6 +1363,33 @@
});
}
+ function zoomPan(scale, translate) {
+ zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
+ // keep the map lines constant width while zooming
+ bgImg.style("stroke-width", 2.0 / scale + "px");
+ }
+
+ function resetZoomPan() {
+ zoomPan(1, [0,0]);
+ zoom.scale(1).translate([0,0]);
+ }
+
+ function setupZoomPan() {
+ function zoomed() {
+ if (!d3.event.sourceEvent.metaKey) {
+ zoomPan(d3.event.scale, d3.event.translate);
+ }
+ }
+
+ zoom = d3.behavior.zoom()
+ .translate([0, 0])
+ .scale(1)
+ .scaleExtent([1, 8])
+ .on("zoom", zoomed);
+
+ svg.call(zoom);
+ }
+
// ==============================
// Test harness code
@@ -1438,11 +1470,15 @@
svg = view.$div.append('svg').attr('viewBox', viewBox);
setSize(svg, view);
+ zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
+
+ setupZoomPan();
+
// add blue glow filter to svg layer
- d3u.appendGlow(svg);
+ d3u.appendGlow(zoomPanContainer);
// group for the topology
- topoG = svg.append('g')
+ topoG = zoomPanContainer.append('g')
.attr('id', 'topo-G')
.attr('transform', fcfg.translate());
@@ -1501,7 +1537,7 @@
.linkStrength(lstrg)
.on('tick', tick);
- network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
+ network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd, true); // true=require meta
// create mask layer for when we lose connection to server.
mask = view.$div.append('div').attr('id','topo-mask');
@@ -1593,15 +1629,15 @@
// [[x1,y1],[x2,y2]]
var b = path.bounds(topoData);
- // TODO: why 1.75?
- var s = 1.75 / Math.max((b[1][0] - b[0][0]) / config.logicalSize, (b[1][1] - b[0][1]) / config.logicalSize);
+ // size map to 95% of minimum dimension to fill space
+ var s = .95 / Math.min((b[1][0] - b[0][0]) / config.logicalSize, (b[1][1] - b[0][1]) / config.logicalSize);
var t = [(config.logicalSize - s * (b[1][0] + b[0][0])) / 2, (config.logicalSize - s * (b[1][1] + b[0][1])) / 2];
geoMapProjection
.scale(s)
.translate(t);
- bgImg = svg.insert("g", '#topo-G');
+ bgImg = zoomPanContainer.insert("g", '#topo-G');
bgImg.attr('id', 'map').selectAll('path')
.data(topoData.features)
.enter()