Skip to content

Instantly share code, notes, and snippets.

@lorenzopub
Created July 23, 2017 08:29
Show Gist options
  • Save lorenzopub/422ea906c71f193098a15be8d0a06ed3 to your computer and use it in GitHub Desktop.
Save lorenzopub/422ea906c71f193098a15be8d0a06ed3 to your computer and use it in GitHub Desktop.
Stack-to-split-Stack Transition with D3 v4
license: mit
{
"assay_types": {
"group_1": ["type_1", "type_2", "type_3", "type_4"],
"group_2": ["type_5", "type_6", "type_7", "type_8"],
"group_3": ["type_9", "type_10", "type_11", "type_12", "type_13"]
},
"stages":[
"1-cell",
"2-cell",
"4-cell",
"8-cell",
"16-cell",
"32-cell",
"64-cell",
"128-cell",
"256-cell",
"512-cell",
"1k-cell",
"High",
"Oblong",
"Sphere",
"Dome",
"30%-epiboly",
"50%-epiboly",
"Germ-ring",
"Shield",
"75%-epiboly",
"90%-epiboly",
"Bud",
"1-4 somites",
"5-9 somites",
"10-13 somites",
"14-19 somites",
"20-25 somites",
"26+ somites",
"Prim-5",
"Prim-15",
"Prim-25",
"High-pec",
"Long-pec",
"Pec-fin",
"Protruding-mouth",
"Day 4",
"Day 5",
"Day 6",
"Days 7-13",
"Days 14-20",
"Days 21-29",
"Days 30-44",
"Days 45-89",
"90 Days-2 Years"
],
"data": [{
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 3,
"type_12": 20,
"type_11": 12,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 12,
"type_6": 1,
"stage": "Oblong",
"type_3": 32,
"type_5": 0
}, {
"type_9": 10,
"type_13": 12,
"type_8": 1,
"type_4": 6,
"type_12": 22,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 2,
"type_10": 11,
"type_6": 1,
"stage": "Bud",
"type_3": 7,
"type_5": 10
}, {
"type_9": 1,
"type_13": 11,
"type_8": 1,
"type_4": 1,
"type_12": 11,
"type_11": 11,
"type_7": 1,
"type_2": 2,
"type_1": 11,
"type_10": 1,
"type_6": 1,
"stage": "Dome",
"type_3": 5,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 2,
"type_4": 10,
"type_12": 21,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "Prim-25",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 12,
"type_12": 20,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 11,
"type_6": 2,
"stage": "26+ somites",
"type_3": 4,
"type_5": 10
}, {
"type_9": 10,
"type_13": 1,
"type_8": 11,
"type_4": 10,
"type_12": 20,
"type_11": 1,
"type_7": 11,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "High-pec",
"type_3": 4,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 20,
"type_12": 20,
"type_11": 11,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 11,
"type_6": 1,
"stage": "High",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 11,
"type_8": 1,
"type_4": 14,
"type_12": 20,
"type_11": 11,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 5,
"stage": "10-13 somites",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 2,
"type_4": 13,
"type_12": 20,
"type_11": 12,
"type_7": 3,
"type_2": 1,
"type_1": 1,
"type_10": 12,
"type_6": 7,
"stage": "Shield",
"type_3": 4,
"type_5": 2
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 19,
"type_12": 20,
"type_11": 1,
"type_7": 10,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 40,
"stage": "20-25 somites",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 8,
"type_12": 20,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 4,
"stage": "Protruding-mouth",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 3,
"type_8": 13,
"type_4": 1,
"type_12": 20,
"type_11": 1,
"type_7": 13,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "Germ-ring",
"type_3": 1,
"type_5": 0
}, {
"type_9": 3,
"type_13": 3,
"type_8": 1,
"type_4": 10,
"type_12": 3,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "128-cell",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 10,
"type_12": 13,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "4-cell",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 5,
"type_8": 1,
"type_4": 10,
"type_12": 8,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "32-cell",
"type_3": 1,
"type_5": 0
}, {
"type_9": 1,
"type_13": 1,
"type_8": 1,
"type_4": 1,
"type_12": 1,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 4,
"stage": "Long-pec",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 3,
"type_8": 1,
"type_4": 10,
"type_12": 11,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 2,
"stage": "16-cell",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 2,
"type_4": 12,
"type_12": 27,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 15,
"stage": "14-19 somites",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 2,
"type_8": 1,
"type_4": 10,
"type_12": 23,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "1k-cell",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 2,
"type_8": 2,
"type_4": 10,
"type_12": 20,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "1-cell",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 10,
"type_12": 5,
"type_11": 1,
"type_7": 1,
"type_2": 2,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "256-cell",
"type_3": 1,
"type_5": 0
}, {
"type_9": 1,
"type_13": 13,
"type_8": 1,
"type_4": 1,
"type_12": 1,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 6,
"type_10": 1,
"type_6": 3,
"stage": "90 Days-2 Years",
"type_3": 2,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 2,
"type_4": 13,
"type_12": 12,
"type_11": 1,
"type_7": 2,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 7,
"stage": "Sphere",
"type_3": 6,
"type_5": 2
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 12,
"type_12": 20,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 4,
"stage": "5-9 somites",
"type_3": 4,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 5,
"type_12": 23,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "90%-epiboly",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 15,
"type_12": 20,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "2-cell",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 14,
"type_12": 20,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 11,
"stage": "Day 5",
"type_3": 1,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 4,
"type_12": 21,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "512-cell",
"type_3": 2,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 11,
"type_12": 14,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "30%-epiboly",
"type_3": 9,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 2,
"type_12": 28,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 3,
"stage": "75%-epiboly",
"type_3": 9,
"type_5": 0
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 13,
"type_12": 23,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "50%-epiboly",
"type_3": 4,
"type_5": 0
}, {
"type_9": 11,
"type_13": 4,
"type_8": 1,
"type_4": 10,
"type_12": 2,
"type_11": 1,
"type_7": 3,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "64-cell",
"type_3": 1,
"type_5": 4
}, {
"type_9": 10,
"type_13": 1,
"type_8": 1,
"type_4": 10,
"type_12": 20,
"type_11": 1,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 1,
"stage": "Prim-15",
"type_3": 1,
"type_5": 0
}, {
"type_9": 1,
"type_13": 1,
"type_8": 3,
"type_4": 1,
"type_12": 1,
"type_11": 3,
"type_7": 1,
"type_2": 1,
"type_1": 1,
"type_10": 1,
"type_6": 5,
"stage": "Prim-5",
"type_3": 9,
"type_5": 0
}]
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
</style>
<form>
<label><input type="radio" name="mode" value="together"checked> together</label>
<label><input type="radio" name="mode" value="split"> split</label>
</form>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {
top: 40,
right: 160,
bottom: 100,
left: 25
},
height = 485 - margin.top - margin.bottom,
width = height * 3 - margin.left - margin.right;
d3.json("example_data.json", function(error, data_input) {
if (error) throw error;
var assay_types = data_input.assay_types;
function arrayAwareInvert(obj) {
var res = {};
for (var p in obj) {
var arr = obj[p],
l = arr.length;
for (var i = 0; i < l; i++) {
res[arr[i]] = p;
}
}
return res;
}
var assay_types_inv = arrayAwareInvert(assay_types);
var assays = [],
assay_groups = [];
for (var i in assay_types) {
assay_types[i].map(function(item) {
assays.push(item);
});
}
for (var i in assay_types_inv) {
assay_groups.push(assay_types_inv[i]);
}
var assay_groups_uniq = Object.keys(assay_types),
n = assay_groups_uniq.length, //number of groups
lastRow = (n - 1),
spacing = {
bottom: 15,
right: 5
},
rowHeight = Math.floor((height - (n - 1) * spacing.bottom) / n);
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.domain(data_input.stages);
var y_split = d3.scaleLinear()
.range([rowHeight, 0]);
var y_stacked = d3.scaleLinear()
.range([height, 0]);
var z = d3.scaleBand().domain(assays).rangeRound([0, x.bandwidth()]);
var color = d3.scaleOrdinal().domain(assays)
.range(['#f03e3e', '#f76707', '#40c057', '#0ca678', '#1098ad', '#0b7285']);
var color_shades = d3.scaleOrdinal().domain(assays)
.range(['#69db7c', '#40c057', '#37b24d', '#2b8a3e',
'#66d9e8', '#22b8cf', '#1098ad', '#0b7285',
'#ffc9c9', '#ffa8a8', '#ff6b6b', '#f03e3e', '#c92a2a'
]);
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 + ")");
var stack = d3.stack().keys(assays),
layers = stack(data_input.data);
layers.forEach(function(d, i) {
d.forEach(function(dd) {
dd.assay = assays[i];
dd.assay_group = assay_groups[i];
});
});
var ySplitMax = d3.max(layers, function(layer) {
return d3.max(layer, function(d) {
return d.data[d.assay];
});
}),
yStackMax = d3.max(layers, function(layer) {
return d3.max(layer, function(d) {
return d[1];
});
});
y_stacked.domain([0, yStackMax]).nice();
y_split.domain([0, ySplitMax]).nice(5);
// gridlines in y axis function
function make_y_gridlines(scale, ticks) {
return d3.axisLeft(scale).ticks(ticks);
}
// add the Y gridlines
svg.append("g")
.attr("class", "grid")
.call(make_y_gridlines(y_stacked)
.tickSize(-width)
.tickFormat("")
);
var layer = svg.selectAll(".layer")
.data(layers, function(d) {
return assay_types_inv[d.key];
})
.enter().append("g")
.attr("class", "layer")
.attr("class", function(d) {
return "layer " + assay_types_inv[d.key];
});
assay_groups_uniq.forEach(function(d) {
svg.select("." + d)
.append("g")
.transition()
.duration(300)
.attr("class", "grid grid--split")
.call(make_y_gridlines(y_split, 4)
.tickSize(-width)
.tickFormat("")
)
.attr('opacity', 0);
svg.select("." + d)
.append('g')
.attr('class', 'axis--y')
.attr('opacity', 0);
svg.select("." + d)
.append("g")
.attr("class", "axis axes--x")
.attr("transform", function() {
return translate(0, rowHeight);
})
.attr('opacity', 0);
});
layer.append('g')
.attr('class', 'axis--y')
.attr('opacity', 0);
layer.append("g")
.attr("class", "axis axes--x")
.attr("transform", function() {
return translate(0, rowHeight);
})
.attr('opacity', 0);
var bars = layer.selectAll("rect")
.data(function(d) {
return d;
}, function(d) {
return d.key;
})
.enter().append("rect")
.style("fill", function(d) {
return color_shades(d.assay);
})
.attr("x", function(d) {
x(d.data.stage);
return x(d.data.stage);
})
.attr("y", function() {
return height;
})
.attr("width", x.bandwidth())
.attr("height", function() {
return 0;
})
.attr("class", function(d) {
return "bar " + d.assay;
})
bars.transition()
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d) {
return y_stacked(d[1]);
})
.attr("height", function(d) {
return y_stacked(d[0]) - y_stacked(d[1]);
});
var legend = svg.selectAll(".legend")
.data(assays)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(30," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) {
return color_shades(d);
})
.on("mouseover", function(d) {
svg.selectAll(".bar:not(." + d.replace(" ", "_") + ")")
.style('opacity', '0.1');
})
.on("mouseout", function(d) {
svg.selectAll(".bar:not(." + d.replace(" ", "_") + ")")
.style('opacity', '1');
});
legend.append("text")
.attr("x", width + 5)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) {
return d;
})
.on("mouseover", function(d) {
svg.selectAll(".bar:not(." + d.replace(" ", "_") + ")")
.transition()
.duration(100)
.style('opacity', '0.25');
})
.on("mouseout", function(d) {
svg.selectAll(".bar:not(." + d.replace(" ", "_") + ")")
.transition()
.duration(500)
.style('opacity', '1');
});
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
.selectAll("text")
.attr("dx", "-0.35em")
.attr("dy", "0.6em")
.attr("transform", "rotate(-45)")
.style("text-anchor", "end");
svg.append("g")
.attr("class", "axis axis--y axis--stacked")
.call(d3.axisLeft(y_stacked).ticks(10))
.append("text")
.attr("x", 6)
.attr("y", y_stacked(y_stacked.ticks(10).pop()))
.attr("dy", "0.35em")
.attr("text-anchor", "start")
.attr("fill", "#000")
.text("counts");
d3.selectAll("input").on("change", change);
function change() {
if (this.value === "split") {
transitionSplit();
} else if (this.value === "together") {
transitionStacked();
}
}
function translate(x, y) {
return "translate(" + x + ", " + y + ")";
}
function rowPosition(d) {
return assay_groups_uniq.indexOf(assay_types_inv[d.key]);
}
function transitionSplit() {
var g = svg.selectAll(".layer")
.transition().duration(750)
.attr("transform", function(d) {
return translate(0, rowPosition(d) * (rowHeight + spacing.bottom));
});
svg.selectAll('g.grid')
.attr('opacity', 0);
layer.selectAll('g.grid--split')
.attr('opacity', 1)
.transition()
.duration(750);
g.selectAll("rect")
.attr("y", function(d, i) {
return y_split(d.data[d.assay]);
})
.attr("height", function(d) {
return rowHeight - y_split(d.data[d.assay]);
});
layer.selectAll('.axes--x')
.transition()
.duration(750)
.attr('opacity', 1)
.call(d3.axisBottom(x).tickSize(0))
.selectAll("text")
.attr('opacity', 0);
layer.selectAll('.axis--y')
.transition()
.duration(750)
.attr('opacity', 1)
.call(d3.axisLeft(y_split).ticks(3));
svg.selectAll('g.axis--stacked')
.attr('opacity', 0);
}
function transitionStacked() {
var t = svg.transition().duration(750),
g = t.selectAll(".layer")
.attr("transform", function() {
return translate(0, 0);
});
g.selectAll("rect")
.attr("y", function(d) {
return y_stacked(d[1]);
})
.attr("height", function(d) {
return y_stacked(d[0]) - y_stacked(d[1]);
});
layer.selectAll('.axes--x')
.attr('opacity', 0);
layer.selectAll('.axis--y')
.transition()
.duration(750)
.attr('opacity', 0);
svg.selectAll('g.grid')
.transition()
.duration(750)
.attr('opacity', 1);
layer.selectAll('g.grid--split')
.transition()
.duration(750)
.attr('opacity', 0);
svg.selectAll('g.axis--stacked')
.transition()
.duration(300)
.attr('opacity', 1);
}
setTimeout(function() {
d3.select("input[value=\"split\"]").property("checked", true).each(change);
}, 2000);
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment