I have a d3 layout with 10 nodes structured as follows...
<body>
<svg id="mainSvg" style="position: relative" width="1200" height = "1200">
<g>
<svg width = "800" height="800>
<g class="nodeGroupSVG" transform=translate(someXValue,someYValue) scale(someScaleValue)
<g class="node" transform=translate(someXValue,someYValue)>
<circle>
<text>
</g>
//9 more of these individual node groupings
</g>
</svg>
</g>
<g class="gToMoveTo">
//starts off empty
</g>
<svg>
</body>
I want to move the nodes to a new container, which can easily be achieved in jquery using...
$('.gToMoveTo').append($('.node'));
But when it comes to animating that transition to a new DOM position, I'm having troubles implementing the idea from the SO answer here: JQuery - animate moving DOM element to new parent?
Using that function has exactly the same effect for me as the simple jquery line (plus a minor delay in updating which I'm assuming is due to the animation function being called - even though there's no gradual transition to the new position).
All that's a long way of asking whether anyone knows of a good reason why the function I've listed would not be suited to animating the transition of SVG group elements? And if so, whether there's any ideas of a way to adapt it for use with SVG.
This is a common algorithm to do that:
Here's the code:
<html>
<head>
<script src="d3.v3.min.js"></script>
<script src="jquery-2.1.0.min.js"></script>
<style>
.svg_contariner {
background-color:#CCCCCC;
}
#origin {
width:200px;
height:200px;
margin:5px;
}
#target {
width:200px;
height:200px;
margin:5px;
}
circle {
fill:#e72;
}
text {
font-family:Tahoma, Geneva, sans-serif;
font-size:10px;
}
.clone {
margin:0;
padding:0;
width:20px;
height:20px;
position:absolute;
}
</style>
</head>
<body>
<div id="data"></div>
<script>
var data_text = "", data = [], r = 10;
/* --- add some random data --- */
for (i = 0; i < 10; i++) {
data.push( {
"x": Math.round(Math.random() * 180)+10,
"y": Math.round(Math.random() * 180)+10,
"text": i
});
data_text += "x:" + data[i].x + " y:" + data[i].y + " text:" + data[i].text + "<br>";
}
/* --- create 2 containers --- */
var svgContainerTar = d3.select("body").append("svg")
.attr("id","target")
.attr("class","svg_contariner");
var svgContainerOrg = d3.select("body").append("svg")
.attr("id","origin")
.attr("class","svg_contariner");
/* --- add g node to origin --- */
var ele = svgContainerOrg.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("class", "node")
.on("click", function (d) { /* --- bind onClick to every g --- */
d3.select(this).remove(); /* --- remove origin element --- */
moveCircle(d); /* --- create, animate ghost --- */
});
/* --- add circle to g --- */
var circles = ele.append("circle")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", r+"px");
/* --- add text to g --- */
var labels = ele.append("text")
.attr("dx", function(d) {return d.x - 2})
.attr("dy", function(d) {return d.y + 3})
.text(function (d) {return d.text});
function moveCircle(d) {
ori_x = d.x;
ori_y = d.y;
ori_tex = d.text;
ori_pos = $("#origin").position();
tar_pos = $("#target").position();
/* --- create ghost using jQuery --- */
$("body").append("<div id='ghost' class='clone' style='left:"+(ori_x - r/2+ori_pos.left)+";top:"+(ori_y - r/2+ori_pos.top)+"';>"+
"<svg width='20px' height='20px'>"+
"<circle cx='"+r+"' cy='"+r+"' r='"+r+"px'></circle>"+
"<text x='"+(r - 2)+"' y='"+(r+3)+"'>"+ori_tex+"</text>"+
"</svg></div>");
/* --- animate ghost --- */
$("#ghost").animate({
"left" : tar_pos.left + ori_x,
"top" : tar_pos.top + ori_y
},100,"swing",
function () {
time = setTimeout(function () { /* --- when animation ends create target element --- */
var new_node = d3.select("#target")
.append ("g")
.attr("class", "node");
new_node.append("circle")
.attr("cx", ori_x+r/2+"px")
.attr("cy", ori_y+r/2+"px")
.attr("r", r+"px")
new_node.append("text")
.attr("dx", ori_x+r/2-2)
.attr("dy", ori_y+r/2+3)
.text(ori_tex);
$("#ghost").remove(); /* --- remove ghost --- */
},100)
});
}
</script>
</body></html>
Hope this help
Related
I have a dashboard with two separate maps of a state showing different data based on years 2014 and 2012. The map when hovered over show the name of area individually. What I need to do is display both 2012 and 2014 maps's tooltips at the same time over the respective maps when I mouseover any one of the two maps. How can I display both at the same time. I would appreciate any help with this. Thanks.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test dashboard</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" >
<style>
#gujarat-viz-2017, #buttons {
border-right: 1px solid #ccc
}
.container {
background-color: #d5e8ec;
}
.const0 {
display: none;
}
.emptyparty {
fill:#f9f9f1;
}
.emptyparty:hover, .constituency:hover {
fill:#ccc;
}
.hidden { display: none; }
.showtooltip { position: absolute; z-index: 10000; background-color: #333;
border-radius: 10px; color: #fff; padding: 5px; }
/*Party colors*/
.bjp{ fill: #f88101;}
.inc{ fill: #6da736;}
.ncp{ fill: #076598;}
.gpp{ fill: #5a469d;}
.ind{ fill: #25a29a;}
.jdu{ fill: #eb4d4c;}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div id="gujarat-viz-2014" class="col-md-6">
<h2>2014</h2>
</div>
<div id="gujarat-viz-2012" class="col-md-6">
<h2>2012</h2>
</div>
</div> <!-- .row -->
</div>
<script src="http://www.thehindu.com/static/js/jquery-1.10.2.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>
function map_function(map_settings){
// Global variables
var margin = { top: 50, left:50, right:50, bottom:50 },
height = 400 - margin.top - margin.bottom,
width = 500 - margin.left - margin.right;
// Create SVG canvas with responsive resizing
var svg = d3.select(map_settings["htmlelement"])
.append("svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMinYMin")
.append("g")
.attr("class", "data"+map_settings["year"])
// Add a tooltip to visualization
var tooltip = d3.select('body').append('div')
.attr('class', 'hidden showtooltip')
.attr('id', "tooltip"+map_settings["year"])
// queue and read the topojson, json data file
d3.queue()
.defer(d3.json, "https://api.myjson.com/bins/17m3if")
.defer(d3.json, map_settings.data)
.await(render_map)
var projection = d3.geoMercator()
.scale(3000)
.center([71.5, 22.3])
.translate([width / 2, height / 2])
var geoPath = d3.geoPath()
.projection(projection)
function render_map(error, mapshape, mapdata){
var constituency = topojson.feature(mapshape, mapshape.objects.collection).features;
dataMap = {};
mapdata.forEach(function(d){
dataMap[d.constNo] = d;
})
var fill_function = function(d) {
// d3.select(this).attr('fill', "white")
} // end of mousemove_function
var mousemove_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
// console.log(constinfo.constituencyName)
// console.log(d3.select(this).data()[0].properties)
var html = "<p>"+constinfo.constituencyName+"</p>"
tooltip.classed('hidden', false)
.html(html)
.style("left", (d3.event.clientX - 10) + "px")
.style("top", (d3.event.clientY - 45) + "px");
} // end of mousemove_function
var class_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
var className = "constituency ";
if(constinfo !== undefined) {
className += ("c"+constinfo.constNo+" ")
className += constinfo.leadingParty.replace(/[^a-zA-Z ]/g, "").toLowerCase()
} else {
className += "emptyparty"
className += " const"
className += d.properties.AC_NO
}
return className;
} // end of class_function
var mouseout_function = function(d) {
tooltip.classed('hidden', true)
} // end of mousemove_function
svg.selectAll(".constituency")
.data(constituency)
.enter().append("path")
.attr("d", geoPath)
.attr('class', class_function)
.attr('fill', "white")
.attr('stroke', "#e8e8e8")
.attr('stroke-width', "0.5")
.on('mouseover', mousemove_function)
.on('mouseout', mouseout_function)
} // render_map
} // map_function
var gujarat_data_2014 = {
htmlelement: "#gujarat-viz-2014",
data: "https://api.myjson.com/bins/yolfr",
year: "2014"
};
var gujarat_data_2012 = {
htmlelement: "#gujarat-viz-2012",
data: "https://api.myjson.com/bins/19ztxj",
year: "2012"
};
map_function(gujarat_data_2014);
map_function(gujarat_data_2012);
</script>
</body>
</html>
I'd modify your mousemove and mouseout to operate on both maps at the same time:
var mousemove_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
var html = "<p>" + constinfo.constituencyName + "</p>"
var tooltips = d3.selectAll('.showtooltip');
// get paths from all maps
d3.selectAll('.c' + constinfo.constNo)
.each(function(d,i){
var pos = this.getBoundingClientRect();
// operate on appropriate tooltip
d3.select(tooltips.nodes()[i]).classed('hidden', false)
.html(html)
.style("left", (pos.x + pos.width/2) + "px")
.style("top", (pos.y - pos.height/2) + "px");
});
} // end of mousemove_function
var mouseout_function = function(d) {
d3.selectAll('.showtooltip').classed('hidden', true);
} // end of mousemove_function
Running code here.
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;
}
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?
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!
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;
}