Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

draw a grid or rectangles using a scale

Tags:

d3.js

I'm building my first line graph in d3:

http://jsfiddle.net/j94RZ/

I want to know how to utilize either the scale or axis allow me to draw a grid (of, presumably rectangles) where I can set a different background colour for each of the section of the grid...so I can alternate colours for each cell of the grid. I want the grid to be drawn and be constrained by the axes of my graph and then also adapt if the spacing of the axes ticks change (i.e. the axes changes like this: http://bl.ocks.org/mbostock/1667367). So if my graph has an x axis with 4 ticks and a y axis of 7 ticks then my graph will have a background grid that's 7 blocks high and 4 blocks wide.

I've been playing with the idea of using a range which starts at zero and ends at the full width of the graph but I don't know what value I can use for the step. Is there any way to sort of query the axis and return how many ticks there are?

var gridRange = d3.range(0, width, step?); 
like image 224
Mike Rifgin Avatar asked Oct 01 '13 18:10

Mike Rifgin


People also ask

How do you draw a grid scale?

To draw this grid, put your ruler at the top of the paper, and make a small mark at every inch. Place the ruler at the bottom of the paper and do the same thing. Then use the ruler to make a straight line connecting each dot at the bottom with its partner at the top.

What is a grid drawing?

Grid drawing is a technique that will help improve your accuracy without compromising the development of your freehand drawing in the long-term. It basically involves placing a grid over your reference photo and canvas, then using that grid to assist with the placement of your drawing.

How do you scale a drawing?

To scale a drawing by hand, start by measuring the width and height of the object you'll be scaling. Next, choose a ratio to resize your drawing, such as 2 to 1 to double the image in size. Then, multiply your measurements by the first number in your ratio to increase the size.

When would you use a grid scale?

One way of transposing images from one piece of paper to another without the use of a computer is to use the grid method. It's simple and can be used by people with varying levels of drawing ability while still yielding great results.


2 Answers

A better approach than your current solution would be to use scale.ticks() explicitly to get the tick values. The advantage of that is that it will still work if you change the number of ticks for some reason.

To get an alternating grid pattern instead of a single fill, you can use something like this code.

.attr("fill", function(d, i) {
    return (i % 2) == 1 ? "green" : "blue";
})

Finally, to get the full grid pattern, you can either use an explicit loop as you've suggested, or nested selections. The idea here is to first pass in the y ticks, create a g element for each and then pass the x ticks to each one of these groups. In code, this looks something like this.

svg.selectAll("g.grid")
   .data(y.ticks()).enter().append("g").attr("class", "grid")
   .selectAll("rect")
   .data(x.ticks()).enter().append("rect");

To set the position, you can access the indices within the top and bottom level data arrays like this.

.attr("x", function(d, i) {
    return xScale(i);
})
.attr("y", function(d, i, j) {
    return yScale(j);
})

To set the x position, you need the index of the inner array (passed to the set of g elements), which can be accessed through the second argument of your callback. For the outer array, simply add another argument (j here).

And that's really all there is to it. Complete jsfiddle here. To update this grid dynamically, you would simply pass in the new tick values (gotten from scale.ticks()), match with the existing data, and handle the enter/update/exit selections in the usual manner.

If you want to do without the auxiliary scales (i.e. without .rangeBand()), you can calculate the width/height of the rectangles by taking the extent of the range of a scale and dividing it by the number of ticks minus 1. Altogether, this makes the code a bit uglier (mostly because you need one fewer rectangle than ticks and therefore need to subtract/remove), but a bit more general. A jsfiddle that takes this approach is here.

like image 112
Lars Kotthoff Avatar answered Oct 27 '22 01:10

Lars Kotthoff


So after a few helpful comments above I've got close to a solution. Using Ordinal rangebands get me close to where I want to go.

I've created the range bands by using the number of ticks on my axis as a basis for the range of the input domain:

  var xScale = d3.scale.ordinal()
            .domain(d3.range(10))
            .rangeRoundBands([0, width],0);


 var yScale = d3.scale.ordinal()
            .domain(d3.range(4))
            .rangeRoundBands([0, height],0);

I've then tried drawing the rectangles out like so:

svg.selectAll("rect")
           .data(p)
           .enter()
           .append("rect")
           .attr("x", function(d, i) {
                return xScale(i);
           })
           .attr("y", function(d,i) {
                0
           })
           .attr("width", xScale.rangeBand())
           .attr("height", yScale.rangeBand())
                       .attr("fill", "green").
                       attr('stroke','red');

This gets me the desired effect but for only one row deep:

http://jsfiddle.net/Ny2FJ/2/

I want,somehow to draw the green blocks for the whole table (and also without having to hard code the amount of ticks in the ordinal scales domain). I tried to then apply the range bands to the y axis like so (knowing that this wouldn't really work though) http://jsfiddle.net/Ny2FJ/3/

svg.selectAll("rect")
       .data(p)
       .enter()
       .append("rect")
       .attr("x", function(d, i) {
            return xScale(i);
       })
       .attr("y", function(d,i) {
            return yScale(i);
       })
       .attr("width", xScale.rangeBand())
       .attr("height", yScale.rangeBand())
                   .attr("fill", "green").
                   attr('stroke','red');

The only way I can think to do this is to introduce a for loop to run the block of code in this fiddle http://jsfiddle.net/Ny2FJ/2/ for each tick of the y axis.

like image 31
Mike Rifgin Avatar answered Oct 27 '22 00:10

Mike Rifgin