How to add seperate lines to map - d3.js

On a d3 v4 chart, I have arcs drawn for multiple coordinates. All is well there. Now I would like to draw the lines from a json request. Any new lines would be drawn, whilst existing lines would remain. As I understand, each line would require its own transition....
If I understand correctly, the iteration of coordinates begins at
line.attr("d", function(c) {...}
Which iterates over each set of coordinates, and then internally groups them so that they are all 'triggered' at the same time, as the one event. Any explanation on this gratefully received.
Plunker: https://plnkr.co/edit/Ax4Tby47lFlryzVWCHi2?p=preview
Kev

Not sure I'm reading your question correctly, but it sounds like you want your animations to start one after another. This can be accomplished with a .delay:
.attr("stroke-dasharray", "0, 1000") //<-- hide the line
.transition()
.delay(function(d, i) {
return 5000 * i; //<-- i is the index of the line, so stagger the animation start by index * duration
})
.duration(5000)
.attrTween("stroke-dasharray", function() {
var len = this.getTotalLength();
return function(t) {
return (d3.interpolateString("0," + len, len + ",0"))(t)
};
})
Full Code:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<style>
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill{
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: 0.5px;
stroke-opacity: 0.5;
}
/* the color of land in countries */
.land {
fill: #222;
}
/* the color of borders */
.boundary {
fill: none;
stroke: #fff;
stroke-width: 0.5px;
}
</style>
<svg width="800" height="600"></svg>
<script src="//d3js.org/d3.v4.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var projection = d3.geoMercator()
.scale((width - 3) / (2 * Math.PI))
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var graticule = d3.geoGraticule();
svg.append("defs").append("path")
.datum({
type: "Sphere"
})
.attr("id", "sphere")
.attr("d", path);
svg.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
svg.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("https://rawgit.com/mbostock/topojson/master/examples/world-50m.json", function(error, world) {
if (error) throw error;
d3.json("https://jsonblob.com/api/5806b733e4b0bcac9f817223", function(coord){
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) {
return a !== b;
}))
.attr("class", "boundary")
.attr("d", path);
var line = svg.selectAll(".paths")
.data(coord)
.enter()
.append("path");
line.attr("d", function(c) {
console.log(c);
var d = {
source: projection(c.source),
target: projection(c.destination)
};
var dx = d.target[0] - d.source[0],
dy = d.target[1] - d.source[1],
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source[0] + "," + d.source[1] + "A" + dr + "," + dr +
" 0 0,1 " + d.target[0] + "," + d.target[1];
})
.style("stroke", "red") // color of the arc line
.style("stroke-width", 5)
.style("fill", "none")
.attr("stroke-dasharray", "0, 1000")
.transition()
.delay(function(d, i) {
return 5000 * i;
})
.duration(5000)
.attrTween("stroke-dasharray", function() {
var len = this.getTotalLength();
return function(t) {
return (d3.interpolateString("0," + len, len + ",0"))(t)
};
})
.on('end', function(d) {
var c = projection(d.destination);
svg.append('circle')
.attr('cx', c[0])
.attr('cy', c[1])
.attr('r', 0)
.style('fill', 'white') // color of the cirle
.style('fill-opacity', '0.5')
.transition()
.duration(2000)
.attr('r', 50)
.on('end', function(d) {
d3.select(this)
.transition()
.duration(2000)
.attr('r', 10);
});
});
});
});
</script>
</body>
</html>

Related

D3 Nodes not appearing while updating it realtime

