Animating circles with D3.js - javascript

So I have been messing around with D3.js for a couple of days now and I have basic circle generation / animation (that act as bubbles), and I was wondering how I could animate the circles on the x axsis, so they wobble back forth as the travel / transition to the top of the page. The current animation can be viewed at chrisrjones.com/bubbles-v1.html

Demo of Solution:
Note the lack of symmetry:
http://jsfiddle.net/blakedietz/R5cRK/1/embedded/result/
Approach:
Determine a mathematical function that would properly model the movement that you want.
In this case we want a sine wave. We can modify aspects of each bubbles characteristics to give a unique movement pattern to each bubble.
Build or find a solution that utilizes the key concepts needed for this problem.
I like to search on bl.ocks.org/mbostock for examples that have the foundational parts of the problem that I'm trying to solve. On the site I found this example:http://bl.ocks.org/mbostock/1371412
Modify the given example to more similarly mirror the specified outcome.
Solution:
Here is a quick demo of a solution. I'll return to this to give you a full walk through. Modifications can be made to make the bubble placement and sizing as well as wiggle random/semi-unique per each bubble.
w = 960,
h = 500;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
var circle = svg.selectAll("circle")
.data(d3.range(70).map(function(datum,interval) {
return {
x: interval*20,
y: 0,
dx: 5,
dy: -3 * (Math.random()+1),
mu: Math.random()*2
};
}))
.enter().append("svg:circle")
.attr("r", 2.5)
.attr("fill","blue")
.attr("opacity",".5");
var text = svg.append("svg:text")
.attr("x", 20)
.attr("y", 20);
var start = Date.now(),
frames = 0;
d3.timer(function()
{
// Update the FPS meter.
var now = Date.now(), duration = now - start;
text.text(~~(++frames * 1000 / duration));
if (duration >= 1000) frames = 0, start = now;
// Update the circle positions.
circle
.attr("cx", function(d) { d.x += Math.random()*3*Math.sin(Math.random()*3*d.x + Math.random()*10); if (d.x > w) d.x -= w; else if (d.x < 0) d.x += w; return d.x; })
.attr("cy", function(d) { d.y += d.dy ; if (d.y > h) d.y -= h; else if (d.y < 0) d.y += h; return d.y; })
.attr("r",function(d)
{
return (d.y < 100) ? d3.select(this).attr("r") : d.mu*500/d.y;
});
});

You can do that using custom tween function for cx:
var circlesTransition = d3.selectAll("circle")
.transition()
.duration(5000)
.attr("cy", "0")
.attrTween('cx', function (d, i, a) {
return function (t) {
// Add salt, pepper and constants as per your taste
return a + (Math.random() - 0.5) * 10;
};
});

Related

How to make drawing data on a d3 map more efficient JavaScript

I am creating a project at the moment where I need to draw large amounts of data on a d3 map. My current solution operates but is quite sluggish at times especially on less powerful CPU's. I am looking for a way to make my generated data points more efficient. I attempted to simulate a fire on a map.
Do y'all have any suggestions on how to make this generation more efficient and reduce lag?
function drawPlot(data) {
//draw the plot again
var locations = svg.selectAll(".location")
.data(data);
//Add placeholder image in tooltip
var string = "<img src= "+ yourImagePath +" />";
// if filtered dataset has more circles than already existing, transition new ones in
var dots = locations.enter()
.append("circle")
.attr("class", "location orange flame")
.attr("cx", function(d){ return projection([parseFloat(d.longitude) + ((Math.random() > 0.5) ? ((-1) * Math.random()) / 2 : Math.random() / 2),parseFloat(d.latitude)+0.5])[0] })
.attr("cy", function(d){ return projection([d.longitude,parseFloat(d.latitude) + 0.5 + ((Math.random() > 0.5) ? ((-1) * Math.random()) / 2 : Math.random() / 2)])[1] })
.style("fill", function(d) { return myScale2(d.incident_date_created)})
.style("stroke", function(d) { return myScale2(d.incident_date_created)})
.style("opacity", function(d) {return 2*myScale1(d.fire_duration)})
dots.attr("r", function(d){if(formatDate(d.incident_date_created)==handle.attr("text")){return myScale(20*d.incident_acres_burned)} else{return 0}})
.transition()
.duration(function (d) {
if (d.fire_duration < 200) {
d.fire_duration = 200
} else if (d.fire_duration > 1000) {
d.fire_duration = 1000
}
return d.fire_duration* SPEED_CONSTANT / MULTI
})
.attr("r", 25)
.transition()
.attr("r", 0)
Thanks :)

