d3.js create objects on top of each other - javascript

I create rectangles in my SVG element using this code:
var rectangles = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect");
rectangles.attr("x", function (d) {
return xScale(getDate(d));
//return xScale(d.start);
})
.attr("y", function (d, i) {
return (i * 33);
})
.attr("height", 30)
.transition()
.duration(1000)
.attr("width", function (d) {
return d.length;
})
.attr("rx", 5)
.attr("ry", 5)
.attr("class", "rectangle")
.attr("onclick", function (d) {
return "runaction(" + d.start + ")";
});
How can I create new rectangles on top of the previous ones?

This is an answer to this question I got from Scott Murray, author of great introductory tutorials for d3.js http://alignedleft.com/tutorials/d3/ which helped me a lot with understanding its functionality. I hope he won't mind me putting his answer here for everyone's benefit.
Thank you very much Scott!
And yes, that's absolutely possible. Taking your example, let's say you want to draw one set of circles with the dataset called "giraffeData" bound to them. You would use:
svg.selectAll("circle")
.data(giraffeData)
.enter()
.append("circle");
But then you have a second data set (really just an array of values) called "zebraData". So you could use the same code, but change which data set you reference here:
svg.selectAll("circle")
.data(zebraData)
.enter()
.append("circle");
Of course, this will inadvertently select all the circles you already created and bind the new data to them — which isn't really what you want. So you'll have to help D3 differentiate between the giraffe circles and the zebra circles. You could do that by assigning them classes:
svg.selectAll("circle.giraffe")
.data(giraffeData)
.enter()
.append("circle")
.attr("class", "giraffe");
svg.selectAll("circle.zebra")
.data(zebraData)
.enter()
.append("circle")
.attr("class", "zebra");
Or, you could group the circles of each type into a separate SVG 'g' element:
var giraffes = svg.append("g")
.attr("class", "giraffe");
giraffes.selectAll("circle")
.data(giraffeData)
.enter()
.append("circle");
var zebras = svg.append("g")
.attr("class", "zebra");
zebras.selectAll("circle")
.data(zebraData)
.enter()
.append("circle");
I'd probably choose the latter, as then your DOM is more cleanly organized, and you don't have to add a class to every circle. You could just know that any circle inside the g with class zebra is a "zebra circle".

Related

d3: how to visualize a simple 2D matrix?

I'm stuck at trying to bind two-dimensional data in d3. I want to display a matrix of green squares. I'm building a matrix like this:
var size = 10;
dataset = [];
for(var y = 0; y<size; y++){
var tempData = [size];
for(var x = 0; x<size; x++){
tempData[x] = 5;
};
dataset.push(tempData);
};
I'm not sure how to bind the data correctly. I sort of understand Mike Bostock's tutorial on nested selections, but he's binding a matrix of fixed size to already existing elements. How would I use enter() to create new rectangles? This is how I tried to apply the tutorial's advice to first bind the outer, then the inner arrays.. not surprised that it doesn't work but I also don't know where to go from here..
svg.selectAll("rect")
.data(dataset)
.selectAll("rect")
.data(function (d,i) {return d;})
.enter()
.append("rect")
.attr("x", function(d,i){
return i*20})
.attr("y", function(d,i){
return i*20;})
.attr("height", 15)
.attr("width", 15)
.attr("fill", "green");
There are two problems. First, you have the second .selectAll() immediately after the first .data(), which means that you'll be operating on the update selection. This is empty as there are no elements in the DOM to start with. You need to operate on the enter selection instead (and it's good practice to use g elements here for the first level):
svg.selectAll("rect")
.data(dataset)
.enter()
.append("g")
.selectAll("rect")
.data(function (d,i) {return d;})
Second, you're putting the rectangles along the diagonal (same x and y coordinates), so even though the correct number of rect elements is there, you don't see all of them because they overlap. To fix, you need to take the index in the parent group into account for one of the coordinates (using the secret third argument):
.append("rect")
.attr("x", function(d,i){
return i*20;
})
.attr("y", function(d, i, j){
return j*20;
})
Complete demo here.

Individual symbols are clipped in d3js

I am basically trying to clip a chart containing "path" elements representing data points.
It seems like, path elements are not initially "transformed" and remain at (0,0) and are thus clipped by rectangle surrounding entire chart.
Basically, I have replaced "circle" elements as drawn in D3 Brush Example with "path" elements of symbol type "circle".
.append("circle")
.attr("class", "point")
.attr("clip-path", "url(#clip)")
.attr("r", function(d){return Math.floor(Math.random() * (20 - 5 + 1) + 5);})
.attr("cx", function(d) { return x(d.index); })
.attr("cy", function(d) { return y(d.value); })
replaced with
.append("path")
.attr("class", "point")
.attr("clip-path","url(#clip)")
.attr("d", d3.svg.symbol().type("circle"))
Here is jsfiddle. As you can see, only lower right quarter of the circle is visible and rest have been clipped out. What is going on? What is the solution?
Edit:
Also, the actual "clipping" is not working for those circles(paths). Why is that? Why does it work for "circle" but not for "path"?

Very Simple D3: How to Draw an Arc?

It would be nice to learn D3. After reading many examples, I think I understand it. My first project is to make a color wheel, without transitions for simplicity. But it appears even that is not simple enough for my first project! For project zero, I am trying to get something to show on the screen. Hopefully something I wrote (and dear read has fixed), and not an example.
What did I do wrong? http://jsfiddle.net/aGdMX/1/
var arc = d3.svg.arc()
.innerRadius(40)
.outerRadius(100)
.startAngle(0)
.endAngle(1)
;
var chart = d3.select("body").append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 420).append("svg:g")
.attr("transform", "translate(200,200)")
;
chart.selectAll("path")
.data(data)
.enter().append("svg:path")
.attr("fill", function(d, i){
return d3.rgb("black");
})
.attr("d", arc)
;
Thank you
Your example here doesn't have any data defined. If you just want to draw the svg statically, skip the selectAll() and data() bindings:
chart
.append("svg:path")
.attr("fill", function(d, i){
return d3.rgb("black");
})
.attr("d", arc)
;
Or define some data and use that to drive the drawing:
http://jsfiddle.net/findango/aGdMX/2/
(plus .attr("fill"... should be .style("fill"...)

D3js - Creating and easily updating a multi-line chart

I've created a little test line chart using D3, but since I am quite new to the library I am not sure what the best way would be to add multiple lines to a chart, at the moment I only have one line displayed in this fiddle.
I would like to display 2 lines on the chart, but I am unsure of how to achieve that without copy pasting code, which I am sure would be very inefficient as I would like to update/animate the graph at regular intervals based on user selection.
Instead of this,
var data = [12345,22345,32345,42345,52345,62345,72345,82345,92345,102345,112345,122345,132345,142345];
I would like to display something like this,
var data = [
[12345,42345,3234,22345,72345,62345,32345,92345,52345,22345], // line one
[1234,4234,3234,2234,7234,6234,3234,9234,5234,2234] // line two
];
Would this be a possibility? If so, what would be the best way to approach this, so that I can easily update/animate the graph when needed?
Note: I am merely trying to learn and to familiarize myself with D3 best practices and the library as a whole. Thanks.
This is possible and reasonable.
There is a tutorial that approaches this at the
D3 Nested Selection Tutorial
which describes nesting of data.
Below is code that I hacked from your fiddle to demonstrate this.
var data = [
[12345,42345,3234,22345,72345,62345,32345,92345,52345,22345],
[1234,4234,3234,2234,7234,6234,3234,9234,5234,2234]
];
var width = 625,
height = 350;
var x = d3.scale.linear()
.domain([0,data[0].length]) // Hack. Computing x-domain from 1st array
.range([0, width]);
var y = d3.scale.linear()
.domain([0,d3.max(data[0])]) // Hack. Computing y-domain from 1st array
.range([height, 0]);
var line = d3.svg.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return y(d); });
var area = d3.svg.area()
.x(line.x())
.y1(line.y())
.y0(y(0));
var svg = d3.select("body").append("svg")
//.datum(data)
.attr("width", width)
.attr("height", height)
//.append("g");
var lines = svg.selectAll( "g" )
.data( data ); // The data here is two arrays
// for each array, create a 'g' line container
var aLineContainer = lines
.enter().append("g");
/*svg.append("path")
.attr("class", "area")
.attr("d", area);*/
aLineContainer.append("path")
.attr("class", "area")
.attr("d", area);
/*svg.append("path")
.attr("class", "line")
.attr("d", line);*/
aLineContainer.append("path")
.attr("class", "line")
.attr("d", line);
/*svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 3.5);*/
// Access the nested data, which are values within the array here
aLineContainer.selectAll(".dot")
.data( function( d, i ) { return d; } ) // This is the nested data call
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 3.5);
​
One deficiency is that I computed the domain for the x and y axes off the first array, which is a hack, but not pertinent to your question exactly.

d3 enter/append/exit/remove sequence only appending

A set of rectangles is drawn initially with the following enter/append/exit/remove sequence, no problem. When I pass different data (meant to replace the existing data entirely) the new rectangles are drawn on top of the existing rectangles.
I am selecting "lgnds" instead of rect, because I have drawn other rectangles that I don't wish to disturb.
var svg = d3.select("#graph").append("svg")
elements = svg
.selectAll("lgnds")
.data(data, function(d){return d;});
elements
.enter()
.append("rect")
.attr("width", 15)
.attr("height", rectHeight)
.attr("x", 5)
.attr("y", function (d,i){return ((i*rectHeight)+(gap*(i+1)));})
.style("fill", function(d){ return d.color;});
elements
.exit()
.remove();

Categories