D3 On click not working for circle - javascript

I am using a modified version of D3 + Google Maps, wherein, I'd like a modal to pop up when I click on an SVG circle.
Here's the JavaScript:
var now = new Date();
var hour = now.getHours();
// Create the Google Map…
var map = new google.maps.Map(d3.select("#map").node(), {
zoom: 16,
center: new google.maps.LatLng(19.134249, 72.913608),
mapTypeId: google.maps.MapTypeId.TERRAIN
});
function assign_dots() {
$("#hour").text(hour);
// Load the station data. When the data comes back, create an overlay.
d3.json("stations.json", function (error, data) {
var overlay = new google.maps.OverlayView();
if (error) throw error;
// Add the container when the overlay is added to the map.
overlay.onAdd = function () {
var layer = d3.select(this.getPanes().overlayMouseTarget).append("div")
.attr("class", "stations");
// Draw each marker as a separate SVG element.
// We could use a single SVG, but what size would it have?
overlay.draw = function () {
var projection = this.getProjection(),
padding = 10;
layer.selectAll("svg").remove();
var marker = layer.selectAll("svg")
.data(d3.entries(data))
.each(transform) // update existing markers
.enter().append("svg")
.each(transform)
.attr("class", "marker");
// Add a circle.
marker.append("circle")
.attr("r", function(d){
return d.value[3][hour]*6;
})
.attr("cx", padding)
.attr("cy", padding)
.on("click", toggleExpand);
// Add a label.
marker.append("text")
.attr("x", padding + 10)
.attr("y", padding)
.attr("dy", ".31em")
.text(function (d) {
if (d.value[3][hour])
return d.key;
});
function transform(d) {
d = new google.maps.LatLng(d.value[1], d.value[0]);
d = projection.fromLatLngToDivPixel(d);
return d3.select(this)
.style("left", (d.x - padding) + "px")
.style("top", (d.y - padding) + "px");
}
function toggleExpand(d) {
$('#myModal').modal('toggle');
console.log(d.value[2]);
console.log("Reaches here");
}
};
};
overlay.onRemove = function() {};
// Bind our overlay to the map
overlay.setMap(map);
});
}
window.onload = assign_dots();
function progress_time() {
hour = hour + 1;
if (hour == 24) hour = 0;
console.log(hour);
d3.selectAll("svg").remove();
assign_dots();
}
function regress_time() {
hour = hour - 1;
if (hour == -1) hour = 23;
console.log(hour);
d3.selectAll("svg").remove();
assign_dots();
}
Basically, in the assign_dots function, when I am appending a circle to the marker, it should set an onclick function to be toggleExpand, but when I am clicking on an svg circle, nothing happens.
What can I be missing?
Edit
Here's the stylesheet:
html, body, #map {
width: 100%;
height: 90%;
margin: 0;
padding: 10px;
}
#map {
border: solid;
}
.stations, .stations svg {
position: absolute;
}
.stations svg {
width: 60px;
height: 20px;
padding-right: 100px;
font: 10px sans-serif;
}
.stations circle {
fill: blue;
stroke: black;
stroke-width: 2px;
}
And I have also included bootstrap.
For some reason, when I remove the bootstrap css, the on click starts working. What could the problem be?

Related

relocate d3 map to center of screen no matter the device

I've got the following d3 map:
but as you can see it's all bunched up there in the left corner of the screen- this is sub opimal- ideally I would like it to be centered.
How can I achieve this?
I guess it should be easy but every time I google something like "center align d3 map" I get things about zooming
:/
maybe I need to create a div or something?
the code is here on my GitHub
also below- it's pretty well commented.
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style>
.border {
stroke: #000;
fill: none;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
div.tooltip {
position: absolute;
text-align: center;
width: 84px;
height: 64px;
padding: 2px;
font: 12px sans-serif;
background: lightgrey;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
</head>
<body>
<h1>Administrative Sub-Regions of Europe</h1>
<!-- this is the form at the bottom to change the NUTS -->
<form>
<select id="json_sources" name="json_sources" >
<option value ="nuts0" selected >Source 0</option>
<option value ="nuts1" >Source 1</option>
<option value ="nuts2" >Source 2</option>
<option value ="nuts3" >Source 3</option>
</select>
<form>
<!-- spinner -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.0.1/spin.min.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://d3js.org/colorbrewer.v1.min.js"></script>
<script src="https://cdn.rawgit.com/rveciana/d3-composite-projections/v0.2.0/composite-projections.min.js"></script>
<!-- why do we need this? -->
<section id='chart'>
</section>
<script>
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var width = 600,
height = 500;
var projection = d3.geo.conicConformalEurope();
var graticule = d3.geo.graticule();
var path = d3.geo.path()
.projection(projection);
// Find new colours here: http://colorbrewer2.org/
var scale = d3.scale
.quantize()
.domain([10,60])
.range(colorbrewer.PuRd[3]);
var svg = d3.select("body")
.append("svg")
.attr("width", width, "100%")
.attr("height", height, "100%")
.call(
d3.
behavior.
zoom().
on("zoom", function () {
svg.attr("transform", "translate(" +
d3.event.translate +
")" + " scale(" +
d3.event.scale +
")")
}
)
)
.on("dblclick.zoom", null)
.append("g")
//what the hell does this do?
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
// pretty self spoken
var dropdown = d3.select("#json_sources")
// config references SPINNER RELATED
var chartConfig = {
target : 'chart',
data_url : './nuts0.json',
width: 600,
height: 500,
val: 90
};
// loader settings SPINNER RELATED
var opts = {
lines: 9, // The number of lines to draw
length: 9, // The length of each line
width: 5, // The line thickness
radius: 14, // The radius of the inner circle
color: '#EE3124', // #rgb or #rrggbb or array of colors
speed: 1.9, // Rounds per second
trail: 40, // Afterglow percentage
className: 'spinner', // The CSS class to assign to the spinner
};
// SPINNER RELATED
var target = document.getElementById(chartConfig.target);
// KICK OFF callback function wrapped for loader in 'init' function
function init() {
// trigger loader initial spinner
var spinner = new Spinner(opts).spin(target);
// load json data and trigger callback
d3.json(chartConfig.data_url, function(data) {
// stop spin.js loader
spinner.stop();
// instantiate chart within callback
chart(data);
});
}
//call that init function we define above
init();
//here where all the real stuff happens
//in fact all that init stuff is just legacy
//from the spinner example
function chart(data) {
//start of map making function
var change = function() {
// trigger loader of the spinner
var spinner = new Spinner(opts).spin(target);
// did they change the NUTS?
var source = dropdown.node().options[dropdown.node().selectedIndex].value;
//necessary data processing
var str1 = source;
var str2 = ".json";
var file = str1.concat(str2);
console.log(file);
d3.json(file, function(error, europe) {
d3.csv("povertry_rate.csv", function(error, povrate) {
//change the map to apadpt to the nuts file
if (source == "nuts1") {
var land = topojson.feature(europe, europe.objects.nuts1);
} else if (source == "nuts2") {
var land = topojson.feature(europe, europe.objects.nuts2);
} else if (source == "nuts3") {
var land = topojson.feature(europe, europe.objects.nuts3);
} else if (source == "nuts0") {
var land = topojson.feature(europe, europe.objects.nuts0);
}
data = {};
povrate.forEach(function(d) {
data[d.GEO] = d['2013'];
});
//clear way for the regeneration
d3.selectAll("path").remove();
//recreate those map lines
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
// stop spin.js loader
spinner.stop();
console.info(data);
svg
.selectAll("path")
.data(land.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke","#000")
.style("stroke-width",".5px")
.style("fill",function(d){
var value = data[d.id];
if (isNaN(value)){
value = data[d.id.substring(0,2)];
}
if (isNaN(value)){
return "#fff";
}
return scale(value);
})
.on("mouseover", function(d,i) {
var value = data[d.id];
if (isNaN(value)){
value = data[d.id.substring(0,2)];
}
div.transition()
.duration(200)
.style("opacity", 0.9);
div.html("<b>"+d.properties.name+"</b><br/>" + value + "%")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d,i) {
div.transition()
.duration(500)
.style("opacity", 0);
});
svg
.append("path")
.style("fill","none")
.style("stroke","#000")
.attr("d", projection.getCompositionBorders());
});
})
}
dropdown.on("change", change)
change(); //call that change function once
}
</script>
</body>
</html>
Not really a d3 question just a little CSS:
svg {
display: block;
margin: auto;
border: 1px solid gray;
}

how to put bird counts data in d3.js to draw bar-chart on google map

I am new to d3.js ,I made reference this example: http://bl.ocks.org/emeeks/4531633 .
I changed the example's map to Google map ,and want to use SVG to draw bar
chart on the Google map.
<!DOCTYPE html>
<title>test</title>
<meta charset="utf-8">
<style type="text/css">
.gmap{ display: block; width: 1000px; height: 800px; }
.stations, .stations svg { position: absolute; }
.stations svg { width: 120px; height: 30px; padding-right: 100px; font: 12px sans-serif; }
.stations circle { fill: yellow; stroke: black; stroke-width: 1.5px; }
</style>
<body>
<div class="gmap" id="map-canvas"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://maps.googleapis.com/maps/api/js"></script>
<script></script>
<script>
var map;
function initialize() {
var mapOptions = { zoom: 8, center: new
google.maps.LatLng(23.7147979,120.7105502) };
map = new google.maps.Map( document.getElementById('map-canvas') , mapOptions);
}
initialize();
var width = 960, height = 960;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.csv("223-loc.csv", function(data) {
var parse = d3.time.format("%Y/%m/%d").parse;
aaa = d3.nest()
.key(function(d){ return d.birdName;})
.entries(stocks = data);
aaa_copy = d3.nest()
.key(function(d){ return d.birdName;})
.entries(stocks = data);
var position2keep= new Array();
var i=0;
aaa.forEach(function(s)
{
for (nn=0; nn<selectAryy.length; nn++)
{
if (s.key == selectAryy[nn])
{ position2keep.push(i); break; }
}
i++;
});
position2keep.sort();
for (j=aaa_copy.length-1; j>=0; j--)
{
if ( position2keep.indexOf(j) == -1)
aaa_copy.splice(j,1);
}
aaa_copy.forEach(function(s) {
s.values.forEach(function(d) {
for (nn=0; nn<selectAryy.length; nn++){
if (d.birdName== selectAryy[nn]){
d.date = parse(d.date);
d.count = +d.count;
d.lat = +d.lat;
d.lng = +d.lng;
}
}
bars = svg.selectAll("g")
.data(s)
.enter()
.append("g")
.attr("class", "bars")
.attr("transform", function(d) {return "translate("+ d.lat +","+
d.lng+")";});
bars.append("rect")
.attr('height', function(d) {return d.count*1000})
.attr('width', 10)
.attr('y', function(d) {return -(d.count)})
.attr("class", "bars")
.style("fill", "green");
bars.append("text")
.text(function(d) {return d.location})
.attr("x", -10)
.attr("y", 18);
bars.setMap(map);
});
});
});
</script>
</body>
My CSV data: https://drive.google.com/open?id=0B6SUWnrBmDwSWkI4bVNtOTNSOTA
I use d3.csv load data,it works.
But When I want to put the data into SVG to draw bar chart,it didn't work.
Can anyone help me to fix it?
In the provided example there are some issues:
selectAryy is not defined
and most importantly bars.setMap(map); does not seem valid. Do you mean setMap function of google.maps.OverlayView object?
In order to create a bar chart on Google Maps i would recommend to implement it as Custom Overlay.
Having said that the below example demonstrates how to add svg objects (bar chart) into Google Maps using overlay technique:
Example
function BarChartOverlay(chartData, map){
this.map_ = map;
this.chartData_ = chartData;
this.div_=null;
this.setMap(map);
}
BarChartOverlay.prototype = new google.maps.OverlayView();
BarChartOverlay.prototype.onAdd = function(){
var overlayProjection = this.getProjection();
var div = document.createElement('div');
div.setAttribute('id','chartDiv');
var chartArea = d3.select(div).append("svg");
this.chartData_.forEach(function(item){
var pos = overlayProjection.fromLatLngToDivPixel(new google.maps.LatLng(item[0], item[1]));
var bar = chartArea
.append("rect")
.attr("x", pos.x)
.attr("y", pos.y)
.attr("width", 40)
.attr("height", item[2])
.attr("fill-opacity", '0.5')
.attr("fill", 'purple');
});
this.div_ = div;
this.chartArea_ = chartArea;
var panes = this.getPanes();
panes.overlayLayer.appendChild(div);
};
BarChartOverlay.prototype.draw = function(){
var overlayProjection = this.getProjection();
var sw = overlayProjection.fromLatLngToDivPixel(this.map_.getBounds().getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.map_.getBounds().getNorthEast());
var chartAreaSize = sw.x + ' ' + ne.y + ' ' + (ne.x - sw.x) + ' ' + (sw.y - ne.y);
this.chartArea_.attr('viewBox',chartAreaSize);
};
BarChartOverlay.prototype.onRemove = function(){
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
};
function initialize() {
var mapOptions = {
zoom: 8, center: new
google.maps.LatLng(23.7147979, 120.7105502)
};
var chartData = [
[25.204757,121.6896172,100],
[22.7972447,121.0713702,130],
[24.254972,120.6011066,80]
];
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var overlay = new BarChartOverlay(chartData, map);
}
initialize();
.gmap {
display: block;
width: 1000px;
height: 800px;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://maps.googleapis.com/maps/api/js"></script>
<div class="gmap" id="map-canvas"></div>

Parsing a JSON file with JavaScript

I am new to JavaScript and coding in general and I have run into an issue trying to resolve a problem with a JSON column. Los Angeles operates a very handy data portal (https://data.lacity.org/A-Safe-City/LAPD-Crime-and-Collision-Raw-Data-2014/eta5-h8qx), the endpoint of which I have been pointing to in my script. However, the lat/long (found under location_1 column at the above link) is in the format (34.0000, -118.0000). I am attempting to make heat maps using this crime data but I need the lat/long values individually to assign as parameters for the library I am attempting to use. Can someone show me how I can separate those values and assign them to individual variables? Here is an excerpt from the script I am borrowing but I would be happy to show the entire thing if you all think it would help.
//let Socrata do the sorting:
http://data.lacity.org/resource/eta5-h8qx.json
var sodaUrl = "http://data.lacity.org/resource/eta5-h8qx.json?crm_cd=330"
//get json from the Socrata API
$.getJSON( sodaUrl, function( rawData ) {
var goodData = [];
for(var i=0;i<rawData.length;i++){
rawData[i].value = 0;
rawData[i].fresh=true;
if(rawData[i].location_1) {
rawData[i].lat = parseFloat(rawData[i].location_1.latitude);
rawData[i].lng = parseFloat(rawData[i].location_1.longitude);
}
rawData[i].date = new Date(rawData[i].date_occ + "-04:00");
if(rawData[i].location_1) {
if (rawData[i].location_1.latitude && rawData[i].location_1.longitude) {
goodData.push(rawData[i]);
};
};
};
Any help is greatly appreciated and I am happy to be more explicit if I have done poorly thus far.
Edit: here is the whole HTML document. When I try to preview in Dreamweaver I only get the basemap.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>PGC Crime Heatmap Test</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<style>
body {
margin:0; padding:0;
color:#fff;
font-family:verdana,sans-serif;
}
#map {
position:absolute; top:0; bottom:0; width:100%;
}
#titleBox {
position: absolute;
height: 50px;
width: 800px;
background: #666;
border-radius: 10px;
right: 0;
left: 0;
top: 20px;
margin: 0 auto;
border: 2px solid #AFAFAF;
font-size: 17px;
font-weight: bold;
line-height: 46px;
text-align: center;
}
#chartBox {
position:absolute;
height:100px;
width:800px;
background:#666;
border-radius:10px;
right:0;
left:0;
bottom:20px;
margin: 0 auto;
border: 2px solid #AFAFAF;
}
svg text {
fill: #fff;
}
svg line {
stroke: #fff;
stroke-width: 1px;
}
svg .domain {
stroke: #fff;
fill: none;
}
svg rect {
fill: #00AEF7;
opacity: .4;
stroke: #FFF;
}
</style>
</head>
<body>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script src='heatmap.js'></script>
<script src='leaflet-heatmap.js'></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id = 'map'></div>
<div id = 'titleBox'>
Zip Code 11201 Vehicle Collisions Animated Heatmap (September 1 - Now)
</div>
<div id = 'chartBox'></div>
<!-- Example data. -->
<script>
//heatmap.js config
var cfg = {
// radius should be small ONLY if scaleRadius is true (or small radius is intended)
// if scaleRadius is false it will be the constant radius used in pixels
"radius": .001,
"maxOpacity": 1,
// scales the radius based on map zoom
"scaleRadius": true,
// if set to false the heatmap uses the global maximum for colorization
// if activated: uses the data maximum within the current map boundaries
// (there will always be a red spot with useLocalExtremas true)
"useLocalExtrema": false,
// which field name in your data represents the latitude - default "lat"
//latField: 'latitude',
// which field name in your data represents the longitude - default "lng"
//lngField: 'longitude',
// which field name in your data represents the data value - default "value"
valueField: 'value'
};
var heatmapLayer = new HeatmapOverlay(cfg);
var baseLayer = L.tileLayer(
'https://{s}.tiles.mapbox.com/v3/cwhong.map-hziyh867/{z}/{x}/{y}.png',{
attribution: "<a href='https://www.mapbox.com/about/maps/' target='_blank'>© Mapbox © OpenStreetMap</a> <a class='mapbox-improve-map' href='https://www.mapbox.com/map-feedback/' target='_blank'>Improve this map</a>",
maxZoom: 18
}
);
//setup map and add layers
var map = new L.Map('map', {
center: new L.LatLng(34.0224, -118.2870),
zoom: 14,
layers: [baseLayer, heatmapLayer]
});
//let Socrata do the sorting:
http://data.lacity.org/resource/eta5-h8qx.json
var sodaUrl = "http://data.lacity.org/resource/eta5-h8qx.json?crm_cd=330"
//get json from the Socrata API
$.getJSON( sodaUrl, function( rawData ) {
var goodData = [];
for(var i=0;i<rawData.length;i++){
rawData[i].value = 0;
rawData[i].fresh=true;
if(rawData[i].location_1) {
rawData[i].lat = parseFloat(rawData[i].location_1.latitude);
rawData[i].lng = parseFloat(rawData[i].location_1.longitude);
}
rawData[i].date = new Date(rawData[i].date_occ + "-04:00");
if(rawData[i].location_1) {
if (rawData[i].location_1.latitude && rawData[i].location_1.longitude) {
goodData.push(rawData[i]);
};
};
};
console.log(goodData);
var nextDate = new Date(goodData[0].date);
console.log("first nextDate: " + nextDate);
//initilaize variables for the D3 chart
var countArray = [],
svg,
day,
x,
y,
margin,
height,
width,
intervalCounter = 10,
index = 0,
lastDate,
data = {
max:15,
min:0,
data:[]
};
initializeChart();
//iterate
setInterval(function () {
//iterates 10 times for each day
if (intervalCounter == 10){
intervalCounter = 0;
getAnotherDay();
} else {
intervalCounter++;
}
//create new array for live points, push it to the map
var newData = [];
for(var j=0;j<data.data.length;j++) {
var point = data.data[j];
if(point.value >= 10) {
point.fresh = false;
}
//fade in fresh points, fade out unfresh points
if(point.fresh) {
point.value = point.value + .8;
} else {
point.value = point.value - .1;
}
if(point.value > 0) {
newData.push(data.data[j]);
}
}
data.data = newData;
heatmapLayer.setData(data);
//update the chart
day = svg.selectAll(".day")
.data(countArray)
.enter()
.append("g")
.attr("class", "day")
.attr("transform", function (d) {
//var yesterday = new Date(d.date);
//yesterday = yesterday.setDate(yesterday.getDate() - 1)
return "translate(" + x(d.date) + ",0)";
})
.append("rect")
.attr("width", 28)
.attr("y", function (d) {
return height - y(d.count);
})
.attr("height", function (d) {
return y(d.count);
})
.attr("class", function (d) {
return (d.date);
})
}, 100);
function getAnotherDay() {
nextDate = new Date(nextDate.setHours(24,0,0,0));
var todayCounter = 0;
//iterate over goodData, push today's events to data.data
for (;;index++) {
var thisDate = goodData[index].date;
console.log(thisDate + nextDate);
if(thisDate.getTime() < nextDate.getTime()) {
data.data.push(goodData[index]);
todayCounter++;
lastDate = thisDate;
} else {
//Still need to increment lastDate if there is no data
if(todayCounter == 0) {
console.log(lastDate);
lastDate = lastDate.getDate() - 1;
}
var todayCount = {
date:lastDate,
count:todayCounter
};
countArray.push(todayCount);
break;
}
}
}
//sets margins and axes for the D3 chart. Borrowed from Chris Metcalf's example on dev.socrata.com
function initializeChart() {
// Set our margins
margin = {
top: 20,
right: 20,
bottom: 30,
left: 60
},
width = 800 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
// Our X scale
x = d3.time.scale()
.domain([new Date(goodData[0].date), d3.time.day.offset(new Date(goodData[goodData.length - 1].date), 1)])
.rangeRound([0, width - margin.left - margin.right])
//.ticks(d3.time.day, 1);
// Our Y scale
y = d3.scale.linear()
.domain([0,100])
.rangeRound([height, 0]);
// Our color bands
var color = d3.scale.ordinal()
.range(["#308fef", "#5fa9f3", "#1176db"]);
// Use our X scale to set a bottom axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// Same for our left axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickValues([0,50,100]);
// Add our chart to the #chart div
svg = d3.select("#chartBox").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
});
</script>
</body>
</html>
It should function exactly like this site, I am merely using a different dataportal and mine is centered on LA. http://chriswhong.github.io/nyc-heatmap/

D3 + Google Maps + multi-point paths

I am plotting points on a Google Map (so far so good) and then plotting a line between each point based on the point order in the underlying data (not so good). Like a trail or route.
Unlike the handful of examples I've seen doing this, my data are not in GeoJSON format, and I would really like to keep it that way if at all possible. I have tried to adapt the exampels posted here and here but without success.
My results end up with no lines being drawn, and I can't tell if that's because of a projection error or something else syntactical with D3. I have tried to debug through console.log() statements, but I am very week on GIS projections.
Here is the code to plots the points
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=true"></script>
<script src="../js/d3.v3.min.js"></script>
<style type="text/css">
html, body, #map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.markers {
position: absolute;
}
svg.pts {
position: absolute;
}
.markers border {
position: absolute;
stroke: black;
stroke-width: 2px;
}
.markers svg.pts {
width: 60px;
height: 20px;
padding-right: 100px;
font: 10px sans-serif;
}
.markers circle {
fill: brown;
stroke: black;
stroke-width: 1.5px;
}
.SvgOverlay path {
stroke: Orange;
stroke-width: 2px;
fill: Orange;
fill-opacity: .3;
}
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
var map = new google.maps.Map(d3.select("#map").node(), {
zoom: 15,
center: new google.maps.LatLng(29.371397, -81.54938), //N/S E/W
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var data = [ //note this is not in GeoJSON format
{name:"pt1",lng:-81.55082967,lat:29.374915304},
{name:"pt2",lng:-81.55211713,lat:29.373504039},
{name:"pt3",lng:-81.5842252,lat:29.417969924},
{name:"pt4",lng:-81.55230021,lat:29.374245073},
{name:"pt5",lng:-81.55115,lat:29.37263},
{name:"pt6",lng:-81.58737814,lat:29.358476912},
{name:"pt7",lng:-81.59230268,lat:29.359308171},
{name:"pt8",lng:-81.58783883,lat:29.356449048},
{name:"pt9",lng:-81.58189168,lat:29.420264027},
{name:"pt10",lng:-81.58288,lat:29.4202},
{name:"pt11",lng:-81.56079477,lat:29.359527893},
{name:"pt12",lng:-81.55861145,lat:29.356670068},
{name:"pt13",lng:-81.57961314,lat:29.420893275},
{name:"pt14",lng:-81.579302,lat:29.419368},
{name:"pt15",lng:-81.55979967,lat:29.359768002},
{name:"pt16",lng:-81.55823261,lat:29.36122515},
{name:"pt17",lng:-81.58189168,lat:29.420264027},
{name:"pt18",lng:-81.57997524,lat:29.421120323},
{name:"pt19",lng:-81.58148399,lat:29.420030491},
{name:"pt20",lng:-81.57839075,lat:29.420766158},
{name:"pt21",lng:-81.57982489,lat:29.42002304},
{name:"pt22",lng:-81.580266,lat:29.420212},
{name:"pt23",lng:-81.5820392,lat:29.42048164},
{name:"pt24",lng:-81.57894731,lat:29.420509033},
{name:"pt25",lng:-81.57819629,lat:29.418834169}
];
var overlay = new google.maps.OverlayView();
overlay.onAdd = function() {
var layer = d3.select(this.getPanes().overlayLayer).append("div")
.attr("height", "100%")
.attr("width", "100%")
.attr("class", "markers")
.attr("id", "layer");
layer[0][0].style.width = "1366px";
layer[0][0].parentNode.style.width = "100%";
layer[0][0].parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.parentNode.parentNode.style.height = "100%";
// Add points
overlay.draw = function() {
var projection = this.getProjection(),
padding = 10;
var point = layer.selectAll("svg")
.data( data )
.each(transform) // update existing markers
.enter().append("svg:svg")
.each(transform)
.attr("class", "point pts")
// Add marker on points
point.append("svg:circle")
.attr("r", 4.5)
.attr("cx", padding )
.attr("cy", padding );
// Add a label on points
point.append("svg:text")
.attr("x", padding + 7)
.attr("y", padding)
.attr("dy", ".31em")
.text( function(d) {
return d.name; }
);
//Here is where I'd like to add lines connecting the points, in order
//of appearance in the data object
function _projection( lat, lng ) {
e = new google.maps.LatLng( lat, lng );
e = projection.fromLatLngToDivPixel(e);
return [ e.x - padding, e.y - padding]
// return [ e.x, e.y ]
}
function transform(d) {
//console.log(d);
e = _projection( d.lat, d.lng )
return d3.select(this)
.style("left", e[0] + "px")
.style("top", e[1] + "px");
}
};
};
// Bind overlay to the map…
overlay.setMap(map);
</script>
</body>
</html>
And here is a JSFiddle
Suggestions to get the path added via the data object as presented are most appreciated.
Ok, so I took a look at your code and refactored it a bit. But here is a basic working version of a path drawn between the points: http://jsfiddle.net/AJvt4/3/. There is one caveat though, and that is that the overlayPane doesn't expand when the map pans. I'm not too familiar with google maps so not sure how much I can help there. Here's an explanation of the changes made:
First I created a encompassing svg to house all of your d3 elements in the onAdd event:
var svg = layer.append('svg')
.attr('x', 0)
.attr('y', 0)
Also in the onAdd event I added a d3 path generator (you can read more here):
var lineFn = d3.svg.line()
.x(function (d) {
e = _projection(d.lat, d.lng);
return e[0] + padding
})
.y(function (d) {
e = _projection(d.lat, d.lng);
return e[1] + padding
})
To actually draw the line, I added this in the add event handler:
var line = svg.selectAll('.path').data([data])
line.enter().append('path')
line.attr('class', 'path')
.attr('d', lineFn)
It's important to note the array around the data ([data]). This is because d3 expects an array of arrays with each inner array holding points to a line. This makes it easier to draw multiple lines. In your case there is only one line.
You'll notice a few other changes to make the code a bit more d3-esque. Hope that helps get you started!

D3 Force Layout: Adding & Removing Nodes On Click

I can't seem to find any other examples which have a similar application to what I'm trying to do, so I thought I'd just ask.
I'm trying to mock up a funky mind map tool using the awesome d3.js library from mike bostock. I'm still trying to learn d3 and as it also turns out, basic coding! What I want to have is a blank canvas, and 3 buttons; 'add', 'remove' & 'edit'. Very basic I hope!
When you select the 'add' button (the top image), and then click on the blank canvas, a node will be added. If you click again nearby, another node will be added and will be linked to the first node.
Selecting the 'remove' button (the middle image) then clicking on a node will delete that node and all touching links.
Selecting the 'edit' button (the bottom image) will allow you to label nodes.
I have step 1 down, and half of step 2. The problem I'm running into goes like this:
Click 'add' button once, add function turned 'on'. Works
Add some nodes. Works
Click 'add' button again, add function turned 'off'. Works
Click the canvas, no nodes are added, but existing nodes are drag-able. Works
Click 'remove' button once, remove function turned 'on'. Works
Click a node to remove it. Broken. Removes everything.
Click 'remove' button again, remove function turned 'off'. Broken
Click 'add' button again, add function turned 'on'. Broken
Does anyone have any suggestions as to why I'm having this problem & how they would go about solving this? I think it has something to do with confusion between state selection. When turning off the 'remove' function, it calls the same function as when you turn off the 'add' function, so it doesnt know what to do and does nothing... I thought that they should not be mutually selectable, but is one state remaining on after it is switched on? I am really stumped :(
I hope there may be parts of this that are useful to other people as well.
Thanks!
Seb
.js below..>>>>
//==D3 STUFFS ======================================================
//height & width of the interactive area
var divh = document.getElementById('container').offsetHeight;
var divw = document.getElementById('container').offsetWidth;
//node size
var radius = 20;
//define the nodes and links as empty data sets
var nodes = [];
var links = [];
//place the interactive area onto the browser UI with the dimensions defined above
var interactiveArea = d3.select("#container").append("svg:svg").attr("width", divw).attr("height", divh);
//enable dragging of node elements
var drag = d3.behavior.drag()
.origin(Object)
.on("drag", dragmove);
//define the physics parameters that will take effect on the nodes and links
var force = d3.layout.force()
.gravity(0.01)
.charge(-80)
.linkDistance(60)
.nodes(nodes)
.links(links)
.size([divw, divh]);
//apply the physics parameters defined above on the nodes and links
force.on("tick", function()
{
interactiveArea.selectAll("line.link")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
interactiveArea.selectAll("circle.node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
//update the position of the object on drag
function dragmove(d)
{
d3.select(this)
.attr("cx", d.x = Math.max(radius, Math.min(divw - radius, d3.event.x)))
.attr("cy", d.y = Math.max(radius, Math.min(divh - radius, d3.event.y)));
}
//update the force layout
function update()
{
interactiveArea.selectAll("line.link")
.data(links)
.enter().insert("svg:line", "circle.node")
.attr("class", "link")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
interactiveArea.selectAll("circle.node")
.data(nodes)
.enter().insert("svg:circle", "circle.cursor")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 10)
.call(force.drag);
force.start();
}
//==============================================================
//==BUTTON & EVENT SELECTOR=======================================
var addCounter = 0;
var removeCounter = 0;
var editCounter = 0;
function addButton_Off()
{
//alert("ADD - off");
document.images["add-button"].src = "love.lost.PNG";
all_Off();
return true;
}
function removeButton_Off()
{
//alert("REMOVE - off");
document.images["remove-button"].src = "love.lost.PNG";
//all_Off();
return true;
}
function editButton_Off()
{
//alert("EDIT - off");
document.images["edit-button"].src = "love.lost.PNG";
return true;
}
function addButton()
{
addCounter++;
if (addCounter%2 == 0)
addButton_Off();
else
addButton_On();
if (removeCounter%2 == 1)
removeCounter++;
removeButton_Off();
if (editCounter%2 == 1)
editCounter++;
editButton_Off();
function addButton_On()
{
//alert("ADD - on");
document.images["add-button"].src = "pop.cloud.PNG";
add_Nodes();
return true;
}
}
function removeButton()
{
removeCounter++;
if (removeCounter%2 == 0)
removeButton_Off();
else
removeButton_On();
if (addCounter%2 == 1)
addCounter++;
addButton_Off();
if (editCounter%2 == 1)
editCounter++;
editButton_Off();
function removeButton_On()
{
//alert("REMOVE - on");
document.images["remove-button"].src = "pop.cloud.PNG";
remove_Nodes();
return true;
}
}
function editButton()
{
editCounter++;
if (editCounter%2 == 0)
editButton_Off();
else
editButton_On();
if (addCounter%2 == 1)
addCounter++;
addButton_Off();
if (removeCounter%2 == 1)
removeCounter++;
removeButton_Off();
function editButton_On()
{
//alert("EDIT - on");
document.images["edit-button"].src = "pop.cloud.PNG";
return true;
}
}
//=============================================================
//==EVENT ACTIONS========================================================
function all_Off()
{
interactiveArea.on("mousedown", function()
{
update();
});
}
function add_Nodes()
{
//do the following actions when the mouse is clicked on the interactiveArea
interactiveArea.on("mousedown", function()
{
// add a node under the mouse cursor
var point = d3.svg.mouse(this),
node = {x: point[0], y: point[1]},
n = nodes.push(node);
nodes.forEach(function(target)
{
var x = target.x - node.x,
y = target.y - node.y;
//if there is a node less than 30 pixels? away, add a link between the 2 nodes
if (Math.sqrt(x * x + y * y) < 30)
{
// add links to any nearby nodes
links.push({source: node, target: target});
}
});
update();
});
}
function remove_Nodes()
{
interactiveArea.on("click", function()
{
var point = d3.select(this);
point.remove();
update();
});
}
//function edit_Nodes()
//==========================================================
html below...>>>>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link type="text/css" rel="stylesheet" href="style.css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
</head>
<body>
<div id="enclosure">
<div id="title">
/
</div>
<div id="button-menu">
<a onMouseDown="return addButton()">
<img name="add-button" id="add-button-img" src="love.lost.PNG" width="80px" height="80px" border = "0" alt="fuchs">
</a>
<a onMouseDown="return removeButton()">
<img name="remove-button" id="remove-button-img" src="love.lost.PNG" width="80px" height="80px" border = "0" alt="fuchs">
</a>
<a onMouseDown="return editButton()">
<img name="edit-button" id="edit-button-img" src="love.lost.PNG" width="80px" height="80px" border = "0" alt="fuchs">
</a>
</div>
<div id="container">
<script type="text/javascript" src="http://mbostock.github.com/d3/talk/20111116/d3/d3.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/talk/20111116/d3/d3.geom.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/talk/20111116/d3/d3.layout.js"></script>
<script type="text/javascript" src="bonquiqui.js"></script>
<!--<script type="text/javascript" src="origin.js"></script>-->
</div>
</div>
</body>
</html>
css below..>>>>
body {
font: 300 36px "Lane - Posh";
height: 100%;
width: 100%;
margin: auto;
overflow: hidden;
position: absolute;
text-align: center;
background: #fff;
}
#enclosure {
margin-top: 3%;
}
#title {
background: #fff;
font: 300 220% "Lane - Posh";
height: 100px;
width: 60%;
margin-left: auto;
margin-right: auto;
}
#button-menu {
background: #eee;
height: 20%;
width: 4%;
position: absolute;
top: 48.0%;
left: 81%;
}
#add-button {
cursor: pointer;
position: relative;
top: 5%;
}
#remove-button {
cursor: pointer;
position: relative;
top: 5%;
}
#edit-button {
cursor: pointer;
position: relative;
top: 5%;
}
#container {
height: 60%;
width: 60%;
margin: auto;
margin-top: 1%;
background: #eee;
overflow: hidden;
}
circle.node
{
cursor: pointer;
stroke: #000;
stroke-width: .5px;
}
line.link
{
fill: none;
stroke: #9ecae1;
stroke-width: 1.5px;
}

Categories

Resources