d3.js - Bar chart won't display with scale - javascript

I updated my code to use the scale method in D3. However, since then my bar chart won't display. What is the cause of this issue?
var dataset = [ ];
for (var i = 0; i < 14; i++) {var newNumber = Math.round(Math.random() * 70);
dataset.push(newNumber);}
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var widthScale = d3.scale.linear()
.domain([0,d3.max(dataset)])
.range([0,w]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 3)
.attr("y", function (d,i) {return i* 36;})
.attr("width", function(d) {return widthScale;})
.attr("height", h / dataset.length - barPadding)
.attr("fill", function(d) {return "rgb(0, 0, " + (d * 10) + ")";});

A D3 scale is just a function translates values from a given input domain (your data values, 0 to max) to a specified output range (the width of your chart). Thus you have to apply the scale to your data, e.g. widthScale( d ). Right now you are assigning the widthScale function to the width attribute of your rect instead of the output value.
See the working fiddle: http://jsfiddle.net/S92u4/

Related

Scaling height of bars to container size

I'm wanting to scale the bar heights to the size of the SVG. http://jsfiddle.net/6d7984oa/
function d3_bar(s, dataset, barPadding) {
var self = s[0],
w = parseInt(d3.select(self).style("width")),
h = parseInt(d3.select(self).style("height")),
svg = d3.select(self)
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - d;
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
}
I've tried quite a few things but the math never works.
.attr("height", function(d) {
var min = 1,
max = h,
diff = max - min;
return h - ( diff ? ((d - min) / diff) * h : 1 );
})
Struggling to see how this may be done.
You should use D3 scales. A lot of info can be found here: Quantitative scales.
A basic linear scale goes like this:
var yScale = d3.scale.linear()
.domain([0, d3.max(yourdata)])
.range([0,h]);
Please look up the d3.max() function. Basically it looks for the largest value in the given array, but you can use a function to specify what value to look at, in case your data is an array of objects.
The linear scale can be explained as following:
The domain stands for the values that go in, the values that need to be scaled into something else. You give a range of values, the min and the max values between '[]'. In your case, this is the data representing the height.
The range of a scale is what comes out. So you define the min and the maximum possible outcomes, in our case 0 and the width of the svg.
So what happens when you use the scale? well, say your data goes from 0 to 100 and the width of your svg is 50 units. When your data is 0, the scale will return 0, when your data is 100, your scale will return 50. So if your data would be 50, then the scale will return.... 25. I choose easy numbers here, it works well on more difficult cases too :-).
EDIT: if forgot to mention how to use the scale for lets say your height attribute:
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - d;
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d){return yScale(d);})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
presuming of course that you named the scale 'yScale'.
Edit 2: My scale code had an error in the range. When scaling the y axes, you need to set the height in the range, not the width. So i fixed that.
Further more, when setting the y attribute of your rect, you need to use the scale for that one as well. Otherwise the bar height is scaled, but the matching y position isn't, resulting in awkward positioning of your rect tags. I should have mentioned that. Here is the correct code:
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d){return yScale(d);})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
You can see that I have changed the y attribute function using the yScale as well.
I apologize for the faulty code, I should check it better in the future.

D3 - How to loop through an object with keys for a bar chart

I am trying to create a bar chart with the dataset below. I am stuck on the part where the height[score] of the bar[country] is determined. How do I loop through the dataset to pull each score for a different country?
Any help would be greatly appreciated :)
var w = 500;
var h = 100;
var barPadding = 1;
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
//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, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
});
In D3, once you load the data through the .data(dataset) command, you can now access each record of the data by inserting the anonymous function function(d, i) { } as you have done in a few of your attributes.
Since your dataset is:
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
each d is a object record e.g. {"country":"Singapore","score":8.54}, while i refers to the index of the object d returned e.g. 1 for our example of d used above.
To access the score of the object record d, this becomes simple Javscript object notation i.e. d.score.
Hence your .attr call should look like:
.attr("height", function(d) {
return d.score * 4;
});
Similarly, you can extract the other fields e.g. country with d.country if you intend to use it in .attr("text", function(d) { return d.country; });
This is the real beauty and power of D3. If you ever want to expand your visualization with more features that is obtained through your data, then all you have to make sure is that your dataset data contains more data attributes, and you can call them later as you iterate through the anonymous functions. And D3 is in the spirit of its name, truly being "data-driven"! :)
You will need to fix d to d.score.
If you want to show country text, write svg.selectAll("text") after svg.selectAll("rect").
Like this:
var w = 500;
var h = 100;
var barPadding = 1;
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
//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, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d.score * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d.score * 4;
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d.country;
})
.attr("transform", function(d, i) {
var barW = w / dataset.length;
return "translate(" +
( barW * i + barW / 2 + barPadding ) + "," +
( h - 5 ) +
")rotate(-90)";
})
.attr("font-size", "8pt")
.attr("fill", "white");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Something like
For( var i =0; i<dataset.length; i++){
// Dataset[i].country
// dataset[i].score
}
You have an array of objects

Donut chart in d3

