Related
Hi I'm trying to use attrTween for my radar chart.
I made it happen for arc chart so I wanted to apply the same logic to my radar chart but it didn't work out.
can anyone let me know which part I missed?
I would like to shrink the radar chart on 'click'.
the code is as below.
d3.selectAll('.btn').on('click', function(d) {
let selectedone = d3.select(this).attr('class').split(' ')[1]
console.log(selectedone);
d3.select(`.radar${selectedone}`)
.transition()
.duration(1000)
.attrTween('d', shrinkRadar(0))
function shrinkRadar(target) {
return function(d) {
console.log(d)
let interpolate = d3.interpolate(rscale(d.value), target)
return function(t) {
console.log(d.radius);
d.radius = interpolate(t)
return radarLine(d)
}
}
}
// .attrTween('d',shrinkRadar(finalvalue))
})
Full code in the following link
https://codepen.io/jotnajoa/pen/dypJzmZ
Your radar chart's line functions take an array of data to output a path (line and area fill). You are attempting, though, to operate the attrTween like it's manipulating a single piece of data and not an array. Take a look at this quick re-write:
function shrinkRadar(target) {
return function(d) {
// create an array of interpolators
// one for each point in the line
// that run the value from starting to 0
var interps = d.map( da => d3.interpolate(da.value, target));
return function(t) {
// for each point call it's interpolator
// and re-assign the value
d.forEach( (da,i) => {
da.value = interps[i](t);
});
// regenerate path with new value
return radarLine(d)
}
}
}
Running code:
let margin = {
top: 100,
bottom: 100,
left: 100,
right: 100
}
let width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right;
let height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20);
var data = [
[ //iPhone
{
axis: "Battery Life",
value: 0.22
},
{
axis: "Brand",
value: 0.28
},
{
axis: "Contract Cost",
value: 0.29
},
{
axis: "Design And Quality",
value: 0.17
},
{
axis: "Have Internet Connectivity",
value: 0.22
},
{
axis: "Large Screen",
value: 0.02
},
{
axis: "Price Of Device",
value: 0.21
},
{
axis: "To Be A Smartphone",
value: 0.50
}
],
[ //Samsung
{
axis: "Battery Life",
value: 0.27
},
{
axis: "Brand",
value: 0.16
},
{
axis: "Contract Cost",
value: 0.35
},
{
axis: "Design And Quality",
value: 0.13
},
{
axis: "Have Internet Connectivity",
value: 0.20
},
{
axis: "Large Screen",
value: 0.13
},
{
axis: "Price Of Device",
value: 0.35
},
{
axis: "To Be A Smartphone",
value: 0.38
}
],
[ //Nokia Smartphone
{
axis: "Battery Life",
value: 0.26
},
{
axis: "Brand",
value: 0.10
},
{
axis: "Contract Cost",
value: 0.30
},
{
axis: "Design And Quality",
value: 0.14
},
{
axis: "Have Internet Connectivity",
value: 0.22
},
{
axis: "Large Screen",
value: 0.04
},
{
axis: "Price Of Device",
value: 0.41
},
{
axis: "To Be A Smartphone",
value: 0.30
}
]
];
var color = d3.scaleOrdinal()
.range(['#6678D9', '#61F06C', '#FF36AA'])
var radarChartOptions = {
w: width,
h: height,
margin: margin,
maxValue: 0.5,
levels: 5,
roundStrokes: true,
color: color
};
// blue, green pink inorder
RadarChart('.radarChart', data, radarChartOptions)
function RadarChart(id, data, options) {
let cfg = {
w: 600,
h: 600,
margin: {
top: 20,
right: 20,
bottom: 20,
left: 20
},
levels: 3,
maxValue: 0,
labelFactor: 1.25,
wrapWidth: 60,
opacityArea: 0.35,
dotRadius: 4,
opacityCircles: 0.1,
strokeWidth: 2,
roundStroke: false,
color: ['#6678D9', '#61F06C', '#FF36AA']
}
if ('undefined' !== typeof options) {
for (var i in options) {
if ('undefined' !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
// 결론적으로 options에 들어가있는 녀석과 함수에있는 녀석을 매치시키는것
var maxValue =
// Math.max(cfg.maxValue,
d3.max(data,
(i) => d3.max(
i.map((o) => o.value)
// 벨류로 이루어진 어레이를 만들고, 그 어레이에서 최대값을 출력해낸다.
)
)
// )
var allAxis = data[0].map((d) => {
return d.axis
})
var total = allAxis.length,
radius = Math.min(cfg.w / 2, cfg.h / 2),
Format = d3.format('%'),
angleSlice = Math.PI * 2 / total
var rscale = d3.scaleLinear()
.range([0, radius])
.domain([0, maxValue])
d3.select(id).select('svg').remove()
var svg = d3.select(id).append('svg')
.attr('width', cfg.w + cfg.margin.left + cfg.margin.right)
.attr('height', cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr('class',
'radar' + id);
var g = svg.append('g').attr('transform', `translate(${cfg.w/2+cfg.margin.left},${cfg.h/2+cfg.margin.top})`)
var filter = g.append('defs').append('filter').attr('id', 'glow')
var feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation', '2.5')
.attr('result', 'coloredBlur')
var feMerge = filter.append('feMerge')
var feMergeNode1 = feMerge.append('feMergeNode').attr('in', 'coloredBlur')
var feMergeNode2 = feMerge.append('feMergeNode').attr('in', 'SourceGraphic')
var axisGrid = g.append('g').attr('class', 'axisWrapper')
axisGrid.selectAll('.levels').data(
d3.range(1, (cfg.levels + 1))
)
.join('circle')
.attr('class', 'gridCircle')
.attr('r', (d) => {
return radius / cfg.levels * d
})
.style('fill', '#CDCDCD')
.style('stroke', '#CDCDCD')
.style('fill-opacity', cfg.opacityCircles)
.style('filter', 'url(#glow)')
axisGrid.selectAll('.axisLabel')
.data(d3.range(1, cfg.levels + 1))
.join('text')
.attr('class', 'axisLabel')
.attr('x', 4)
.attr('y', (d) => {
return -d * radius / cfg.levels
})
.attr('dy', '0.4em')
.style('font-size', '10px')
.attr('fill', '#737373')
.text((d, i) => {
return Format(maxValue * d / cfg.levels)
})
var axis = axisGrid.selectAll('.axis')
.data(allAxis)
.join('g')
.attr('class', 'axis')
axis.append('line')
.attr('x1', 0)
.attr('y1', 0)
.attr("x2", function(d, i) {
return rscale(maxValue * 1.1) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y2", function(d, i) {
return rscale(maxValue * 1.1) * Math.sin(angleSlice * i - Math.PI / 2);
})
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
axis.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", function(d, i) {
return rscale(maxValue * cfg.labelFactor) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y", function(d, i) {
return rscale(maxValue * cfg.labelFactor) * Math.sin(angleSlice * i - Math.PI / 2);
})
.text(function(d) {
return d
})
.call(wrap, cfg.wrapWidth)
// .call(testfunc, 'testString')
var radarLine = d3.radialLine()
.radius(function(d) {
return rscale(d.value)
})
.angle(function(d, i, t) {
return i * angleSlice
})
.curve(d3.curveCardinalClosed)
// if (cfg.roundStrokes) {
// radarLine.interpolate("cardinal-closed");
// }
const blobWrapper = g.selectAll(".radarWrapper")
.data(data)
.join('g')
.attr("class", "radarWrapper");
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", function(d) {
return radarLine(d)
})
.attr('class', function(d, i) {
return `radar${i}`
})
.style("fill", (d, i) => cfg.color(i))
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function(d, i) {
d3.selectAll('.radarArea').transition().duration(200)
.style('fill-opacity', 0.1)
d3.select(this).transition().duration(200).style('fill-opacity', cfg.opacityArea);
})
.on('mouseout', function() {
//Bring back all blobs
d3.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", cfg.opacityArea);
});
blobWrapper.append('path')
.attr('class', 'radarStroke')
.attr('d', radarLine)
.attr('class', function(d, i) {
return `radarStroke${i}`
})
.style('stroke', (d, i) => cfg.color(i))
.style('stroke-width', cfg.strokeWidth + 'px')
.style('fill', 'none')
.style('filter', 'url(#glow)')
let round = 0;
blobWrapper.selectAll(".radarCircle")
.data(data)
.data(function(d) {
return d
})
.join('circle')
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", function(d, i) {
return rscale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function(d, i) {
return rscale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", function(d, i, j) {
if (i == 0) {
round = round + 1;;
}
return cfg.color(round - 1);
})
.style("fill-opacity", 0.8);
var blobCircleWrapper = g.selectAll('.radarCircleWrapper')
.data(data)
.join('g')
.attr('class', 'radarCircleWrapper')
blobCircleWrapper.selectAll('circles').data(function(d) {
return d
})
.join('circle').attr('class', 'invisibleCircles')
.attr('r', cfg.dotRadius * 1.5)
.attr("cx", function(d, i) {
return rscale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function(d, i) {
return rscale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style('fill', 'none')
.style('pointer-events', 'all')
.on('mouseover', function(d, i) {
newX = parseFloat(d3.select(this).attr('cx')) - 10;
newY = parseFloat(d3.select(this).attr('cy')) - 10;
tooltip
.attr('x', newX)
.attr('y', newY)
.text(Format(d.value))
.transition().duration(200)
.style('opacity', 1);
})
.on("mouseout", function() {
tooltip.transition().duration(200)
.style("opacity", 0);
});
var tooltip = g.append("text")
.attr("class", "tooltip")
.style("opacity", 0);
d3.selectAll('.btn').on('click', function(d) {
let selectedone = d3.select(this).attr('class').split(' ')[1]
console.log(selectedone);
d3.select(`.radar${selectedone}`)
.transition()
.duration(1000)
.attrTween('d', shrinkRadar(0))
function shrinkRadar(target) {
return function(d) {
var interps = d.map(da => d3.interpolate(da.value, target));
return function(t) {
d.forEach((da, i) => {
da.value = interps[i](t);
});
return radarLine(d)
}
}
}
// .attrTween('d',shrinkRadar(finalvalue))
})
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
.radarChart {
width: 1000px;
height: 500px;
}
.buttonwrap {
margin-left: 200px;
text-align: left;
/* border: dashed grey 0.5px; */
}
.btn {
text-align: center;
display: inline-block;
width: 100px;
height: 20px;
border: solid 1px navy;
border-radius: 5px;
margin: auto;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.3.1/d3.min.js"></script>
<svg class="radarChart"></svg>
<div class="buttonwrap">
<div class="btn 0">Show 1</div>
<div class="btn 1">Show 2</div>
<div class="btn 2">show 3</div>
</div>
Based on Simple graph with grid lines in v4 (for example), I would like to add laced bands between grid lines, similar to the picture below:
How could I achieve that?
Thanks.
I was able to solve this using rect elements and enabling opacity based on their index. First I get all the values of the yaxis into an array so that I can get the y attribute for each rect and then just append those rects in a group g.
// add the Y Axis
svg.append("g").attr('class', 'y axis')
.call(d3.axisLeft(y));
//Store all values of the y-axis to an array
var yval = []
d3.selectAll('.y.axis>g>text').each(function(d) {
yval.push(d);
});
// Create rects and assign opacity based on index
rects.selectAll('rect').data(yval).enter().append('rect')
.attr('x', 0).attr('y', function(d) { return y(d); })
.attr('height', height / yval.length)
.attr('width', width).style('fill-opacity', function(d, i) {
if (i == 0) {
return 0;
}
if (i % 2 == 0) {
return 0.1;
} else {
return 0;
}
});
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%d-%b-%y");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var valueline = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// gridlines in x axis function
function make_x_gridlines() {
return d3.axisBottom(x)
.ticks(5)
}
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y)
.ticks(5)
}
// Get the data
var data = [{
"date": "1-May-12",
"close": 58.13
},
{
"date": "30-Apr-12",
"close": 53.98
},
{
"date": "27-Apr-12",
"close": 67
},
{
"date": "26-Apr-12",
"close": 89.7
},
{
"date": "25-Apr-12",
"close": 99
},
{
"date": "24-Apr-12",
"close": 130.28
},
{
"date": "23-Apr-12",
"close": 166.7
},
{
"date": "20-Apr-12",
"close": 234.98
},
{
"date": "19-Apr-12",
"close": 345.44
},
{
"date": "18-Apr-12",
"close": 443.34
},
{
"date": "17-Apr-12",
"close": 543.7
},
{
"date": "16-Apr-12",
"close": 580.13
},
{
"date": "13-Apr-12",
"close": 605.23
},
{
"date": "12-Apr-12",
"close": 622.77
},
{
"date": "11-Apr-12",
"close": 626.2
},
{
"date": "10-Apr-12",
"close": 628.44
},
{
"date": "9-Apr-12",
"close": 636.23
},
{
"date": "5-Apr-12",
"close": 633.68
},
{
"date": "4-Apr-12",
"close": 624.31
},
{
"date": "3-Apr-12",
"close": 629.32
},
{
"date": "2-Apr-12",
"close": 618.63
},
{
"date": "30-Mar-12",
"close": 599.55
},
{
"date": "29-Mar-12",
"close": 609.86
},
{
"date": "28-Mar-12",
"close": 617.62
},
{
"date": "27-Mar-12",
"close": 614.48
},
{
"date": "26-Mar-12",
"close": 606.98
}
]
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.close;
})]);
// add the X gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_gridlines()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "grid")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
)
var rects = svg.append('g').attr('class', 'intBands')
// add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g").attr('class', 'y axis')
.call(d3.axisLeft(y));
var yval = []
d3.selectAll('.y.axis>g>text').each(function(d) {
yval.push(d);
});
rects.selectAll('rect').data(yval).enter().append('rect')
.attr('x', 0).attr('y', function(d) {
return y(d)
}).attr('height', height / yval.length).attr('width', width).style('fill-opacity', function(d, i) {
if (i == 0) {
return 0;
}
if (i % 2 == 0) {
return 0.1;
} else {
return 0;
}
});
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/* set the CSS */
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
</script>
</body>
If anyone has a better way of doing this, please post your solution. Thanks.
One way consists in getting ranges between grid lines from the y-axis and for each of them to include a rectangle:
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%d-%b-%y");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// gridlines in x axis function
function make_x_gridlines() {
return d3.axisBottom(x)
.ticks(5)
}
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y)
.ticks(5)
}
// Get the data
var data = [
{ date: "1-May-12", close: 58.13 },
{ date: "30-Apr-12", close: 53.98 },
{ date: "27-Apr-12", close: 67.00 },
{ date: "26-Apr-12", close: 89.70 },
{ date: "25-Apr-12", close: 99.00 },
{ date: "24-Apr-12", close: 130.28 },
{ date: "23-Apr-12", close: 166.70 },
{ date: "20-Apr-12", close: 234.98 },
{ date: "19-Apr-12", close: 345.44 },
{ date: "18-Apr-12", close: 443.34 },
{ date: "17-Apr-12", close: 543.70 },
{ date: "16-Apr-12", close: 580.13 },
{ date: "13-Apr-12", close: 605.23 },
{ date: "12-Apr-12", close: 622.77 },
{ date: "11-Apr-12", close: 626.20 },
{ date: "10-Apr-12", close: 628.44 },
{ date: "9-Apr-12", close: 636.23 },
{ date: "5-Apr-12", close: 633.68 },
{ date: "4-Apr-12", close: 624.31 },
{ date: "3-Apr-12", close: 629.32 },
{ date: "2-Apr-12", close: 618.63 },
{ date: "30-Mar-12", close: 599.55 },
{ date: "29-Mar-12", close: 609.86 },
{ date: "28-Mar-12", close: 617.62 },
{ date: "27-Mar-12", close: 614.48 },
{ date: "26-Mar-12", close: 606.98 }
]
// d3.csv("data.csv").then(data) {
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// add the X gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_gridlines()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "grid ylines")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
)
// add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
var bands = [];
var start;
d3.selectAll(".ylines .tick line")
.each(function(d, i) {
if (i % 2 == 0)
start = d;
else {
bands.push({ "start": start, "end": d })
start = null;
}
});
// If it remains the top band:
if (start)
bands.push({ "start": start, "end": d3.max(data, function(d) { return d.close; }) })
svg.append("g").selectAll("band")
.data(bands)
.enter().append("rect")
.attr("y", d => y(d.end))
.attr("height", d => y(d.start) - y(d.end))
.attr("x", d => 0)
.attr("width", d => width)
.style("opacity", 0.1)
.style("stroke", "#005e23")
.style("fill", "#005e23");
// });
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
</body>
// Let's retrieve from the grid lines the associated bands y-extremities:
var bands = [];
var start;
d3.selectAll(".ylines .tick line")
.each(function(d, i) {
if (i % 2 == 0)
start = d;
else {
bands.push({ "start": start, "end": d })
start = null; // in order to know if we should use the top band
}
});
// If it remains a top band:
if (start)
bands.push({ "start": start, "end": d3.max(data, function(d) { return d.close; }) })
// Our bands look like:
// [{start: 0,end: 100}, {start: 200,end: 300}, ..., {start: 600,end: 636.23}]
svg.append("g").selectAll("band")
.data(bands)
.enter().append("rect")
.attr("y", d => y(d.end))
.attr("height", d => y(d.start) - y(d.end))
.attr("x", d => 0)
.attr("width", d => width)
.style("opacity", 0.1)
.style("stroke", "#005e23")
.style("fill", "#005e23");
Using grid lines rather than ticks gives the choice to have more ticks than grid lines.
My requirement is to generate Bar Chart With Panning And Zooming Via Brushing to overcome the x axis label overlapping id data is more.I implemented bar chart panning and zooming via brushing based on below link example
http://bl.ocks.org/MartynJones87/7db0d637e178e7204c0a
I am able to get panning and zooming but x axis labels are not displayed on axis.I don't know where i did mistake.
here is the plunker link for code.
On data load the core code called is:
var xBrush = d3.svg.brush().x(min_x).on("brush", xBrushed);
// Called to re-draw the bars on the main chart when the brush on the x axis
// has been altered.
function xBrushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(xBrush.empty() ? originalRange : xBrush.extent());
x.rangeRoundBands([main_xZoom(originalRange[0]), main_xZoom(originalRange[1])], .2);
//main_x1.rangeRoundBands([0, x.rangeBand()], 0);
main.selectAll("rect")
.data(data)
.attr("width", function (d) {
return x.rangeBand();
})
.attr("x", function (d) {
// alert("d is"+JSON.stringify(d));
return x(d.letter);
});
main.select("g.x.axis").call(xAxis).selectAll(".tick text").call(wrap, x.rangeBand());
};
I was able to find two issues in your code.
The clipPath rectangle used in svg defs is also getting updated within xBrushed function. The problem is with the selector you used.
You should have used
main.selectAll(".rect") //which selects all elements with class rect in main selection.
instead of
main.selectAll("rect") //which selects all rect elements in main selection which includes clipPath rect also.
within the xBrushed function.
You will need to apply the clip-path attribute to the group element which holds the rectangle bar elements.
Working code snippet:
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 860 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var min_margin = {
top: height,
right: margin.right + 10,
bottom: margin.bottom,
left: margin.left + 10
},
min_height = 10,
min_width = 860 - min_margin.left - min_margin.right;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .2);
var min_x = d3.scale.ordinal().rangeRoundBands([0, width], .2);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var min_xAxis = d3.svg.axis()
.scale(min_x)
.orient("bottom");
var main_xZoom = d3.scale.linear()
.range([0, width])
.domain([0, width]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "%");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var main = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
main.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height + min_height + margin.bottom);
var mini_x_append = svg.append("g")
.attr("transform", "translate(" + min_margin.left + "," + (margin.top + height) + ")")
.attr("width", min_width);
var data = [{
"letter": "A~q112",
"frequency": 0.08167
}, {
"letter": "B~12",
"frequency": 0.01492
}, {
"letter": "C~c",
"frequency": 0.02782
}, {
"letter": "D~1a",
"frequency": 0.04253
}, {
"letter": "E~d",
"frequency": 0.12702
}, {
"letter": "F~x",
"frequency": 0.02288
}, {
"letter": "G~v",
"frequency": 0.02015
}, {
"letter": "H~xs",
"frequency": 0.06094
}, {
"letter": "I~za",
"frequency": 0.06966
}, {
"letter": "J~mj",
"frequency": 0.00153
}, {
"letter": "K~th",
"frequency": 0.00772
}, {
"letter": "L~ws",
"frequency": 0.04025
}, {
"letter": "M~qjl",
"frequency": 0.02406
}, {
"letter": "N~i",
"frequency": 0.06749
}, {
"letter": "O~p",
"frequency": 0.07507
}, {
"letter": "P~zs",
"frequency": 0.01929
}, {
"letter": "Q~rip",
"frequency": 0.00095
}, {
"letter": "R~hi",
"frequency": 0.05987
}, {
"letter": "S~eop",
"frequency": 0.06327
}, {
"letter": "T~tl",
"frequency": 0.09056
}, {
"letter": "U~se",
"frequency": 0.02758
}, {
"letter": "V~wh",
"frequency": 0.00978
}, {
"letter": "W~jl",
"frequency": 0.0236
}, {
"letter": "X~y",
"frequency": 0.0015
}, {
"letter": "Y~ty",
"frequency": 0.01974
}, {
"letter": "Z~o",
"frequency": 0.00074
}, {
"letter": "A~q12",
"frequency": 0.08167
}, {
"letter": "B~122",
"frequency": 0.01492
}, {
"letter": "C~c2",
"frequency": 0.02782
}, {
"letter": "D~1a2",
"frequency": 0.04253
}, {
"letter": "E~d2",
"frequency": 0.12702
}, {
"letter": "F~x2",
"frequency": 0.02288
}, {
"letter": "G~v2",
"frequency": 0.02015
}, {
"letter": "H~xs2",
"frequency": 0.06094
}, {
"letter": "I~za2",
"frequency": 0.06966
}, {
"letter": "J~mj2",
"frequency": 0.00153
}, {
"letter": "K~th2",
"frequency": 0.00772
}, {
"letter": "L~ws2",
"frequency": 0.04025
}, {
"letter": "M~qjl2",
"frequency": 0.02406
}, {
"letter": "N~i2",
"frequency": 0.06749
}, {
"letter": "O~p2",
"frequency": 0.07507
}, {
"letter": "P~zs2",
"frequency": 0.01929
}, {
"letter": "Q~rip2",
"frequency": 0.00095
}, {
"letter": "R~hi2",
"frequency": 0.05987
}, {
"letter": "S~eo2p",
"frequency": 0.06327
}, {
"letter": "T~tl2",
"frequency": 0.09056
}, {
"letter": "U~se2",
"frequency": 0.02758
}, {
"letter": "V~wh2",
"frequency": 0.00978
}, {
"letter": "W~jl2",
"frequency": 0.0236
}, {
"letter": "X~y2",
"frequency": 0.0015
}, {
"letter": "Y~ty2",
"frequency": 0.01974
}, {
"letter": "Z~o2",
"frequency": 0.00074
}];
x.domain(data.map(function(d) {
return d.letter;
}));
min_x.domain(data.map(function(d) {
return d.letter;
}));
y.domain([0, d3.max(data, function(d) {
return d.frequency;
})]);
var xBrush = d3.svg.brush().x(min_x).on("brush", xBrushed);
// Add the x axis
main.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + min_height) + ")")
.attr("clip-path", "url(#clip)")
.call(xAxis)
.selectAll(".tick text")
.call(wrap, x.rangeBand());
var x_arc = d3.svg.arc()
.outerRadius(min_height / 2)
.startAngle(0)
.endAngle(function(d, i) {
return i ? -Math.PI : Math.PI;
});
var brush_x_grab = mini_x_append.append("g")
.attr("class", "x brush")
.call(xBrush);
brush_x_grab.selectAll(".resize").append("path")
.attr("transform", "translate(0," + min_height / 2 + ")")
.attr("d", x_arc);
brush_x_grab.selectAll("rect").attr("height", min_height);
main.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = main.append("g")
.attr("clip-path", "url(#clip)")
.selectAll(".rect")
.data(data)
.enter().append("rect")
.attr("class", "rect")
.attr("x", function(d) {
return x(d.letter);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.frequency);
})
.attr("height", function(d) {
return height - y(d.frequency);
});
// Called to re-draw the bars on the main chart when the brush on the x axis
// has been altered.
function xBrushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(xBrush.empty() ? originalRange : xBrush.extent());
x.rangeRoundBands([main_xZoom(originalRange[0]), main_xZoom(originalRange[1])], .2);
// min_x.rangeRoundBands([0, x.rangeBand()], 0);
main.selectAll(".rect")
.data(data)
.attr("width", function(d) {
return x.rangeBand();
})
.attr("x", function(d) {
// alert("d is"+JSON.stringify(d));
return x(d.letter);
});
main.select("g.x.axis").call(xAxis).selectAll(".tick text").call(wrap, x.rangeBand());
};
// This comes from the example at http://bl.ocks.org/mbostock/7555321
// for wrapping long axis tick labels
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
// console.log("wrap is"+JSON.stringify(word));
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
};
// Set the initial brush selections.
// svg.select(".x.brush").call(xBrush.extent(main_xZoom.domain()));
svg.select(".x.brush").call(xBrush.extent([0, 110]));
//svg.select(".y.brush").call(yBrush.extent(mini_y0.domain()));
// Forces a refresh of the brushes and main chart based
// on the selected extents.
xBrushed();
//yBrushed();
function type(d) {
d.frequency = +d.frequency;
return d;
}
g.axis path,
g.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
g.brush rect.extent {
fill-opacity: 0.2;
}
.resize path {
fill-opacity: 0.2;
}
.bar {
fill: steelblue;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDIT:
I found it is more easy to implement this functionality by converting the json data you have to the same format used at this link.
var nestedData;
var main_margin = {
top: 25,
right: 80,
bottom: 60,
left: 70
},
width = 900 - main_margin.left - main_margin.right,
mini_x_height = 10;
main_height = 525 - main_margin.top - main_margin.bottom,
mini_x_margin = {
top: main_height,
right: main_margin.right + 10,
bottom: main_margin.bottom,
left: main_margin.left + 10
},
mini_x_width = 900 - mini_x_margin.left - mini_x_margin.right,
mini_y_margin = {
top: main_margin.top + 10,
right: 0,
bottom: main_margin.bottom + 10,
left: 0
},
mini_y_width = 10,
mini_y_height = 525 - mini_y_margin.top - mini_y_margin.bottom;
var color = d3.scale.category10();
// x0 is the groups scale on the x axis.
var main_x0 = d3.scale.ordinal().rangeRoundBands([0, width], 0.2);
var mini_x0 = d3.scale.ordinal().rangeRoundBands([0, width], 0.2);
var main_xZoom = d3.scale.linear()
.range([0, width])
.domain([0, width]);
// x1 is the series scale on the x axis.
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();
// y is the value scale on the y axis.
var main_y0 = d3.scale.linear().range([main_height, 0]);
var mini_y0 = d3.scale.linear().range([mini_y_height, 0]);
var main_xAxis = d3.svg.axis()
.scale(main_x0)
.orient("bottom");
var mini_xAxis = d3.svg.axis()
.scale(mini_x0)
.orient("bottom");
var main_yAxis = d3.svg.axis()
.scale(main_y0)
.orient("left");
var mini_yAxis = d3.svg.axis()
.scale(mini_y0)
.orient("left");
var svg = d3.select("#chart").append("svg")
.attr("width", width + main_margin.left + main_margin.right)
.attr("height", main_height + main_margin.top + main_margin.bottom);
var main = svg.append("g")
.attr("transform", "translate(" + main_margin.left + "," + main_margin.top + ")");
main.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", main_height + mini_x_height + main_margin.bottom);
var mini_x = svg.append("g")
.attr("transform", "translate(" + mini_x_margin.left + "," + (main_margin.top + main_height) + ")")
.attr("width", mini_x_width);
var mini_y = svg.append("g")
.attr("width", mini_y_width)
.attr("transform", "translate(" + (main_margin.left - mini_y_width) + ", " + mini_y_margin.top + ")")
.attr("height", mini_y_height);
var data = [{
key: 'Mechanical',
values: [{
key: 'Gear',
value: 11
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}]
}, {
key: 'Electrical',
values: [{
key: 'Switch',
value: 19
}, {
key: 'Plug',
value: 12
}, {
key: 'Cord',
value: 11
}, {
key: 'Fuse',
value: 3
}, {
key: 'Bulb',
value: 2
}]
}, {
key: 'Hydraulic',
values: [{
key: 'Pump',
value: 4
}, {
key: 'Leak',
value: 3
}, {
key: 'Seals',
value: 1
},{
key: 'Switch',
value: 19
}, {
key: 'Plug',
value: 12
}, {
key: 'Cord',
value: 11
}, {
key: 'Fuse',
value: 3
}, {
key: 'Bulb',
value: 2
}]
}];
var res = [];
data.forEach(function(d){
res.push(d.values.map(function(o){
o.item=o.key;
o.subject = d.key; return o; }));
});
data = [].concat.apply([], res);
res.sort(function(a,b){ return a.subject<b.subject });
//console.log(data);
var seriesValues = d3.set(data.map(function(x) {
return x.item;
})).values().sort(d3.ascending);
nestedData = d3.nest()
.key(function(d) {
return d.subject;
})
.sortKeys(d3.ascending)
.sortValues(function(a, b) {
return a.item - b.item;
})
.entries(data);
var groupValues = d3.set(data.map(function(x) {
return x.subject;
})).values();
// Define the axis domains
main_x0.domain(groupValues);
mini_x0.domain(groupValues);
main_x1.domain(seriesValues).rangeRoundBands([0, main_x0.rangeBand()], 0);
mini_x1.domain(seriesValues).rangeRoundBands([0, main_x0.rangeBand()], 0);
main_y0.domain([0, d3.max(nestedData, function(d) {
return d3.max(d.values, function(d) {
return d.value;
});
})]);
mini_y0.domain(main_y0.domain());
var xBrush = d3.svg.brush().x(mini_x0).on("brush", xBrushed);
var yBrush = d3.svg.brush().y(mini_y0).on("brush", yBrushed);
// Add the x axis
main.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (main_height + mini_x_height) + ")")
.attr("clip-path", "url(#clip)")
.call(main_xAxis)
.selectAll(".tick text")
.call(wrap, main_x0.rangeBand());
// Add the y axis
main.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (-mini_y_width) + ", 0)")
.call(main_yAxis)
.append("text")
.attr("transform", "rotate(-90), translate(" + -(main_height / 2) + ", " + -(mini_y_width + main_margin.left - 20) + ")")
.attr("dy", ".71em")
.style("text-anchor", "middle")
.text("value");
var x_arc = d3.svg.arc()
.outerRadius(mini_x_height / 2)
.startAngle(0)
.endAngle(function(d, i) {
return i ? -Math.PI : Math.PI;
});
var brush_x_grab = mini_x.append("g")
.attr("class", "x brush")
.call(xBrush);
brush_x_grab.selectAll(".resize").append("path")
.attr("transform", "translate(0," + mini_x_height / 2 + ")")
.attr("d", x_arc);
brush_x_grab.selectAll("rect").attr("height", mini_x_height);
var y_arc = d3.svg.arc()
.outerRadius(mini_y_width / 2)
.startAngle(-(Math.PI / 2))
.endAngle(function(d, i) {
return i ? -((3 * Math.PI) / 2) : ((Math.PI) / 2);
});
var brush_y_grab = mini_y.append("g")
.attr("class", "y brush")
.call(yBrush);
brush_y_grab.selectAll(".resize").append("path")
.attr("transform", "translate(" + (mini_y_width / 2) + ", 0)")
.attr("d", y_arc);
brush_y_grab.selectAll("rect").attr("width", mini_y_width);
// Create the main bars
var bar = main.selectAll(".bars")
.data(nestedData)
.enter().append("g")
.attr("clip-path", "url(#clip)")
.attr("class", function(d) {
return d.key + "-group bar";
});
bar.selectAll("rect")
.data(function(d) {
return d.values;
})
.enter().append("rect")
.attr("class", function(d) {
return d.item;
})
.attr("transform", function(d) {
return "translate(" + main_x0(d.subject) + ",0)";
})
.attr("width", function(d) {
return main_x1.rangeBand();
})
.attr("x", function(d) {
return main_x1(d.item);
})
.attr("y", function(d) {
return main_y0(d.value);
})
.attr("height", function(d) {
return main_height - main_y0(d.value);
})
.style("fill", function(d) {
return color(d.item);
});
// Draws the series items onto a legend
var legend = svg.selectAll(".legend")
.data(seriesValues.slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(50," + (main_margin.top + (i * 20)) + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
// Called to re-draw the bars on the main chart when the brush on the x axis
// has been altered.
function xBrushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(xBrush.empty() ? originalRange : xBrush.extent());
main_x0.rangeRoundBands([main_xZoom(originalRange[0]), main_xZoom(originalRange[1])], .1);
main_x1.rangeRoundBands([0, main_x0.rangeBand()], 0);
bar.selectAll("rect")
.attr("transform", function(d) {
return "translate(" + main_x0(d.subject) + ",0)";
})
.attr("width", function(d) {
return main_x1.rangeBand();
})
.attr("x", function(d) {
return main_x1(d.item);
});
main.select("g.x.axis").call(main_xAxis).selectAll(".tick text").call(wrap, main_x0.rangeBand());
};
// Called to re-draw the bars on the main chart when the
// brush on the y axis has been altered.
function yBrushed() {
main_y0.domain(yBrush.empty() ? mini_y0.domain() : yBrush.extent());
bar.selectAll("rect")
.attr("y", function(d) {
return main_y0(d.value);
})
.attr("height", function(d) {
return main_height - main_y0(d.value);
});
main.select("g.y.axis").call(main_yAxis);
};
// This comes from the example at http://bl.ocks.org/mbostock/7555321
// for wrapping long axis tick labels
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
};
// Set the initial brush selections.
// svg.select(".x.brush").call(xBrush.extent(main_xZoom.domain()));
svg.select(".x.brush").call(xBrush.extent([0, 610]));
svg.select(".y.brush").call(yBrush.extent(mini_y0.domain()));
// Forces a refresh of the brushes and main chart based
// on the selected extents.
xBrushed();
yBrushed();
//});
g.axis path,
g.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
g.brush rect.extent {
fill-opacity: 0.2;
}
.resize path {
fill-opacity: 0.2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
I have a 'parent' rectangle which has been drawn around smaller rectangles. Here's a live example
The data looks like this:
var allData = [{x:50, y: 60, height: 40, width: 30,
defects: [
{ x: 53, y: 61, width: 10, height: 10 },
{ x: 55, y: 71, width: 10, height: 10 },
{ x: 60, y: 76, width: 10, height: 10 }]},
{x:150, y: 160, height: 50, width: 40,
defects: [
{ x: 151, y: 165, width: 5, height: 5 },
{ x: 160, y: 169, width: 5, height: 5 },
{ x: 165, y: 170, width: 5, height: 5 }]
}];
As you can see, there are 2 rectangles each with 3 smaller rectangles in it called defects.
I'm trying to visualize it but want to do it in 1 selection so I can transform it easier later on.
Here's what I got so far:
var svg = d3.select('#test svg')
.attr("width", 800)
.attr("height", 500);
var groups = svg.selectAll('.defect-group')
.data(allData)
.enter()
.append('g').attr('class', 'defect-group')
.append('rect')
.attr('class', 'defect-area')
.attr('width', function(d) {return d.width})
.attr('height', function(d) {return d.height})
.attr('x', function(d) {return d.x})
.attr('y', function(d) {return d.y});
var defects = groups.selectAll('.defects')
.data(function(d) {
return d.defects;
})
.enter()
.append('rect')
.attr('class', 'defect')
.attr('width', function(d) {return d.width})
.attr('height', function(d) {return d.height})
.attr('x', function(d) {return d.x})
.attr('y', function(d) {return d.y});
The result of this is:
As you can see, I (accidentally) nested the defects inside the 'rect' elements. Since a rect cannot have child elements I would like to put the defects also in the 'defect-group' group, I've tried but can't seem to figure out how to put those in the parent group.
How to do this?
Just get the g element out and append everything to that.
In your code, change
var groups = svg.selectAll('.defect-group')
.data(allData)
.enter()
.append('g').attr('class', 'defect-group')
.append('rect')
.attr('class', 'defect-area')
.attr('width', function(d) {return d.width})
.attr('height', function(d) {return d.height})
.attr('x', function(d) {return d.x})
.attr('y', function(d) {return d.y});
to
var groups = svg.selectAll('.defect-group')
.data(allData)
.enter()
.append('g').attr('class', 'defect-group')
groups
.append('rect')
.attr('class', 'defect-area')
.attr('width', function (d) { return d.width })
.attr('height', function (d) { return d.height })
.attr('x', function (d) { return d.x })
.attr('y', function (d) { return d.y });
Chapter 11 of "Interactive Data Visualization for the Web" shows how to create stacked bar charts with the D3.js library. The example produces an upside down chart with the bars attached to the top of the x-axis.
Flipping the chart and attaching them to the bottom is left as an exercise for the reader.
Given this starting point:
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript">
var w = 500;
var h = 300;
var dataset = [[ { x: 0, y: 5 }, { x: 1, y: 4 }, { x: 2, y: 2 }, { x: 3, y: 7 }, { x: 4, y: 23 }],
[ { x: 0, y: 10 }, { x: 1, y: 12 }, { x: 2, y: 19 }, { x: 3, y: 23 }, { x: 4, y: 17 } ],
[ { x: 0, y: 22 }, { x: 1, y: 28 }, { x: 2, y: 32 }, { x: 3, y: 35 }, { x: 4, y: 43 } ]];
var stack = d3.layout.stack();
stack(dataset);
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length)).rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d3.max(d, function(d) { return d.y0 + d.y; });
})])
.range([0, h]);
var colors = d3.scale.category10();
var svg = d3.select("body").append("svg").attr("width", w).attr("height", h);
var groups = svg.selectAll("g").data(dataset).enter().append("g")
.style("fill", function(d, i) { return colors(i); });
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) { return xScale(i); })
.attr("y", function(d) { return yScale(d.y0); })
.attr("height", function(d) { return yScale(d.y); })
.attr("width", xScale.rangeBand());
</script>
What needs to be done to flip the chart?
The solution I came up with involves three changes:
Change the yScale .range from:
.range([0, h]);
to:
.range([h, 0]);
Change the rect "y" .attr from:
.attr("y", function(d) { return yScale(d.y0); })
to:
.attr("y", function(d) { return yScale(d.y0) + yScale(d.y) - h; })
Change the rect "height" .attr from:
.attr("height", function(d) { return yScale(d.y); })
to:
.attr("height", function(d) { return h - yScale(d.y); })
With those changes applied, the stacks attach to the bottom and still maintain their relative sizes.