Scaling D3 bar chart to container size - javascript

I'm wanting to scale this bar chart to the size of it's container. Ideally do something like w = '100%', h = '100%'. Any way I may do this?
http://jsfiddle.net/mo363jm7/
<div class="test" style="height:50px;width:100px;overflow:hidden"></div>
//Width and height
var w = 200,
h = 100,
barPadding = 1,
dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
//Create SVG element
var svg = d3.select(".test")
.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;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});

You can ask for the width of your container (or any given element) with the style function, like this:
var width = parseInt(d3.select(".test").style("width"),10);
var height = parseInt(d3.select(".test").style("height"),10);
The last parameter (10) indicates that you want to use the decimal system (if I am not mistaken).
So you can set the width of your svg element to that width. I would even suggest you make a function out of it and call that function on a resize event, so your svg looks like a fluid/responsive element.
you can do that as following:
d3.select(window).on('resize', function(){/* your svg and chart code */});
EDIT: On rereading your question, it appears to me that I might have misunderstood your question. D3 has scaling functions to scale your data so it fits into your svg container. If your div element is not responsive, then you should just set your svg width and height the same as your div element, and use scales on your data so your chart fits in the svg container. More info on scales you can find here: quantitative scales

try to change the height and width of your div to its column height and width like this http://jsfiddle.net/elviz/mo363jm7/2/
<div class="test" style="height:100px;width:200px;overflow:hidden"></div>

Related

Use of "i" in function (d, i) in d3.js

Can somebody please explain the use of "i". I do understand x, y, width and height. I also understand i is an index, but what exactly it does? If I'm changing my example to i * 2 or i * 10, rectangle just getting wider but remains single, i * 21 makes or i * 42 diving it into multiple rectangles.
//D3 goes under here
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var w = 500;
var h = 100;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.text(function(d){
return d;
})
.attr("x", function(d, i){
return i * 21;
})
.attr("y", 0)
.attr("width", 20)
.attr("height", 100)
</script>
The second argument, traditionally named i, is the index of the element.
For instance:
var foo = d3.select("body").selectAll(null)
.data(["foo", "bar", "baz"])
.enter()
.append("foo")
.each(function(d, i) {
console.log("datum " + d + " has the index " + i)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
In your case, you said:
If I'm changing my example to i * 2 or i * 10, rectangle just getting wider but remains single
And the explanation for this is very simple:
If you do i * 10, you are setting the y position of the next rectangle 10px to the right of the previous one. However, you set the width for all of them as 20px.
Look what happens if you set the width for a smaller value, for instance 8px:
var dataset = [5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25
];
var w = 500;
var h = 100;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return i * 10;
})
.attr("y", 0)
.attr("width", 8)
.attr("height", 100)
<script src="https://d3js.org/d3.v4.min.js"></script>
Therefore, since you set the width of all rectangles as 20px, any i value less than that will make the rectangles appear as a single one: the next rectangle will be painted over the previous one, and because of that there will be no space between them. Besides that, the fact that there is no difference between the "fill" and "stroke" colours made you think that you had a single rectangle, when in fact you have a bunch of them.

D3.js: Why the bar chart turns around if I add this two statements

Hey I had a barChart and I'm aware in svg the x=0 and y=0 is on the top-left corner so the graphics shows downwards. If I add a couple of statements it turns around.
var w = 500;
var h = 100;
var barPadding = 1;
var dataset = [5, 4, 6, 7, 8, 10, 2, 3, 4, 5, 6, 25, 34, 54, 64, 32, 11, 32, 42, 5, 10, 15, 20, 25, 30, 35, 40];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var rectangles = 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 * 4;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
I have two questions,
a)why on the examples they use h-d for the y positioning?
If the svg height is 100 for example and a number on the dataset is 60, shouldn't that position the bar on the y=40 from the top left corner? Yet that is not the case when I render this, the bar shows on the same y position for all the bars
Why is it rendering the bars correctly with those given values? What makes the bar "grow from the bottom" in this case, cause if I do the math it makes me expect a different thing.
In this particular case, the reason why the y values are cut off at the correct place is because the svg has a height of 100. But if you carefully inspect the svg, every rectangle extends beyond the bottom of the svg, they are just not visible since there is no overflow.
h-d is correct for the starting y position, if a value was 60, then (100-60) would equal 40 as you have correctly stated. However, the actual height should be just d rather than d * 4. I do not know where the d * 4 came from.
So if your value is 60, then y would be 40, height would be 60 which would extend to the bottom of the svg.
Hope this helps.

Created A Bar Chart But Height Is Not Reciprocating Changes

