|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Demonstration fo ArcGeometries 2</title> |
|
<meta charset="utf-8" /> |
|
<script src="http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.js"></script> |
|
<script src="http://d3js.org/d3.v3.min.js"></script> |
|
<script src="http://d3js.org/topojson.v1.min.js"></script> |
|
<script src="http://mourner.github.io/simplify-js/simplify.js"></script> |
|
|
|
<script src="http://bl.ocks.org/milkbread/raw/5779939/RKMapping_0.4.4.js"></script> <!--http://bl.ocks.org/milkbread/raw/5779939--> |
|
<script src="http://bl.ocks.org/milkbread/raw/5829814/RKAggregation_1.0.js"></script> <!--http://bl.ocks.org/milkbread/raw/5829814 this function needs RKMapping.js additionally--> |
|
<script src="http://bl.ocks.org/milkbread/raw/5917907/slider.js"></script> |
|
<script src="http://bl.ocks.org/milkbread/raw/5947143/crossfilter.min.js"></script> |
|
|
|
<style> |
|
@import url(http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.css); |
|
@import url(http://bl.ocks.org/milkbread/raw/5743667/RKToolbox_0.1.css); |
|
.axis text { |
|
font-family: sans-serif; |
|
font-size: 16px; |
|
text-anchor:left; |
|
fill:#0f0; |
|
opacity:0.8; |
|
} |
|
#arcs{ |
|
overflow-x: auto; |
|
overflow-y: auto; |
|
padding-bottom: 15px; |
|
max-width: 100%; |
|
} |
|
#info { |
|
background-color:#c8ba09; |
|
} |
|
#overlay{ |
|
fill:None; |
|
stroke-width:1.5px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="slider"style="width: 960px; height: 80px"></div> |
|
<div id="info"style="width: 960px;"></div> |
|
<div id="map" style="width: 960px; height: 300px"></div> |
|
<div id="arcs" style="width: 960px; height: 80px; background-color:#c8ba09"></div> |
|
<script> |
|
var strokeColors = ['#b30032','#070'] |
|
|
|
//Sorted arcs |
|
var svg = d3.select("#arcs").append("svg") |
|
.attr("height",60); |
|
var svgGroup = svg.append('g'); |
|
|
|
var path = d3.geo.path().projection(normalize_projection); |
|
var arcBounds, sWidth = 50, sOffset = 10; |
|
var scaleLat = d3.scale.linear(); |
|
var scaleLng = d3.scale.linear(); |
|
|
|
//Slider |
|
var slider = new slider(d3.select("#slider"),960,75); |
|
slider.reDefineIndicator(6); |
|
|
|
//Leaflet Map |
|
var map = L.map('map').setView([51, 11], 5); |
|
var toolserver = L.tileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png'); |
|
var stamen = L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png', {attribution: 'Add some attributes here!'}).addTo(map); |
|
var baseLayers = {"stamen": stamen, "toolserver-mapnik":toolserver}; |
|
var control = L.control.layers(baseLayers).addTo(map); |
|
|
|
//Map overlay |
|
var svgContainer= d3.select(map.getPanes().overlayPane).append("svg"); |
|
var group= svgContainer.append("g").attr("class", "leaflet-zoom-hide"); |
|
var path2 = d3.geo.path().projection(project); |
|
|
|
var selMaxVal = -1, counter; |
|
|
|
//show the user some infos |
|
var countInfo = d3.select('#info').append('text').text('Number of Arcs: ') |
|
|
|
//1. Open data |
|
d3.json("vg250_states_topo.json", function(error, topology) { |
|
|
|
//define a click-action-listener of the slider |
|
slider.addHoverListener(reset0); |
|
slider.addClickListener(reset); |
|
|
|
//set the featureStorage and the featureObjects |
|
var pure_features = topology.objects.vg250_bld; |
|
var featureStorage = new featureObjStorage(); |
|
pure_features.geometries.forEach(function(d){ featureStorage.addFeatObj(d); }) |
|
var featuresObjects = featureStorage.featureObjects; |
|
|
|
//define bounds of overlay |
|
var overlayBounds = d3.geo.bounds(topojson.feature(topology, pure_features)); |
|
|
|
//2. get the ARCS from the featureObjects |
|
var arcCollection = getArcs(topology, featuresObjects); |
|
|
|
//we need an array of objects not an object, so loop all arcs ... simplify them AND push them to an array |
|
var arcCollectionArray_ = [], maxLength = 0; |
|
for (arc in arcCollection){ |
|
var cache = arcCollection[arc]; |
|
//simplify LineStrings of the arcs |
|
cache.coordinates = pointsConversion(simplify(pointsConversion(cache.coordinates, 'toObject'),0.01,false), 'toArray') |
|
arcCollectionArray_.push(cache) |
|
//directly get the maximum value (needed for the slider) |
|
if(maxLength<cache.length)maxLength=cache.length; |
|
} |
|
//now we can define the scalebar of the 'slider', because we know the maximum length |
|
slider.addScalebar([0,maxLength+100000]); |
|
|
|
//set up a new 'Crossfilter' and get the length-dimension |
|
var arcCollectionCF = crossfilter(arcCollectionArray_); |
|
var lengthDimension = arcCollectionCF.dimension(function(d){return d.length;}) |
|
|
|
map.on("viewreset", reset); |
|
|
|
reset(); |
|
|
|
function reset0(){ |
|
//1st time: set to O, else set to slider-value |
|
if(selMaxVal!=-1)selMaxVal = slider.getIndicPosition(); |
|
else selMaxVal = 0; |
|
|
|
//Filtering |
|
lengthDimension.filterAll(); //reset filter |
|
var origCount = arcCollectionCF.groupAll().reduceCount().value(); //count elements initially |
|
lengthDimension.filter(function(d){return (d < selMaxVal)}) //filter the dataset in relation to the slider-value |
|
counter = arcCollectionCF.groupAll().reduceCount().value(); //count elements after filtering |
|
countInfo.text('Number of Arcs: ' + counter +" (Max: "+ origCount +" | Sorting: DESC)") //give infos to user |
|
} |
|
|
|
function reset() { |
|
|
|
var bottomLeft = project(overlayBounds[0]), |
|
topRight = project(overlayBounds[1]); |
|
|
|
svgContainer.attr("width", topRight[0] - bottomLeft[0]) |
|
.attr("height", bottomLeft[1] - topRight[1]) |
|
.style("margin-left", bottomLeft[0] + "px") |
|
.style("margin-top", topRight[1] + "px"); |
|
|
|
group.attr("transform", "translate(" + -bottomLeft[0] + "," + -topRight[1] + ")"); |
|
|
|
//get all filtered elements of the dataset |
|
var arcCollectionArray = lengthDimension.top(counter); |
|
|
|
//1. JOIN |
|
var feature = group.selectAll("path").data(arcCollectionArray, function(d){return d.coordinates}) |
|
|
|
//2. UPDATE |
|
feature.attr("d", setPath).transition().duration(2000) |
|
.attr("stroke",strokeColors[1]).attr('opacity',1); |
|
|
|
//3. ENTER |
|
feature.enter() |
|
.append("path") |
|
.attr("id","overlay") |
|
.attr('stroke',strokeColors[0]) |
|
.attr("transform","translate(0,0) scale(-2,-2)") |
|
.attr("d", setPath) |
|
.transition().duration(2000) |
|
.attr("transform","translate(0,0) scale(1,1)"); |
|
|
|
//5. EXIT |
|
feature.exit() |
|
.attr('stroke',strokeColors[0]) |
|
.transition().duration(2000) |
|
.attr("transform","translate(-500,0) scale(-1,1)") |
|
.remove(); |
|
|
|
function setPath(d){return path2({type: 'LineString', coordinates: d.coordinates})} |
|
|
|
showSortedArcs(arcCollectionArray); |
|
|
|
} |
|
|
|
function showSortedArcs(arcCollectionArray__){ |
|
//re-define the size of the svg-container ... depending on number of data-elements |
|
svg.attr("width", sWidth*counter) |
|
|
|
//1. JOIN ... bind data to the corresponding svg-elements |
|
var arcVis = svgGroup.selectAll('path').data(arcCollectionArray__, function(d) { return d.coordinates; }); |
|
|
|
//2. UPDATE |
|
arcVis.transition().duration(2000) |
|
.attr('stroke',strokeColors[1]) |
|
.attr("transform",function(d,i){return "translate("+(i+1)*sWidth+",0) scale(-1,1)"}); |
|
|
|
//3. ENTER |
|
arcVis.enter().append("path") |
|
.on('mousedown',goToArc) |
|
.attr("d",setPath) //see function... |
|
.attr('fill','None') |
|
.attr('stroke',strokeColors[0]) |
|
.attr('stroke-width',2) |
|
.attr("transform",function(d,i){return "translate(0,0) scale(-1,1)"}) |
|
.transition().duration(2000) |
|
.attr("transform",function(d,i){return "translate("+(i+1)*sWidth+",0) scale(-1,1)"}); |
|
|
|
//5. EXIT |
|
arcVis.exit() |
|
.attr('stroke',strokeColors[0]) |
|
.transition().duration(2000) |
|
.attr("transform",function(d,i){return "translate(0,0) scale(-1,1)"}) |
|
.remove(); |
|
|
|
function setPath(d,i){ |
|
var geometry = d; |
|
arcBounds=d3.geo.bounds(d); //save arcBounds globally |
|
var new_path = path({type: 'LineString', coordinates: geometry.coordinates}) |
|
return new_path; |
|
} |
|
|
|
function goToArc(d){ |
|
var local_bounds = d3.geo.bounds(d), |
|
center = new L.LatLng(local_bounds[0][1]+((local_bounds[1][1]-local_bounds[0][1])/2),local_bounds[0][0]+((local_bounds[1][0]-local_bounds[0][0])/2)), |
|
southWest = new L.LatLng(local_bounds[0][1], local_bounds[0][0]), |
|
northEast = new L.LatLng(local_bounds[1][1], local_bounds[1][0]); |
|
local_bounds = new L.LatLngBounds(southWest, northEast); |
|
map.setView( center, map.getBoundsZoom( local_bounds)); |
|
countInfo.text('This Arcs has a length of: ' + d.length + ' and has '+ d.members.length + ' members!') |
|
|
|
} |
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
function normalize_projection(x) { |
|
scaleLat.domain([arcBounds[0][0],arcBounds[1][0]]) |
|
.range([sWidth+sOffset,sOffset]); |
|
scaleLng.domain([arcBounds[0][1],arcBounds[1][1]]) |
|
.range([sWidth+sOffset,sOffset]); |
|
|
|
return [scaleLat(x[0]), scaleLng(x[1])] |
|
} |
|
function pointsConversion(points, direction){ |
|
var cache; |
|
if(direction=='toObject'){ |
|
cache = points.map(function(point){return {'y':point[0],'x':point[1]}}); |
|
} |
|
else if(direction=='toArray'){ |
|
cache = points.map(function(point){return [point.y,point.x]}); |
|
} |
|
return cache; |
|
} |
|
|
|
|
|
|
|
function project(point) { |
|
var latlng = new L.LatLng(point[1], point[0]); |
|
var layerPoint = map.latLngToLayerPoint(latlng); |
|
return [layerPoint.x, layerPoint.y]; |
|
} |
|
|
|
|
|
function pointsConversion(points, direction){ |
|
var cache; |
|
if(direction=='toObject'){ |
|
cache = points.map(function(point){return {'y':point[0],'x':point[1]}}); |
|
} |
|
else if(direction=='toArray'){ |
|
cache = points.map(function(point){return [point.y,point.x]}); |
|
} |
|
return cache; |
|
} |
|
</script> |
|
</body> |
|
</html> |