|
<!DOCTYPE html> |
|
<meta charset='utf-8'> |
|
<head> |
|
<title>Animated Sticky Note Hierarchy</title> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
</head> |
|
|
|
<style> |
|
rect.main { |
|
stroke: #05668D; |
|
fill: white; |
|
stroke-width: 2px; |
|
} |
|
|
|
rect.second { |
|
fill: white; |
|
opacity: 0; |
|
} |
|
|
|
path { |
|
fill: none; |
|
stroke: #05668D; |
|
stroke-width: 2px; |
|
} |
|
|
|
text { |
|
font-family: "Comic Sans MS", Verdana, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif; |
|
font-size: 12px; |
|
fill: black; |
|
} |
|
|
|
</style> |
|
<button id="phase">Next</button> |
|
<svg> |
|
<g></g> |
|
</svg> |
|
|
|
<script> |
|
var vWidth = 350; |
|
var vHeight = 600; |
|
var vRad = 25; |
|
var vColor = ['#feff9c', '#7afcff', '#ff7eb9']; |
|
var vRects; |
|
var vLines; |
|
var vPhase = 1; |
|
|
|
// Prepare our physical space |
|
var g = d3.select('svg').attr('width', vWidth).attr('height', vHeight) |
|
.select('g').attr('transform', 'translate(40, 20)'); |
|
|
|
// Declare d3 layout |
|
var vLayout = d3.tree().size([vHeight * 0.9, vWidth * 0.7]); |
|
|
|
// Get the data from our JSON file |
|
d3.csv('data.csv', function(error, vCsvData) { |
|
if (error) throw error; |
|
|
|
vCsvData = vCsvData.filter( function(node) { return node.round === "1"; }); |
|
|
|
vData = d3.stratify()(vCsvData); |
|
drawTree(vData); |
|
}); |
|
|
|
/** |
|
* Draw our sunburst |
|
* Time: 0 |
|
* @param {object} data - Hierarchical data |
|
*/ |
|
function drawTree(data) { |
|
// Layout + Data |
|
var vRoot = d3.hierarchy(data); |
|
var vNodes = vRoot.descendants(); |
|
var vLinks = vLayout(vRoot).links(); |
|
|
|
// Draw on screen |
|
vLines = g.selectAll('path').data(vLinks).enter().append('path') |
|
.attr('d', d3.linkHorizontal() |
|
.x(function(d) { return d.y; }) |
|
.y(function(d) { return d.x; })); |
|
|
|
// Add <g>s |
|
vRects = g.selectAll('g').data(vNodes).enter().append('g') |
|
.attr('transform', function (d) { return 'translate(' + (d.y - vRad) + ',' + (d.x - vRad) + ')'; }); |
|
|
|
// Draw <rect>s |
|
vRects.append('rect').attr('class','main').attr('width', vRad * 2).attr('height', vRad * 2) |
|
.attr('rx', vRad) |
|
.attr('transform', 'rotate(5)') |
|
.each( function(d) { d.data.color = vColor[d.depth]}); |
|
} |
|
|
|
/** |
|
* Animation 1: Move from tree diagram to sticky notes in tree shape |
|
* End: 2500 (vDelay + (2 * vDuration)) |
|
*/ |
|
function animateTree1() { |
|
var vDelay = 250; |
|
var vDuration = 1000; |
|
|
|
// Clear lines |
|
vLines.transition().delay(vDelay).duration(vDuration * 2).style('opacity', 0); |
|
|
|
// Circles to sticky notes |
|
vRects.selectAll('rect.main') |
|
.transition().delay( function(d) { return ripple(vDelay, d.depth); }).duration(vDuration) |
|
.attr('rx', 0).attr('width', vRad * 2).attr('height', vRad * 2).style('fill', function(d) { return d.data.color; }) |
|
.style('stroke', function(d) { return d.data.color; }).style('opacity', 1); |
|
|
|
// Add <text>s and labels |
|
vRects.append('text') |
|
.attr('text-anchor', 'middle') |
|
.attr("dominant-baseline", "central") |
|
.attr('transform', function (d) { return 'translate(' + (vRad - 3) + ',' + vRad + ')'; }) |
|
.text(function(d) { return d.data.id }).style('opacity', 0) |
|
.transition().delay( function(d) { return ripple(vDelay, d.depth); }).duration(vDuration) |
|
.style('opacity', 1); |
|
} |
|
|
|
/** |
|
* Animation 2: Move from sticky notes in tree shape to sticky notes in tabular shape |
|
* End: |
|
*/ |
|
function animateTree2() { |
|
var vDelay = 250; // 3000; |
|
var vDuration = 1000; |
|
vRad = 25; |
|
|
|
// Move <g>s to Tabular Location |
|
vRects.transition().delay(vDelay).duration(vDuration) |
|
.attr('transform', function(d, i) { return 'translate(40, ' + (i * 50 + 15) + ')'; } ); |
|
|
|
vRects.selectAll('.main').transition().delay(vDelay).duration(vDuration) |
|
.attr('width', vRad * 2).attr('height', vRad * 2); |
|
|
|
// Add 2nd column of <rect>s |
|
vRects.append('rect').attr('class', 'second') |
|
.attr('transform', function (d) { return 'translate(60, 0) rotate(5)'; }) |
|
.attr('width', vRad * 2).attr('height', vRad * 2).attr('rx', vRad).attr('rx', 0) |
|
.transition().delay( function(d) { return ripple(vDelay, d.depth); }).duration(vDuration) |
|
.style('fill', function(d) { return d.parent ? d.parent.data.color : 'white'; }) |
|
.style('stroke', function(d) { return d.parent ? d.parent.data.color : 'white'; }) |
|
.style('opacity', 1); |
|
|
|
// Add 2nd column <text>s |
|
vRects.append('text').attr('class', 'second') |
|
.attr('text-anchor', 'middle') |
|
.attr("dominant-baseline", "central") |
|
.attr('transform', function (d) { return 'translate(' + (57 + vRad) + ', ' + vRad + ')'; }) |
|
.text(function(d) { return d.parent ? d.parent.data.id : ''; }).style('opacity', 0) |
|
.transition().delay( function(d) { return ripple(vDelay, d.depth); }).duration(vDuration) |
|
.style('opacity', 1); |
|
|
|
// Add labels at top |
|
g.append('text').attr('text-anchor', 'middle').attr('class', 'second') |
|
.attr('transform', function (d) { return 'translate(65, 0)'; }) |
|
.transition().delay(vDelay).text('id'); |
|
g.append('text').attr('text-anchor', 'middle').attr('class','second') |
|
.attr('transform', function (d) { return 'translate(130, 0)'; }) |
|
.transition().delay(vDelay).text('parentId'); |
|
} |
|
|
|
/** |
|
* Animation 3: Get back to our original tree diagram |
|
*/ |
|
function resetTree() { |
|
var vDelay = 250; // 6000; |
|
var vDuration = 1000; |
|
vRad = 30; |
|
|
|
vLines.transition().delay(vDelay * 3).duration(vDuration).style('opacity', 1); |
|
vRects.transition().delay( function(d) { return ripple(vDelay, d.depth); }).duration(vDuration) |
|
.attr('transform', function (d) { return 'translate(' + (d.y - vRad) + ',' + (d.x - vRad) + ')'; }) |
|
.selectAll('rect').attr('rx', vRad) |
|
.style('fill', 'white') |
|
.style('stroke', '#05668D').style('opacity', 1); |
|
vRects.selectAll('.second').transition().delay(vDelay).duration(vDuration).style('opacity', 0); |
|
d3.selectAll('text').transition().delay(vDelay).duration(vDuration).style('opacity', 0); |
|
|
|
} |
|
|
|
function ripple(baseDelay, groupNumber) { |
|
return ((groupNumber + 1) * 250) + baseDelay; |
|
} |
|
|
|
d3.select('button#phase').on('click', function() { |
|
switch (vPhase) { |
|
case 1: |
|
animateTree1(); |
|
vPhase = 2; |
|
break; |
|
case 2: |
|
animateTree2(); |
|
vPhase = 3; |
|
break; |
|
case 3: |
|
resetTree(); |
|
vPhase = 1; |
|
break; |
|
}}); |
|
</script> |