How to bind a changing datasource to a set of svg? - javascript

I would like to visualize a circle with a random colour at a random x and y coordinate, then add an extra colourful circle at a random location every second.
I am using d3.timer to run a function that appends x and y coordinates to my dataset object, which is bound to all of the circle objects. When I print the dataset object, I can see that my function does in fact append the new x and y coordinates to my dataset object. However, the visualization does not update with the new circles. How can I add a new circle every second?
Relevant functions below:
var reshuffleData = function(){
for (var i=0; i<5; i++){
console.log('Reshuffling')
dataset.push({x: randomXPosition(), y: randomYPosition()})
}
console.log(dataset)
return true
}
d3.timer(reshuffleData, 10);
Full jsfiddle here: http://jsfiddle.net/d74Le5xk/

It wasn't not working because d3.timer is used incorrectly. As d3.timer just takes a function to paint next animation frame. We do not control when the this function will be called but it will be called most likely (1/ frames per second seconds). Where FPS may change every second.
If you want to do something periodically use setInterval also you need to redraw the circles once the dataset size is changed.
Following is the jsfiddle link for the working code.
http://jsfiddle.net/d74Le5xk/3/
Also attaching the code here for reference.
HTML
<svg class='canvas'></svg>
Javascript
(function () {
var width = 420, height = 200;
var randomXPosition = function(d){
return Math.random() * width;
}
var randomYPosition = function(d){
return Math.random() * height;
}
var dataset = [];
var circleBatchSize = 5;
var maxCircleCount = 100;
for (var i=0; i < circleBatchSize; i++){
dataset.push({x: randomXPosition(), y: randomYPosition()})
}
var testInterval = null;
var reshuffleData = function(){
for (var i=0; i<circleBatchSize; i++){
dataset.push({x: randomXPosition(), y: randomYPosition()})
//return true;
}
console.log('Reshuffled ' + dataset.length)
console.log(dataset)
if(dataset.length > maxCircleCount) {
clearInterval(testInterval);
}
}
console.log(dataset);
var colours = ['#FDBB30', '#EE3124', '#EC008C', '#F47521', '#7AC143', '#00B0DD'];
var randomColour = function() {
return colours[Math.floor(Math.random() * colours.length)];
}
//d3.timer(reshuffleData, 0, 5000);
testInterval = window.setInterval(reshuffleData, 2000);
var canvas = d3.select('.canvas')
.attr('width', width)
.attr('height', height)
.style('background-color', 'black');
var datasetOldLength = 0;
function drawCircles() {
if(datasetOldLength === dataset.length ) {
return;
}
datasetOldLength = dataset.length;
var circles = canvas.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
.style('r', 20)
.style('fill', randomColour)
.style('cx', function(d) { return d.x} )
.style('cy', function(d) { return d.y} );
if(dataset.length > maxCircleCount) {
return true;
}
}
d3.timer(drawCircles, 1000);
})();
d3.timer usage explanation
# d3.timer(function[, delay[, time]])
[function] argument is called at every frame rendering by d3. It's called until it returns true.
optional [delay] in milliseconds to delay the first invocation of the [function]. Delay is taken since the [time] passed in third argument. If [time] is not passed delay starts from new Date().getTime().
optional [time] is the epoch time from when the delay is considered.
Reference https://github.com/mbostock/d3/wiki/Transitions#timers

Related

If condition never met and hence my bars in a simple bar chart are always blue, while I want them to be red for values below a certain number

I have been trying this code to create a bar chart with given data. It works but it never returns a red, rather returns blue always. I have tried putting b[i]>0, still only blue bars. The function wit parameter b is executed for sure and I have tried (b,i) too.
<!DOCTYPE html>
<html>
<head>
<script src = "https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<svg height = "250px" width="500px"></svg>
<script>
var b = [5,9,6,4,3];
var x = 100;
const height = 250;
for(var i=0; i<b.length;i++)
{
var svg = d3.select("svg").data(b).append("rect").attr("x",x)
.attr("y",height - (b[i]*20))
.attr("width",20).attr("height", b[i]*20).attr("fill", function(b){
if(b[i]<6) //This if condition never gets checked and hence doesn't work
{
return "red";
}
else
{
return "blue"; //This executes
}
})
x = x + 25; //Increment for the position of next bar
}
</script>
</body>
</html>
You make a shadow variable b, as an argument of the callback, so it is not an array, but the item of it. Since you are using cycle for drawing each point separately, you could avoid use callback for this.
So instead of
.attr("fill", function(b) { ... })
you need to write this
.attr("fill", b[i] < 6 ? "red" : "blue")
Btw, d3 js can iterate points for you:
<script>
var b = [5, 9, 6, 4, 3];
var xOffset = 100;
const height = 250;
var svg = d3.select("svg")
.selectAll('rect')
.data(b)
.enter()
.append('rect')
.attr("x", (d, i) => xOffset + 25 * i)
.attr("y", d => height - d * 20)
.attr("width", 20)
.attr("height", d => d * 20)
.attr("fill", d => d < 6 ? "red" : "blue");
</script>

