Paul Greyson
Committed by Gerrit Code Review

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
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
23 (function (onos) { 23 (function (onos) {
24 'use strict'; 24 'use strict';
25 25
26 - function createDragBehavior(force, selectCb, atDragEnd) { 26 + function createDragBehavior(force, selectCb, atDragEnd, requireMeta) {
27 var draggedThreshold = d3.scale.linear() 27 var draggedThreshold = d3.scale.linear()
28 .domain([0, 0.1]) 28 .domain([0, 0.1])
29 .range([5, 20]) 29 .range([5, 20])
...@@ -51,29 +51,39 @@ ...@@ -51,29 +51,39 @@
51 drag = d3.behavior.drag() 51 drag = d3.behavior.drag()
52 .origin(function(d) { return d; }) 52 .origin(function(d) { return d; })
53 .on('dragstart', function(d) { 53 .on('dragstart', function(d) {
54 - d.oldX = d.x; 54 + if (requireMeta ^ !d3.event.sourceEvent.metaKey) {
55 - d.oldY = d.y; 55 + d3.event.sourceEvent.stopPropagation();
56 - d.dragged = false; 56 +
57 - d.fixed |= 2; 57 + d.oldX = d.x;
58 + d.oldY = d.y;
59 + d.dragged = false;
60 + d.fixed |= 2;
61 + d.dragStarted = true;
62 + }
58 }) 63 })
59 .on('drag', function(d) { 64 .on('drag', function(d) {
60 - d.px = d3.event.x; 65 + if (requireMeta ^ !d3.event.sourceEvent.metaKey) {
61 - d.py = d3.event.y; 66 + d.px = d3.event.x;
62 - if (dragged(d)) { 67 + d.py = d3.event.y;
63 - if (!force.alpha()) { 68 + if (dragged(d)) {
64 - force.alpha(.025); 69 + if (!force.alpha()) {
70 + force.alpha(.025);
71 + }
65 } 72 }
66 } 73 }
67 }) 74 })
68 .on('dragend', function(d) { 75 .on('dragend', function(d) {
69 - if (!dragged(d)) { 76 + if (d.dragStarted) {
70 - // consider this the same as a 'click' (selection of node) 77 + d.dragStarted = false;
71 - selectCb(d, this); // TODO: set 'this' context instead of param 78 + if (!dragged(d)) {
72 - } 79 + // consider this the same as a 'click' (selection of node)
73 - d.fixed &= ~6; 80 + selectCb(d, this); // TODO: set 'this' context instead of param
81 + }
82 + d.fixed &= ~6;
74 83
75 - // hook at the end of a drag gesture 84 + // hook at the end of a drag gesture
76 - atDragEnd(d, this); // TODO: set 'this' context instead of param 85 + atDragEnd(d, this); // TODO: set 'this' context instead of param
86 + }
77 }); 87 });
78 88
79 return drag; 89 return drag;
......
...@@ -5,11 +5,57 @@ npm install -g topojson ...@@ -5,11 +5,57 @@ npm install -g topojson
5 5
6 To generate continental US map: 6 To generate continental US map:
7 7
8 -$ wget 'http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_1_states_provinces_lakes.zip' 8 + $ wget 'http://www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_1_states_provinces_lakes.zip'
9 -$ unzip ne_50m_admin_1_states_provinces_lakes.zip 9 + $ unzip ne_50m_admin_1_states_provinces_lakes.zip
10 -$ ogr2ogr -f GeoJSON -where "sr_adm0_a3 IN ('USA')" states.json ne_50m_admin_1_states_provinces_lakes.shp 10 + $ ogr2ogr -f GeoJSON -where "sr_adm0_a3 IN ('USA')" states.json ne_50m_admin_1_states_provinces_lakes.shp
11 11
12 edit states.json to remove data for Hawaii and Alaska 12 edit states.json to remove data for Hawaii and Alaska
13 13
14 -$ topojson states.json > topology.json 14 + $ topojson states.json > topology.json
15 +
16 +
17 +The .shp file above is incomplete (USA and part of Candada.)
18 +So it may be that each region requires a bit of research to generate.
19 +Ideally a source for public domain shp files can be found that covers all geographic regions.
20 +
21 +
22 +For Canada:
23 +
24 + # wget 'http://www12.statcan.gc.ca/census-recensement/2011/geo/bound-limit/files-fichiers/gpr_000b11a_e.zip'
25 + # unzip gpr_000b11a_e.zip
26 + # ogr2ogr -f "GeoJSON" -s_srs EPSG:21781 -t_srs EPSG:4326 canada.json gpr_000b11a_e.shp
27 + # topojson --id-property CFSAUID -p name=PRNAME -p name canada.json > topology.json
28 +
29 +
30 +This produces a very large (5MB) file and draws very slowly in Chrome.
31 +So some additional processing is required to simplify the geometry. (It is not checked in.)
32 +
33 +Also, the specification of object structure within the geojson is unclear.
34 +In the US map the geojson structure is
35 +
36 + json.objects.states
37 +
38 +but in the Canadian data it's
39 +
40 + json.objects.canada
41 +
42 +
43 +Lastly, the projection that is used may be tailored to the region.
44 +The preferred projection for the US is "albers" and d3 provides a "albersUSA" which can be used to
45 + project hawaii and alaska as well
46 +
47 +For Canada, apparantly a "Lambert" projection (called conicConformal in d3) is preferred
48 +
49 +see:
50 + https://github.com/mbostock/d3/wiki/Geo-Projections
51 + http://www.statcan.gc.ca/pub/92-195-x/2011001/other-autre/mapproj-projcarte/m-c-eng.htm
52 +
53 +
54 +Summary:
55 +- some additional work is required to fully generalize maps functionality.
56 +- it may be worthwhile for ON.LAB to provide the topo files for key regions since producing these
57 + files is non-trivial
58 +
59 +
60 +
15 61
......
...@@ -128,6 +128,7 @@ ...@@ -128,6 +128,7 @@
128 L: cycleLabels, 128 L: cycleLabels,
129 P: togglePorts, 129 P: togglePorts,
130 U: unpin, 130 U: unpin,
131 + R: resetZoomPan,
131 132
132 W: requestTraffic, // bag of selections 133 W: requestTraffic, // bag of selections
133 X: cancelTraffic, 134 X: cancelTraffic,
...@@ -168,6 +169,7 @@ ...@@ -168,6 +169,7 @@
168 169
169 // D3 selections 170 // D3 selections
170 var svg, 171 var svg,
172 + zoomPanContainer,
171 bgImg, 173 bgImg,
172 topoG, 174 topoG,
173 nodeG, 175 nodeG,
...@@ -179,6 +181,9 @@ ...@@ -179,6 +181,9 @@
179 // the projection for the map background 181 // the projection for the map background
180 var geoMapProjection; 182 var geoMapProjection;
181 183
184 + // the zoom function
185 + var zoom;
186 +
182 // ============================== 187 // ==============================
183 // For Debugging / Development 188 // For Debugging / Development
184 189
...@@ -1358,6 +1363,33 @@ ...@@ -1358,6 +1363,33 @@
1358 }); 1363 });
1359 } 1364 }
1360 1365
1366 + function zoomPan(scale, translate) {
1367 + zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
1368 + // keep the map lines constant width while zooming
1369 + bgImg.style("stroke-width", 2.0 / scale + "px");
1370 + }
1371 +
1372 + function resetZoomPan() {
1373 + zoomPan(1, [0,0]);
1374 + zoom.scale(1).translate([0,0]);
1375 + }
1376 +
1377 + function setupZoomPan() {
1378 + function zoomed() {
1379 + if (!d3.event.sourceEvent.metaKey) {
1380 + zoomPan(d3.event.scale, d3.event.translate);
1381 + }
1382 + }
1383 +
1384 + zoom = d3.behavior.zoom()
1385 + .translate([0, 0])
1386 + .scale(1)
1387 + .scaleExtent([1, 8])
1388 + .on("zoom", zoomed);
1389 +
1390 + svg.call(zoom);
1391 + }
1392 +
1361 // ============================== 1393 // ==============================
1362 // Test harness code 1394 // Test harness code
1363 1395
...@@ -1438,11 +1470,15 @@ ...@@ -1438,11 +1470,15 @@
1438 svg = view.$div.append('svg').attr('viewBox', viewBox); 1470 svg = view.$div.append('svg').attr('viewBox', viewBox);
1439 setSize(svg, view); 1471 setSize(svg, view);
1440 1472
1473 + zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
1474 +
1475 + setupZoomPan();
1476 +
1441 // add blue glow filter to svg layer 1477 // add blue glow filter to svg layer
1442 - d3u.appendGlow(svg); 1478 + d3u.appendGlow(zoomPanContainer);
1443 1479
1444 // group for the topology 1480 // group for the topology
1445 - topoG = svg.append('g') 1481 + topoG = zoomPanContainer.append('g')
1446 .attr('id', 'topo-G') 1482 .attr('id', 'topo-G')
1447 .attr('transform', fcfg.translate()); 1483 .attr('transform', fcfg.translate());
1448 1484
...@@ -1501,7 +1537,7 @@ ...@@ -1501,7 +1537,7 @@
1501 .linkStrength(lstrg) 1537 .linkStrength(lstrg)
1502 .on('tick', tick); 1538 .on('tick', tick);
1503 1539
1504 - network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd); 1540 + network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd, true); // true=require meta
1505 1541
1506 // create mask layer for when we lose connection to server. 1542 // create mask layer for when we lose connection to server.
1507 mask = view.$div.append('div').attr('id','topo-mask'); 1543 mask = view.$div.append('div').attr('id','topo-mask');
...@@ -1593,15 +1629,15 @@ ...@@ -1593,15 +1629,15 @@
1593 1629
1594 // [[x1,y1],[x2,y2]] 1630 // [[x1,y1],[x2,y2]]
1595 var b = path.bounds(topoData); 1631 var b = path.bounds(topoData);
1596 - // TODO: why 1.75? 1632 + // size map to 95% of minimum dimension to fill space
1597 - var s = 1.75 / Math.max((b[1][0] - b[0][0]) / config.logicalSize, (b[1][1] - b[0][1]) / config.logicalSize); 1633 + var s = .95 / Math.min((b[1][0] - b[0][0]) / config.logicalSize, (b[1][1] - b[0][1]) / config.logicalSize);
1598 var t = [(config.logicalSize - s * (b[1][0] + b[0][0])) / 2, (config.logicalSize - s * (b[1][1] + b[0][1])) / 2]; 1634 var t = [(config.logicalSize - s * (b[1][0] + b[0][0])) / 2, (config.logicalSize - s * (b[1][1] + b[0][1])) / 2];
1599 1635
1600 geoMapProjection 1636 geoMapProjection
1601 .scale(s) 1637 .scale(s)
1602 .translate(t); 1638 .translate(t);
1603 1639
1604 - bgImg = svg.insert("g", '#topo-G'); 1640 + bgImg = zoomPanContainer.insert("g", '#topo-G');
1605 bgImg.attr('id', 'map').selectAll('path') 1641 bgImg.attr('id', 'map').selectAll('path')
1606 .data(topoData.features) 1642 .data(topoData.features)
1607 .enter() 1643 .enter()
......