Curved labels on circular diagram (D3.js)

So, I'm basically trying to make a multilevel circular partition (aka sunburst diagram) with D3.js (v4) and a JSON data.
I placed some labels, which must have different angles depending of their levels (circles) on the partition :
- Level < 3 must be curved and "follow" the arc radius.
- level == 3 must be straight and perpendicular of the arc radius.
I didn't use textPath tags, because I'm not really experienced in SVG and it looks overly complicated to me, and I don't really know how to use it.
here's my code (without the JSON but this is a really classical one, I can add a part of it if it is needed):
var width = 800;
var height = 800;
var radius = 400;
var formatNumber = d3.format(",d");
var x = d3.scaleLinear().range([0, 2 * Math.PI]);
var y = d3.scaleSqrt().range([0, radius]);
var arc = d3.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); })
.innerRadius(function(d) { return setRadius("inner", d.data.level); })
.outerRadius(function(d) { return setRadius("outer", d.data.level); });
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + (height/2) + ")");
var hierarchy = d3.hierarchy(dataset)
.sum(function(d) { return d.size; });
var partition = d3.partition();
svg.selectAll("path")
.data(partition(hierarchy).descendants())
.enter().append("path")
.attr("id", function(d, i){ return "path" + i; })
.attr("d", arc)
.attr("stroke", "white")
.attr("stroke-width", "1px")
.style("fill", function(d) { return (d.data.color) ? d.data.color : 'black'; });
svg.selectAll("text")
.data(partition(hierarchy).descendants())
.enter().append("text")
.attr("transform", function(d){ return setLabelPosition(d); })
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-size", "18px")
.attr("fill", function(d){ return d.data.textcolor; })
.text(function(d){ if(parseInt(d.data.level) > 0 && parseInt(d.data.level) < 4){ return (d.data.name).toUpperCase(); }});
d3.select(self.frameElement)
.style("height", height + "px");
function setRadius(side, level){
var result = 0;
var innerValues = [0, 120, 180, 240, 365];
var outerValues = [0, 180, 240, 365, 400];
if(!side){
throw error;
}
if(side === "inner"){
result = innerValues[level];
}
if(side === "outer"){
result = outerValues[level];
}
return result;
};
function setLabelPosition(d){
var result = '';
var angle = 0;
var centroid = arc.centroid(d);
if(parseInt(d.data.level) === 3){
angle = (180/Math.PI * (arc.startAngle()(d) + arc.endAngle()(d))/2 - 90);
if(angle > 90){
angle = angle - 180;
}
result = "translate(" + centroid + ")rotate(" + angle + ")";
} else {
angle = (180/Math.PI * (arc.startAngle()(d) + arc.endAngle()(d))/2);
result = "translate(" + centroid + ")rotate(" + angle + ")";
}
return result;
};
And the result :
My problem is, how to curve these level 1 & 2 labels (like the one which have a red border), but keep my lvl 3 labels as they currently are.
It's really a pain in the head, and I did many search (on Google and SO) but I didn't find any satisfying answer.
A solution without using a textPath will be awesome if possible, but any advice is welcome.
Many thanks guys and sorry for my English (as you can probably see it's not my birth language).
PS : This is D3.js v4.

How to update a d3 visualization without redrawing every frame?

I am using d3 to create a waveform visualization. I have it looking great, but in my efforts to design this waveform I have paid no attention to doing it an optimized way; I just was trying to get it to work.
setInterval(function() {
if (refresh) waveform();
if (palette_refresh) {
palette_refresh = false;
updateColorPalette();
}
}, options.refresh_rate);
function waveform() {
analyser.getByteFrequencyData(fd);
document.getElementById('wf_box').innerHTML = "";
var data = ... irrelevant implementation details ...
var chart = d3.select("#wf_box").append("svg")
.attr("class", "chart")
.attr("width", "" + percent_offset + "%")
.attr("style", "padding-left:" + (100 - percent_offset) + "%;")
.attr("viewBox", "0 0 " + Math.max(w * data.length, 0) + " " + Math.max(h, 0));
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) {
var x_offset = x(i) - Math.max(w * max / 900 - 0.25, 0.1) / 2;
return x_offset;
})
.attr("y", function(d, i) {
var height = y(d * options.bar_height) / h;
return h - Math.pow(Math.max(height, 0.01), 1.5);
})
.attr("width", function(d) {
return '1px';
})
.attr("height", function(d, i) {
var height = y(d * options.bar_height) / h;
return Math.pow(Math.max(height, 0.01), 1.5) + options.bar_y_offset;
});
}
As you can see, I am calling the waveform function repeatedly using options.refresh_rate as my speed. I would imagine that there is a more efficient way of updating the data used in my waveform than deleting the waveform from the DOM and redrawing it every frame. Does anybody have any insight to how I can accomplish this?
Thanks!
Take a look at the enter/exit/update-pattern in d3 https://bl.ocks.org/mbostock/3808218 and see if that can give you a hint.