planetaryjs globe not working - javascript

I want this type of globe and I am using planetaryjs for this.
I have added the necessary resources for that in external resources link
including all js files and data file.
why the globe is not loading?
jsfiddle link
(function() {
var canvas = document.getElementById('quakeCanvas');
// Create our Planetary.js planet and set some initial values;
// we use several custom plugins, defined at the bottom of the file
var planet = planetaryjs.planet();
planet.loadPlugin(autocenter({extraHeight: -120}));
planet.loadPlugin(autoscale({extraHeight: -120}));
planet.loadPlugin(planetaryjs.plugins.earth({
topojson: { file: '/world-110m.json' },
oceans: { fill: '#001320' },
land: { fill: '#06304e' },
borders: { stroke: '#001320' }
}));
planet.loadPlugin(planetaryjs.plugins.pings());
planet.loadPlugin(planetaryjs.plugins.zoom({
scaleExtent: [50, 5000]
}));
planet.loadPlugin(planetaryjs.plugins.drag({
onDragStart: function() {
this.plugins.autorotate.pause();
},
onDragEnd: function() {
this.plugins.autorotate.resume();
}
}));
planet.loadPlugin(autorotate(5));
planet.projection.rotate([100, -10, 0]);
planet.draw(canvas);
// Create a color scale for the various earthquake magnitudes; the
// minimum magnitude in our data set is 2.5.
var colors = d3.scale.pow()
.exponent(3)
.domain([2, 4, 6, 8, 10])
.range(['white', 'yellow', 'orange', 'red', 'purple']);
// Also create a scale for mapping magnitudes to ping angle sizes
var angles = d3.scale.pow()
.exponent(3)
.domain([2.5, 10])
.range([0.5, 15]);
// And finally, a scale for mapping magnitudes to ping TTLs
var ttls = d3.scale.pow()
.exponent(3)
.domain([2.5, 10])
.range([2000, 5000]);
// Create a key to show the magnitudes and their colors
d3.select('#magnitudes').selectAll('li')
.data(colors.ticks(9))
.enter()
.append('li')
.style('color', colors)
.text(function(d) {
return "Magnitude " + d;
});
// Load our earthquake data and set up the controls.
// The data consists of an array of objects in the following format:
// {
// mag: magnitude_of_quake
// lng: longitude_coordinates
// lat: latitude_coordinates
// time: timestamp_of_quake
// }
// The data is ordered, with the earliest data being the first in the file.
d3.json('/examples/quake/year_quakes_small.json', function(err, data) {
if (err) {
alert("Problem loading the quake data.");
return;
}
var start = parseInt(data[0].time, 10);
var end = parseInt(data[data.length - 1].time, 10);
var currentTime = start;
var lastTick = new Date().getTime();
var updateDate = function() {
d3.select('#date').text(moment(currentTime).utc().format("MMM DD YYYY HH:mm UTC"));
};
// A scale that maps a percentage of playback to a time
// from the data; for example, `50` would map to the halfway
// mark between the first and last items in our data array.
var percentToDate = d3.scale.linear()
.domain([0, 100])
.range([start, end]);
// A scale that maps real time passage to data playback time.
// 12 minutes of real time maps to the entirety of the
// timespan covered by the data.
var realToData = d3.scale.linear()
.domain([0, 1000 * 60 * 12])
.range([0, end - start]);
var paused = false;
// Pause playback and update the time display
// while scrubbing using the range input.
d3.select('#slider')
.on('change', function(d) {
currentTime = percentToDate(d3.event.target.value);
updateDate();
})
.call(d3.behavior.drag()
.on('dragstart', function() {
paused = true;
})
.on('dragend', function() {
paused = false;
})
);
// The main playback loop; for each tick, we'll see how much
// time passed in our accelerated playback reel and find all
// the earthquakes that happened in that timespan, adding
// them to the globe with a color and angle relative to their magnitudes.
d3.timer(function() {
var now = new Date().getTime();
if (paused) {
lastTick = now;
return;
}
var realDelta = now - lastTick;
// Avoid switching back to the window only to see thousands of pings;
// if it's been more than 500 milliseconds since we've updated playback,
// we'll just set the value to 500 milliseconds.
if (realDelta > 500) realDelta = 500;
var dataDelta = realToData(realDelta);
var toPing = data.filter(function(d) {
return d.time > currentTime && d.time <= currentTime + dataDelta;
});
for (var i = 0; i < toPing.length; i++) {
var ping = toPing[i];
planet.plugins.pings.add(ping.lng, ping.lat, {
// Here we use the `angles` and `colors` scales we built earlier
// to convert magnitudes to appropriate angles and colors.
angle: angles(ping.mag),
color: colors(ping.mag),
ttl: ttls(ping.mag)
});
}
currentTime += dataDelta;
if (currentTime > end) currentTime = start;
updateDate();
d3.select('#slider').property('value', percentToDate.invert(currentTime));
lastTick = now;
});
});
// Plugin to resize the canvas to fill the window and to
// automatically center the planet when the window size changes
function autocenter(options) {
options = options || {};
var needsCentering = false;
var globe = null;
var resize = function() {
var width = window.innerWidth + (options.extraWidth || 0);
var height = window.innerHeight + (options.extraHeight || 0);
globe.canvas.width = width;
globe.canvas.height = height;
globe.projection.translate([width / 2, height / 2]);
};
return function(planet) {
globe = planet;
planet.onInit(function() {
needsCentering = true;
d3.select(window).on('resize', function() {
needsCentering = true;
});
});
planet.onDraw(function() {
if (needsCentering) { resize(); needsCentering = false; }
});
};
};
// Plugin to automatically scale the planet's projection based
// on the window size when the planet is initialized
function autoscale(options) {
options = options || {};
return function(planet) {
planet.onInit(function() {
var width = window.innerWidth + (options.extraWidth || 0);
var height = window.innerHeight + (options.extraHeight || 0);
planet.projection.scale(Math.min(width, height) / 2);
});
};
};
// Plugin to automatically rotate the globe around its vertical
// axis a configured number of degrees every second.
function autorotate(degPerSec) {
return function(planet) {
var lastTick = null;
var paused = false;
planet.plugins.autorotate = {
pause: function() { paused = true; },
resume: function() { paused = false; }
};
planet.onDraw(function() {
if (paused || !lastTick) {
lastTick = new Date();
} else {
var now = new Date();
var delta = now - lastTick;
var rotation = planet.projection.rotate();
rotation[0] += degPerSec * delta / 1000;
if (rotation[0] >= 180) rotation[0] -= 360;
planet.projection.rotate(rotation);
lastTick = now;
}
});
};
};
})();
Your globe is not loading for multiple reasons. First, if you open up your console you would see that the external resources were not loaded properly. As the error notes, your external resources failed to load because:
MIME type ('text/plain') is not executable, and strict MIME type checking is enabled.
You could overcome this issue in a twofold manner. You could either refer to this answer--which will help you sort the MIME type issue--or you may just use other links for you external resources. If you rather take the second route, you should add the links below to your external resources section, in the following order:
moment.js
d3.js
planetary.js
After you solved the first issue, you will see that you would not be able to link your JSON file by simply adding it to your external resources. These answers will offer you multiple approaches that will help you solve the JSON issue as well.
After you have successfully linked all of your external files, you will face a third problem. If you look at your code you will see that you are trying to get the time property for your data (JSON) object in line 75--var start = parseInt(data[0].time, 10);. However, as far as I could tell, your data object does not hold a time property--go ahead, and console.log() your data object to see its structure and properties. In other words, you might want to double check if the world-110m.json is the file that you want to work with.
Hope this helps.