Hello Created A Bar Char Using D3 Using Below Code
var dataset = [5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25];
var w = 500;
var h = 100;
var barpadding = 1;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var rect = 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("height", function (d) {
return (d *2)
})
.attr("width", w / dataset.length - barpadding)
.attr("fill", function (d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
<div class="bar"></div>
As You Can See At Starting My Height Attribute Is Like
.attr("height", function (d) {
return (d *2)
})
And Corresponding Image Is
Now I Have Changed It To 5 Times Like
.attr("height", function (d) {
return (d *5)
})
But Can't See Any Changes In My Bar's Height Any Help ??
fiddle link
You need to multiply the 'd' variable in the 'y' attribute change as well as the height. So the 'y' function ends up as:
.attr("y", function (d) {
return (h - (d * 5));
})
If you took out the subtracting from 'h' in the 'y' attribute and just left the 'y' attribute with no change, you see that your graph's height does change. The y attribute function is regulating the position of each rectangle so that the extended portion from multiplying it by 2 or 5 is hidden below the graph.

Simple way to use existing object as a clipping path?

I have the following simple example, When the line extends outside the rectangle, I want to clip it. I already have the rectangle used as an outline, what is a simple way to the same rectangle as a clipping path? My current approach using id is ignored. This related question has an answer but it requires creating the clip area separately. I'd like to re-use my info rather than repeat nearly the same info.
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src = "http://d3js.org/d3.v3.min.js"> </script>
<script>
var margin = {top: 100, right: 20, bottom: 20, left: 20},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var xdata = d3.range(0, 20);
var ydata = [1, 4, 5, 9, 10, 14, 15, 15, 11, 10, 5, 5, 4, 8, 7, 5, 5, 5, 8, 10];
var xy = []; // start empty, add each element one at a time
for(var i = 0; i < xdata.length; i++ ) {
xy.push({x: xdata[i], y: ydata[i]});
}
var xscl = d3.scale.linear()
.domain(d3.extent(xy, function(d) {return d.x;})) //use just the x part
.range([margin.left, width + margin.left])
var yscl = d3.scale.linear()
.domain([1, 8]) // use just the y part
.range([height + margin.top, margin.top])
var slice = d3.svg.line()
.x(function(d) { return xscl(d.x);}) // apply the x scale to the x data
.y(function(d) { return yscl(d.y);}) // apply the y scale to the y data
var svg = d3.select("body")
.append("svg")
svg.append('rect') // outline for reference
.attr({x: margin.left, y: margin.top,
width: width,
height: height,
id: "xSliceBox",
stroke: 'black',
'stroke-width': 0.5,
fill:'white'});
svg.append("path")
.attr("class", "line")
.attr("d", slice(xy))
.attr("clip-path", "#xSliceBox")
.style("fill", "none")
.style("stroke", "red")
.style("stroke-width", 2);
</script>
</body>
You can't reference the rectangle directly in the clip-path property, you need to create a <clipPath> element. Then, inside the <clipPath> element, you can use a <use> element to reference the rectangle.
(Yes, it's round-about and more complicated that you would think it should be, but that's how the SVG specs defined it.)
Working from your code:
var svg = d3.select("body")
.append("svg")
var clip = svg.append("defs").append("clipPath")
.attr("id", "clipBox");
svg.append('rect') // outline for reference
.attr({x: margin.left, y: margin.top,
width: width,
height: height,
id: "xSliceBox",
stroke: 'black',
'stroke-width': 0.5,
fill:'white'});
clip.append("use").attr("xlink:href", "#xSliceBox");
svg.append("path")
.attr("class", "line")
.attr("d", slice(xy))
.attr("clip-path", "url(#clipBox)") //CORRECTION
.style("fill", "none")
.style("stroke", "red")
.style("stroke-width", 2);
You could also do this the other way around, defining the rectangle within the clipPath element and then using a <use> element to actually draw it to the screen. Either way, you want to only define the rectangle once, so that if you decide to change it you only have to do it in one place and the other will update to match.

Creating a Text Labeled x-Axis with an Ordinal Scale in D3.js

I'm building a bar chart in d3.js with an ordinal x-axis whose ticks should label the chart with text. Could anyone explain how the ordinal scale "maps" x ticks to the corresponding bar positions? Specifically, if I want to designate the x tick labels with an array of text values to the corresponding bars in a bar chart.
Currently I'm setting the domain as the following:
var labels = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"];
var xScale = d3.scale.ordinal()
.domain(labels)
However, values of 1-19 are showing after the text labels.
As seen in this fiddle:
http://jsfiddle.net/chartguy/FbqjD/
Associated Fiddle Source Code:
//Width and height
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = 600 - margin.left - margin.right;
var height= 500-margin.top -margin.bottom;
var w = width;
var h = height;
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var labels = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"];
var xScale = d3.scale.ordinal()
.domain(labels)
.rangeRoundBands([margin.left, width], 0.05);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([h,0]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return h - yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, 0)";
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 0 + ")")
.call(xAxis);
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
You can set the tick values of an ordinal axis explicitly using d3.svg.axis().tickValues(*array*).
But this is an odd way to do it because it dangerously separates your keys and values, meaning you have to take care to manually align the scales and make sure that your data corresponds correctly. It helps to group the keys and values in a single object and then use the format:
axis.domain(array.map(function (d) { return d.value; }))
to map your axis domains.
I have reworked your data and fiddle to do it in what I see as the more d3 way. (Also note that I made some other changes just for fun, namely improved the margins and cleaned up the axis alignment, etc.)

Categories