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 have been searching on the internet for a simple fiddle of taking text with two x y coordinates and transitioning it from point 1 to point 2 but cannot seem to find anything if anyone knows of any examples it would be much appreciated. However on to the main point This first snippet of code successfully plots text at the center of the point.
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.Name;
})
.attr("x", function(d) {
return xScale(d.x);
})
.attr("y", function(d) {
return yScale(d.y);
})
.attr("font-family", "sans-serif")
.attr("font-size", "0.5px")
.style('text-anchor','middle')
.attr("fill", "black")
This transition is an attempt to change the text and transition to new locations
svg.selectAll('text')
.on('click', function (d) {
svg.selectAll("text")
d3.selectAll("text")
.transition()
.duration(1000)
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.newNames;
})
.attr("x", function(d) {
return xScale(d.xx);
})
.attr("y", function(d) {
return yScale(d.yy);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black")
})
Which does not work and does not show the text but also raises no errors in the console. I have done this before with svg shapes and it works the exact same way but for whatever reason I cant seem to get it to transition with text any help is appreciated Thanks
Your code kinda works, but is a bit strange. First, your initial font-size of 0.5px doesn't render for me in chrome. Second, your transition code has a syntax error in:
svg.selectAll("text")
d3.selectAll("text")
Third, you should be binding data, then doing the transition and not binding during the transition. Also, if you only want to move the existing text and your data didn't change, why rebind at all? And you certainly don't need to .enter().append().
Here's what I think you are after:
svg.selectAll('text')
.on('click', function(d) {
svg.selectAll("text")
.transition()
.duration(1000)
.text(function(d) {
return d.newNames;
})
.attr("x", function(d) {
return d.xx;
})
.attr("y", function(d) {
return d.yy;
})
.attr("font-family", "sans-serif")
.attr("font-size", "30px")
.attr("fill", "black")
});
Full code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<script>
var data = [{
x: 20,
y: 20,
xx: 200,
yy: 200,
Name: "Hello",
newNames: "GoodBye"
},{
x: 100,
y: 20,
xx: 300,
yy: 200,
Name: "Love",
newNames: "Hate"
}];
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.Name;
})
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.style('text-anchor', 'middle')
.attr("fill", "black");
svg.selectAll('text')
.on('click', function(d) {
svg.selectAll("text")
.transition()
.duration(1000)
.text(function(d) {
return d.newNames;
})
.attr("x", function(d) {
return d.xx;
})
.attr("y", function(d) {
return d.yy;
})
.attr("font-family", "sans-serif")
.attr("font-size", "30px")
.attr("fill", "black")
})
</script>
</body>
</html>
I've been working on a sunburst visualization example provided by the following link http://bl.ocks.org/mbostock/4063423. I want the label to display the name of the partition in question that has been moused over. Right now whenever I mouseover a partition it shows "flare" in the middle only. Is there a way for me to access the names of the children?
d3.json("flare.json", function(error, root) {
var path = svg.datum(root).selectAll("path")
.data(partition.nodes) //access the nodes
.enter()
.append("path")
.attr("display", function(d) { return d.depth ? null : 'none';}) //hide inner ring
.attr("d", arc)//used whenever I come across a path
.style("stroke", "#fff")
.style("fill", function(d) { return color((d.children ? d : d.parent).name);})
.style("fill-rule", "evenodd")
.each(stash)
.on("mouseover", mouseover)
.on("mouseout", mouseout);
var label = svg.append("text")
.attr("id", "tooltip")
.attr("x", 10)
.attr("y", 50)
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.style("opacity", 0)
.text(function(d) { return d.name; });
function mouseover(d) {
d3.select(this)
.transition()
.duration(100)
.style("opacity", 0.3);
label.style("opacity", .9);
console.log('mouseover', mouseover);
};
function mouseout(d) {
d3.select(this)
.transition()
.duration(100)
.style("opacity", 1);
label.style("opacity", 0);
console.log('mouseout', mouseout);
};
This problem is solved by first of all appending text elements to a g element and not svg element. Second you want to create a text element outside the mousehandler with a specific id then call it using that Id within the event handler like so.
d3.json("flare.json", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path =
g.append("path")
.attr("display", function(d) { return d.depth ? null : 'none';}) //hide inner ring
.attr("d", arc)//used whenever I come across a path
.attr("id", "part")
.style("stroke", "#fff")
.style("fill", function(d) { return color((d.children ? d : d.parent).name);})
.style("fill-rule", "evenodd")
.each(stash)
.on("mouseover", mouseover)
.on("mouseout", mouseout);
var text = g.selectAll("text")
.data(partition.nodes(root))
.enter()
.append("text")
.attr("id", "tip")
.attr("x", 10)
.attr("y", 50)
.attr("font-size", "11px")
.style("opacity", 0);
function mouseover(d) {
d3.select(this)
.transition()
.duration(1000)
.ease('elastic')
.style("opacity", 0.3);
//label.style("opacity", .9);
d3.select("#tip").text(d.name).style("opacity", 0.9);
console.log('mouseover', mouseover);
};
function mouseout(d) {
d3.select(this)
.transition()
.duration(100)
.style("opacity", 1);
d3.select("#tip").text(d.name).style("opacity", 0);
console.log('mouseout', mouseout);
};
I draw a line graph using D3.js and i am using tooltip in line graph,but i am also using tooltip in legends but enable to show the tooltip in legends,i wrote given code,it is possible to show the tooltip in legends.
var dates=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31];
var legend = canvas.append("g")
.attr("class", "legend")
// .attr("transform", "translate(70,10)");
var legendRect = legend.selectAll('rect').data(dates);
legendRect.enter()
.append("rect")
.attr("x", function (d,i){return i*14;})
.attr("width", 12)
.attr("height", 20)
.attr("y", 10)
.style("fill","steelblue")
legend.selectAll("text")
.data(dates)
.enter()
.append("text")
.attr("x", function (d,i){return i*14;})
.attr("y", 25)
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white")
.text(function(d) {
return d;
})
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html(formatTime(d.key) + "<br/>" + d.value)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY-28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
Iam adding tooltip in text but enable to show the tooltip in my values(I have only problem in legend tooltip)
Check out this code. It does exactly what you want.
This is also a great example.
I have code like this that creates multiple D3 donut multiples.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
svg {
padding: 10px 0 0 10px;
}
.arc {
stroke: #fff;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var radius = 74,
padding = 10;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - 30);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
d3.csv("data.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; }));
data.forEach(function(d) {
d.ages = color.domain().map(function(name) {
return {name: name, population: +d[name]};
});
});
var legend = d3.select("body").append("svg")
.attr("class", "legend")
.attr("width", radius * 2)
.attr("height", radius * 2)
.selectAll("g")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", 24)
.attr("y", 9)
.attr("dy", ".35em")
.text(function(d) { return d; });
var svg = d3.select("body").selectAll(".pie")
.data(data)
.enter().append("svg")
.attr("class", "pie")
.attr("width", radius * 2)
.attr("height", radius * 2)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.name); });
svg.append("text")
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.State; });
});
</script>
I am looking for a way to implement the D3 tooltip so that I can see the exact data of each chunk of the donut when I put my cursor over it. I understand that there are other examples of the tooltip on here but none of them have worked with the donut multiples example.
Here is some example data
State,Under 5 Years,5 to 13 Years,14 to 17 Years,18 to 24 Years,25 to 44 Years,45 to 64 Years,65 Years and Over
AL,310504,552339,259034,450818,1231572,1215966,641667
AK,52083,85640,42153,74257,198724,183159,50277
AZ,515910,828669,362642,601943,1804762,1523681,862573
AR,202070,343207,157204,264160,754420,727124,407205
CA,2704659,4499890,2159981,3853788,10604510,8819342,4114496
CO,358280,587154,261701,466194,1464939,1290094,511094
CT,211637,403658,196918,325110,916955,968967,478007
DE,59319,99496,47414,84464,230183,230528,121688
DC,36352,50439,25225,75569,193557,140043,70648
FL,1140516,1938695,925060,1607297,4782119,4746856,3187797
GA,740521,1250460,557860,919876,2846985,2389018,981024
HI,87207,134025,64011,124834,356237,331817,190067
ID,121746,201192,89702,147606,406247,375173,182150
IL,894368,1558919,725973,1311479,3596343,3239173,1575308
IN,443089,780199,361393,605863,1724528,1647881,813839
IA,201321,345409,165883,306398,750505,788485,444554
KS,202529,342134,155822,293114,728166,713663,366706
KY,284601,493536,229927,381394,1179637,1134283,565867
LA,310716,542341,254916,471275,1162463,1128771,540314
ME,71459,133656,69752,112682,331809,397911,199187
MD,371787,651923,316873,543470,1556225,1513754,679565
The D3 doc of this can be found at
http://bl.ocks.org/mbostock/3888852
I'm not sure what you're referring to when you say "the D3 tooltip", because d3 doesn't have any built-in tooltip functionality. That said, there are some good third-party plugins out there for doing tooltips in d3.
d3-tip is one that would work well for what you're trying to do.
You can create a tooltip function to display your population data for each arc like this:
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) { return d.data.population; })
.direction('s');
Then you can call the function on your svg selection:
svg.call(tip);
Finally, you can use mouse event listeners on your arcs to show and hide the tooltip:
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.name); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
Here's a PLUNK with a working example.
You can also check up on the d3-tip documentation here.
I too got the same TypeError while debugging my javascript code using d3js while adding tooltip for my barcharts.
bars.append('rect')
.attr('y', maxHeight)
.attr('height', 0)
.attr('width', function (d) { return x.rangeBand(d) - 1; })
.attr('class', 'bar')
.transition().duration(1500)
.attr('y', function (d, i) { return y(d.y); })
.attr('height', function (d, i) { return maxHeight - y(d.y); });
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
In the above code i used ".on('mouseover', tip.show)" on "bars.append()".This is wrong usage.
Later, i found that ".on('mouseover', tip.show)" should be applied on function ".select('rect') or .selectall('rect')".
Now below snippet is working correctly for my application.
bars.append('rect')
.attr('y', maxHeight)
.attr('height', 0)
.attr('width', function (d) { return x.rangeBand(d) - 1; })
.attr('class', 'bar')
.transition().duration(1500)
.attr('y', function (d, i) { return y(d.y); })
.attr('height', function (d, i) { return maxHeight - y(d.y); });
bars.select('rect')
.on('mouseover', tip.show)
.on('mouseout', tip.hide);