Create an Animated Pulsing Circle with D3 - javascript

I know that similar questions have been asked before here on stackoverflow, but I have fairly specific requirements. I'm trying to generate a pulsing dot for a D3 chart.
I modified some code on codepen.io and came up with this.
How would I do the same thing using a D3 transition() rather than the (cheesy) CSS classes?
Something more along the lines of:
circle = circle.transition()
.duration(2000)
.attr("stroke-width", 20)
.attr("r", 10)
.transition()
.duration(2000)
.attr('stroke-width', 0.5)
.attr("r", 200)
.ease('sine')
.each("end", repeat);
Feel free to fork my sample pen.
Thanks!

Have a look at the example on GitHub by whityiu
Note that this is using d3 version 3.
I have adapted that code to produce something like you are after in the fiddle below.
var width = 500,
height = 450,
radius = 2.5,
dotFill = "#700f44",
outlineColor = "#700f44",
pulseLineColor = "#e61b8a",
bgColor = "#000",
pulseAnimationIntervalId;
var nodesArray = [{
"x": 100,
"y": 100
}];
// Set up the SVG display area
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("fill", bgColor)
.classed('visual-area', true);
var bgRect = d3.select("svg").append("rect")
.attr("width", width)
.attr("height", height);
var linkSet = null,
nodeSet = null;
// Create data object sets
nodeSet = svg.selectAll(".node").data(nodesArray)
.enter().append("g")
.attr("class", "node")
.on('click', function() {
// Clear the pulse animation
clearInterval(pulseAnimationIntervalId);
});
// Draw outlines
nodeSet.append("circle")
.classed("outline pulse", true)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("fill", 'none')
.attr("stroke", pulseLineColor)
.attr("r", radius);
// Draw nodes on top of outlines
nodeSet.append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("fill", dotFill)
.attr("stroke", outlineColor)
.attr("r", radius);
// Set pulse animation on interval
pulseAnimationIntervalId = setInterval(function() {
var times = 100,
distance = 8,
duration = 1000;
var outlines = svg.selectAll(".pulse");
// Function to handle one pulse animation
function repeat(iteration) {
if (iteration < times) {
outlines.transition()
.duration(duration)
.each("start", function() {
d3.select(".outline").attr("r", radius).attr("stroke", pulseLineColor);
})
.attrTween("r", function() {
return d3.interpolate(radius, radius + distance);
})
.styleTween("stroke", function() {
return d3.interpolate(pulseLineColor, bgColor);
})
.each("end", function() {
repeat(iteration + 1);
});
}
}
if (!outlines.empty()) {
repeat(0);
}
}, 6000);
Fiddle

Related

How to add a SVG element as a html string in d3.js? [duplicate]

I have looked for answer to this but none of the similar questions help me in my situation. I have a D3 tree that creates new nodes at runtime. I would like to add HTML (so I can format) to a node when I mouseover that particular node. Right now I can add HTML but its unformatted. Please help!
JSFiddle: http://jsfiddle.net/Srx7z/
JS Code:
var width = 960,
height = 500;
var tree = d3.layout.tree()
.size([width - 20, height - 60]);
var root = {},
nodes = tree(root);
root.parent = root;
root.px = root.x;
root.py = root.y;
var diagonal = d3.svg.diagonal();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(-30,40)");
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
var duration = 750;
$("#submit_button").click(function() {
update();
});
function update() {
if (nodes.length >= 500) return clearInterval(timer);
// Add a new node to a random parent.
var n = {id: nodes.length},
p = nodes[Math.random() * nodes.length | 0];
if (p.children) p.children.push(n); else p.children = [n];
nodes.push(n);
// Recompute the layout and data join.
node = node.data(tree.nodes(root), function (d) {
return d.id;
});
link = link.data(tree.links(nodes), function (d) {
return d.source.id + "-" + d.target.id;
});
// Add entering nodes in the parent’s old position.
var gelement = node.enter().append("g");
gelement.append("circle")
.attr("class", "node")
.attr("r", 20)
.attr("cx", function (d) {
return d.parent.px;
})
.attr("cy", function (d) {
return d.parent.py;
});
// Add entering links in the parent’s old position.
link.enter().insert("path", ".g.node")
.attr("class", "link")
.attr("d", function (d) {
var o = {x: d.source.px, y: d.source.py};
return diagonal({source: o, target: o});
})
.attr('pointer-events', 'none');
node.on("mouseover", function (d) {
var g = d3.select(this);
g.append("text").html('First Line <br> Second Line')
.classed('info', true)
.attr("x", function (d) {
return (d.x+20);
})
.attr("y", function (d) {
return (d.y);
})
.attr('pointer-events', 'none');
});
node.on("mouseout", function (d) {
d3.select(this).select('text.info').remove();
});
// Transition nodes and links to their new positions.
var t = svg.transition()
.duration(duration);
t.selectAll(".link")
.attr("d", diagonal);
t.selectAll(".node")
.attr("cx", function (d) {
return d.px = d.x;
})
.attr("cy", function (d) {
return d.py = d.y;
});
}
Using Lars Kotthoff's excellent direction, I got it working so I decided to post it for others and my own reference:
http://jsfiddle.net/FV4rL/2/
with the following code appended:
node.on("mouseover", function (d) {
var g = d3.select(this); // The node
var div = d3.select("body").append("div")
.attr('pointer-events', 'none')
.attr("class", "tooltip")
.style("opacity", 1)
.html("FIRST LINE <br> SECOND LINE")
.style("left", (d.x + 50 + "px"))
.style("top", (d.y +"px"));
});

