unexpected d3.stack() output - javascript

I'm using d3's stack layout for the first time to produce a stream graph.
This code:
var w = 600,
h = 350;
var x = d3.scale.linear().range([0, w])
y = d3.scale.linear().range([h, 0])
//...
d3.csv("filePath", function(error, input)){
var data = parseData(input);
setDomains(data); //sets x and y domain values. These are return expected values
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h) //.attr("height", h*3) is used to produce second result
.append("g")
//.attr("transform", "translate(0, 250)"); is used to produce second result
var stack = d3.layout.stack().offset("silhouette"),
layers = stack(data);
var area = d3.svg.area()
.interpolate("basis")
.x(function (d, i) {return x(d.x);})
.y0(function (d) {return y(d.y0);})
.y1(function (d) {return y(d.y0 + d.y);});
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function (d) {return area(d);})
.style("fill", function (d, i) {return color(d[0].type);});
is giving me this output:
By increasing the height of the svg and translating the parent g down by 240px (see comments in code), I get my expected result:
The scale of those images is a bit off - in reality, I'm getting the same graphic, but it's placed ~250 pixels higher than expected.
I'm trying to understand why might that be?
I can post setDomains() if needed, but my x() and y() functions are returning expected outputs for given inputs in debugging - i.e. a call to y(maxDataVal) returns 0, as expected. I'm thinking this is related to something I'm not understanding about stack(), which hopefully is evident in the above.

Related

Increasing size of d3 scatterplot

I have a scatter plot made in d3.v3 and no matter how large i increase the width and height variables it does not take up more screen space.
var w = 700;
var h = 700;
var dataset = [
"Over 50 pairs of coordinates that look like [0,1][0,43],
];
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d) {
return d[0];
})
.attr("y", function(d) {
return d[1];
})
.attr("width",5)
.attr("height",5);
There are more than 50 coordinates in my dataset and i want to be able to display them well so that is why i want this to take up more screen space. Currently there is nothing in my html, and no css for this. How can i adjust this so that the scatter plot takes more screen space?
The code you show doesn't place data points with any consideration of width or height, it places data points based on the values in the data:
.attr("x", function(d) {
return d[0];
})
.attr("y", function(d) {
return d[1];
})
The x and y attributes, without any SVG transformation, expect pixel values. If a point has the datum [25,25] it will be placed 25 pixels from the top and left. Height and width of the svg do not matter - you are stating the pixel position based on the data, not based on the svg dimensions in combination with the data.
Scales are a fundamental part of d3 - you can scale the x and y values of your data points across a range of pixel values. To do this we need to know the domain of the input data - the extent of input values - and the range of the output values - the extent of output values.
There are a number of scales built into D3, including power, logarithmic and linear. I'll demonstrate a linear scale below, but the form is very similar for other continuous scales, such as those noted above.
var xScale = d3.scaleLinear() // create a new linear scale
.domain([0,50]) // map values from 0 to 50 to:
.range([0,width]) // to pixel values of 0 through width
var yScale = d3.scaleLinear() // create a new linear scale
.domain([0,50]) // map values from 0 to 50 to:
.range([height,0]) // to pixel values of height through 0
Since in SVG coordinate space y=0 is the top of the SVG, and normally we want data with y=0 to be displayed at the bottom of the graph, we use a range of [height,0] rather than [0,height]
With the scales set up, to return the pixel value for a given data value we use:
xScale(d[0]); // assuming d[0] holds the x value
or
yScale(d[1]); // assuming d[1] holds the y value
Together this gives us:
var w = 700;
var h = 700;
var dataset = d3.range(50).map(function(d) {
return [Math.random()*50,Math.random()*50];
})
var x = d3.scaleLinear()
.domain([0,50]) // input extent
.range([0,w]) // output extent
var y = d3.scaleLinear()
.domain([0,50])
.range([h,0])
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d[0]);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("width",5)
.attr("height",5);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Of course we might not know the domain of the data if its dynamic, so d3 has a few built in helpers including d3.min, d3.max, d3.extent.
d3.min and d3.max iterate through an array to find the minimum and maximum values of some property of each item in the data array:
d3.min(dataArray, function(d) {
return d.property;
})
d3.max(dataArray, function(d) {
return d.property;
})
D3.extent does both at the same time returning an array containing min and max:
d3.extent(dataArray, function(d) {
return d.property;
})
We can plug those into the scales too:
var w = 700;
var h = 700;
var dataset = d3.range(50).map(function(d) {
return [Math.random()*50,Math.random()*50];
})
var x = d3.scaleLinear()
.domain([0,d3.max(dataset, function(d) { return d[0]; }) ]) // input extent
.range([0,w]) // output extent
var y = d3.scaleLinear()
.domain( d3.extent(dataset, function(d) { return d[1]; }) )
.range([h,0])
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d[0]);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("width",5)
.attr("height",5);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
The below works perfectly fine for me. The things you might need to check is :
The coordinates range is more than 5. (or you might need to use scale and axis)
Is there a overriding styles to your g
var w = 700;
var h = 700;
var dataset = [[10,10],[10,30],[30,50],[30,70]];
//Create SVG element
var svg = d3.select("#scatterplot")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d) {
return d[0];
})
.attr("y", function(d) {
return d[1];
})
.attr("width",5)
.attr("height",5);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='scatterplot'></div>

