I want to make a draggable <g> element containing a <circle> and a <text> but I failed.
Now I have this code:
var margin = {top: -5, right: -5, bottom: -5, left: -5},
width = 1960 - margin.left - margin.right,
height = 1500 - margin.top - margin.bottom;
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var dotContainer = svg.append("g")
.attr("class", "dotContainer")
.datum({x:220, y:120})
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.call(drag);
var dot = dotContainer.append("circle")
.attr("class", "dot")
.datum({x:220, y:120})
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5).call(drag);
var text = dotContainer.append("text")
.datum({x:220, y:120})
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text('Title');
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<circle> element is perfectly draggable, ok. But I need to apply dragging to the whole <g> element to drag my circle together with the <text> element.
How can I do this?
When I apply call(drag) to dotContainer like this
dotContainer.call(drag);
then nothing works.
You should rewrite your dotContainer variable this way:
var dotContainer = svg.append("g")
.attr("class", "dotContainer")
.datum({x:220, y:120})
.attr("transform", function(d) { return 'translate(' + d.x + ' '+ d.y + ')'; })
.call(drag);
Remove .call(drag) for dot variable and rewrite dragged function like this:
function dragged(d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
});
}
Thus we use transform attribute for an initial container position and for the position update during the dragging.
Check demo in the hidden snippet:
var margin = {top: -5, right: -5, bottom: -5, left: -5},
width = 1960 - margin.left - margin.right,
height = 1500 - margin.top - margin.bottom;
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var dotContainer = svg.append("g")
.attr("class", "dotContainer")
.datum({x:20, y:20})
.attr("transform", function(d) { return 'translate(' + d.x + ' '+ d.y + ')'; })
.call(drag);
var dot = dotContainer.append("circle")
.attr("class", "dot")
.datum({x:20, y:20})
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5)
var text = dotContainer.append("text")
.datum({x:20, y:20})
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text('Title');
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
});
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
.dotContainer {
cursor: move;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
Related
var drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault;
start_x_old = +d3.event.x;
}
function dragged(e) {
xDrag.attr("transform", "translate(" + [( d3.event.x - start_x_old), 0] + ")");
xDrag1.attr("transform", "translate(" + [(d3.event.x - start_x_old), height] + ")");
}
function dragended(d) {
start_x_new = event.clientX;
console.log("old`enter code here`", start_x_old);
console.log("new", start_x_new);
//for future use
}`
This is the code that i wrote for drag where xDrag and xDrag1 are elements on which drag is being called. For the entire working code visit the fiddle https://jsfiddle.net/zaarkoevilor/vzqu1wrc/15/
You don't need to play around with start_x_old and start_x_new...
Just set a datum to your selections:
var xDrag = svg.selectAll(".serie")
.datum({x: 0})
.call(drag);
var xDrag1 = svg.selectAll(".axis--x")
.datum({x: 0})
.call(drag);
And use that in your drag:
function dragged(d) {
xDrag.attr("transform", "translate(" + [(d.x = d3.event.x), 0] + ")");
xDrag1.attr("transform", "translate(" + [(d.x = d3.event.x), height] + ")");
}
Here is your updated fiddle: https://jsfiddle.net/z0d8xpjg/
And the same code in the Stack snippet:
var start_x_old, start_x_new = 0,
x_diff, xtranslated,
svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = [{
labelName: "Financial Data",
no: 27,
yes: 10
}, {
labelName: "Production & completion data",
no: 24,
yes: 32
}, {
labelName: "Support Center Data",
no: 18,
yes: 21
}];
/*var drag = d3.drag()
.on('start.interrupt', function () {
xDrag.interrupt();
xDrag1.interrupt();
})
.on('start drag', function () {
xDrag.attr("transform", "translate(" + (d3.event.x) + "," + 0 +")")
xDrag1.attr("transform", "translate(" + (d3.event.x) + "," + height +")")
});*/
var drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault;
}
function dragged(d) {
//console.log("dragging");
//x_diff = start_x_old - start_x_new;
xDrag.attr("transform", "translate(" + [(d.x = d3.event.x), 0] + ")");
xDrag1.attr("transform", "translate(" + [(d.x = d3.event.x), height] + ")");
}
function dragended(d) {
xDrag.datum({
x: d3.event.x
});
xDrag1.datum({
x: d3.event.x
});
}
data.sort(function(a, b) {
return (b.yes + b.no) - (a.yes + a.no);
});
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.align(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(['#42c8c5', '#d05a59']);
var stack = d3.stack();
data.sort(function(a, b) {
return (b.yes + b.no) - (a.yes + a.no);
});
x.domain(data.map(function(d) {
return d.labelName;
}));
y.domain([0, d3.max(data, function(d) {
return (d.yes + d.no);
})]).nice();
z.domain(Object.keys(data[0]).slice(1));
g.selectAll(".serie")
.data(stack.keys(Object.keys(data[0]).slice(1))(data))
.enter().append("g")
.attr("class", "serie")
.attr("fill", function(d) {
return z(d.key);
})
.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.data.labelName);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", x.bandwidth());
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.style("text-anchor", "end")
.attr("transform", "rotate(-60)");
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(10, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks(10).pop()))
.attr("dy", "0.35em")
.attr("text-anchor", "start")
.attr("fill", "#000");
var xDrag = svg.selectAll(".serie")
.datum({
x: 0
})
.call(drag);
var xDrag1 = svg.selectAll(".axis--x")
.datum({
x: 0
})
.call(drag);
.bar {
fill: steelblue;
}
.axis path {
display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="700" height="500"></svg>
I'm using d3js v3, and I want to upgrade to v4, but upgrading to v4 causes zoom to be undefined
here's the code :
var zoom = d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoomed);
Error as shown in the console :
index.html:192 Uncaught TypeError: Cannot read property 'zoom' of undefined
I used this example as a reference to my implementation :
https://bl.ocks.org/mbostock/6123708
The code # Github pages :
http://jmargieh.github.io/NBA-shots-chart-d3js/
Thanks for the help.
Try this, somethings have been renamed thats all.
var margin = {top: -5, right: -5, bottom: -5, left: -5},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var zoom = d3.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var drag = d3.drag()
.subject(function(d) { return d; })
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
container.append("g")
.attr("class", "x axis")
.selectAll("line")
.data(d3.range(0, width, 10))
.enter().append("line")
.attr("x1", function(d) { return d; })
.attr("y1", 0)
.attr("x2", function(d) { return d; })
.attr("y2", height);
container.append("g")
.attr("class", "y axis")
.selectAll("line")
.data(d3.range(0, height, 10))
.enter().append("line")
.attr("x1", 0)
.attr("y1", function(d) { return d; })
.attr("x2", width)
.attr("y2", function(d) { return d; });
d3.tsv("dots.tsv", dottype, function(error, dots) {
dot = container.append("g")
.attr("class", "dot")
.selectAll("circle")
.data(dots)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.call(drag);
});
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
For zooming v3 vs v5 (which might be the same changes to goto v4), I posted in another related stackoverflow about this if this is useful to people visiting this issue: dagre-d3 js Zoom Fit to all contents
Code Snippet :
We are using d3.js for this.
Sankey diagrams is made up of nodes and links.
Here the data comes from json file.
So how to make all the nodes clickable.
Which methods can we use with the rectangles so that we can make the nodes clickable.
<script>
var margin = {top: 1, right: 1, bottom: 6, left: 1},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), //decimal places
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scale.category20();
var svg = d3.select("#chart").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 + ")");
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
//d3.json("energy.json", function(energy) {
d3.json("numbers-subset.json", function(energy) {
sankey
.nodes(energy.nodes)
.links(energy.links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(energy.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(energy.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.split("|")[0]); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
});
</script>
D3.js is very powerful library give you control over each event to each pixel.
but we need to do one thing in the case of events.
Overlapping event we need to by forget .
Add following code in code.
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; })
.on("click",function(d){
if (d3.event.defaultPrevented) return;
alert("clicked!"+d.value);
})
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() {
this.parentNode.appendChild(this); })
.on("drag", dragmove));
I would like to force one branch of sankey diagram to be on top.
Instead of diagram like this:
would like to generate diagram where nodes 1, 2, 7, 15, 10 and 14 are always on top:
Link to fiddle with current code: http://jsfiddle.net/w5jfp9t0/1/
var margin = {top: 1, right: 1, bottom: 6, left: 1};
var width = 1052 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d); },
color = d3.scale.category20();
var svg = d3.select("#chart_sankey").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 + ")");
var sankey = d3.sankey()
.nodeWidth(35)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
raw = '{"nodes":[{"name":"Node 1"},{"name":"Node 2"},{"name":"Node 3"},{"name":"Node 4"},{"name":"Node 5"},{"name":"Node 6"},{"name":"Node 7"},{"name":"Node 8"},{"name":"Node 9"},{"name":"Node 10"},{"name":"Node 11"},{"name":"Node 12"},{"name":"Node 13"},{"name":"Node 14"},{"name":"Node 15"}],"links":[{"source":9,"target":13,"value":25},{"source":14,"target":9,"value":37},{"source":14,"target":11,"value":16},{"source":14,"target":12,"value":8},{"source":14,"target":10,"value":68},{"source":6,"target":14,"value":154},{"source":6,"target":8,"value":40},{"source":1,"target":6,"value":345},{"source":1,"target":7,"value":66},{"source":1,"target":3,"value":17},{"source":1,"target":4,"value":25},{"source":1,"target":5,"value":117},{"source":0,"target":1,"value":692},{"source":0,"target":2,"value":19}]}';
data = JSON.parse(raw);
sankey.nodes(data.nodes)
.links(data.links)
.layout(32);
var link = svg.append("g")
.selectAll(".link")
.data(data.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
var nodes = data.nodes;
var node = svg.append("g").selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
sankey.relayout();
node.filter(function(d) { return d.value != 0; }) // append text only if node value is not zero
.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.filter(function(d) { return d.value != 0; }) // append text only if node value is not zero
.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x == 0; }) // at first column append text after column
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
What do I need to change to accomplish this?
Note
To be honest, I never used that plugin. I don't see an option to get the desired behaviour directly - thus I looked at the source of sankey.js to make the adjustments. Below I show how I'd modify - you might want to do it more thoroughly :)
Idea
Looking at the code of sankey.js, you see that the nodes are placed (y-direction) using the center function:
function center(node) {
return node.y + node.dy / 2;
}
As I don't see a parameter to change that behaviour, you can change it to:
function center(node) {
return 0;
}
If you then also revert the sorting order:
function ascendingDepth(a, b) {
return b.y - a.y;
}
you get the following picture:
First, I'm quite new in D3.
I'm trying to implement different behaviors in a single D3 graph, using these examples :
Drag + Zoom
Force-directed Graph
But my graph freezes after few seconds and I don't understand why...
Here is my code : http://jsfiddle.net/blt909/aVhd8/20/ [WORKING FINE VERSION]
var margin = {top: -5, right: -5, bottom: -5, left: -5};
var width = 500 - margin.left - margin.right,
height = 400- margin.top - margin.bottom;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom]);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var svg = d3.select("#map").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var tooltip = d3.select("body")
.append("foreignObject")
.append("xhtml:div")
.attr("id", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("color", "#eeeeee")
.style("visibility", "hidden")
.text("");
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.call(drag);
node.append("circle")
.attr("r", function(d) { return d.weight * 2+ 12; })
.style("fill", function(d) { return color(1/d.rating); });
force.on("tick", function() {
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 + ")"; });
});
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index];
}
node.on("mouseover", function(d){
node.classed("node-active", function(o) {
thisOpacity = isConnected(d, o) ? true : false;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.classed("link-active", function(o) {
return o.source === d || o.target === d ? true : false;
});
d3.select(this).classed("node-active", true);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", (d.weight * 2+ 12)*1.5);
})
.on("mouseout", function(d){
node.classed("node-active", false);
link.classed("link-active", false);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", d.weight * 2+ 12);
});
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
//ADDING force.start(); HERE SOLVES THE ISSUE
force.start();
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
Thanks for your help
Putting force.start(); inside dragstarted function stops freezing.
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
force.start();
};
Here is fiddle