How can I access/modify an data related SVG element in place? I got an interval function, which executes every 3 seconds to control the data.
window.setInterval(function() {
for (var i = 0; i < data.length; i++) {
if(data[i].active) {
//THIS SVG node.. .style("fill", "green")
} else {
//THIS SVG node.. .style("fill", "red")
}
}
}, 3000)
Or do I need to re-initialize the whole D3.js graph each time?
Related
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.
While I'm on my way into learning the secrets of the beautiful world of Data Visualization, I'm encountering some difficulties with D3.js and the loading process of external data from a csv or a json file.
I'm kinda a newbie with JS so I'd like some help from experts.
Below my code:
var w = 500;
var h = 500;
// I'm setting up an empty array
var csvData = [];
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// Get the data
d3.csv("csv/cities.csv", function(dataset) {
for(var i = 0; i < dataset.length; i++) {
csvData.push(dataset[i]);
}
});
// Draw data
svg.select("body").selectAll("p")
.data(csvData)
.enter()
.append("p")
.text(function(d) { return d });
Well, I'm not sure I did understand well the correct way to load data and process these values. Can someone be so kind to give me an hint? I have access to the csvData array (using the console from the developers tools) but I can't see any data returned with the // Draw data section.
This is the csv file:
csv/cities.csv
city,state,population,land area
seattle,WA,652405,83.9
new york,NY,8405837,302.6
boston,MA,645966,48.3
kansas city,MO,467007,315.0
Thanks
The snippet below is an ajax call which loads csv/cities.csv asynchronously:
// Get the data
d3.csv("csv/cities.csv", function(dataset) {
for(var i = 0; i < dataset.length; i++) {
csvData.push(dataset[i]);
}
});
Thus the draw data section should have been like shown below:
// Get the data
d3.csv("csv/cities.csv", function(dataset) {
for(var i = 0; i < dataset.length; i++) {
csvData.push(dataset[i]);
}
// Draw data
svg.select("body").selectAll("p")
.data(csvData)
.enter()
.append("p")
.text(function(d) { return d });
});
Next Mistake:
You cannot add a p DOM inside SVG, that is the reason why you don't see the p DOM elements. I have appended the p DOM element into the body DOM that fixed the problem.
Working fiddle here
I have the following function to draw different SVG shapes with D3.
updateChart(props){
var svg = d3.select(this.refs.svg);
let prefix = "XX";
// Render SVG Tags with ID and without attributes first
let objectsRender = svg.selectAll("line")
.data(props.data)
.enter()
.append(function(d) {
return document.createElementNS(
"http://www.w3.org/2000/svg",
d.objType);
})
.attr("id", function(d){ return prefix +d._id;})
.attr("class", "no-attr")
.on("click",(d)=>{this.modeClick(d);});
// Set Attributes for all classes with no attributes set so far
let objectsAttributes= svg.selectAll(".no-attr").each(function(){
let id= d3.select(this).attr("id");
let data = null;
data = props.data[0];
for(i = 0;i < props.data.length; i++){
if(id == prefix +props.data[i]._id){
data = props.data[i];
break;
}
}
if( data !== null){
d3.select(this)
.attr(data.coordinates)
.attr(data.style)
.attr("class", null);
}
}
);
}
props.data contains all the objects with the relevant svg data.
The problem I have right now, is that sometimes when this method is invoked,
D3 starts duplicating already existing SVG elements, even though the amount of data in props.data has not changed.
Can anyoe help me with that?
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
Is there any way to transition between text values for a particular element - e.g. fading between them or something similar? I've found no relevant references to this, but D3 seems built for this kind of thing. This jsfiddle may assist: http://jsfiddle.net/geotheory/2wJr7/
<p>Intro text - click mouse on this text</p>
<script>
var i = 0;
var data = ['text 1','text 2','text 3','text 4','text 5','text 6','text 7','text 8'];
var change = d3.select('p').on("mousedown", function(){transition();});
function transition() {
change.transition().duration(1000).text(data[i]);
i = i + 1;
}
</script>
You can do this by setting the opacity to 0 and then to 1 in two connected transitions:
change.transition().duration(500)
.style("opacity", 0)
.transition().duration(500)
.style("opacity", 1)
.text(data[i]);
Complete jsfiddle here.
Like this?
var data = ['text 1','text 2','text 3','text 4','text 5','text 6','text 7','text 8'];
var change = d3.select('p').on("mousedown", function(){transition();});
function transition() {
for (i=0; i < data.length; i++)
change
.transition()
.delay(i * 1000)
.duration(500)
.text(data[i]);
}
This is more beautiful than changing opacity
function transition() {
d3.select('text').transition()
.duration(500)
.style("font-size","1px")
.transition()
.duration(500)
.text(data[i])
.style("font-size","16px");
i = i + 1;
}