simple, exasperating d3.js example

I am entering d3.js at ground level. There's a steep learning curve ahead of me, I know, but I didn't quite expect to get stuck on the second simple tutorial I tried. Now's the change to make the maximal marginal contribution to a fellow programmer's understanding!
I'm trying to get a simple svg bar chart to work using my data, which is virtually identical to the sample.
Bostock's data
name value
Locke 4
Reyes 8
Ford 15
Jarrah 16
Shephard 23
Kwon 42
My data
year value
year2013 2476
year2014 7215
year2015 23633
year2016thru229 21752
Note that our second columns have the same name. Both contain numbers. I gave my dataset the same name he did. Thus I should be able to run his code (below) without changing a thing, but it returns this:
Unexpected value NaN parsing width attribute.
...uments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function
d3.min.js (line 1, col 2575)
Unexpected value NaN parsing x attribute.
...uments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function
d3.min.js (line 1, col 2575)
His code as well as the tutorial are here.
You're not feeding D3 your data in the correct format.
https://jsfiddle.net/guanzo/kdcLj5jj/1/
var data = [
{year:'year2013',value:2476},
{year:'year2014',value:7215},
{year:'year2015',value:23633},
{year:'year2016thru229',value:21752}
]
var width = 420,
barHeight = 20;
var x = d3.scale.linear()
.range([0, width]);
var chart = d3.select(".chart")
.attr("width", width);
x.domain([0, d3.max(data, function(d) { return d.value; })]);
chart.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", function(d) { return x(d.value); })
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) { return x(d.value) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d.value; });
function type(d) {
d.value = +d.value; // coerce to number
return d;
}

rotate d3 horizontal bar graph to vertical bar graph [duplicate]

I have a vertical bar chart that is grouped in pairs. I was trying to play around with how to flip it horizontally. In my case, the keywords would appear on the y axis, and the scale would appear on the x-axis.
I tried switching various x/y variables, but that of course just produced funky results. Which areas of my code do I need to focus on in order to switch it from vertical bars to horizontal ones?
My JSFiddle: Full Code
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return (d.local > d.global) ? d.local : d.global;
})])
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.tickFormat(function (d) {
return dataset[d].keyword;
})
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Graph Bars
var sets = svg.selectAll(".set")
.data(dataset)
.enter()
.append("g")
.attr("class", "set")
.attr("transform", function (d, i) {
return "translate(" + xScale(i) + ",0)";
});
sets.append("rect")
.attr("class", "local")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.local);
})
.attr("x", xScale.rangeBand() / 2)
.attr("height", function (d) {
return h - yScale(d.local);
})
.attr("fill", colors[0][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
I just did the same thing last night, and I basically ended up rewriting the code as it was quicker than fixing all the bugs but here's the tips I can give you.
The biggest issues with flipping the x and y axis will be with things like return h - yScale(d.global) because height is calculated from the "top" of the page not the bottom.
Another key thing to remember is that when you set .attr("x", ..) make sure you set it to 0 (plus any padding for the left side) so = .attr("x", 0)"
I used this tutorial to help me think about my own code in terms of horizontal bars instead - it really helped
http://hdnrnzk.me/2012/07/04/creating-a-bar-graph-using-d3js/
here's my own code making it horizontal if it helps:
var w = 600;
var h = 600;
var padding = 30;
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d){
return d.values[0]; })]) //note I'm using an array here to grab the value hence the [0]
.range([padding, w - (padding*2)]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([padding, h- padding], 0.05);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0 + padding)
.attr("y", function(d, i){
return yScale(i);
})
.attr("width", function(d) {
return xScale(d.values[0]);
})
.attr("height", yScale.rangeBand())
An alternative is to rotate the chart (see this). This is a bit hacky as then you need to maintain the swapped axes in your head (the height is actually the width etc), but it is arguably simpler if you already have a working vertical chart.
An example of rotating the chart is below. You might need to rotate the text as well to make it nice.
_chart.select('g').attr("transform","rotate(90 200 200)");
Here is the procedure I use in this case:
1) Inverse all Xs and Ys
2) Remember that the 0 for y is on top, thus you will have to inverse lots of values as previous values for y will be inversed (you don't want your x axis to go from left to right) and the new y axis will be inversed too.
3) Make sure the bars display correctly
4) Adapt legends if there are problems
This question may help in the sense that it shows how to go from horizontal bar charts to vertical: d3.js histogram with positive and negative values

D3js Converting a vertical bar chart to horizontal bar chart [duplicate]