i have donut chart with legend specification. I have 2 values in dataset. But here with this code i'm getting only the first value, "Unresolved".
var dataset = {
Unresolved: [3],
Resolved:[7]
};
var keyValue=[];
for(key in dataset){
keyValue.push(key);
}
var width = 260,
height = 300,
radius = Math.min(width, height) / 2;
var color = ["#9F134C", "#ccc"];
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 90)
.outerRadius(radius - 80);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d,i) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { console.log("log", keyValue[i]);return color[i]; }) //Here i'm getting only the 1st value "unresolved".
.attr("d", arc);
var legendCircle = d3.select("body").append("svg").selectAll("g").data(keyValue).enter().append("g")
.attr("class","legend")
.attr("width", radius)
.attr("height", radius * 2)
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legendCircle.append("rect")
.attr("width", 18)
.attr("height", 10)
.style("fill", function(d, i) { return color[i];});
legendCircle.append("text")
.attr("x", 24)
.attr("y", 5)
.attr("dy", ".35em")
.text(function(d) { return d; });
The output i'm getting is,
Can anyone help on this? Thanks.
It looks like you're doing a nested selection in your code, which you would usually only need for nested data. Your data is not nested however -- there's a single level with 2 values. What's happening is that, by using a nested selection, you're descending into the value arrays, each of which contains only a single value.
It works fine if you do away with the nested selection and pass your original data to the pie layout.
var gs = svg.selectAll("g").data(pie(d3.values(dataset))).enter().append("g");
var path = gs.append("path")
.attr("fill", function(d, i) { return color[i]; })
.attr("d", arc);
Complete example here.

Why must my D3 selection come before the D3 code itself?

I spent hours trying to figure out why my code was not working. I then arbitrarily moved my button code from after the D3 code (at the end between </script> and </body>) to the top (between <script type="text/javascript"> and <body>). It works now, but I don't know why. I don't want to make this mistake again or confuse myself in the future.
<body>
<button>Update</button>
<script type="text/javascript">
var w = 500;
var h = 500;
var barPadding = 1;
var dataset = [ ];
for (var i = 0; i < 14; i++) {var newNumber = Math.round(Math.random() * 70);
dataset.push(newNumber);}
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create Scales for Data conversion
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d,i) {return d;})]) //input
.range([0,w]); // output
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, h], 0.05); //Vertical separation + barpadding
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 3)
.attr("y", function (d,i) {return yScale(i);})
.attr("width", function(d,i) {return xScale(d);})
.attr("height", yScale.rangeBand())
.attr("fill", function(d) {return "rgb(" + (d * 10) + ", 0,0 )";});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {return d;})
.attr("x", function(d) {return xScale(d) -15;})
.attr("y", function(d, i) {return yScale(i) +5 +yScale.rangeBand() / 2;})
.attr("fill", "white")
.attr("font-family", "sans-serif")
.attr("text-anchor", "middle");
//Create Data Update and transition
d3.select("button")
.on("click", function() {
//New values for dataset
dataset = [ ];
for (var i = 0; i < 14; i++) {var newNumber = Math.round(Math.random() * 70);
dataset.push(newNumber);}
//Update all rects, and color gradient
svg.selectAll("rect")
.data(dataset)
.transition()
.attr("x", 3)
.attr("width", function(d,i) {return xScale(d);})
.attr("fill", function(d) {return "rgb(" + (d * 10) + ", 0,0 )";});
//Update text label and position
svg.selectAll("text")
.data(dataset)
.text(function(d) {return d;})
.attr("x", function(d) {return xScale(d) -15;})
.attr("y", function(d, i) {return yScale(i) +5 + yScale.rangeBand() / 2;});
});
</script>
</body>
If you're saying that the code as shown in your question works, with the <button> element before the <script> element, it's because <script> elements are executed as the browser encounters them, top-to-bottom while parsing the page. Which means that any JavaScript that you use is only able to reference elements that are higher in the page source because the browser doesn't know about the later elements yet.
Unless you have code within functions that don't get called until after the DOM is complete, for example if you assign a DOM ready or onload event handler, or a delegated click handler or something.

Bar Chart Part 1 D3 why does it skip data that is the same value?

Not sure why but my code...which is also very closely following the D3 Bar graph .js tutorial found here: http://mbostock.github.com/d3/tutorial/bar-1.html
Does not draw rectangles for data with the same values from the variable "dataset". Can anyone explain why? or how to fix it?
var dataset = [5, 2, 1, 1, 1, 50] ;
var w = setWidthToWindow(); //setWidthToWindow
var h = setHeightToWindow(); //setHeightToWindow
var x = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([0, w/2]);
var y = d3.scale.ordinal()
.domain(dataset)
.rangeBands([0, 120]);
var chart = d3.select("#over_rating")
.append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", 20 * dataset.length);
chart.selectAll("rect")
.data(dataset)
.enter().append("rect")
.attr("y", y)
.attr("width", x )
.attr("height", y.rangeBand());
chart.selectAll("text")
.data(dataset)
.enter().append("text")
.attr("x", w/2 + 15)
.attr("y", function(d) { return y(d) + y.rangeBand() / 2; })
.attr("dx", 3) // padding-right
.attr("dy", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
You're using an ordinal scale and positioning your bars based on the data value, not the index, so all the data with the value '1' scales to exactly the same position. If you look at your svg, you'll see there are three bars drawn in exactly the same place.
I guess you could set up the scale with index values:
var y = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeBands([0, 120]);
and then scale by the index:
.attr("y", function(d,i) { return y(i); })
which would allow you to add more data and have the width of the bars adjust to accomodate it.
http://jsfiddle.net/findango/nfdST/

Categories