// browser check for zoom var isOpera = !!(window.opera && window.opera.version); // Opera 8.0+ var isFirefox = testCSS('MozBoxSizing'); // FF 0.8+ var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; // At least Safari 3+: "[object HTMLElementConstructor]" var isChrome = !isSafari && testCSS('WebkitTransform'); // Chrome 1+ var isIE = /*@cc_on!@*/false || testCSS('msTransform'); // At least IE6 //console.log("isChrome= ",isChrome,", isFirefox= ",isFirefox, ", isSafari= ",isSafari, ", isIE= ", isIE,", isOpera= ", isOpera); var selectedData=[]; var circles; var margin = {top: 5, right: 20, bottom: 20, left: 20}, width = 1280 - margin.left - margin.right, height = 517 - margin.top - margin.bottom; var projection = d3.geo.equirectangular() .scale(170) .translate([width / 2, height / 2]) .precision(.1); /*var projection = d3.geo.azimuthalEquidistant() .scale(150) .translate([width / 2, height / 2]) .clipAngle(180 - 1e-3) .precision(.1);*/ var path = d3.geo.path() .projection(projection); var zoom = d3.behavior.zoom() .translate(projection.translate()) .scale(projection.scale()) //.scaleExtent([height, 8 * height]) .on("zoom", move); var color = d3.scale.ordinal() // .range(["#999353","#17AACC"]); .range(["#E88D0C","#FFEC09"]); var tooltipdiv = d3.select("body") .append("div") .attr("class", "tooltip"); var svg = d3.select("#map_background").append("svg") .attr("width", width+ margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .style("display","block") .style("margin","auto") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .call(zoom); var mapSvg = svg.append("g") .attr("id", "map"); mapSvg.append("rect") .attr("class", "background") //.attr("width", width + margin.left + margin.right) //.attr("height", height + margin.top + margin.bottom); var circlesSvg = svg.append("g") .attr("id","circles"); var margin1 = {top: 1, right: 30, bottom: 20, left: 30}, width1 = 1260 - margin1.left - margin1.right, height1 = 150 - margin1.top - margin1.bottom; var charts = d3.select("#charts").append("svg") .attr("width", width1 + margin1.left + margin1.right) .attr("height", height1 + margin1.top + margin1.bottom) .append("g") .attr("transform", "translate(" + margin1.left + "," + margin1.top + ")"); d3.json("world-countries.json", function(json) { mapSvg.selectAll("path") .data(json.features) .enter().append("path") .attr("d", path) d3.csv("meteorites_new1.csv", function(error, data) { var data1400=[]; console.log("--data.length=",data.length); data.forEach(function(d){ if (+d.year >= 1400) data1400.push(d); }); visualize(data1400); }); }); //zoom in buttons var zoomin = d3.select("body") .append("div") .attr("class","zoom") .style("top", "300px") .style("left","30px") .style("padding-top","2px") .text("+") .on("click", function(){ d3.select("#map_background svg").attr("width","100%"); fireZoomEvent( -0.2); }); var zoomout = d3.select("body") .append("div") .attr("class","zoom") .style("top", "335px") .style("left","30px") .style("padding-top","1px") .text("-") .on("click", function(){ fireZoomEvent(0.2); }); //help text /*d3.select("#charts").append("div") .attr("class","help") .text('Click and drag to select a period, click to deselect') .style("top","10px") .style("right","10px") .style("color","#FFF") .style("position","relative")*/ function visualize(data){ function rScale(value){ if (value < 100) return 3; else if (value < 500) return 5; else if (value < 1000) return 8; else if (value < 5000) return 12; else if (value < 10000) return 16; else return 20; } drawAllCircles(data); svg.append("text") .style("fill","white") .text("start") .attr("x", "20px") .attr("y", "20px") .attr("id","button") .on("click", reset); svg.append("text") .style("fill","white") .text("stop") .attr("x", "70px") .attr("y", "20px") .attr("id","button") .on("click", stop); var meteorites = crossfilter(data); meteorites.fell = meteorites.dimension(function(d){return d.fell}); meteorites.year = meteorites.dimension(function(d){return +d.year;}); var yearCount = meteorites.year.group().top(Infinity); meteorites.type = meteorites.dimension(function(d){return d.recclass;}); var typeCount = meteorites.type.group().top(10); typeCount.sort(function(a, b){ return a.key-b.key }) // menu area ------ d3.select("#menu").append('div') .attr("class","help") .text("Move your mouse over the circles for more information, click on them to go to the database record (external page)"); d3.select("#menu").append("h4") .text("Select") var found_fellMenu = d3.select("#menu").append('div') .attr("class","found_fellMenu"); found_fellMenu.append("div") //menu .attr("class","help") .text("Finds and Falls:"); var found_fellList = [{name:'Finds',id:'found'},{name:'Falls',id:'fell'},{name:'All',id:'all'}]; found_fellMenu.selectAll('#menuItem') .data(found_fellList) .enter() .append("div") .attr("id","menuItem") .attr("class",function(d){ if (d.id ==='all')return 'active last'; }) .html(function(d){return d.name;}) .on('click', function(d){ d3.selectAll('.found_fellMenu #menuItem').classed('active',false); switch (d.id){ case 'found': d3.select(this).classed('active',true); selectedData = meteorites.fell.filter('Found').top(Infinity); circles.remove(); drawAllCircles(selectedData); break; case 'fell': d3.select(this).classed('active',true); selectedData = meteorites.fell.filter('Fell').top(Infinity); circles.remove(); drawAllCircles(selectedData); break; case 'all': d3.select(this).classed('active',true); selectedData = meteorites.fell.filterAll().top(Infinity); circles.remove(); drawAllCircles(selectedData); break; } }); var typeMenu = d3.select("#menu").append('div') .attr("class","typeMenu"); typeMenu.append("div") //menu .attr("class","help") .text("According to type:"); var type_List = [{name:'Stony',id:'stony'},{name:'Iron',id:'iron'},{name:'Stony-iron',id:'stony-iron'},{name:'All',id:'all'}]; typeMenu.selectAll('#menuItem') .data(type_List) .enter() .append("div") .attr("id","menuItem") .attr("class",function(d){ if (d.id ==='all')return 'active last'; }) .html(function(d){return d.name;}) .on('click', function(d){ d3.selectAll('.typeMenu #menuItem').classed('active',false); switch (d.id){ case 'stony': d3.select(this).classed('active',true); selectedData = meteorites.type.filter(function(d1){return d1.indexOf("Mesosiderite") < 0 && d1.indexOf("Pallasite") < 0 && d1.charAt(0) !="I";}).top(Infinity); circles.remove(); drawAllCircles(selectedData); break; case 'iron': d3.select(this).classed('active',true); selectedData = meteorites.type.filter(function(d1){return d1.charAt(0) =="I";}).top(Infinity); circles.remove(); drawAllCircles(selectedData); break; case 'stony-iron': d3.select(this).classed('active',true); selectedData = meteorites.type.filter(function(d1){return d1.indexOf("Mesosiderite") >=0 || d1.indexOf("Pallasite") >=0;}).top(Infinity); circles.remove(); drawAllCircles(selectedData); break; case 'all': d3.select(this).classed('active',true); selectedData = meteorites.type.filterAll().top(Infinity); circles.remove(); drawAllCircles(selectedData); break; } }); // found - fell var lunarMenu = d3.select("#menu").append('div') .attr("class","lunarMenu"); lunarMenu.append("div") //menu .attr("class","help") .text("Meteorites coming from the Moon and Mars:") var lunarList = [{name:'Lunar',id:'lunar'},{name:'Martian',id:'martian'},{name:'All',id:'all'}]; lunarMenu.selectAll('#menuItem') .data(lunarList) .enter() .append("div") .attr("id","menuItem") .attr("class",function(d){ if (d.id ==='all')return 'active last'; }) .html(function(d){return d.name;}) .on('click', function(d){ d3.selectAll('.lunarMenu #menuItem').classed('active',false); switch (d.id){ case 'lunar': d3.select(this).classed('active',true); selectedData = meteorites.type.filter(function(d1){return d1.indexOf("Lunar") >= 0;}).top(Infinity); circles.remove(); drawAllCircles(selectedData); break; case 'martian': d3.select(this).classed('active',true); selectedData = meteorites.type.filter(function(d1){return d1.indexOf("Martian") >= 0;}).top(Infinity); circles.remove(); drawAllCircles(selectedData); break; case 'all': d3.select(this).classed('active',true); selectedData = meteorites.type.filterAll().top(Infinity); circles.remove(); drawAllCircles(selectedData); break; } }); // end menu area ------ //console.log("data.length=",data.length); var xMin = d3.min(yearCount, function(d){return d.key;}); var xMax = d3.max(yearCount, function(d){return d.key;}); var yMax = d3.max(yearCount, function(d){return d.value;}); //var x = d3.scale.linear() var x= d3.scale.pow() .exponent(6) .domain([xMin,xMax]) .range([0, width1]); var y = d3.scale.linear() .domain([0, yMax]) .range([height1, 0]); var xAxis = d3.svg.axis() .scale(x) .tickSize(3) .tickFormat(d3.format("")) .tickValues([1400,1500,1600,1700,1800,1850,1900,1950,2000,2013]) .orient("bottom"); var chartBrush = d3.svg.brush() .x(x) //.on("brushstart", brushstart) .on("brush", brushmove) .on("brushend", brushend); charts.append("g") .attr("class", "brush") .call(chartBrush) .selectAll("rect") .attr("height", height1+2) .attr("transform", "translate( 0,-1)"); var bar = charts.selectAll("#bar") .data(yearCount) .enter().append("g") .each(function(d){ d._fellCount = $.grep(data, function(e){ return e.fell == "Fell" && e.year == d.key; }).length; d._foundCount = d.value - d._fellCount; }) .attr("id", "bar") .attr("transform", function(d) { return "translate(" + x(d.key) + ",0)"; }) .attr("class","year") .on("mouseover", function(d){ var textTooltip = ""+d.key+'
Falls: '+d._fellCount+'
Finds: ' +d._foundCount; tooltipdiv.html(textTooltip) .style("top", d3.event.pageY - 20 + "px") .style("left", d3.event.pageX + 20 + "px") .style("visibility", "visible"); }) .on("mouseout", function(){tooltipdiv.style("visibility", "hidden"); }); bar.append("rect") //found .attr("width", 3) .attr("y", function(d) { return y(d._foundCount); }) .attr("height", function(d) { return y(0) - y(d._foundCount); }) .style("fill", "#E88D0C") bar.append("rect") //fell .attr("width", 3) .attr("y", function(d) {return y(d.fell); }) .attr("height", function(d) { return y(0)-y(d._fellCount); }) .style("fill", "#FFEC09") charts.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height1 + ")") .call(xAxis) .selectAll("text") .style("text-anchor", "middle") charts.append("line") .attr("x1", 0) .attr("y1", -0.5) .attr("x2", width1) .attr("y2", -0.5) .attr("id","chartAxis") charts.append("line") .attr("x1", 0) .attr("y1", height1+1) .attr("x2", width1) .attr("y2", height1+1) .attr("id","chartAxis") //charts.call(chartBrush); charts.append("line") .attr("x1", -2) .attr("y1", -2) .attr("x2", -2) .attr("y2", 132) .attr("id","movingLine") .style("stroke", "#FFF") .style("stroke-width","2px") .style("position","absolute"); charts.append("text") .text("Number of Falls") .attr("x", "0px") .attr("y", "20px") .attr("class","label") .style("fill","#FFF") charts.append("text") .text("Number of Finds") .attr("x", "0px") .attr("y", "120px") .attr("class","label") .style("fill","#FFF") var yearPanel = svg.append("text") .attr("class","yearText") .text('2013') .attr('y',50) .attr('x',500); var year = 1400, yearEnd = 2013, animate; start(); function start() { if (year === 1400) circles.style("visibility","hidden"); var xValue = x(year) yearPanel.text(year); charts.select("#movingLine") .attr("x1", xValue) .attr("x2", xValue); //console.log(year); if (year < yearEnd) animate = setTimeout(start, 100); else { d3.select(".background").style("opacity",1); d3.select("#map") .transition() .duration(2000) .style("opacity",1); //circles.style("stroke-width","0.5").style("stroke","#FFFFFF"); d3.select('#menu').transition().duration(2000).style("opacity",0.9); } var yearCircles = circles.filter(function(d){ if (year < 1800) return +d.year > year-5 && +d.year <= year ; else return +d.year === year }); yearCircles.filter(function(d){ return d.fell === "Fell" }) .attr("cx",function(d) { return projection([d.longitude,d.latitude])[0] - (Math.floor(Math.random() * 801) - 400);}) .attr("cy",function(d) { return projection([d.longitude,d.latitude])[1] - (Math.floor(Math.random() * 801) - 400);}) .attr("r", 1) .style("opacity",0) .style("visibility","visible") .transition() .duration(1000) .attr("cx",function(d) { return projection([d.longitude,d.latitude])[0];}) .attr("cy",function(d) { return projection([d.longitude,d.latitude])[1];}) .attr("r", function(d) { return rScale(d.mass);}) .style("opacity",0.8) yearCircles.filter(function(d){ return d.fell === "Found" }) .attr("r", 1) .style("visibility","visible") .transition() .duration(2000) .attr("r", function(d) { return rScale(d.mass);}) if (year < 1800) year += 5; else year += 1; } function stop(){ clearTimeout(animate) circles.style("visibility","visible"); year = yearEnd; var xValue = x(year) charts.select("#movingLine") .attr("x1", xValue) .attr("x2", xValue); yearPanel.text('2013'); d3.select(".background").style("opacity",1); d3.select("#map") .transition() .duration(2000) .style("opacity",1); d3.select('#menu').transition().duration(2000).style("opacity",0.9); } function reset(){ year = 1400; start(); } function brushstart() { //charts.classed("selecting", true); } function brushmove() { } function brushend() { if (chartBrush.empty()){ selectedData = meteorites.year.filterAll().top(Infinity); yearPanel.text('2013'); } else { var extent = d3.event.target.extent(); //console.log("in brushend = ", extent); bar.classed("selected", function(d) {return extent[0] <= +d.key && +d.key <= extent[1]; }); //update crossfilter selectedData = meteorites.year.filterRange([extent[0],extent[1]]).top(Infinity); yearPanel.text(Math.floor(extent[0]) +' - '+ Math.floor(extent[1])); } circles.remove(); drawAllCircles(selectedData); } function drawAllCircles(data){ circles = circlesSvg.selectAll("circle") .data(data) .enter().append("svg:a") .attr("xlink:href", function(d){return "http://www.lpi.usra.edu/meteor/metbull.php?code="+d.id;}) .attr("target","_blank") .append("svg:circle") .attr("cx",function(d) { return projection([d.longitude,d.latitude])[0];}) .attr("cy",function(d) { return projection([d.longitude,d.latitude])[1];}) .attr("opacity",0.8) .attr("r", function(d) { return rScale(d.mass);}) .style("fill", function(d) { return color(d.fell); }) .style("stroke-width",function(d){if (d.history) return 1;}) .on("mouseover", function(d){ var textTooltip = ''+d.name+'
type: '+d.recclass+'
mass: '+(d.mass)+' kg
'+d.fell+'
year: '+d.year; if (d.history) textTooltip = textTooltip + '
History
'+ d.history +'
'; tooltipdiv.html(textTooltip) .style("top", d3.event.pageY - 20 + "px") .style("left", d3.event.pageX + 20 + "px") .style("visibility", "visible"); }) .on("mouseout", function(){tooltipdiv.style("visibility", "hidden"); }) /*.on("click", function(){ tooltipdiv.append("div") .append("div") .attr('class',"closeButton") .append('img') .attr('src',"images/close.png") .on("click",function(){tooltipdiv.style("visibility", "hidden"); }) })*/ } } function move() { projection.translate(d3.event.translate).scale(d3.event.scale); mapSvg.selectAll("path").attr("d", path); circles .attr("cx",function(d) {return projection([d.longitude,d.latitude])[0];}) .attr("cy",function(d) {return projection([d.longitude,d.latitude])[1];}) } function fireZoomEvent(zoomVal) { //console.log('in fireZoomEvent'); if (isOpera){ var evt = document.createEvent('MouseEvent'); evt.initMouseEvent('mousewheel', true, true, window, (zoomVal) * 40, width/2, height/2, width/2, height/2, 0, 0, 0, 0, 0, document.body); } else if (isChrome || isSafari) { var evt = document.createEvent("WheelEvent"); evt.initWebKitWheelEvent(0, (-zoomVal) * 20, window, width/2, height/2, width/2, height/2,false, false, false, false); } else if (isIE) { var evt = document.createEvent ("MouseWheelEvent"); evt.initMouseWheelEvent('mousewheel', true, true, window, 0, width/2, height/2, width/2, height/2, 0, document.body, '', (zoomVal) * -900); } else if (isFirefox){ var evt = document.createEvent ("MouseEvents"); evt.initMouseEvent('DOMMouseScroll', true, true, window, (zoomVal) * 250, width/2, height/2, width/2, height/2, 0, 0, 0, 0, 0, document.body); } document.getElementById('map').dispatchEvent(evt); } function testCSS(prop) { return prop in document.documentElement.style; }