I've tried this different ways, but nothing seems to be working. Here is what I currently have:
var vis = d3.select("#chart").append("svg")
.attr("width", 1000)
.attr("height", 667),
scaleX = d3.scale.linear()
.domain([-30,30])
.range([0,600]),
scaleY = d3.scale.linear()
.domain([0,50])
.range([500,0]),
poly = [{"x":0, "y":25},
{"x":8.5,"y":23.4},
{"x":13.0,"y":21.0},
{"x":19.0,"y":15.5}];
vis.selectAll("polygon")
.data(poly)
.enter()
.append("polygon")
.attr("points",function(d) {
return [scaleX(d.x),scaleY(d.y)].join(",")})
.attr("stroke","black")
.attr("stroke-width",2);
I assume the problem here is either with the way I am defining the points data as an array of individual point objects, or something to do with how I'm writing the function for the .attr("points",...
I've been looking all over the web for a tutorial or example of how to draw a simple polygon, but I just can't seem to find it.
A polygon looks something like: <polygon points="200,10 250,190 160,210" />
So, your full poly array should produce one long string that will create one polygon. Because we are talking about one polygon the data join should also be an array with only one element. That is why the code below shows: data([poly]) if you wanted two identical polygons you would change this to data([poly, poly]).
The goal is to create one string from your array with 4 objects. I used a map and another join for this:
poly = [{"x":0.0, "y":25.0},
{"x":8.5,"y":23.4},
{"x":13.0,"y":21.0},
{"x":19.0,"y":15.5}];
vis.selectAll("polygon")
.data([poly])
.enter().append("polygon")
.attr("points",function(d) {
return d.map(function(d) {
return [scaleX(d.x),scaleY(d.y)].join(",");
}).join(" ");
})
.attr("stroke","black")
.attr("stroke-width",2);
See working fiddle: http://jsfiddle.net/4xXQT/
The above answers are needlessly complicated.
All you have to do is specify the points as a string and everything works fine. Something like this below will work.
var canvas = d3.select("body")
.append("svg")
.attr("height", 600)
.attr("width", 600);
canvas.append("polygon")
.attr("points", "200,10 250,190 160,210")
.style("fill", "green")
.style("stroke", "black")
.style("strokeWidth", "10px");
are you trying to draw polygon shapes? - like this. http://bl.ocks.org/2271944 The start of your code looks like a typical chart - which would usually conclude something like this.
chart.selectAll("line")
.data(x.ticks(10))
.enter().append("line")
.attr("x1", x)
.attr("x2", x)
.attr("y1", 0)
.attr("y2", 120)
.style("stroke", "#ccc");
Related
I need to change the order of all the shapes created by d3 in a svg container. But I'm unable to do it. The following script in JSFiddle, when executed locally, gives "a and b not defined". How should it be corrected?
var svg = d3.select("body")
.append("svg")
.attr("width", 100)
.attr("height", 100);
svg.append("rect")
.attr({x:0,y:0,height:10,width:10,fill:'yellow'})
.attr("sort",3);
svg.append("rect")
.attr({x:0,y:20,height:10,width:10,fill:'red'})
.attr("sort",2);
svg.append("rect")
.attr({x:0,y:40,height:10,width:10,fill:'blue'})
.attr("sort",1);
d3.selectAll("rect").sort(function(a,b){return a.attr("sort")-b.attr("sort")});
d3.selectAll("rect")
.each(function(d,i){alert(d3.select(this).attr("sort"))});
You need to sort like this:
var rects = d3.selectAll("rect")[0].sort(function(a, b) {
return d3.select(a).attr("sort") - d3.select(b).attr("sort")
}); //here rects will store the correct sorted order rectangle DOMs.
instead of doing like this:
d3.selectAll("rect").sort(function(a,b){return a.attr("sort")-b.attr("sort")});
Now to change the rectangle as per the sort do like this:
rects.forEach(function(rect, i){
d3.select(rect).attr("y", (i*20));//change the attribute of y
})
working code here
NOTE: I am just answering this as per the info you have provided but the correct approach would be to do it the data way suggested by #meetamit in the comment.
I have a map of the USA that I'm trying to display lat/lon points over. I've mashed together a few examples to get this far, but I've hit a wall. My points are in a csv file, which I'm not sure how to upload here, but it's just 65,000 rows of number pairs. For instance 31.4671154,-84.9486771.
I'm mostly following the example from Scott Murray's book here.
I'm using the Albers USA projection.
var projection = d3.geo.albersUsa()
.scale(1200)
.translate([w / 2, h / 2]);
And setting up the landmarks as an svg group appended to the map container.
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.on("click", stopped, true);
svg.append("rect")
.attr("class", "background")
.attr("width", w)
.attr("height", h)
.on("click", reset);
var g = svg.append("g");
var landmarks = svg.append("g")
I read the data and try to set circles at each lat/lon point.
d3.csv("./public/assets/data/landmark_latlon_edited.csv", function(error, latlon){
console.log(latlon);
landmarks.selectAll("circle")
.data(latlon)
.enter()
.append("circle")
.attr({
'fill': '#F00',
'r': 3
})
.attr('cx', function(d){
return projection([d.lon, d.lat][0]);
})
.attr('cy', function(d){
return projection([d.lon, d.lat])[1];
})
.style({
'opacity': .75
});
});
Now, the problem is that the cx property is not receiving a value. When viewed in the inspector the circles don't show a cx, and, indeed, appear in the svg at the appropriate y values, but in a stacked column at x=0.
<circle fill="#F00" r="3" cy="520.8602676002965" style="opacity: 0.75;"></circle>
I found an old issue I thought might be related here which states that the projection method will return null if you try to feed it values outside of its normal bounding box. I opened the csv in Tableau and saw a couple values that were in Canada or some U.S. territory in the middle of the Pacific (not Hawaii), and I removed those, but that didn't solve the problem.
I'm decidedly novice here, and I'm sure I'm missing something obvious, but if anyone can help me figure out where to look I would greatly appreciate it. Lots of positive vibes for you. If I can add anything to clarify the problem please let me know.
Thanks,
Brian
I had the same problem when I updated to d3 v3.5.6. Here is what I did to check for null values, so that you don't try to access the [0] position of null:
.attr("cx", function(d) {
var coords = projection([d.lon, d.lat]);
if (coords) {
return coords[0];
}
})
I'm sure there is a cleaner way to do this, but it worked for me.
You have a little error in your function generating cx values which messes it all up. It's just one parenthesis in the wrong place:
.attr('cx', function(d){
return projection([d.lon, d.lat][0]);
})
By coding [d.lon, d.lat][0] you are just passing the first value of the array, which is d.lon, to the projection and are returning the result of projection() which is an array. Instead, you have to place the [0] outside the call of projection() because you want to access the value it returned. Check your function for cy where you got things right. Adjusting it as follows should yield the correct values for cx:
.attr('cx', function(d){
return projection([d.lon, d.lat])[0];
})
Currently I am learning some "D3.js" and attempting to get my head around the way data is processed and selected.
I'm stuck on the following task I've created for myself.
Ideally, I want something that is functionally equivalent to:
<svg>
<circle r="20.5" cx="100" cy="200"></circle>
<circle r="20.5" cx="300" cy="10"></circle>
</svg>
What I have currently (with my logic) is:
var matrix = [ [{ "x": 100, "y": 200 }], [{ "x": 300, "y": 10 }]];
var result = d3.select("body").append("svg") // Append SVG to end of Body
.data(matrix) // select this data
.selectAll("g") //g is a svg grouping tag
.data(function (d) { return d; }) //Unwrap the first part of the array
.enter() // Grab all the data that is new to the selection in each array
.selectAll("g")
.data(function (d) { return d;}) // foreach each item inside the 1D array
.enter() // For all the data that doesn't exist already in the SVG
.append("circle") // Append Circle to the DOM with the following attributes
.attr("r", 20.5)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
};
Weirdly enough the following :
var result = d3.select("body").append("svg")
.data(matrix)
.selectAll("g")
.enter()
.append("circle")
.attr("r", 20.5)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
};
Seems somehow able to get the first item in the array but doesn't iterate correctly. I'm not quite sure how it's entering the array.
D3 seems to be quite a big step away from the programming paradigms I'm used to, and more difficult to debug so it would be awesome if someone could explain where I'm going wrong.
Oh, and while the example is quite useless and I could flatten it using the merge command - for the purposes of fully understanding D3 manipulation. I'd like to draw the couple of circles without the merge :)
Thanks!
Seeing you mention that you're new to d3 I'll make a few comments on the basics.
The first is that we're trying to place some svg elements on the DOM, so first we have to have a svg canvas to work on. Typically its set up early in the code and looks something like this:
var svg = d3.select("body")
.append("svg")
.attr("width", 350)
.attr("height", 250);
Note that it would be best to define variables for height and width (but I'm being lazy).
So now you have your canvas lets look at how d3 iterates. d3 iterates over an array, so you don't have to have an array within an array for your example as in:
var matrix = [ { "x": 100, "y": 200 }], [{ "x": 300, "y": 10 }];
Now you're second block of code is almost there, with just a bit of rearrangement. The first thing we need to do is t create placeholders for the circles in your svg canvas using svg.selectAll("circle"). Next we introduce the data to the empty placeholders using data(matrix) and this is bound using 'enter()`. Now all we have to do is append the circles and give them some attributes which is all the rest of the code does
svg.selectAll("circle")
.data(matrix)
.enter()
.append("circle")
.attr("r", 20.5)
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
You can see this all put together in this fiddle with some minor changes.
If you want to get to know d3 I'd really recommend get Scott Murray book on d3 it's an excellent introduction
m creating a scatter plot and I have problems plotting my circles (datapoints).
When appending the first set of circles it is drawn fine on the graph. However the next set I try to append won't be draw for some reason. How can this be? Am I using the Enter/append/select wrong the 2nd time?
I have a JSFiddle with my code: http://jsfiddle.net/4wptM/
I have uncommented the parts where I load and manipulate my data and created the same array with a smaller sample. As it can be seen only the first set of circles are shown and the 2nd ones aren't.
My code of the circle section is pasted below:
var circle = SVGbody
.selectAll("circle")
.data(graphData[0])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
circle = SVGbody
.selectAll("circle")
.data(graphData[1])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(200);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
I just have those 2 copied to try and figure out the problem. The actual code I have is in the following for loop. When I run it in this loop it draws the circles for when the index is 1 and a few of when the index is 3.
for(var i = 0;i < graphData.length;i++){
var circle = SVGbody
.selectAll("circle")
.data(graphData[i])
.enter()
.append("circle")
.attr("cx",function(d){return xScale((i*100)+100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",20);
//console.log(i + " ::::::: " + graphData[i])
}
Some help would be so greatly appretiated. I really can't figure out what I'm doing wrong.
When operating on a existing set of elements (as it is the case here on the second .selectAll("circle") the .data() method uses only these elements from the array which have no corresponding index in the selecton made with .selectAll(). To prevent this behaviour you will have to add a key function as the second parameter of .data() as explained here and here.
Here this function only has to return each element of the array:
function (d) {
return d;
}
In the fiddle it would look something like this:
var circle = SVGbody
.selectAll("circle")
.data(graphData[0], function(d) { return d; }) // <-- this one
.enter()
.append("circle")
.attr("cx",function(d){return xScale(100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
fiddle
And to change the fill color of an element you have to use .style("fill", ...) and not .attr("fill", ...)
.style("fill", "green")
I am using d3.js to build a stacked bar graph. I am referring to this graph http://bl.ocks.org/mbostock/3886208
I want to add a small square of different color on the bars which have equal value. For example in this graph- if population of 25 to 44 Years and 45 to 64 Years is equal then i want to show a square of 10,10(width,height) on both bars related to CA. This is what I was doing but its not showing on the bar:
var equalBar = svg.selectAll(".equalBar")
.data(data)
.enter().append("g")
.attr("class", "equalBar")
.attr("transform", function(d){ return "translate(" + x(d.states) + ",0"; });
equalBar.selectAll("rect")
.data(function(d) { return d.ages;} )
.enter().append("rect")
.attr("width", 10)
.attr("y", function(d){
return y(d.y1);
})
.attr("height", function(d)
{ return 10; })
.style("fill", "green");
Thanks a lot for help.
What you're trying to do isn't really compatible with the D3 approach to data management. The idea of selections relies on the data items to be independent, whereas in your case you want to compare them explicitly.
The approach I would take to do this is to create a new data structure that contains the result of these comparisons. That is, for each population group it tells you what other groups it is equal to. You can then use this new data to create the appropriate rectangles.