I am playing with the Mike Bostock's mobile patent suits example
I am adding ~100 nodes using forEach loop in this JSFiddle; In reality, these nodes will be passed by an external service in real-time.
The page is loading a few static nodes initially. The reset button calls the forEach loop to create the new nodes using the function add_prc. This method pushes the node to nodes list (used to display in SVG) and then calls the refresh method but the nodes do not appear on the screen properly.
I see a dot at top-left of the screen but I can't select it or drag it to the center.
Once you add the nodes/links using the forEach loop, you aren't translating them anywhere. Check out this screenshot of the console:
As I mentioned in the comments, the tick function applies transform to the previously added path, circle and text but does it add anything to the newly added nodes, links? NO. So that's the thing that's missing. As I know your previous question, I'm adding the code from that to this:
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;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
Now, this would add all nodes at (100, 100) (I know you're just testing things). Anyway, I've used random x and ys. Here's a snippet and a JSFIDDLE
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<button id="reset" onclick="reset()">reset</button>
<button id="ref" onclick="refresh()">refresh</button>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960, height = 500;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var links1 = [{"source":"ctfa","target":"tfa"},
{"source":"cea","target":"tea"},
{"source":"ctfe","target":"tfe"},
{"source":"ctee","target":"tee"},
{"source":"ctfu","target":"tfu"},
{"source":"cteu","target":"teu"},
{"source":"rfa","target":"tfa"},
{"source":"rea","target":"tea"},
{"source":"rfe","target":"tfe"},
{"source":"ree","target":"tee"},
{"source":"rfu","target":"tfu"},
{"source":"reu","target":"teu"},
{"source":"r1fa","target":"rfa"},
{"source":"r1fa","target":"gfa"},
{"source":"r1fa","target":"ggf"},
{"source":"r1ea","target":"rea"},
{"source":"r1ea","target":"gea"},
{"source":"r1ea","target":"gge"},
{"source":"r1fe","target":"rfe"},
{"source":"r1fe","target":"gfe"},
{"source":"r1fe","target":"ggf"},
{"source":"r1ee","target":"ree"},
{"source":"r1ee","target":"gee"},
{"source":"r1ee","target":"gge"},
{"source":"r1fu","target":"rfu"},
{"source":"r1fu","target":"gfu"},
{"source":"r1fu","target":"ggf"},
{"source":"r1eu","target":"reu"},
{"source":"r1eu","target":"geu"},
{"source":"r1eu","target":"gge"},
{"source":"hh1fa","target":"ggf"},
{"source":"hh1ea","target":"gge"},
{"source":"hh1fe","target":"ggf"},
{"source":"hh1ee","target":"gge"},
{"source":"hh1fu","target":"ggf"},
{"source":"hh1eu","target":"gge"},
{"source":"dbfa","target":"gfa"},
{"source":"dbea","target":"gea"},
{"source":"dbfe","target":"gfe"},
{"source":"dbee","target":"gee"},
{"source":"dbfu","target":"gfu"},
{"source":"dbeu","target":"geu"},
{"source":"hflse","target":"tee"},
{"source":"hfnyse","target":"teu"},
{"source":"hfnse","target":"teu"},
{"source":"hfret","target":"tfu"},
{"source":"hfebs","target":"tfe"},
{"source":"hfint","target":"tfu"},
{"source":"c1e","target":"ctee"},
{"source":"c1e","target":"gge"},
{"source":"c2e","target":"ctee"},
{"source":"c3e","target":"cteu"},
{"source":"c4e","target":"cteu"},
{"source":"c5e","target":"ggf"},
{"source":"d1e","target":"ctee"},
{"source":"c1f","target":"ctfe"},
{"source":"c2f","target":"ctfe"},
{"source":"c3f","target":"ggf"},
{"source":"c4f","target":"gge"},
{"source":"c5f","target":"ctfa"},
{"source":"d1f","target":"ctfe"}];
var nodes1 = [{"id":"tfa"},
{"id":"tea"},
{"id":"tfe"},
{"id":"tee"},
{"id":"tfu"},
{"id":"teu"},
{"id":"ctfa"},
{"id":"cea"},
{"id":"ctfe"},
{"id":"ctee"},
{"id":"ctfu"},
{"id":"cteu"},
{"id":"rfa"},
{"id":"rea"},
{"id":"rfe"},
{"id":"ree"},
{"id":"rfu"},
{"id":"reu"},
{"id":"r1fa"},
{"id":"r1ea"},
{"id":"r1fe"},
{"id":"r1ee"},
{"id":"r1fu"},
{"id":"r1eu"},
{"id":"hh1fa"},
{"id":"hh1ea"},
{"id":"hh1fe"},
{"id":"hh1ee"},
{"id":"hh1fu"},
{"id":"hh1eu"},
{"id":"dbfa"},
{"id":"dbea"},
{"id":"dbfe"},
{"id":"dbee"},
{"id":"dbfu"},
{"id":"dbeu"},
{"id":"gfa"},
{"id":"gea"},
{"id":"gfe"},
{"id":"gee"},
{"id":"gfu"},
{"id":"geu"},
{"id":"gge"},
{"id":"ggf"},
{"id":"hflse"},
{"id":"hfnyse"},
{"id":"hfnse"},
{"id":"hfret"},
{"id":"hfebs"},
{"id":"hfint"},
{"id":"c1e"},
{"id":"c2e"},
{"id":"c3e"},
{"id":"c4e"},
{"id":"c5e"},
{"id":"d1e"},
{"id":"c1f"},
{"id":"c2f"},
{"id":"c3f"},
{"id":"c4f"},
{"id":"c5f"},
{"id":"d1f"}];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
nodes1.forEach(function(d){ add_prc(d) });
links1.forEach(function(d){ add_con(d) });
}
function add_prc(newNode) {
//console.log(newNode);
addNodeCanvas(newNode.id,newNode.grp);
}
function add_con(newConnection) {
//console.log(newConnection);
addLinkCanvas( newConnection.source,newConnection.target);
}
//setInterval(refresh, 15000);
function addNodeCanvas(nodeName,g) {
var node1 = { x: Math.floor(Math.random()*200+100), y: Math.floor(Math.random()*200+100), id: nodeName, grp:g };
var n = nodes.push(node1);
//console.log(node1);
refresh();
}
function addLinkCanvas(idSrc, idTarget) {
if (idSrc != idTarget) {
var s = {}, t = {};
nodes.forEach(function(curNode) {
if (typeof curNode.id != "undefined") {
if (curNode.id == idSrc) { s = curNode; }
if (curNode.id == idTarget) { t = curNode; }
}
});
//console.log( { s,t});
links.push({ source: s, target: t });
};
refresh();
}
var width = 900,
height = 600,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
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;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
function refresh(){
node = node.data(nodes);
node.enter().insert("circle", ".cursor")
.attr("class", "node")
.attr("r", 5)
.on("mousedown", mousedownNode);
node.exit()
.remove();
link = link.data(links);
link.enter().insert("line", ".node")
.attr("class", "link");
link.exit()
.remove();
force.start();
}
</script>

