I'm currently working on a quite basic graph using 2 ordinal axes. X axis shows 4 categories, Y axis shows 3. For some reason, the plotted circles don't align with the plotted axes.
An example can be seen at http://jsfiddle.net/SrdY6/. Problem seems to be translation-related, but the only translation in there is applied to the large containing <g> element:
var lunchgraph = svg.append("g")
.attr("class", "lunchgraph")
.attr("transform", "translate(" + lunchmargin.left + "," + lunchmargin.top + ")");
I've been looking at this for some time now, but can't spot where things go wrong... Anyone with more insight?
Nothing like putting a question out there and risking public shame, only to find out the answer within minutes after posting.
For ordinal axes configured with rangeBands or rangeRoundBands, the scale function returns the lower value of the given input. To have the plot align with the exact categorical labels, you need to add half of the rangeBand to the calculated coordinate.
So: no problem with the translations or anything, but with the computation of cx and cy coordinates for placing the circles in the graph.
Correct code:
.attr("cx", function(d) { return x(d.label) + x.rangeBand()/2 ;} )
.attr("cy", function(d) { return y(d.sqid) + y.rangeBand()/2 ; } )
Related
My code is safely stored on my bl.ocks site
I've effectively converted a scatter plot into a hexagon plot by defining a polyline shape with 6 edges and referencing HEX in the code instead of circle. However, my scatter plot started neatly at (0,0) on the origin of my graph scales. My hex plot starts offset by -50,-20 and I can't find a way around it to revert it back to (0,0).
Here is the scatter plot code for info
You need to remove the transform on this line.
svg.append("g")
.attr("class", "path.hex")
.attr("transform", "translate(50, 20)")
.call(hex);
And potentially the 50,20 translating on this line (I think you may still need the scales)
hexs.attr("transform", function(d) {
return "translate(" + xScale(d.Board) + 50 + "," + yScale(d.Alight) + 20 + ")" + " scale(" + rScale(d.Totals) + ")"
})
I am triying to create a line chart which will have multiple y axis but a common x axis using d3, can somebod provide me the example how to create it with D3 library. It should be something like as shown below
Quite simply: Just draw 2 charts but only append one x-axis, here's a fiddle to get you started: http://jsfiddle.net/henbox/fg18eru3/1/
In this example, I've assumed that the two different datasets have different y-domains but the same x-domain. If that's not the case, you should just get the max and min from the combined x-domains of both sets.
If you start by defining 2 g elements that will contain the two charts, and transforming the bottom chart down so they don't overlap:
var topchart = svg.append("g").attr("class", "topchart");
var bottomchart = svg.append("g").attr("class", "bottomchart")
.attr("transform", "translate(0," + height/2 + ")");
... then append the path and y-axis to the appropriate g, but only add the x-axis to the bottom chart:
bottomchart.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + (height/2 - padding) + ")")
.call(xAxis);
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?);
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.
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.
I've been extending the code in this example:
http://bl.ocks.org/mbostock/5914438
But I can't seem to figure out how to place points on this map in a manner in which they can be integrated in the zoom. Paths work fine but it seems like the modified projection can't be used to project the coordinates of the points, and in the zoomed function, the scale() addition to the transform() when applied to the element containing the points seems to scale the points so large that they fill the entire screen. Here's my additional points:
var sitesG = svg.append("g").attr("id","sitesG");
var osites = sitesG.selectAll(".sites")
.data(sites)
.enter()
.append("g")
.attr("transform", function(d) {return "translate(" + projection([d.x,d.y]) + ")scale(" + projection.scale() + ")"})
osites.append("circle").attr("r", 10)
And here's the function in zoomed():
d3.select("#sitesG")
.attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")")
I've tried this with individual elements for the sites as well, but with no success. Does anyone have an example that puts points on geo.tile?
I managed to figure it out. My main problem was that I was not scaling the "r" attribute of the circles to match the zoom.scale() when I was properly projecting the points, which caused them to have 13,000px+ radii.
You can see it working here:
http://bl.ocks.org/emeeks/6147081
I have a zoomable area plot done in D3, which works well. Now I am trying to add a rectangle to the specified location along x-axis in the middle of the plot. However, I can't seem to figure out how to do that. "rect" element is specified using absolute (x,y) of the plot and so when using zooms it stays in the same position.
So I was wondering if there is a way to tie "rect" to the axis when plotting, so that it benefits from all the zoom and translate behaviour or do I need to manually edit the x,y,width and length of the rectangle according to translation as well as figuring out where the corresponding x and y coordinates are on the graph? I am trying to use "rect" because it seems the most flexible element to use.
Thanks
Alex
I'm not sure how you are doing the zooming, but I am guessing you are changing the parameters of the scales you use with your axis? You should be able to use the same scales to place your rectangle.
If you are starting with plot coordinates then maybe using the invert function on the scale will help (available at least for quantitive scales), e.g. https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-linear_invert
You should be able to take initial plot coordinates and invert them to determine data coordinates that can then move with changes in the scale.
If the scale is linear you can probably invert the length and width too, but you will have to compute offsets if your domain does not include 0. Easiest is to compute the rectangle's end points, something like:
var dataX0 = xScale.invert(rect.x);
var dataX1 = xScale.invert(rect.x + rect.width);
var dataWidth = dataX1 - dataX0;
If you have the data in axes coordinates already you should be able to do something like:
var rectData = [{x: 'April 1, 1999', y: 10000, width: 100, height:100}];
svg.selectAll('rect.boxy')
.data(rectData)
.enter().append('rect').classed('boxy', true)
.style('fill','black');
svg.selectAll('rect.boxy')
.attr('x', function(d) { return x(new Date(d.x));} )
.attr('y', function(d) { return y(d.y);})
.attr('width', function(d) { return d.width;} )
.attr('height', function(d) { return d.height;} );
Based on the example you shared where x and y (as functions) are the scales the axes are based on.