I have a vertical bar chart that is grouped in pairs. I was trying to play around with how to flip it horizontally. In my case, the keywords would appear on the y axis, and the scale would appear on the x-axis.
I tried switching various x/y variables, but that of course just produced funky results. Which areas of my code do I need to focus on in order to switch it from vertical bars to horizontal ones?
My JSFiddle: Full Code
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return (d.local > d.global) ? d.local : d.global;
})])
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.tickFormat(function (d) {
return dataset[d].keyword;
})
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Graph Bars
var sets = svg.selectAll(".set")
.data(dataset)
.enter()
.append("g")
.attr("class", "set")
.attr("transform", function (d, i) {
return "translate(" + xScale(i) + ",0)";
});
sets.append("rect")
.attr("class", "local")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.local);
})
.attr("x", xScale.rangeBand() / 2)
.attr("height", function (d) {
return h - yScale(d.local);
})
.attr("fill", colors[0][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
I just did the same thing last night, and I basically ended up rewriting the code as it was quicker than fixing all the bugs but here's the tips I can give you.
The biggest issues with flipping the x and y axis will be with things like return h - yScale(d.global) because height is calculated from the "top" of the page not the bottom.
Another key thing to remember is that when you set .attr("x", ..) make sure you set it to 0 (plus any padding for the left side) so = .attr("x", 0)"
I used this tutorial to help me think about my own code in terms of horizontal bars instead - it really helped
http://hdnrnzk.me/2012/07/04/creating-a-bar-graph-using-d3js/
here's my own code making it horizontal if it helps:
var w = 600;
var h = 600;
var padding = 30;
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d){
return d.values[0]; })]) //note I'm using an array here to grab the value hence the [0]
.range([padding, w - (padding*2)]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([padding, h- padding], 0.05);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0 + padding)
.attr("y", function(d, i){
return yScale(i);
})
.attr("width", function(d) {
return xScale(d.values[0]);
})
.attr("height", yScale.rangeBand())
An alternative is to rotate the chart (see this). This is a bit hacky as then you need to maintain the swapped axes in your head (the height is actually the width etc), but it is arguably simpler if you already have a working vertical chart.
An example of rotating the chart is below. You might need to rotate the text as well to make it nice.
_chart.select('g').attr("transform","rotate(90 200 200)");
Here is the procedure I use in this case:
1) Inverse all Xs and Ys
2) Remember that the 0 for y is on top, thus you will have to inverse lots of values as previous values for y will be inversed (you don't want your x axis to go from left to right) and the new y axis will be inversed too.
3) Make sure the bars display correctly
4) Adapt legends if there are problems
This question may help in the sense that it shows how to go from horizontal bar charts to vertical: d3.js histogram with positive and negative values

Linear cx scaling with javascript objects in D3

I am attempting to plot a simple dataset consisting of an array of javascript objects. Here is the array in JSON format.
[{"key":"FITC","count":24},{"key":"PERCP","count":16},{"key":"PERCP-CY5.5","count":16},{"key":"APC-H7","count":1},{"key":"APC","count":23},{"key":"APC-CY7","count":15},{"key":"ALEXA700","count":4},{"key":"E660","count":1},{"key":"ALEXA647","count":17},{"key":"PE-CY5","count":4},{"key":"PE","count":38},{"key":"PE-CY7","count":18}]
Each object simply contains a String: "key", and a Integer: "count".
Now, I am plotting these in D3 as follows.
function key(d) {
return d.key;
}
function count(d) {
return parseInt(d.count);
}
var w = 1000,
h = 300,
//x = d3.scale.ordinal()
//.domain([count(lookup)]).rangePoints([0,w],1);
//y = d3.scale.ordinal()
//.domain(count(lookup)).rangePoints([0,h],2);
var svg = d3.select(".chart").append("svg")
.attr("width", w)
.attr("height", h);
var abs = svg.selectAll(".little")
.data(lookup)
.enter().append("circle")
.attr("cx", function(d,i){return ((i + 0.5)/lookup.length) * w;})
.attr("cy", h/2).attr("r", function(d){ return d.count * 1.5})
Here is what this looks like thus far.
What I am concerned about is how I am mapping my "cx" coordinates. Shouldn't the x() scaling function take care of this automatically, as opposed to scaling as I currently handle it? I've also tried .attr("cx", function(d,i){return x(i)}).
What I eventually want to do is label these circles with their appropriate "keys". Any help would be much appreciated.
Update:
I should mention that the following worked fine when I was dealing with an array of only the counts, as opposed to an array of objects:
x = d3.scale.ordinal().domain(nums).rangePoints([0, w], 1),
y = d3.scale.ordinal().domain(nums).rangePoints([0, h], 2);
Your code is doing what you want...I just added the text part. Here is the FIDDLE.
var txt = svg.selectAll(".txt")
.data(lookup)
.enter().append("text")
.attr("x", function (d, i) {
return ((i + 0.5) / lookup.length) * w;
})
.attr("y", h / 2)
.text(function(d) {return d.key;});
I commented out the scales, they were not being used...as already noted by you.

Categories

Resources