How to animate drawing a sequence of line segments

I would like to draw a point and after 1 sec or so I would like to draw the next point. Is this somehow possible:
I already tried:
function simulate(i) {
setTimeout(function() { drawPoint(vis,i,i); }, 1000);
}
for (var i = 1; i <= 200; ++i)
simulate(i);
function drawPoint(vis,x,y){
var svg = vis.append("circle")
.attr("cx", function(d) {
console.log(x);
return 700/2+x;
})
.attr("cy", function(d) {
return 700/2+y;
})
.attr("r", function(d) {
console.log(x);
return 6;
});
}
Unfortunately, this is not working. It just draws the whole line immediately.
This will not work, because the for loop will immediately run to the end, the setTimeouts will be scheduled simultaneously and all the functions will fire at the same time.
Instead of that, do this:
var i = 1;
(function loop(){
if(i++ > 200) return;
setTimeout(function(){
drawPoint(vis,i,i);
loop()
}, 1000)
})();
Explanation:
This IIFE will run for the first time with i = 1. Then, the if increases i (doing i++) and checks if it is bigger than 200. If it is, the function loop returns. If it's not, a setTimeout is scheduled, which calls drawnPoint and the very function loop again.

paper.js animate point on graph

I'm trying to build an animated graph with paper.js that can react to different input. So I want to smoothly animate one point vertically to a different point.
I've looked at different examples and the closest ones to mine is this one:
paper.tool.onMouseDown = function(event) {
x = event.event.offsetX;
y = event.event.offsetY;
paper.view.attach('frame', moveSeg);
}
var x;
var y;
function moveSeg(event) {
event.count = 1;
if(event.count <= 100) {
myPath.firstSegment.point._x += (x / 100);
myPath.firstSegment.point._y += (y / 100);
for (var i = 0; i < points - 1; i++) {
var segment = myPath.segments[i];
var nextSegment = segment.next;
var vector = new paper.Point(segment.point.x - nextSegment.point.x,segment.point.y - nextSegment.point.y);
vector.length = length;
nextSegment.point = new paper.Point(segment.point.x - vector.x,segment.point.y - vector.y);
}
myPath.smooth();
}
}
This Code animates one Point to the click position, but I couldn't change it to my needs.
What I need is:
var aim = [120, 100];
var target = aim;
// how many frames does it take to reach a target
var steps = 200;
// Segment I want to move
myPath.segments[3].point.x
And then I dont know how to write the loop that will produce a smooth animation.
example of the graph:
I worked out the answer. The following steps in paperscript:
Generate Path
Set aim for the point
OnFrame Event that does the moving (eased)
for further animations just change the currentAim variable.
var myPath = new Path({
segments: [[0,100],[50,100],[100,100]]});
// styling
myPath.strokeColor = '#c4c4c4'; // red
myPath.strokeWidth = 8;
myPath.strokeJoin = 'round';
myPath.smooth();
// where the middle dot should go
var currentAim = [100,100];
// Speed
var steps = 10;
//Animation
function onFrame(event) {
dX1 = (currentAim[0] - myPath.segments[1].point.x )/steps;
dY1 = (currentAim[1] - myPath.segments[1].point.y )/steps;
myPath.segments[1].point.x += dX1;
myPath.segments[1].point.y += dY1;
}