How to make D3js object changing it's properties on mouse move near the object?

The idea is to make an object reacting on mouse moves in vicinity of the object.
That is how far I got on this by now:
//declaring a canvas
var canvas = d3.select("body")
.append("svg")
.attr("width", 100)
.attr("height", 100);
//create circle with attributes and listeners
var circles = canvas.selectAll("circle")
.data([1])
.enter()
.append("circle")
.attr("cy", 50).attr("cx", 50).attr("r", 20)
.style({"fill": "grey"})
.on("mousemove", handleMouseMove);
//listener on mouse move inside the canvas
function handleMouseMove(){
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
console.log(x,y);
console.log(this.attributes);
}
See example on codepen
I can get the object with it's properties only when hovering over it - see last console.log();. And I am stuck on it. Please share your ideas regarding the solution if any.
Maybe the cleanest way, if you can, is by putting another circle with bigger radius just under your existing circle with a fill of transparent:
var g = canvas.selectAll("g")
.data([1])
.enter()
.append("g")
.on("mouseover", handleMouseOver) // event handlers here are applied
.on("mouseout", handleMouseOut) // to both 'circle'
g.append('circle').classed('invisible', true) // this goes before the
.attr("cy", 50) // visible circle
.attr("cx", 50)
.attr("r", 40)
.style({"fill": "transparent"});
g.append('circle').classed('visible', true)
.attr("cy", 50)
.attr("cx", 50)
.attr("r", 20)
.style({"fill": "grey"});
function handleMouseOver(d,i){
d3.select(this).select('.visible').transition()
.style({"fill": "red"});
};
function handleMouseOut(d,i){
d3.select(this).select('.visible').transition()
.style({"fill": "green"});
};
Or if you want to use mouse positions:
var circles = canvas.selectAll("circle")
.data([1])
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", 50)
.attr("r", 20)
.style({"fill": "grey"})
.on("mousemove", handleMouseMove);
function handleMouseMove(){
var coordinates = [];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
if (x>10 && x<90 && y>10 && y<90) { // example values; this is a rectangle but you can use more complex shapes
d3.select('.visible').style({"fill": "red"});
}
else {
d3.select('.visible').style({"fill": "green"});
}
Here is the FIDDLE where you can see both versions.
Hope it helps...
If you want to detect that the mouse is near the circle you need to set up your event handler on the object that contains the circle, in this case the svg contained in your canvas variable. Then to determine if the mouse is close, I'd use the point distance formula.
function handleMouseMove(){
var coordinates = d3.mouse(this),
x = coordinates[0],
y = coordinates[1];
var dist = Math.sqrt(Math.pow(circPos.x - x, 2) + Math.pow(circPos.y - y, 2));
console.log("distance to circle center is " + dist);
}
UPDATE FOR COMMENTS
var canvas = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500)
.on("mousemove", handleMouseMove);
var data = [];
for (var i = 0; i < 10; i++) {
data.push({
x: Math.random() * 500,
y: Math.random() * 500
});
}
var circles = canvas.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cy", function(d) {
return d.y;
})
.attr("cx", function(d) {
return d.x;
})
.attr("r", 10)
.style({
"fill": "grey"
});
function handleMouseMove() {
var coordinates = d3.mouse(this),
x = coordinates[0],
y = coordinates[1];
circles.style("fill", "grey");
var closestCircle = {
obj: null,
dist: 1e100
};
circles.each(function(d) {
var dist = Math.sqrt(Math.pow(d.x - x, 2) + Math.pow(d.y - y, 2));
if (dist < closestCircle.dist) {
closestCircle = {
obj: this,
dist: dist
};
}
});
d3.select(closestCircle.obj).style("fill", "green");
}
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
Voroni Example
var canvas = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
var data = [];
for (var i = 0; i < 10; i++) {
data.push({
x: Math.random() * 500,
y: Math.random() * 500,
id: i
});
}
var circles = canvas.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cy", function(d) {
return d.y;
})
.attr("cx", function(d) {
return d.x;
})
.attr("id", function(d) {
return "circle" + d.id;
})
.attr("r", 10)
.style("fill", "grey");
var voronoi = d3.voronoi()
.extent([
[-1, -1],
[500 + 1, 500 + 1]
])
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
var voronoiGroup = canvas.append("g")
.attr("class", "voronoi");
voronoiGroup.selectAll("path")
.data(voronoi.polygons(data))
.enter().append("path")
.attr("d", function(d) {
return d ? "M" + d.join("L") + "Z" : null;
})
.style("pointer-events", "all")
.style("fill", "none")
.style("stroke", "steelblue")
.style("opacity", "0.5")
.on("mouseover", mouseover);
function mouseover(d) {
circles.style("fill", "grey");
d3.select("#circle" + d.data.id).style("fill", "green");
}
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>

