|
<!-- |
|
Features: |
|
- No object constancy (if same State column remains, should just move to new position) |
|
- Exit and Enter transitions currently overlap (should be sequential instead) |
|
- Transitions per rect, per column, are not currently staggered |
|
--> |
|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script> |
|
<style>s |
|
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<script> |
|
|
|
var margin = {top: 20, right: 20, bottom: 30, left: 40}, |
|
outerWidth = 960, |
|
outerHeight = 500, |
|
width = outerWidth - margin.left - margin.right, |
|
height = outerHeight - margin.top - margin.bottom, |
|
svg = d3.select("body").append("svg") |
|
.attr("width", outerWidth) |
|
.attr("height", outerHeight), |
|
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"), |
|
data = undefined, |
|
serie, |
|
serieEnter, |
|
rect, |
|
rectEnter; |
|
|
|
var x = d3.scaleBand() |
|
.rangeRound([0, width]) |
|
.padding(0.1) |
|
.align(0.1); |
|
|
|
var y = d3.scaleLinear() |
|
.rangeRound([height, 0]); |
|
|
|
var z = d3.scaleOrdinal() |
|
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); |
|
|
|
var stack = d3.stack(); |
|
|
|
d3.csv("data.csv", type, function(error, iData) { |
|
if (error) throw error; |
|
|
|
data = iData; |
|
data.sort(function(a, b) { return b.total - a.total; }); |
|
|
|
x.domain(data.map(function(d) { return d.State; })); |
|
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice(); |
|
z.domain(data.columns.slice(1)); |
|
|
|
// Button to generate new data. |
|
g.append("rect") |
|
.attr("class", "btn-update-data") |
|
.attr("width", 60) |
|
.attr("height", 20) |
|
.attr("transform", "translate("+ (width-200) +", 0)") |
|
.attr("fill", "red") |
|
.on("click", function() { |
|
updateChart(jumbleData(data), false); |
|
}); |
|
|
|
var legend = g.selectAll(".legend") |
|
.data(data.columns.slice(1).reverse()) |
|
.enter().append("g") |
|
.attr("class", "legend") |
|
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }) |
|
.style("font", "10px sans-serif"); |
|
|
|
legend.append("rect") |
|
.attr("x", width - 18) |
|
.attr("width", 18) |
|
.attr("height", 18) |
|
.attr("fill", z); |
|
|
|
legend.append("text") |
|
.attr("x", width - 24) |
|
.attr("y", 9) |
|
.attr("dy", ".35em") |
|
.attr("text-anchor", "end") |
|
.text(function(d) { return d; }); |
|
|
|
updateChart(data, true); |
|
|
|
}); |
|
|
|
// Update the chart with the specified data. |
|
function updateChart(iData, initializing) { |
|
console.log("************* ENTER-updateChart") |
|
console.log("iData", iData) |
|
|
|
x.domain(iData.map(function(d) { return d.State; })); |
|
y.domain([0, d3.max(iData, function(d) { return d.total; })]).nice(); |
|
|
|
// ---------------------------------------------------------------- |
|
// SERIE |
|
// ---------------------------------------------------------------- |
|
|
|
// SERIE ENTER ----------------------- |
|
var stacks = stack.keys(iData.columns.slice(1))(iData); |
|
|
|
console.log("stacks", stacks); |
|
|
|
// flatten stacks array |
|
var stackData = []; |
|
for (var i = 0; i < stacks.length; i++) { |
|
for (var j = 0; j < stacks[i].length; j++) { |
|
stacks[i][j].key = stacks[i].key; |
|
// var factor = Math.random() + 0.5; |
|
// factor = factor <= 1 ? factor : factor - 1; |
|
// var diff = Math.round(stacks[i][j][0] * factor); |
|
// stacks[i][j][0] = stacks[i][j][0] - diff; |
|
|
|
stackData.push(stacks[i][j]); |
|
} |
|
} |
|
|
|
console.log("stackData", stackData); |
|
|
|
if (initializing) { |
|
// Parent group |
|
serieEnter = g.append("g").attr("class", "rects"); |
|
} else { |
|
|
|
/* |
|
serieEnter = g.select("g.rects"); |
|
|
|
console.log("serieEnter", serieEnter); |
|
|
|
|
|
rect = serieEnter.selectAll("rect.chart-rect") |
|
.data(stackData, function(d){ return d.data.State; }); |
|
|
|
rect.enter().append("rect") |
|
.attr("class", "chart-rect") |
|
.attr("state", function(d) {return d.data.State; }) |
|
.attr("fill", function(d) { return z(d.key)}) |
|
.attr("x", function(d) { return x(d.data.State); }) |
|
.attr("y", function(d) { return y(d[1]); }) |
|
.attr("width", x.bandwidth()) |
|
.attr("height", 0) |
|
.transition() |
|
.duration(1000) |
|
.attr("height", function(d) { return y(d[0]) - y(d[1]); }); |
|
|
|
rect |
|
.attr("state", function(d) {return d.data.State; }) |
|
.attr("fill", function(d) { return z(d.key)}) |
|
.attr("x", function(d) { return x(d.data.State); }) |
|
.attr("y", function(d) { return y(d[1]); }) |
|
.attr("width", x.bandwidth()) |
|
.attr("height", 0) |
|
.transition() |
|
.duration(1000) |
|
.attr("height", function(d) { return y(d[0]) - y(d[1]); }); |
|
|
|
rect.exit() |
|
.transition() |
|
.duration(1000) |
|
.attr("height", 0) |
|
.remove(); |
|
*/ |
|
|
|
serieEnter = g.select("g.rects"); |
|
|
|
rect = serieEnter.selectAll("rect.chart-rect") |
|
.data(stackData, function(d){ return d.data.State; }); |
|
|
|
var duration = 50; |
|
var _d = 0; |
|
|
|
// 2. update |
|
rect |
|
.attr("class", function(d){ return "chart-rect "+ d.data.State; }) |
|
.attr("fill", function(d) { return z(d.key)}) |
|
.attr("x", function(d) { return x(d.data.State); }) |
|
.attr("y", function(d) { return y(d[1]); }) |
|
.attr("width", x.bandwidth()) |
|
.attr("height", function(d) { return y(d[0]) - y(d[1]); }); |
|
|
|
|
|
|
|
// console.log(state); |
|
// }) |
|
// .each(function(d,i){ |
|
// // _d = _d + duration; |
|
// d3.select(this) |
|
// .transition() |
|
// .duration(duration) |
|
// .delay(_d) |
|
// .attr("x", function(d) { return x(d.data.State); }) |
|
// .attr("height", function(d) { return y(d[0]) - y(d[1]); }); |
|
// }); |
|
|
|
console.log("rect", rect) |
|
|
|
// 3. enter |
|
var enterRects = rect.enter().append("rect") |
|
.attr("class", function(d){ return "chart-rect "+ d.data.State; }) |
|
.attr("state", function(d) {return d.data.State; }) |
|
.attr("fill", function(d) { return z(d.key)}) |
|
.attr("x", function(d) { return x(d.data.State); }) |
|
.attr("y", function(d) { return y(d[1]); }) |
|
.attr("width", x.bandwidth()) |
|
.attr("height", 0); |
|
|
|
console.log("x.domain()", x.domain()); |
|
|
|
if (x.domain()) { |
|
x.domain().forEach(function(state){ |
|
|
|
_d = _d + duration; |
|
|
|
console.log("------------------"); |
|
console.log(state); |
|
console.log(enterRects.selectAll("[state="+ state +"]")); |
|
console.log(_d); |
|
|
|
g.selectAll("."+ state) |
|
.transition() |
|
.duration(duration) |
|
.delay(_d) |
|
.attr("height", function(d) { return y(d[0]) - y(d[1]); }); |
|
|
|
}); |
|
} |
|
|
|
// 1. exit |
|
rect.exit() |
|
.each(function(d){ |
|
d3.select(this) |
|
.transition() |
|
.duration(duration) |
|
.attr("height", 0) |
|
.remove(); |
|
}) |
|
|
|
|
|
} |
|
|
|
// ---------------------------------------------------------------- |
|
// AXES |
|
// ---------------------------------------------------------------- |
|
|
|
if (initializing) { |
|
// X axis |
|
g.append("g") |
|
.attr("class", "axis axis-x") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(d3.axisBottom(x)); |
|
|
|
// Y axis |
|
g.append("g") |
|
.attr("class", "axis axis-y") |
|
.call(d3.axisLeft(y).ticks(10, "s")) |
|
.append("text") |
|
.attr("x", 2) |
|
.attr("y", y(y.ticks(10).pop())) |
|
.attr("dy", "0.35em") |
|
.attr("text-anchor", "start") |
|
.attr("fill", "#000") |
|
.text("Population"); |
|
} else { |
|
// // remove xAxis |
|
// g.select("g.axis-x").remove(); |
|
|
|
// // X axis |
|
// g.append("g") |
|
// .attr("class", "axis axis-x") |
|
// .attr("transform", "translate(0," + height + ")") |
|
// .call(d3.axisBottom(x)); |
|
|
|
d3.transition(g).select("g.axis-x").call(d3.axisBottom(x)); |
|
d3.transition(g).select("g.axis-y").call(d3.axisLeft(y).ticks(10, "s")); |
|
|
|
} |
|
} |
|
|
|
function jumbleData() { |
|
|
|
console.log("*************** ENTER-jumbleData") |
|
// console.log("data", data) |
|
|
|
var _result = undefined; |
|
var _oldColumns = data.columns; |
|
var _data = _.cloneDeep(data); // for some reason, clone() removes the 'columns' obj in iData, so we don't need to remove it after cloning... nice |
|
var _numRandomStates = Math.floor(Math.random() * data.length); |
|
|
|
console.log("_data", _data) |
|
|
|
_result = _.sampleSize(_data, _numRandomStates); |
|
_result.columns = _oldColumns; |
|
|
|
console.log("_result", _result) |
|
|
|
return _result; |
|
} |
|
|
|
/* |
|
// Jumble the data so we can test transitioning to new data. |
|
function jumbleData(iData) { |
|
|
|
// console.log("*************** ENTER-jumbleData") |
|
// console.log("iData", iData) |
|
|
|
var result = undefined; |
|
var oldColumns = iData.columns; |
|
var data = _.cloneDeep(iData); // for some reason, clone() removes the 'columns' obj in iData, so we don't need to remove it after cloning... nice |
|
|
|
// console.log("data", data) |
|
|
|
for (var i = 0; i < data.length; i++) { |
|
|
|
// console.log("------------- i", i) |
|
|
|
var cur = data[i]; |
|
var itemToSwapDataWith = _.sample(data) |
|
|
|
// console.log("cur", cur) |
|
// console.log("itemToSwapDataWith", itemToSwapDataWith) |
|
|
|
// Swap state names for the 2 objects, so their data is swaped, essentially. |
|
var curState = cur.State; |
|
cur.State = itemToSwapDataWith.State; |
|
itemToSwapDataWith.State = curState; |
|
|
|
// console.log("----- SWAP -----") |
|
// console.log("cur", cur) |
|
// console.log("itemToSwapDataWith", itemToSwapDataWith) |
|
|
|
} |
|
|
|
data.columns = oldColumns; |
|
result = data; |
|
|
|
// console.log("result", result) |
|
|
|
return result; |
|
|
|
} |
|
*/ |
|
|
|
function type(d, i, columns) { |
|
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]]; |
|
d.total = t; |
|
return d; |
|
} |
|
|
|
</script> |
|
</body> |