exit().remove() is not removing the first bar

Whenever the slider is dragged and a new date is selected it does not redraw the first bar, it redraws all the other bars. For example, try date 09/06 and then 09/17. The first bar for id 54042 will not redraw. This is the link to my bl.ocks http://blockbuilder.org/fall16mis/87a39bc00b1b356f78dfd0954f345444
This is the code:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.tooltip {
opacity: 0;
background-color: #ffe047;
position: absolute;
}
.grid line {
stroke: lightgrey;
shape-rendering: crispEdges;
}
.ticks {
font-size: 11px;
}
.track,
.track-inset,
.track-overlay {
stroke-linecap: round;
}
.track {
stroke: #000;
stroke-opacity: 0.3;
stroke-width: 10px;
}
.track-inset {
stroke: #ddd;
stroke-width: 8px;
}
.track-overlay {
pointer-events: stroke;
stroke-width: 50px;
stroke: transparent;
cursor: crosshair;
}
.handle {
fill: #fff;
stroke: #000;
stroke-opacity: 0.5;
stroke-width: 1.25px;
}
.bar:hover{
fill:#0f9fff;
}
.legend{
color:#005ebc;
z-index:0;
}
</style>
</head>
<body>
<div id="slider"></div>
<script>
var json_data = "id,date,start_time,end_time\n\
54042,2017/09/06,5.50,5.53\n\
54042,2017/09/06,7.55,9.19\n\
54042,2017/09/16,11.12,12.28\n\
54042,2017/09/23,13.56,15.03\n\
54042,2017/09/07,16.29,17.33\n\
54042,2017/09/06,19.56,20.53\n\
54042,2017/09/20,21.3,22.14\n\
98765,2017/09/06,5.1,6.51\n\
98765,2017/09/06,11.4,11.53\n\
98765,2017/09/06,12.2,12.42\n\
98765,2017/09/06,12.55,14.2\n\
98765,2017/09/16,21.42,21.59\n\
98765,2017/09/16,22.01,23.13\n\
98765,2017/09/16,23.16,23.51\n\
98765,2017/09/23,13.41,14.03\n\
65299,2017/09/06,7.23,8.21\n\
65299,2017/09/06,9.37,10.23\n\
65299,2017/09/06,11.46,13.29\n\
65299,2017/09/06,18.07,19.57\n\
65299,2017/09/17,14.41,16.22\n\
65299,2017/09/17,21.39,23.39\n\
79408,2017/09/06,9.37,10.17\n\
79408,2017/09/06,11.03,12.08\n\
79408,2017/09/06,13.14,15.53\n\
79408,2017/09/06,16.05,17.48\n\
79408,2017/09/06,19.47,20.23\n\
38338,2017/09/06,8.22,9.28\n\
38338,2017/09/06,11.34,12.17\n\
38338,2017/09/07,12.43,13.35\n\
38338,2017/09/07,14.12,15.48\n\
38338,2017/09/07,16.09,17.23\n\
38338,2017/09/07,18.31,19.19\n\
38338,2017/09/07,21.49,23.26\n\
81757,2017/09/06,6.31,7.41\n\
81757,2017/09/06,8.18,9.39\n\
81757,2017/09/06,10.18,11.23\n\
81757,2017/09/06,13.02,14.04\n\
81757,2017/09/07,15.22,17.23\n\
81757,2017/09/07,20.32,22.01\n\
68077,2017/09/06,11.1,12.45\n\
68077,2017/09/06,15.23,16.54\n\
68077,2017/09/06,17.31,19.05\n\
68077,2017/09/06,20.39,21.3\n\
68077,2017/09/06,21.41,22.37\n\
58381,2017/09/06,16.51,17.55\n\
58381,2017/09/06,19.34,20.55\n\
58381,2017/09/06,21.33,22.51\n\
58381,2017/09/07,14.46,16.15\n\
37500,2017/09/06,8.2,10.18\n\
37500,2017/09/06,11.37,13.34\n\
37500,2017/09/06,19.22,20.16\n\
37500,2017/09/06,21.55,22.09\n\
37500,2017/09/16,14.16,16.26\n\
37500,2017/09/16,16.58,17.48\n\
39146,2017/09/06,19.47,20.21\n\
39146,2017/09/06,20.35,21.29\n\
39146,2017/09/06,22.01,23.25\n\
39146,2017/09/16,8.03,9.56\n\
39146,2017/09/16,10.23,12.52\n\
39146,2017/09/16,13.25,14.28";
window.data = d3.csvParse(json_data, function(d){ return {
id:d.id,
date:d.date,
start_time:+d.start_time,
end_time:+d.end_time}; });
window.minDate = d3.min(window.data,function(d){ return d.date; });
window.maxDate = d3.max(window.data,function(d){ return d.date; });
window.names = window.data.map(function(d){
return d.id; });
var parseDate = d3.timeParse("%Y/%m/%d");
var displayDate = d3.timeFormat("%m/%d");
var forChartDate = d3.timeFormat("%Y/%m/%d");
var height = 500;
var width = 800;
var margin = {left: 50, right: 20, bottom: 0, top: 70};
var tooltip = d3.select("body").append("div").attr("class", "tooltip")
var svg = d3.select("body").append("svg").attr("height","1000px").attr("width","100%");
var chartGroup = svg.append("g").attr("transform","translate("+margin.left+","+(margin.top+10)+")");
var legend = svg.append("g").attr("transform","translate("+margin.left+","+(margin.top+10)+")");
var chartDate = window.minDate;
var displaySlider = function(data){
window.x = d3.scaleLinear()
.domain([0, 24])
.range([0, width]);
window.y = d3.scaleBand()
.domain(window.names)
.rangeRound([height, 0])
.paddingInner(0.3);
function make_y_gridlines() {
return d3.axisLeft(y)
};
function make_x_gridlines() {
return d3.axisBottom(x)
};
chartGroup.append("g")
.attr("class","axis y")
.call(d3.axisLeft(y))
chartGroup.append("g")
.attr("class","axis x")
.call(d3.axisBottom(x))
.attr("transform","translate(0,"+height+")")
.call(d3.axisBottom(x)
.ticks(24));
chartGroup.append("g")
.attr("class", "grid")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
);
chartGroup.append("g")
.attr("class", "grid")
.call(make_x_gridlines()
.tickSize(height)
.tickFormat("")
)
var newData = data.filter(function(d){
return d.date==chartDate;
})
displayBar(newData);
var x1 = d3.scaleTime()
.range([0, width])
.domain([parseDate(minDate), parseDate(maxDate)])
.clamp(true);
var slider = svg.append("g")
.attr("class", "slider")
.attr("transform", "translate(" + margin.left + ",30)");
slider.append("line")
.attr("class", "track")
.attr("x1", x1.range()[0])
.attr("x2", x1.range()[1])
.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "track-inset")
.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "track-overlay")
.call(d3.drag()
.on("start.interrupt", function() { slider.interrupt(); })
.on("drag end", function() { sliderFunc(x1.invert(d3.event.x)); }));
slider.insert("g", ".track-overlay")
.attr("class", "ticks")
.attr("transform", "translate(0," + 10 + ")")
.selectAll("text")
.data(x1.ticks(15))
.enter()
.append("text")
.attr("x", x1)
.attr("y", 10)
.attr("text-anchor", "middle")
.text(function(d) { return displayDate(d); });
var label = slider.append("text")
.attr("class", "label")
.attr("text-anchor", "middle")
.text(minDate)
.attr("transform", "translate(0," + (-10) + ")");
var handle = slider.insert("circle", ".track-overlay")
.attr("class", "handle")
.attr("r", 7);
function sliderFunc(h) {
handle.attr("cx", x1(h));
label.attr("x", x1(h))
.text(displayDate(h));
chartDate = forChartDate(h);
console.log(chartDate);
newData = data.filter(function(d){
return d.date==chartDate;
})
//displayText(newData);
displayBar(newData);
}
};
var displayBar = function(data){
var bars = chartGroup.selectAll(".bar")
.data(data, function(d){
console.log(d);
return d;
});
bars.exit().remove();
bar_enter = bars.enter().append("rect")
bar_enter.attr("class", "bar")
.attr("x", function(d) {
console.log(d.start_time);
return window.x(d.start_time); })
.attr("y", function(d) { return window.y(d.id); })
.attr("height", window.y.bandwidth())
.attr("fill", "green")
.transition()
.duration(600)
.attr("width", function(d) { return window.x(d.end_time-d.start_time); });
bar_enter.on('mousemove', function(d,i){
tooltip.style("opacity","1")
.style("left",(d3.event.pageX+10)+"px")
.style("top",d3.event.pageY+"px");
tooltip.html(" Start Time:"+d.start_time+" End Time:"+d.end_time);
})
bar_enter.on('mouseout', function(){
tooltip.style("opacity","0")
});
};
displaySlider(window.data);
</script>
</body>
You cannot use the whole object in the key function:
var bars = chartGroup.selectAll(".bar")
.data(data, function(d){
return d;
});
The API explains it:
key function may be specified to control which datum is assigned to which element, replacing the default join-by-index, by computing a string identifier for each datum and element. (emphasis mine)
Therefore, instead of using the whole object, use a property (like the id):
var bars = chartGroup.selectAll(".bar")
.data(data, function(d){
return d.id;
});
Here is the updated bl.ocks: https://bl.ocks.org/GerardoFurtado/57d014aa5124bdbe5774e1457816ff43/07a233d46aa3ae91bcffb8682de9ed8376c99b9a

