Related
This is a follow on from my previous question
d3 rect in one group interfering with rect in another group
Two issues:
Incorrect drag behaviour. When clicking on the second rect to drag it, it jumps to where the third one is.
I added a anonymous function which runs when the svg in clicked on anywhere. This should add a new rect. However that is the working.
I know I should have only one issue per question but these are related and I suspect they will be solved together.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*.active {
stroke: #000;
stroke-width: 2px;
}*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
// create group
var group = svg.selectAll("g")
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 200)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
}
data.push(newData);
group.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
</script>
Here are the explanations to your issues:
You are reassigning var groups, that is, you have two var groups in your code, the last one overwriting the first one. Just remove the last variable.
In your function to append new rectangles, you are using an update selection that selects rectangles. However, your enter selection appends groups (<g>) elements, not rectangles.
Have a look at the refactored function, it binds the data to a newly created group and appends the rectangle to that group:
var newGroup = svg.selectAll(".group")
.data(data, function(d) {
return d.x
})
.enter()
.append("g")
//etc...
newGroup.append("rect")
//etc...
Also, use a key selection in the data binding, so you know exactly what rectangle is being dragged:
.data(data, function(d){return d.x})
Here is your code with those changes:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*.active {
stroke: #000;
stroke-width: 2px;
}*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data, function(d){return d.x})
.enter().append("g")
.attr("class", "group")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: coords[0],
}
data.push(newData);
var newGroup = svg.selectAll(".group")
.data(data, function(d){return d.x})
.enter()
.append("g")
.attr("class", "group")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
newGroup.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
</script>
For correctly drag-and-drop behavior, rewrite your code like this:
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
}
data.push(newData);
group.selectAll("rect")
.data(data)
.exit()
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.2/d3.min.js"></script>
<svg width="960" height="500"></svg>
But, what the problem with adding the new element, I have no idea.
I am looking to animate this chart.
http://jsfiddle.net/NYEaX/1554/
var invisiblebubble = mask.append("circle")
.data(data);
invisiblebubble
.attr("cx", 550)
.attr("cy", 250)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
I've animated the mask circle - looking to implement other animations/suggestions for the labels. If they tween like a pie chart, tween in an arc, fade in etc..
I did create a transition on the radius of the circle - kind of looked like the warner bros ending.
var invisiblebubble = mask.append("circle")
.data(data);
invisiblebubble
.attr("cx", 550)
.attr("cy", 250)
.transition()
.duration(2000)
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
How do I animate other features like the labels/pointers
I've managed to improve the inverse bubble chart with this code.
Where I have to set a fixed size for the circle first, mask it, then animate it - for the purpose of the labels.
function maskMaker(el){
var backcolor = $(el).data("color");
var backopacity = $(el).data("opacity");
var height = $(el).data("height");
var width = $(el).data("width");
var labelName = $(el).data("label-name");
var bubbleValue = $(el).data("bubble-value");
var displaceLeft = $(el).data("displace-left");
var displaceTop = $(el).data("displace-top");
var data = [{
"label": labelName,
"x": displaceLeft,
"y": displaceTop,
"value": bubbleValue
}];
console.log("MASK data", data);
// Set the main elements for the series chart
var svgroot = d3.select($(el)[0]).append("svg");
// filters go in defs element
var defs = svgroot.append("defs");
var mask = defs.append("mask")
.attr("id", "myMask");
mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "white")
.style("opacity", backopacity);
var invisiblebubble = mask.append("circle")
.data(data);
//create a fixed bubble first
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", function(d) {
return d.value-20;
});
//now mask the fixed circle
var masker = defs.append(function() {
return mask.node().cloneNode(true)
})
.attr("id", "myMaskForPointer")
.select("rect")
.style("opacity", 0.8);
//animate this circle
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
//apply the rest of the chart elements
var svg = svgroot
.attr("class", "series")
.attr("width", "1120px")
.attr("height", "500px")
.append("g")
.attr("transform", "translate(0,0)")
var rect = svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("mask", "url(#myMask)")
.style("fill", backcolor);
/*
//__labels
var centrallabel = svgroot.append("g")
.attr("class", "centrallabel")
.data(data);
centrallabel
.append("text")
.attr("text-anchor", "middle")
.attr("x", 550)
.attr("y", 250 + 10)
.text(function(d) {
return "200";
})
*/
function addLabel(){
//__labels
var labels = svgroot.append("g")
.attr("class", "labels")
//__ enter
var labels = labels.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
//__ update
//labels
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y-10;
})
.text(function(d) {
return d.label;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width / 2 - 2;
d.ox = d.x + bbox.width / 2 + 2;
d.sy = d.oy = d.y + 5;
d.cx = 550;
d.cy = 250;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
//__ exit
labels.exit().remove();
//__labels
}
function addPointer(){
//__pointers
var pointers = svgroot.append("g")
.attr("class", "pointers");
var dots = defs.append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3);
var pointers = pointers.selectAll("path.pointer")
.data(data);
//__ enter
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.attr("marker-end", "url(#circ)")
.attr("mask", "url(#myMaskForPointer)")
//__ update
//pointers
.attr("d", function(d) {
if (d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
//__ exit
pointers.exit().remove();
//__pointers
}
//delay for the mask
setTimeout(function(){
addLabel();
addPointer();
}, 1000);
}
Fade in could be implementing like :
centrallabel
.append("text")
.attr("text-anchor", "middle")
.attr("x", 550)
.style("opacity",0)
.attr("y", 250 + 10)
.text(function(d) {
return "200";
})
.transition()
.duration(2000)
.style("opacity", 1)
As far as other animations you could start the pointer line off screen then transition it to it's endpoint. Or start with its length at 0 and transition it to full size. Transform/Translate would probably be useful - see (https://bl.ocks.org/mbostock/1345853)
I would like to prevent the mouseout action of an element when the user click on this element. For an example see this JSFiddle (the circle disappear even if I click on the label).
Is there an easy way to achieve my objective with d3.js? Thank you!
The JSFiddle example code:
var svg = d3.select("#content")
.append("svg")
.attr("width", 600)
.attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 50)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var circle = g.append("circle")
.style("fill", "Orange")
.attr("cx", 150)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass", true);
function mouseover(p){
d3.selectAll("circle.someClass").classed("hide", false);
}
function mouseout(p){
d3.selectAll("circle.someClass").classed("hide", true);
}
function click(p){
d3.selectAll("circle.someClass").classed("hide", false);
}
If you plan to only have one element that controls the circle, use a "flag". Messing with conditionally registering/unregistering events is not a good idea.
Check this updated version of your fiddle:
https://jsfiddle.net/cze9rqf7/
var svg = d3.select("#content")
.append("svg")
.attr("width", 600)
.attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 50)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var circle = g.append("circle")
.style("fill", "Orange")
.attr("cx", 150)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass", true);
var isClicked = false;
function mouseover(p){
d3.selectAll("circle.someClass").classed("hide", false);
}
function mouseout(p){
if(!isClicked) {
d3.selectAll("circle.someClass").classed("hide", true);
}
}
function click(p){
isClicked = !isClicked;
d3.selectAll("circle.someClass").classed("hide", false);
}
EDITS For Comments
If you need to "remember" state per element, instead of using a global, you should be using data-binding on those elements:
var text = g.append("text")
.datum({isClicked: false})
.text("Click me")
...
function mouseout(p){
// p is the data-bound object
if(!p.isClicked) {
var className = d3.select(this).attr("class");
d3.selectAll("circle."+className).classed("hide", true);
}
}
function click(p){
// on click edit the data-bound object
p.isClicked = !p.isClicked;
var className = d3.select(this).attr("class");
d3.selectAll("circle."+className).classed("hide", false);
}
Updated fiddle here.
Here is an answer without any "flag":
Given that you have many labels, one option is removing the mouseout for the clicked element:
d3.select(this).on("mouseout", null);
Here is your updated fiddle: https://jsfiddle.net/gerardofurtado/38p18pLt/
And the same code in the Stack snippet:
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 50)
.classed("someClass", true)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var text2 = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 150)
.classed("someClass2", true)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var circle = g.append("circle")
.style("fill", "Orange")
.attr("cx", 150)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass", true);
var circle2 = g.append("circle")
.style("fill", "Green")
.attr("cx", 250)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass2", true);
function mouseover(p) {
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", false);
}
function mouseout(p) {
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", true);
}
function click(p) {
d3.select(this).on("mouseout", null);
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", false);
}
.hide {
display: none;
}
text {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You can also toggle the click, making the mouseout work again:
if(d3.select(this)[0][0].__onmouseout){
d3.select(this).on("mouseout", null);
} else {
d3.select(this).on("mouseout", mouseout);
}
Here is the fiddle with the "toggle" function: https://jsfiddle.net/gerardofurtado/4zb9gL9r/1/
And the same code in the Stack snippet:
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 50)
.classed("someClass", true)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var text2 = g.append("text")
.text("Click me")
.style("fill", "Blue")
.attr("x", 50)
.attr("y", 150)
.classed("someClass2", true)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click);
var circle = g.append("circle")
.style("fill", "Orange")
.attr("cx", 150)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass", true);
var circle2 = g.append("circle")
.style("fill", "Green")
.attr("cx", 250)
.attr("cy", 90)
.attr("r", 15)
.classed("hide", true)
.classed("someClass2", true);
function mouseover(p) {
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", false);
}
function mouseout(p) {
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", true);
}
function click(p) {
if (d3.select(this)[0][0].__onmouseout) {
d3.select(this).on("mouseout", null);
} else {
d3.select(this).on("mouseout", mouseout);
}
var className = d3.select(this).attr("class");
d3.selectAll("circle." + className).classed("hide", false);
}
.hide {
display: none;
}
text {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I'm trying to create something that allows the user to scroll through an array of different links. The nodes stay the same, but the links change when the user clicks to see the next one.
However, it only shows one link in each array. Some arrays only have one link so that's okay, but the ones with more than one link need to be displayed correctly.
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//var links = <?php echo json_encode(array_slice($data["links"], 0, 1)); ?>;
var links = <?php echo json_encode($links); ?>;
var nodes = <?php echo json_encode($nodes); ?>;
var count = <?php echo count($data); ?>;
var data_set = <?php echo json_encode($data); ?>;
//console.log(data_set);
//document.getElementById("demo").innerHTML = links;
max_count = links.length;
function draw()
{
function update_clique_view(direction)
{
geo_long_12_index += direction;
if(geo_long_12_index > max_count)
geo_long_12_index = 0;
else if(geo_long_12_index < 0)
geo_long_12_index = max_count - 1;
current_clique = links[geo_long_12_index];
d3.select("#link_number").remove();
svg.append("text")
.attr("id", "link_number")
.attr("x", width/2)
.attr("y", 20)
.attr("dy", ".35em")
.attr("opacity", 0.7)
.style("fill", "#FFFFFF")
.text(geo_long_12_index);
console.log(current_clique);
link.data(current_clique);
force.links(current_clique);
force.start();
}
var width = 800,
height = 600;
geo_long_12_index = 0;
if(links.length > 0)
{
current_clique = links[geo_long_12_index];
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("rx", "20")
.attr("ry", "20")
.style("fill", "#000022")
.attr("class", "background");
svg.append("text")
.attr("id", "link_number")
.attr("x", width/2)
.attr("y", 20)
.attr("dy", ".35em")
.attr("opacity", 0.7)
.style("fill", "#FFFFFF")
.text(geo_long_12_index);
// Navigation arrows
var leftarrow = svg.append("svg:image")
.attr("xlink:href", "/cgi-bin/images/left_arrow.svg")
.attr("width", 20)
.attr("height", 20)
.attr("x", (width / 2) - 25)
.attr("y",20)
.attr("opacity", 0.7)
.on("click", function(d,i) {update_clique_view(-1);})
.on("mouseover", function(d) {d3.select(this).attr("opacity", 1);})
.on("mouseout", function(d) {d3.select(this).attr("opacity", 0.7);});
var rightarrow = svg.append("svg:image")
.attr("xlink:href","/cgi-bin/images/right_arrow.svg")
.attr("width", 20)
.attr("height", 20)
.attr("x", (width / 2) + 35)
.attr("y",20)
.attr("opacity", 0.7)
.on("click", function(d,i) {update_clique_view(1);})
.on("mouseover", function(d) {d3.select(this).attr("opacity", 1);})
.on("mouseout", function(d) {d3.select(this).attr("opacity", 0.7);});
var position_label = svg.append("text")
.attr("x", 10)
.attr("y", 30);
var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([width, height]);
force
.nodes(nodes)
.links(current_clique)
.start();
var link = svg.selectAll(".link")
.data(current_clique)
.enter().append("line")
.style("stroke", function(d) { return d.colour; })
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 5)
.style("fill", function(d) { return d.colour; })
.attr("class", "planet")
.on("mouseover", function(d) {d3.select(this).attr("r", 10);})
.on("mouseout", function(d) {d3.select(this).attr("r", 5);});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
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 + ")"; });
});
}
draw();
</script>
var links = [[{"source":17,"target":22,"orb":0.058311111111109,"colour":"grey"}],[{"source":20,"target":21,"orb":0.2079,"colour":"grey"}],[{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":3,"target":21,"orb":1.0416277777778,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":0,"target":17,"orb":0.71342777777778,"colour":"grey"},{"source":0,"target":22,"orb":0.77173888888888,"colour":"grey"},{"source":17,"target":22,"orb":0.058311111111109,"colour":"grey"}],[{"source":11,"target":14,"orb":-1.6950111111111,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":7,"target":8,"orb":0.41579999999996,"colour":"grey"},{"source":7,"target":12,"orb":-0.047427777777784,"colour":"grey"},{"source":7,"target":15,"orb":0.6553222222222,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":4,"target":7,"orb":-0.53862777777772,"colour":"grey"},{"source":4,"target":8,"orb":-0.12282777777776,"colour":"grey"},{"source":4,"target":12,"orb":-0.5860555555555,"colour":"grey"},{"source":4,"target":15,"orb":0.11669444444448,"colour":"grey"},{"source":7,"target":8,"orb":0.41579999999996,"colour":"grey"},{"source":7,"target":12,"orb":-0.047427777777784,"colour":"grey"},{"source":7,"target":15,"orb":0.6553222222222,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":19,"orb":-1.8974666666667,"colour":"grey"},{"source":13,"target":19,"orb":2.8450944444444,"colour":"grey"}],[{"source":11,"target":20,"orb":-2.9160166666667,"colour":"grey"},{"source":11,"target":21,"orb":-2.7081166666667,"colour":"grey"},{"source":20,"target":21,"orb":0.2079,"colour":"grey"}],[{"source":3,"target":9,"orb":-2.1064388888889,"colour":"grey"},{"source":3,"target":20,"orb":0.83372777777777,"colour":"grey"},{"source":9,"target":20,"orb":-2.9401666666667,"colour":"grey"}],[{"source":2,"target":9,"orb":2.9672611111111,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":9,"target":13,"orb":2.0196333333333,"colour":"grey"},{"source":9,"target":16,"orb":1.6376055555556,"colour":"grey"},{"source":9,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":2,"target":5,"orb":-2.4144611111111,"colour":"grey"},{"source":2,"target":6,"orb":1.3803722222222,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":2,"target":19,"orb":-1.8974666666667,"colour":"grey"},{"source":5,"target":6,"orb":-1.0340888888889,"colour":"grey"},{"source":5,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":5,"target":19,"orb":-0.51699444444445,"colour":"grey"},{"source":6,"target":18,"orb":1.6567722222222,"colour":"grey"},{"source":6,"target":19,"orb":-0.51709444444444,"colour":"grey"},{"source":18,"target":19,"orb":2.1738666666667,"colour":"grey"}],[{"source":1,"target":2,"orb":-1.2271277777778,"colour":"grey"},{"source":1,"target":6,"orb":-2.6075,"colour":"grey"},{"source":1,"target":13,"orb":-0.27950000000001,"colour":"grey"},{"source":1,"target":16,"orb":0.10252777777777,"colour":"grey"},{"source":1,"target":18,"orb":-0.95072777777779,"colour":"grey"},{"source":2,"target":6,"orb":1.3803722222222,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":6,"target":13,"orb":2.328,"colour":"grey"},{"source":6,"target":16,"orb":2.7100277777778,"colour":"grey"},{"source":6,"target":18,"orb":1.6567722222222,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":1,"target":2,"orb":-1.2271277777778,"colour":"grey"},{"source":1,"target":9,"orb":1.7401333333333,"colour":"grey"},{"source":1,"target":13,"orb":-0.27950000000001,"colour":"grey"},{"source":1,"target":16,"orb":0.10252777777777,"colour":"grey"},{"source":1,"target":18,"orb":-0.95072777777779,"colour":"grey"},{"source":2,"target":9,"orb":2.9672611111111,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":9,"target":13,"orb":2.0196333333333,"colour":"grey"},{"source":9,"target":16,"orb":1.6376055555556,"colour":"grey"},{"source":9,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}]];
var nodes = [{"name":"EARTHSUN","colour":"darkgreen"},{"name":"MERCURY","colour":"cyan"},{"name":"VENUS","colour":"magenta"},{"name":"MARS","colour":"red"},{"name":"JUPITER","colour":"gold"},{"name":"SATURN","colour":"darkslategray"},{"name":"URANUS","colour":"blue"},{"name":"NEPTUNE","colour":"blueviolet"},{"name":"PLUTO","colour":"maroon"},{"name":"CHIRON","colour":"orange"},{"name":"CERES","colour":"sandybrown"},{"name":"JUNO","colour":"orchid"},{"name":"PALLAS","colour":"royalblue"},{"name":"VESTA","colour":"pink"},{"name":"SAPPHO","colour":"plum"},{"name":"SEDNA","colour":"slategray"},{"name":"MARSSATURN","colour":null},{"name":"JUPITERSATURN","colour":"brown"},{"name":"SATURNCHIRON","colour":"indianred"},{"name":"SATURNURANUS","colour":null},{"name":"SATURNNEPTUNE","colour":null},{"name":"SATURNPLUTO","colour":null},{"name":"SATURNSEDNA","colour":null}];
Thanks for looking at this.
I have a problem with link.exit().remove(); and node.exit().remove();. If I set it in the initializeGraph method then I get several errors with the tick function I think. So my question is how or why do I get those errors:
Uncaught TypeError: undefined is not a function graph-d3.js:156initializeGraph graph-d3.js:156updateForceUsingNewNodes graph-d3.js:108createGraph graph-d3.js:18$.ajax.success ajax-stuff.js:106j jquery-2.1.1.min.js:2k.fireWith jquery-2.1.1.min.js:2x jquery-2.1.1.min.js:4n.prop.on.c jquery-2.1.1.min.js:4n.event.dispatch jquery-2.1.1.min.js:3r.handle jquery-2.1.1.min.js:3
3Error: Invalid value for <line> attribute x1="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute y1="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute x2="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute y2="NaN" d3.min.js:1
Uncaught TypeError: Cannot read property 'attr' of undefined
Here is an exerpt of the source code. Not important lines are removed:
var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var width = 700, height = 200;
var boxIDName = "#main-rightinfo";
var currentJSON;
var container;
var zoom = d3.behavior.zoom()
.scaleExtent([0.4, 5]);
var drag = force.drag();
function createGraph(newJSON){
if (alreadyThere){
svg.remove();
nodeCircles = {};
}
updateForceUsingNewNodes(generateObjects(newJSON));
alreadyThere = true;
currentJSON = newJSON;
force.start();
}
function updateGraph(newJSON){
svg.remove();
findDuplicatesAndSetEmpty(newJSON);
deleteEmptyObjectsInJSON(newJSON);
currentJSON = currentJSON.concat(newJSON);
updateForceUsingNewNodes(generateObjects(currentJSON));
force.start();
}
//here are some methods forming the json and array...
function initializeGraph(){
zoom.on("zoom", zoomed);
drag.on("dragstart", dragstart);
force
.size([width, height])
.gravity(.1)
.charge(-400)
.friction(0.9)
.theta(0.9)
.linkStrength(0.9)
.distance(55)
.alpha(0.1)
.on("tick", tick);
svg = d3.select("#main-right")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom).on("dblclick.zoom", null);
svg
.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 32)
.attr("refY", -0.05)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr('fill', '#359AF4');
container = svg.append("g");
link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)");
node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", function(d) { click(d); })
.on("dblclick", function(d) { dblclick(d); })
.on('contextmenu', function(data, index) {
d3.event.preventDefault();
updateGraphByRemoveElement(data, index);
})
.call(drag);
node
.append("circle")
.attr("r", 20)
.attr("cx", 0)
.attr("cy", 0)
.style("fill", '#eee')
.style("stroke", '#fff')
.style("stroke-width", '0.5px');
node
.append("image")
.attr("xlink:href", function(d) {
if (d.class == "Person") {
return "pics/node_person.svg";
} else {
return "pics/node_appln.svg";
}} )
.attr("x", -20)
.attr("y", -20)
.attr("width", 40)
.attr("height", 40)
.attr("color", "white");
node
.append("text")
.attr("x", 20)
.attr("y", 4)
.style("fill", "#bbb")
.text(function(d) { return d.name; });
node
.append("circle")
.attr("r", 7)
.attr("cx", 0)
.attr("cy", -16)
.style("fill", '#359AF4');
node
.append("text")
.attr("text-anchor", "center")
.attr("x", -3)
.attr("y", -13)
.style("fill", "white")
.text(function(d) { return d.linkCount; });
function tick() {
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 + ")"; });
}
}
//here are some functions tick, mousedown and so on...
Well as you can see the svg.remove(); from the code is not needed. But until the exit().remove() do not work, it's needed. So yeah how to handle that with the tickevent/ .exit().remove().
Thank you for any tips.
PS: I used this very basic one https://gist.github.com/mbostock/1095795
and another which is very close to mine D3.js - exit() section does not remove all data
and this working not working, too Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't?
Whole code or let's say how I think it should run, but currently getting some errors (not identical to the code above, but just changed some lines)
var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var width = 700, height = 400;
var boxIDName = "#main-rightinfo";
var currentJSON;
var container;
var zoom = d3.behavior.zoom()
.scaleExtent([0.4, 5]);
var drag = force.drag();
function createGraph(newJSON){
if (alreadyThere){
//svg.remove();
nodeCircles = {};
}
updateForceUsingNewNodes(generateObjects(newJSON));
alreadyThere = true;
currentJSON = newJSON;
force.start();
}
function updateGraph(newJSON){
//svg.remove();
findDuplicatesAndSetEmpty(newJSON);
deleteEmptyObjectsInJSON(newJSON);
currentJSON = currentJSON.concat(newJSON);
updateForceUsingNewNodes(generateObjects(currentJSON));
force.start();
}
function findDuplicatesAndSetEmpty(newJSON){
for (var i = 0; i < currentJSON.length; i++) {
for (var o = 0; o < newJSON.length; o++) {
if ((currentJSON[i].source.ID == newJSON[o].source) && (currentJSON[i].target.ID == newJSON[o].target)
|| (currentJSON[i].source.ID == newJSON[o].target) && (currentJSON[i].target.ID == newJSON[o].source)){
newJSON[o] = {};
}
}
}
}
function deleteEmptyObjectsInJSON(json){
for (var i = 0; i < json.length; i++) {
var y = json[i].source;
if (y==="null" || y===null || y==="" || typeof y === "undefined"){
json.splice(i,1);
i--;
}
}
}
function updateGraphByRemoveElement(clickedNode, index){
svg.remove();
var json4Splicing = currentJSON;
for (var i = 0; i < json4Splicing.length; i++) {
if (json4Splicing[i].source.ID == clickedNode.ID){
json4Splicing[i] = {};
} else if (json4Splicing[i].target.ID == clickedNode.ID){
json4Splicing[i] = {};
}
}
deleteEmptyObjectsInJSON(json4Splicing);
deleteNode(force.nodes(),clickedNode);
currentJSON = json4Splicing;
updateForceRemoveElement(generateObjects(currentJSON));
}
function deleteNode(allNodes, clickedNode){
allNodes.forEach(function(node) {
if (node == clickedNode){
force.links().forEach(function(link) {
if (node.ID == link.source.ID){
link.target.linkCount--;
}
if (node.ID == link.target.ID){
link.source.linkCount--;
}
});
node.linkCount = 0;
}
});
}
function generateObjects(json) {
json.forEach(function(link) {
if (typeof(link.source) == "string"){
link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, ID: link.source, class: link.sourceClass, linkCount:0});
link.source.linkCount++;
}
if (typeof(link.target) == "string"){
link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, ID: link.target, class: link.targetClass, linkCount:0});
link.target.linkCount++;
}
});
return json;
}
function updateForceRemoveElement(links){
force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) );
force.links(d3.values(links));
initializeGraph();
}
function updateForceUsingNewNodes(links){
force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) );
force.links(d3.values(links));
initializeGraph();
}
function initializeGraph(){
zoom.on("zoom", zoomed);
drag.on("dragstart", dragstart);
force
.size([width, height])
.gravity(.1)
.charge(-400)
.friction(0.9)
.theta(0.9)
.linkStrength(0.9)
.distance(55)
.alpha(0.1)
.on("tick", tick);
svg = d3.select("#main-right")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom).on("dblclick.zoom", null);
svg
.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 32)
.attr("refY", -0.05)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr('fill', '#359AF4');
container = svg.append("g");
link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)");
link.exit().remove();
node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", function(d) { click(d); })
.on("dblclick", function(d) { dblclick(d); })
.on('contextmenu', function(data, index) {
d3.event.preventDefault();
updateGraphByRemoveElement(data, index);
})
.call(drag);
node
.append("circle")
.attr("r", 20)
.attr("cx", 0)
.attr("cy", 0)
.style("fill", '#eee')
.style("stroke", '#fff')
.style("stroke-width", '0.5px');
node
.append("image")
.attr("xlink:href", function(d) {
if (d.class == "Person") {
return "pics/node_person.svg";
} else {
return "pics/node_appln.svg";
}} )
.attr("x", -20)
.attr("y", -20)
.attr("width", 40)
.attr("height", 40)
.attr("color", "white");
node
.append("text")
.attr("x", 20)
.attr("y", 4)
.style("fill", "#bbb")
.text(function(d) { return d.name; });
node
.append("circle")
.attr("r", 7)
.attr("cx", 0)
.attr("cy", -16)
.style("fill", '#359AF4');
node
.append("text")
.attr("text-anchor", "center")
.attr("x", -3)
.attr("y", -13)
.style("fill", "white")
.text(function(d) { return d.linkCount; });
node.exit().remove();
}
function tick() {
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 + ")"; });
}
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
function zoomed() {
d3.event.sourceEvent.stopPropagation();
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstart(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("fixed", d.fixed = true);
}
function mouseover() {
d3.select(this).select("text").transition()
.duration(750)
.style("font-size","15px")
.style("fill","black");
d3.select(this).moveToFront();
}
function mouseout() {
d3.select(this).select("text").transition()
.duration(750)
.style("font-size","10px")
.style("fill","#ccc");
}
function click(d) {
$(boxIDName).empty();
if (d.class == "Person"){
$(boxIDName).append("<h2>Person/Company</h2>");
getNodeInfoPerson(d.ID);
}
if (d.class == "Appln"){
$(boxIDName).append("<h2>Application</h2>");
getNodeInfoAppln(d.ID);
}
}
function dblclick(d) {
entryClicked(d.ID+"|"+d.class,false);
}