D3 Plot array of circles

I've tried the circle plot example as the following:
var x=20, y=20, r=50;
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 800)
.attr("height", 600);
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", r)
.attr("cx", x)
.attr("cy", y);
But I want to figure out how to plot without a loop a sequence of circles from an array like:
data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
Maybe this example could help?
var data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
var height = 300,
width = 500;
var svg = d3.select('body').append('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', 'translate(30, 30)');
// Bind each nested array to a group element.
// This will create 3 group elements, each of which will hold 3 circles.
var circleRow = svg.selectAll('.row')
.data(data)
.enter().append('g')
.attr('transform', function(d, i) {
return 'translate(30,' + i * 60 + ')';
});
// For each group element 3 circle elements will be appended.
// This is done by binding each element in a nested array to a
// circle element.
circleRow.selectAll('.circle')
.data(function(d, i) { return data[i]; })
.enter().append('circle')
.attr('r', function(d) { return d; })
.attr('cx', function(d, i) { return i * 60; })
.attr('cy', 0);
Live Fiddle

How to make plotting possible on x-axis lines only? (d3.js)

I've just started with trying out the d3 library.
I am trying to create an interactive line chart where people can plot their own points. You can find it over here: http://jsfiddle.net/6FjJ2/
My question is: how can I make sure that plotting can only be done on the x-axis' lines? If you check out my example, you will see it kind of works, but with a lot of cheating. Check out the ok variable... What would be the correct way of achieving this? I have no idea how I can achieve this with a ... so I'm getting a lot of seperate 's.
var data = [2, 3, 4, 3, 4, 5],
w = 1000,
h = 300,
monthsData = [],
months = 18;
for(i = 0; i < months; i++) {
monthsData.push(i);
}
var max = d3.max(monthsData),
x = d3.scale.linear().domain([0, monthsData.length]).range([0, w]),
y = d3.scale.linear().domain([0, max]).range([h, 0]),
pointpos = [];
lvl = [0, 10],
lvly = d3.scale.linear().domain([0, d3.max(lvl)]).range([h, 0]);
svg = d3.select(".chart")
.attr("width", w)
.attr("height", h);
svg.selectAll('path.line')
// Return "data" array which will form the path coordinates
.data([data])
// Add path
.enter().append("svg:path")
.attr("d", d3.svg.line()
.x(function(d, i) { return x(i); })
.y(y));
// Y-axis ticks
ticks = svg.selectAll(".ticky")
// Change number of ticks for more gridlines!
.data(lvly.ticks(10))
.enter().append("svg:g")
.attr("transform", function(d) { return "translate(0, " + (lvly(d)) + ")"; })
.attr("class", "ticky");
ticks.append("svg:line")
.attr("y1", 0)
.attr("y2", 0)
.attr("x1", 0)
.attr("x2", w);
ticks.append("svg:text")
.text( function(d) { return d; })
.attr("text-anchor","end")
.attr("dy", 2)
.attr("dx", -4);
// X-axis ticks
ticks = svg.selectAll(".tickx")
.data(x.ticks(monthsData.length))
.enter().append("svg:g")
.attr("transform", function(d, i) { return "translate(" + (x(i)) + ", 0)"; })
.attr("class", "tickx");
ticks.append("svg:line")
.attr("y1", h)
.attr("y2", 0)
.attr("x1", 0)
.attr("x2", 0);
ticks.append("svg:text")
.text( function(d, i) { return i; })
.attr("y", h)
.attr("dy", 15)
.attr("dx", -2);
// var d = $(".tickx:first line").css({"stroke-width" : "2", opacity : "1"});
var line;
var ok = -55;
svg.on("mousedown", mouseDown)
.on("mouseup", mouseUp);
function mouseDown() {
var m = d3.mouse(this);
line = svg.append("line")
.data(monthsData)
/* .attr("x1", m[0]) */
.attr("x1", function(d, i) { pointpos.push(m[0]); ok += 55; return ok;})
.attr("y1", m[1])
.attr("x2", function(d, i) { return ok + 56; })
/* .attr("x2", function(d, i) {return 300; }) */
.attr("y2", m[1]);
svg.on("mousemove", mouseMove);
var m = d3.mouse(this);
var point = svg.append("circle")
.attr("cx", function(d, i) { return ok; })
.attr("cy", function(d, i) { return m[1]; })
.attr("r", 8);
lvl.push(100);
}
function mouseMove() {
var m = d3.mouse(this);
line.attr("y2", m[1]);
/* .attr("y1", m[0]); */
}
function mouseUp() {
// Change null to mousemove for a graph kinda draw mode
svg.on("mousemove", mouseMove);
}
Excuse my bad code!
Thanks in advance.
It looks like you need:
histogram layout for binning your points.
ordinal scales for restricting their x-axis positions according to the bin
As a sidenote, you can use d3.svg.axis to draw the axis for you.