D3 transition along segments of path and pause at coordinate values

I would like to be able to click on a circle (coordinate points); bring the marker to the position of the circle and pause at the position of the circle and then resume again along the path.
In addition I would like to activate a circle when marker is paused on them - they are clicked (or their Voronoi cell is clicked). My intention is to have an on click function to an href for the circle coordinates eventually.
I think I need to pass the index of the path coordinates into the translateAlong function instead of the time variables but can't work out how to do this.
I’m not sure if the Voronoi cells are necessary - I tried to add this thinking I could pause my transition and activate my circles with the Voronoi cells. In any case I can’t activate the circle with the Voronoi cell.
I was helped considerably recently on Stackoverflow d3 on click on circle pause and resume transition of marker along line
and I am hoping for assistance again
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>basic_animateBetweenCircles</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
path {
stroke: #848484;
fill: none;
}
circle {
fill: steelblue;
stroke: steelblue;
stroke-width: 3px;
}
.line {
fill: none;
stroke: #FE642E;
stroke-width: 4;
stroke-dasharray: 4px, 8px;
}
.point{
fill:#DF013A;
}
</style>
</head>
<body>
<script>
var width = 960,
height = 500;
var data = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
//check index of path data
for (var i = 0; i < data.length; i++) {
var coordindex = i + " " + data[i];
console.log("Coordindex: " + coordindex);
//return coordindex;
};
var duration = 20000;
var line = d3.line()
.x(function(d) {return (d)[0];})
.y(function(d) {return (d)[1];});
var voronoi = d3.voronoi()
.extent([[0, 0], [width, height]]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//path to animate - marker transitions along this path
var path = svg.append("path")
.data([data])
.attr("d", line)
.attr('class', 'line')
.attr("d", function(d) {
return line(d)
});
//voronoi
var voronoiPath = svg.append("g")
.selectAll("path")
.data(voronoi.polygons(data))
.enter().append("path")
.attr("d", polygon)
.on("touchmove mousemove", function() {
d3.select(this)
.style("fill", "purple");
});
//Want to activate circles when marker paused on them / in voronoi cell - intention is to have on click to href
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("r", 10)
.attr("transform", function(d) { return "translate(" + d + ")"; })
.on('click', function(d, i) {
d3.select(this)
.style("fill", "green");
if (d3.active(this)) {
marker.transition();
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
//console.log(pauseValues);
}, 100);
} else {
transition();
}
});
var pauseValues = {
lastTime: 0,
currentTime: 0
};
//marker to transition along path
var marker = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + (data[0]) + ")")
.on('click', function(d, i) {
if (d3.active(this)) {
marker.transition();
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
//console.log(pauseValues);
}, 100);
} else {
transition();
}
});
function transition() {
marker.transition()
.duration(duration - (duration * pauseValues.lastTime))
.attrTween("transform", translateAlong(path.node()))
.on("end", function() {
pauseValues = {
lastTime: 0,
currentTime: 0
};
transition()
});
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
t += pauseValues.lastTime;
var p = path.getPointAtLength(t * l);
pauseValues.currentTime = t;
return "translate(" + p.x + "," + p.y + ")";
};
};
}
function polygon(d) {
return "M" + d.join("L") + "Z";
}
</script>
</body>
If you want to pause at points, I would not run one transition across the entire path. Instead, I would break it up into N transitions, moving from point to point. Before starting the circle on it's next leg, you can pause it for a time. To do this, I would just transition along each line segment with a little algebra:
// copy our data
transData = data.slice();
function transition() {
marker.transition()
.ease(d3.easeLinear)
.duration(duration)
.attrTween("transform", function(){
// get our two points
// slope between them
// and intercetp
var p0 = transData.shift(),
p1 = transData[0];
m = (p0[1] - p1[1]) / (p0[0] - p1[0]),
b = p0[1] - (m * p0[0]),
i = d3.interpolateNumber(p0[0], p1[0]);
// move the point along the line
return function(t){
var x = i(t),
y = m*x + b;
return "translate(" + x + "," + y + ")";
}
})
// one line segment is complete
.on("end", function(){
// if no more movements, stop
if (transData.length <= 1) return;
iter++;
// determine if this is a "pause"
setTimeout(transition, pausePoints.indexOf(iter) !== -1 ? pauseTime : 0);
});
Running code, click a dot to start you can pause a multiple points:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>basic_animateBetweenCircles</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
path {
stroke: #848484;
fill: none;
}
circle {
fill: steelblue;
stroke: steelblue;
stroke-width: 3px;
}
.line {
fill: none;
stroke: #FE642E;
stroke-width: 4;
stroke-dasharray: 4px, 8px;
}
.point {
fill: #DF013A;
}
</style>
</head>
<body>
<script>
var width = 960,
height = 500;
var data = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var duration = 20000/data.length,
pauseTime = 2000;
var line = d3.line()
.x(function(d) {
return (d)[0];
})
.y(function(d) {
return (d)[1];
});
var voronoi = d3.voronoi()
.extent([
[0, 0],
[width, height]
]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//path to animate - marker transitions along this path
var path = svg.append("path")
.data([data])
.attr("d", line)
.attr('class', 'line')
.attr("d", function(d) {
return line(d)
});
//voronoi
var voronoiPath = svg.append("g")
.selectAll("path")
.data(voronoi.polygons(data))
.enter().append("path")
.attr("d", polygon);
//Want to activate circles when marker paused on them / in voronoi cell - intention is to have on click to href
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("r", 10)
.attr("transform", function(d) {
return "translate(" + d + ")";
})
.on('click', function(d, i) {
d3.select(this)
.style("fill", "green");
pausePoints.push(i);
if (pausePoints.length === 1)
transition();
});
//marker to transition along path
var marker = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + (data[0]) + ")");
var pausePoints = [],
iter = 0,
transData = data.slice();
function transition() {
marker.transition()
.ease(d3.easeLinear)
.duration(duration)
.attrTween("transform", function(){
var p0 = transData.shift(),
p1 = transData[0];
m = (p0[1] - p1[1]) / (p0[0] - p1[0]),
b = p0[1] - (m * p0[0]),
i = d3.interpolateNumber(p0[0], p1[0]);
return function(t){
var x = i(t),
y = m*x + b;
return "translate(" + x + "," + y + ")";
}
})
.on("end", function(){
if (transData.length <= 1) return;
iter++;
setTimeout(transition, pausePoints.indexOf(iter) !== -1 ? pauseTime : 0);
});
}
function polygon(d) {
return "M" + d.join("L") + "Z";
}
</script>
</body>

Draw curve line between nodes bugs in d3.js

I was trying to draw curves between two nodes using d3.js.
<meta charset="utf-8">
<!-- ----------------- -->
<!-- THIS PART IS CSS -->
<!-- ----------------- -->
<style>
body
{
background: lightskyblue;
}
.node {
stroke: black;
stroke-width: 4.0px;
}
.link {
stroke: slategray;
stroke-opacity: 0.6;
}
</style>
</head>
<body>
<!-- ------------------------ -->
<!-- THIS PART IS JAVASCRIPT -->
<!-- ------------------------ -->
<script src="d3.v3.js"></script>
<script>
////////////////////////////////////////////////////////////////
// Function to prepare topology background
////////////////////////////////////////////////////////////////
var width = 1280, height = 630;
var color = d3.scale.category20();
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("border","7px solid black")
;
var nodes = [], links = [];
var graph = test_case();
nodes = graph.nodes;
links = graph.links;
for(var i = 0; i < nodes.length; i++)
{
element = nodes[i];
if(element.group == 0)
{
element.x = width/2;
element.y = height/2;
element.fixed = true;
}
}
var force = d3.layout.force()
.charge(-400)
.linkDistance(300)
.size([width, height])
.links(links)
.nodes(nodes)
.on("tick", tick);
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
drawGraph();
function test_case()
{
graph =
{
"nodes":[
{"name":0,"group":0,"pos":[216,140]},
{"name":1,"group":1,"pos":[350,200]},
{"name":2,"group":1,"pos":[478,438]},
{"name":3,"group":1,"pos":[596,498]},
{"name":4,"group":1,"pos":[422,295]},
{"name":5,"group":1,"pos":[597,357]},
{"name":6,"group":1,"pos":[530,238]},
{"name":7,"group":1,"pos":[506,100]},
{"name":8,"group":1,"pos":[793,265]},
{"name":9,"group":1,"pos":[972,228]}
],
"links":[
{"source":1,"target":2,"rssi":25,"lqi":25,"lr":120},
{"source":2,"target":0,"rssi":50,"lqi":50,"lr":80},
{"source":3,"target":0,"rssi":12,"lqi":12,"lr":200},
{"source":3,"target":2,"rssi":65,"lqi":65,"lr":40},
{"source":4,"target":0,"rssi":80,"lqi":80,"lr":170},
{"source":5,"target":0,"rssi":60,"lqi":60,"lr":110},
{"source":6,"target":7,"rssi":30,"lqi":30,"lr":64},
{"source":7,"target":0,"rssi":21,"lqi":21,"lr":97},
{"source":8,"target":3,"rssi":57,"lqi":57,"lr":190},
{"source":9,"target":0,"rssi":72,"lqi":72,"lr":12}
]
};
return graph;
}
////////////////////////////////////////////////////////////////
// Function to draw topology, including nodes and links
////////////////////////////////////////////////////////////////
function drawGraph()
{
//Add links
link = link.data(force.links());
link.enter()
.append("path")
// .insert("line", ".gnode")
.attr("class", "link")
// .style("stroke", function(d) {
// if(d.rssi<=25) return "crimson";
// else if(d.rssi<=50) return "orange";
// else if(d.rssi<=75) return "royalblue";
// else return "green"; })
//.style("stroke-dasharray" ,function(d) { if(d.target==0) return "10,10"; else return "";})
// .style("stroke-width", function(d) { return d.lqi/20 + 5; })
// .style("stroke-opacity", function(d) { return (d.lr/51)*0.1 + 0.5; });
link.exit().remove();
//Add nodes with texts in them
node = node.data(force.nodes());
node.enter().append("g").attr("class", "gnode").call(force.drag);
node.append("circle")
.attr("class", function(d) { return "node group" + d.group })
.attr("r", 25)
.style("fill", function(d) { if(d.group==0) return "mediumaquamarine"; else return "beige" });
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", "+7")
.text(function(d) { return d.name; })
.attr("font-size", "22px")
.attr("font-weight", "bold")
.style("fill", "black");
node.exit().remove();
force.start();
}
function tick()
{
node.attr("transform", function(d) { return "translate(" + d.pos + ")";});
link.attr("d", function(d) {
var dx = d.target.pos[0] - d.source.pos[0],
dy = d.target.pos[1] - d.source.pos[1],
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.pos[0] + "," + d.source.pos[1] + "A" + dr + "," + dr + " 0 0,1 " + d.target.pos[0] + "," + d.target.pos[1];
});
}
</script>
</body>
</html>
but the result of the code was not as I expected which was supposed to be like this http://bl.ocks.org/d3noob/5155181 with addition of colours, opacity and width. I also want to draw arrows. Could anybody figure this out please?
I'm sorry I'm very new to stackoverflow; it's my first time I have written this topic.
For adding arrow in path following steps:
Append Marker:
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 45)
.attr("refY",-5.2)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
Append arrow in Path
link = link.data(force.links());
link.enter()
.append("path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
Path CSS:
.link {
stroke: slategray;
stroke-opacity: 0.6;
fill: none;
stroke-width: 1.5px;
}
DEMO for your code
According to radius you want to change the arrow position refer this ,and it will help for dragging nodes
First step:
Make fill none :)
.link {
stroke: slategray;
fill: none;
stroke-opacity: 0.6;
}
Working on the arrows
JSFIDDLE