Relative positioning of generated svg objects in D3

Let me start with the context:
I'm using D3 to generate randomized worksheets for my wife's students who are learning their numbers from 11 to 99. It creates columns of 10 circles to represent the tens place, and a single column of 1-9 circles for the units place.
I've been able to successfully build all of the objects I need, and it randomizes on refresh, which is perfect for my needs at this point, but I'm having trouble with alignment.
Here's the (somewhat messy) example: CodePen - Montessori Number Generator
The concept is based on this example
I'd prefer the columns to be centred in the space, but I've not yet been able to get the math to work out for it (I've ended up aligning the tens to the left and the units to the right).
The crucial equation I'm trying to figure out is for the cx value on the generated circles.
For the Tens group:
var tens = d3.range(10).map(function(r, d) {return {
radius: 15,
cx: svgWidth / 2 - r*50 + 9 * (r + 4),
cy: d * 30 + 72 }})
And the cx value for the Units is calculated using the width of the Tens group:
var w = d3.select('#tens')[0][0].getBBox().width;
for (var j = 0; j < units.length; j++) {
gUnits.append("circle")
.attr("cx", ( (svgWidth + w ) / 2 + w/45 ) )
.attr("cy", units[j].cy)
.attr("r", units[j].radius)
.attr("fill", "white")
.style("stroke", "black")
.style("stroke-width", 3);
}
The example works reasonably well for mid-range numbers (40-60 range), but smaller numbers cause overlap, and larger numbers get pushed off the side of the canvas, and none are actually perfectly centred.
It's also possible that I've gone about this all wrong, and there's a much simpler solution that I'm just not seeing.
If there is, the other big two requirements that I need to meet for my wife is this: there must be some space between the tens group and the units group, and the final number can't be a multiple of 10 (i.e. the units group cannot be zero).
Thanks for any thoughts!
Here is how I would do it.
//generate a number between 11 and 99
var randNumber = Math.round(Math.random() * 90 + 11)
//Get difference when divide rand number by 10
var circleUnits = Math.round(randNumber%10);
//get how many 10's you can fit in what is left over
var circleTens = Math.round(randNumber-circleUnits)/10 ;
//use this data to generate arrays to work with
var circleUnitsData = d3.range(circleUnits).map(function(j, i) {return {
count : i,
radius: 15
}})
var circleTensData = d3.range(circleTens).map(function(j, i) {return {
count : i+1,
radius: 15
}})
Here is how I generated the circles for the tens. Because there are always going to be 10 circles in each column (that's why I divided it by 10 above) :
var nodeRadius = 15;
var tensContainer = gTens.selectAll('circle') //put this outside the foreach so you don't overwrite any other circles
var difference = 5; //gap between circles
circleTensData.forEach(function(d ,i){
tensContainer.data([1,2,3,4,5,6,7,8,9,10])
.enter()
.append('circle')
.attr("cx",function(e,j) {
// console.log(e)
return 100 + i * nodeRadius*2 + (i*difference) //use i here to get what set of 10 it is in
})
.attr("cy", function(e,j) {
return 100 + j * nodeRadius*2 + (j*difference) //use j here to increment downwards
})
.attr("r", function(e) {
return nodeRadius //hard coded radius above
})
.attr("fill", "white")
.style("stroke", "black")
.style("stroke-width", 3);
})
Then the units are even easier. To work out the x position I multiplied the size of the 10's dataset by the width of the node plus the difference and added 40 to give it an offset. So every time you refresh, the units are 40 units to the right :
var circlesUnits = gUnits.selectAll('circle')
.data(circleUnitsData)
.enter()
.append('circle')
.attr("cx", function(d) {
var i = circleTensData.length;
console.log(400 + i * nodeRadius*2 + (i*difference))
return 140 + i * nodeRadius*2 + (i*difference)
})
.attr("cy", function(d, i) {
return 100 + i * nodeRadius*2 + (i *difference)
})
.attr("r", function(d) {
return d.radius
})
.attr("fill", "white")
.style("stroke", "black")
.style("stroke-width", 3);
On refresh I added an alert to show you the random number.
If I have misread the question, or have any questions, please feel free to comment and I will adjust. Hopefully this helps you out :)
Update fiddle : https://jsfiddle.net/thatOneGuy/8hc0sfbg/1/
EDIT
You say you don't want the number to end in 0. The following will work :
function getRand() {
var randNumber = Math.round(Math.random() * 90 + 11) // number between 0-99
if (randNumber % 10 == 0) { //if ends in 0 run again
return getRand();
}
return randNumber;
}
I have wrapped the code in a function so you can call it when you want. So the above code will run when calling the new function. Click the button 'Click Me' to run the function
Updated fiddle : https://jsfiddle.net/thatOneGuy/8hc0sfbg/5/
EDIT
You wish to center the circles. I would remove any starting movements that you set, i.e when setting the cx and cy like so :
return 100 + i * nodeRadius*2 + (i*difference)
Just get rid of the 100 which resets it. I would then translate it half of the width and remove half the width of the tens and units bounding box. Which should center it. Something like so :
var circleContainer = d3.select('#circleContainer')
var circleTensContainer = d3.select('#tens')
var circleUnitsContainer = d3.select('#units')
var tensBBox = circleTensContainer.node().getBBox();
var unitsBBox = circleUnitsContainer.node().getBBox();
var containerSize = 40 + tensBBox.width + unitsBBox.width;
var thisMovX = svgWidth/2 - containerSize/2;
circleContainer.attr('transform', 'translate(' + thisMovX + ',0)' )
You can do something similar if you want the y centered too. I have also changed where you draw the lines too. Hope this helps :)
Updated fiddle : https://jsfiddle.net/thatOneGuy/8hc0sfbg/8/
Why you don't use D3 powerful data management?
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js" charset="utf-8"></script>
<style>
svg {
width:480px;
height:600px;
margin:auto 50%;
}
circle { stroke:#ccc;stroke-width:1px; }
.units { fill:#09C; }
.tens { fill:#F00; }
</style>
</head>
<body>
<script>
var tens = [], units=[], radio=40;
for (i=0;i<Math.random()* 90 + 10;i++) tens.push(i); // [10-99]
for (i=0;i<Math.random()* 10;i++) units.push(i); // [0-9]
var svg = d3.select("body").append("svg")
var units = svg.selectAll("circle.units") // draw units
.data(units, function (d) {return d; })
units.enter().append("circle")
.attr("class", "units")
.attr("cx", function (d) {return radio + Math.floor( d / 10 )*radio })
.attr("cy", function (d) {return radio + d % 10 * radio} )
.attr("r", radio/2)
var tens = svg.selectAll("circle.tens") // draw tens
.data(tens, function (d) {return d; })
tens.enter().append("circle")
.attr("class", "tens")
.attr("cx", function (d) {return radio + Math.floor( d / 10 )*radio })
.attr("cy", function (d) {return radio + d % 10 * radio} )
.attr("r", radio/2)
.attr("transform","translate("+radio*2+", 0)")
svg.append('line') //draw lines
.attr('x1', 0)
.attr('y1', 480)
.attr('x2', 80)
.attr('y2', 480)
.attr('stroke', 'black')
.attr('stroke-width', 3);
svg.append('line')
.attr('x1', 110)
.attr('y1', 480)
.attr('x2', 280)
.attr('y2', 480)
.attr('stroke', 'black')
.attr('stroke-width', 3);
</script>
</body>
</html>
Here de jsfiddle: https://jsfiddle.net/50j9wv2w/

ember-cli Unclosed element `d.y` causing ember app to crash

I have the following file bubbles.hbs in a ember-cli app with the following code, and I am getting the following error, Unclosed element d.y (on line 56). I tried googling the problem, but could not find a solution.
UPDATE
Here is a fiddle I made https://jsfiddle.net/395jthv9/1/ of the code below, and appears to be working.
<script type="text/javascript">
// #5b342f
var beerBackground = d3.select("body").transition()
.duration(2000)
// carter wilson color = FFE152
.style("background-color","#f5d037");
</script>
<script type="text/javascript">
w = window.innerWidth,
h = window.innerHeight;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
var circle = svg.selectAll("circle")
.data(d3.range(70).map(function(datum,interval) {
return {
x: interval*20,
y: 0,
// stroke-width="1",
dx: 5,
dy: -3 * (Math.random()+1),
mu: Math.random()*2
};
}))
.enter().append("svg:circle")
.attr("r", 2.5)
.attr("fill","#FFEFA0") // fill
.attr("stroke","white") // stroke
.attr("stroke-width", "1")
// .attr("opacity",".0.2")
.style("stroke-opacity", "1.0")
.style("fill-opacity", ".5");
var text = svg.append("svg:text")
.attr("x", 20)
.attr("y", 20);
var start = Date.now(),
frames = 0;
d3.timer(function()
{
// Update the circle positions.
circle
.attr("cx", function(d) {
d.x += Math.random()*3*Math.sin(Math.random()*3*d.x + Math.random()*10); if (d.x > w) d.x -= w; else if (d.x < 0) d.x += w; return d.x; })
.attr("cy", function(d) {
d.y += d.dy ;
if (d.y > h) {
d.y -= h;
}
// And the below line is giving me an error.
else if (d.y < 0) {
d.y += h;
}
return d.y;
})
.attr("r",function(d) {
return (d.y < 100) ? d3.select(this).attr("r") : d.mu*500/d.y;
}); --}}
});
</script>
You shouldn't put your JavaScript code in .hbs file using <script> tag. Move this code to JavaScript file instead.
So, move that code from bubbles.hbs to bubbles.js. You could for example execute that logic in didInsertElement hook if it's component, or after init event.

Categories

Resources