performance optimization of snap points using drag and drop in RaphealJS

I have a demo page where I am looking at the performance of around 300 points that an object should snap to when being dragged.
http://jsfiddle.net/digiguru/rVFje/
I have optimized it by loading the bounds of each "snap" point into 4 arrays when the drag is started.
var circleT = [];
var circleR = [];
var circleB = [];
var circleL = [];
var start = function (event) {
this.ox = this.attr("cx");
this.oy = this.attr("cy");
var threshold = 15;
for (var myCircle in circles) {
circleT[myCircle] = circles[myCircle].attr("cy") - threshold;
circleR[myCircle] = circles[myCircle].attr("cx") + threshold;
circleB[myCircle] = circles[myCircle].attr("cy") + threshold;
circleL[myCircle] = circles[myCircle].attr("cx") - threshold;
}
circle.animate({ r: 20,fill: "#319F40", "stroke-width": 1 }, 200);
},
Then in the move event we use the following to calculate the dragged object from each of the snap points...
move = function (mx, my) {
var inrange = false;
var mouseCX = this.ox + mx;
var mouseCY = this.oy + my;
var lockX = 0;
var lockY = 0;
for (var myCircle in circles) {
if ((circleT[myCircle] < mouseCY
&& circleB[myCircle] > mouseCY )
&& (circleR[myCircle] > mouseCX
&& circleL[myCircle] < mouseCX )) {
inrange = true;
lockX = circles[myCircle].attr("cx");
lockY = circles[myCircle].attr("cy");
}
}
if (inrange) {
this.attr({
cx: lockX ,
cy: lockY
});
} else {
this.attr({
cx: mouseCX ,
cy: mouseCY
});
}
},
Genrally performance, is good, but you can notice frames dropping on slightly older IE browsers (IE8 and below). Is there a ninja way to improve performance at all? Perhaps I can improve the 4 if statements? Would using another javascript library like processing JS yeild better results?
Currently you consider every circle on every iteration. You could improve performance by considering fewer circles using a technique similar to this.
http://en.wikipedia.org/wiki/Quadtree
Basically, create bounding boxes for collections of circles. If the circle you are dragging is out of bounds then don't consider any circle within the bounding box. I hope this helps.

Categories

Resources