D3.JS - how do I add gridlines to my pie chart

I have extended the pie-chart example at:
with pies that vary in radius depending on a percentage. I would like to add gridlines (circles) every 20 percent, but I can't figure out how.
here is the updated csv:
age,population,percent
<5,2704659,67
5-13,4499890,38
14-17,2159981,91
18-24,3853788,49
25-44,14106543,71
45-64,8819342,88
=65,612463,64
and here is the updated code with pie-parts of different radius:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
background: #333;
}
.arc path {
stroke: #fff;
stroke-width: 2px;
}
.arc grid {
stroke: #fff;
stroke-width: 1;
stroke-dasharray: 5,5;
}
.arc text {
fill:#fff;
font-size:12px;
font-weight:bold;
}
.arc line {
stroke: #fff;
}
</style>
<body>
<script src="d3.js"></script>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2 - 10;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(function(d) { return 50 + (radius - 50) * d.data.percent / 100; })
.innerRadius(20);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var grid = d3.svg.area.radial()
.radius(150);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
d.percent = d.percent;
});
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
});
</script>
First set the number of ticks:
var numTicks = 5; // Each tick is 20%
Then create the data to create the gridlines:
var sdat = [];
for (i=0; i<=numTicks; i++) {
sdat[i] = (radius/numTicks) * i;
}
And then you can use a function to create the radial gridlines, and you can call it from within the d3.csv block:
addCircleAxes = function() {
var circleAxes, i;
svg.selectAll('.circle-ticks').remove();
circleAxes = svg.selectAll('.circle-ticks')
.data(sdat)
.enter().append('svg:g')
.attr("class", "circle-ticks");
// radial tick lines
circleAxes.append("svg:circle")
.attr("r", String)
.attr("class", "circle")
.style("stroke", "#CCC")
.style("opacity", 0.5)
.style("fill", "none");
// Labels for each circle
circleAxes.append("svg:text")
.attr("text-anchor", "center")
.attr("dy", function(d) { return d - 5 })
.style("fill", "#fff")
.text(function(d,i) { return i * (100/numTicks) });
};
An example is here: http://bl.ocks.org/3994129
(Borrowed from: http://kreese.net/blog/2012/08/26/d3-js-creating-a-polar-area-diagram-radial-bar-chart/)

Resources