Binding Data from tutorial example

I'm pretty new to d3 and have been following this tutorial: http://christopheviau.com/d3_tutorial/
I'm stuck on the 'Binding Data' example - it's pretty simple but the code just won't produce anything. I've poked around here and haven't found the question listed so I thought I'd ask away.
Here's the code:
var dataset = [],
i = 0;
for(i = 0; i < 5; i++) {
dataset.push(Math.round(Math.random() * 100));
}
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 400)
.attr("height", 75);
sampleSVG.selectAll("circle")
.data(dataset)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("height", 40)
.attr("width", 75)
.attr("x", function (d, i) {
return i * 80
})
.attr("y", 20);
Other examples on the site work fine.
Thanks in advance - any ideas would be appreciated.
Unfortunately the code listed in the tutorial is incorrect. The svg element "circle" is specified by three attributes, "cx", x-axis coordinate of the center of the circle, "cy", y-axis coordinate of the center of the circle, and "r", the radius of the circle. I got this information from the w3 specification for an SVG circle.
I would recommend inspecting the JavaScript in the tutorial page to help iron out any other inconsistencies. Here it is:
<script type="text/javascript">
var dataset = [],
i = 0;
for(i=0; i<5; i++){
dataset.push(Math.round(Math.random()*100));
}
var sampleSVG = d3.select("#viz5")
.append("svg")
.attr("width", 400)
.attr("height", 100);
sampleSVG.selectAll("circle")
.data(dataset)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", function(d, i){return i*80+40})
.attr("cy", 50)
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", "white");})
.on("mousedown", animateFirstStep);
function animateFirstStep(){
d3.select(this)
.transition()
.delay(0)
.duration(1000)
.attr("r", 10)
.each("end", animateSecondStep);
};
function animateSecondStep(){
d3.select(this)
.transition()
.duration(1000)
.attr("r", 40);
};
</script>
I also created a JSFiddle which you can utilize to get the basic idea that the author of the tutorial is trying to convey, with respect to utilizing d3.js data, here.
svg circles use cx, cy, and r - not x, y, height, and width. I've correct the example code below:
var dataset = [];
for(var i = 0; i < 5; i++) {
dataset.push(Math.round(Math.random() * 100));
}
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 400)
.attr("height", 400);
sampleSVG.selectAll("circle")
.data(dataset)
.enter().append("circle")
.style("stroke", "black")
.attr("r", 10)
.attr("cx", function (d, i) {
return i * 80 + 10;
})
.attr("cy", function (d, i) {
return d;
});
http://jsfiddle.net/q3P4v/7/
MDN on svg circles: https://developer.mozilla.org/en-US/docs/SVG/Element/circle

Categories

Resources