I am working on an D3 forced graph and got a challenge. I added a "mouseenter" function which increase the radius of nodes if getting hovered.
.on("mouseenter", function(d) {
d3.select(this)
.transition()
.duration(200)
.attr("r", 50)
Now I want to exclude certain nodes and thought to filter would help. Unfortunately it doesn´t. Probably my code is wrong or I need to add those filtering later one. Any idea?
.on("mouseenter", function(d) {
d3.select(this)
.filter(function() {
return !this.graph.nodes.name("usv"))
})
.transition()
.duration(200)
.attr("r", 50)
Best,
Kristian
Here's an example implementation:
<!doctype html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<svg width="500" height="500"></svg>
<script>
let data = d3.range(10)
.map(function(d){
return {
name: Math.random() > 0.5 ? "usv" : "na",
x: Math.random() * 480,
y: Math.random() * 480
};
});
d3.select('svg')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 20)
.attr("fill", d => d.name == "usv" ? "red" : "black")
.filter((d) => {
return d.name === "usv";
})
.on("mouseenter", function(){
d3.select(this)
.transition()
.duration(200)
.attr("r", 50);
});
</script>
</body>
</html>
Related
What needs to be done that the second transition of the green circle behaves like the second transition of the blue circle?
I would have expected that the transitions of both circles behave the same. However it seems that the first transition of the green circle is applied in place of the second transition.
const svg = d3.select("svg");
const blueCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 10)
.attr("r", 5)
.style("fill", "blue");
const greenCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.style("fill", "green");
blueCircle
.transition()
.duration(4000)
.ease(d3.easeLinear)
.attr("cx", 100)
.transition()
.duration(2000)
.ease(d3.easeElastic)
.attr("cx", 200);
const firstTransition = d3.transition()
.duration(4000)
.ease(d3.easeLinear);
const secondTransition = d3.transition()
.duration(2000)
.ease(d3.easeElastic);
greenCircle
.transition(firstTransition)
.attr("cx", 100)
.transition(secondTransition)
.attr("cx", 200);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>
Update
Thanks to Coola's answer and this question, I found a possibility to make the second transition of the green circle work as expected:
const greenCircle = svg.append("circle")
.attr("class", "green")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.style("fill", "green");
const firstTransition = d3.transition()
.duration(4000)
.ease(d3.easeLinear);
const secondTransition = firstTransition.transition()
.duration(2000)
.ease(d3.easeElastic);
firstTransition
.select("circle.green")
.attr("cx", 100);
secondTransition
.select("circle.green")
.attr("cx", 200);
However, this code has still the following flaws:
The transitions are not independent, thus cannot be reused in a different order.
You cannot insert an already selected element (i.e. greenCircle) into the select method of the transition (it results in a "Failed to execute 'querySelector' on 'Element': '[object Object]' is not a valid selector." exception).
The typical method chaining concept of D3.js is not used.
Does anybody know a solution without these issues, especially for the first point?
In order to chain the transitions you have to use the on("end", function(){<do something>}).
You can read more about advanced control flows in the documentation.
greenCircle
.transition(firstTransition)
.attr("cx", 100)
.on("end", () => {
greenCircle.transition(secondTransition)
.attr("cx", 200);
});
Full Snippet:
const svg = d3.select("svg");
const blueCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 10)
.attr("r", 5)
.style("fill", "blue");
const greenCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.style("fill", "green");
blueCircle
.transition()
.duration(4000)
.ease(d3.easeLinear)
.attr("cx", 100)
.transition()
.duration(2000)
.ease(d3.easeElastic)
.attr("cx", 200);
const firstTransition = d3.transition()
.duration(4000)
.ease(d3.easeLinear);
const secondTransition = d3.transition()
.duration(2000)
.ease(d3.easeElastic);
greenCircle
.transition(firstTransition)
.attr("cx", 100)
.on("end", () => {
greenCircle.transition(secondTransition)
.attr("cx", 200);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>
UPDATE:
The above code solves only part of the problem. You will notice the animation is not exactly identical between the blue and green circles.
You also need to chain the firstTransition into the secondTransition const. Like:
const secondTransition = firstTransition.transition()
.duration(2000)
.ease(d3.easeElastic);
Full snippet:
const svg = d3.select("svg");
const blueCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 10)
.attr("r", 5)
.style("fill", "blue");
const greenCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.style("fill", "green");
blueCircle
.transition()
.duration(4000)
.ease(d3.easeLinear)
.attr("cx", 100)
.transition()
.duration(2000)
.ease(d3.easeElastic)
.attr("cx", 200);
const firstTransition = d3.transition()
.duration(4000)
.ease(d3.easeLinear);
const secondTransition = firstTransition.transition()
.duration(2000)
.ease(d3.easeElastic);
greenCircle
.transition(firstTransition)
.attr("cx", 100)
.on("end", function () {
greenCircle.transition(secondTransition)
.attr("cx", 200);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>
I'm using click events to log data to the console, but i'd like to display this data in a separate box (which i have created). Does anyone have any advice or suggestions for this? Or is there a decent library that can help me achieve this?
Cheers
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 7)
.attr("cx", function(d) { return xScale(d[1]); })
.attr("cy", function(d) { return yScale(d[2]); })
.on('click', function(d, i) {
console.log("click", d[0]);
})
.attr("fill", function(d) {
var result = null;
if (data.indexOf(d) >= 0) {
result = colours(d);
} else {
result = "white";
}
return result;
});
var textBox = svg.append("rect")
.attr("x", 5)
.attr("y", 385)
.attr("height", 150)
.attr("width", 509)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
In the "click" listener just select your box, or use the selection you already have:
circles.on("click", function(d) {
selection.append("text")
//etc...
})
Here is a simple demo, click the circle:
var svg = d3.select("svg");
var circle = svg.append("circle")
.datum({
name: "foo"
})
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 60)
.style("fill", "teal");
var box = svg.append("g")
.attr("transform", "translate(300,50)");
box.append("rect")
.attr("height", 50)
.attr("width", 100)
.style("stroke", "black")
.style("fill", "none")
.style("stroke-width", "2px");
circle.on("click", function(d) {
box.append("text")
.attr("x", 10)
.attr("y", 20)
.text(d.name)
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="300"></svg>
Finally, two tips: make your selection a group or any other valid container for the text, not a rectangle, because you cannot append a text to a rectangle. Also, be prepared for all kinds of problems trying to fit your texts inside that rectangle: wrapping texts in an SVG is notoriously complicated.
I am trying to create a function that will be the basis of a repeated drawing of lines and fading them out. I am roughly inspired by Peter Cook's Wind Map, which he discusses here: http://prcweb.co.uk/making-the-uk-wind-chart/. I am trying to recreate his test lines.
I can get the test lines to work -- but only if they draw or swing to the left, i.e. when the original x2 is greater than the new x2 they transition to. The code below works the way I expect. But if I change the x2 it is drawing to (marked with the comment 'ISSUE IS HERE') to be greater than 500, it won't draw. I'm very confused. Why would it care which direction it draws in?
<!DOCTYPE html>
<html>
<head>
<title>TITLE GOES HERE</title>
</head>
<body>
<svg></svg>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
function lineAnimate(selection) {
selection
.attr('x1', 500)
.attr('x2', 500)
.attr("stroke-width", 2)
.attr("stroke", "black")
.attr('y1', function(d) {return d;})
.attr('y2', function(d) {return d;})
.style('opacity', 0.5)
.transition()
.ease('linear')
.duration(1000)
// .delay(function(d) {return d*10;})
.attr('x2', 200) // ISSUE IS HERE
.transition()
.delay(1000)
.duration(1000)
.style('opacity', 0)
.each('end', function() {d3.select(this).call(lineAnimate)})
console.log("done");
}
var svg = d3.select('svg')
.selectAll('line')
.data([5, 10, 15, 20, 25])
.enter()
.append('line')
.call(lineAnimate);
console.log(svg.size());
</script>
</body>
</html>
The transition is working, regardless the fact that the new x2 is bigger or smaller than the original value, that's not the problem.
You don't see anything because, by default, an SVG has a width of 300px (so, in your "working" code, you're only seeing a small fraction of the line, from 300 to 200 in the x coordinate; all the segment from 500 to 300 is not visible).
Just change the width:
<svg width="600"></svg>
Here is your code:
<svg width="600"></svg>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
function lineAnimate(selection) {
selection
.attr('x1', 500)
.attr('x2', 500)
.attr("stroke-width", 2)
.attr("stroke", "black")
.attr('y1', function(d) {return d;})
.attr('y2', function(d) {return d;})
.style('opacity', 0.5)
.transition()
.ease('linear')
.duration(1000)
// .delay(function(d) {return d*10;})
.attr('x2', 600) // ISSUE IS HERE
.transition()
.delay(1000)
.duration(1000)
.style('opacity', 0)
.each('end', function() {d3.select(this).call(lineAnimate)})
}
var svg = d3.select('svg')
.selectAll('line')
.data([5, 10, 15, 20, 25])
.enter()
.append('line')
.call(lineAnimate);
</script>
Here is simple html, which will have 2 circle and label on it
<!DOCTYPE html>
1<html>
<head>
<script type="text/javascript" src="d3.min.js"></script>
<script type="text/javascript" src="simple.js"></script>
<link href="style 2.css" type="text/css" rel="stylesheet">
</head>
<body>
<h1>Test</h1>
<div id="small-gr"></div>
</body>
</html>
simple.js this is simple example for d3 dashing site .
var circleData = [
{ "cx": 20, "cy": 20, "radius": 20, "color" : "green" },
{ "cx": 70, "cy": 70, "radius": 20, "color" : "purple" }];
//Create the SVG Viewport
var svgContainer = d3.selectAll("body").append("svg")
.attr("width",200)
.attr("height",200);
//Add circles to the svgContainer
var circles = svgContainer.selectAll("circle")
.data(circleData)
.enter()
.append("circle");
// //Add the circle attributes
var circleAttributes = circles
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.radius; })
.style("fill", function (d) { return d.color; });
//Add the SVG Text Element to the svgContainer
var text = svgContainer.selectAll("text")
.data(circleData)
.enter()
.append("text");
//Add SVG Text Element Attributes
var textLabels = text
.attr("x", function(d) { return d.cx; })
.attr("y", function(d) { return d.cy; })
.text( function (d) { return "( " + d.cx + ", " + d.cy +" )"; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "red");
Our problem is very weird , if you see the <html> tag you can find 1 there , that's what we have to use so that the code works, but if you remove it the code doesn't work , this has no edit it's directly taken from d3 site. Can anyone explain why is this happening ?
var svgcanvas = d3.select("body").append("svg:svg")
.attr("width", 725)
.attr("height", 500);
The Dataset
var jsoncirclehigh = [
{cx:100, cy: 100, r: 2.5,
label:"technology"},
{cx:200, cy: 200, r: 2.5,
label:"rocks"},
{cx:50, cy:50, r:2.5,
label:"blah"}
];
The actual shape that I have created
svgcanvas.append("svg:path")
.attr("d","M -200,0 A200,200 0 0,0 500,0 L -200,0")
.attr("transform", "translate(220,400) scale(1, -1)")
.style("stroke-width", 2)
.style("stroke", "steelblue")
.style("fill", "yellow");
I wish the circles to be restricted within the shape above
svgcanvas.selectAll("circle")
.data(jsoncirclehigh)
.enter().append("circle")
.attr("r", function (d) { return d.r; })
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function() {d3.select(this).style("fill", "blue");})
.style("stroke", "steelblue")
.style("fill", "blue");
svgcanvas.selectAll("text")
.data(jsoncirclehigh)
.enter().append("svg:text")
.text(function(d) { return d.label; })
.attr("x", function (d) { return d.cx + 10; })
.attr("y", function (d) { return d.cy + 10; });
I have tried using d3.scale, but it hasn't worked out for me
This may be more simple than you're looking for, but have you looked into using a clipPath?