Skip to content

Instantly share code, notes, and snippets.

@Kerrin631
Created December 9, 2016 15:46
Show Gist options
  • Save Kerrin631/27ac8a486ee612590effa33c7807841e to your computer and use it in GitHub Desktop.
Save Kerrin631/27ac8a486ee612590effa33c7807841e to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>SunburstTest</title>
</head>
<style type="text/css">
path.selected {
fill-opacity: 100%;
shape-rendering: crisp-edges;
}
path:hover {
fill: yellow;
fill-opacity: 100%;
shape-rendering: crisp-edges;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
.invisible {
display: none;
}
</style>
<script src="https://d3js.org/d3.v3.min.js"></script>
<body>
<form>
<label><input type="radio" name="mode" value="impaired"> Visually Enhanced
</label>
<label><input type="radio" name="mode" value="normal" checked> Normal</label>
</form>
<script type="text/javascript">
var width = 900,
height = 760,
radius = (Math.min(width, height) / 2) - 10;
var color = d3.scale.category20c();
var impairedColor = d3.scale.category20b();
var newColor = d3.scale.ordinal()
.range(["#E50001", "#E12800", "#DD5100", "#D97900", '#D5A000', '#D2C400', '#B3CE00', '#8ACA00', '#62C600', '#3BC200', '#16BF00']);
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var partition = d3.layout.partition()
.sort(null)
.value(function(d) { return 1; });
// var arc = d3.svg.arc()
// .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
// .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
// .innerRadius(function(d) { return Math.max(d.depth * 20 - 5, y(d.y)) })
// .outerRadius(function(d) { return Math.max(d.depth * 20 + 10, y(d.y + d.dy)) })
// .cornerRadius(function(d) { return 3; });
var arcStart = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return d.depth > 2 ? Math.max(d.depth * 54, y(d.y)) : Math.max(d.depth * 20, y(d.y)) })
.outerRadius(function(d) { return d.depth > 2 ? Math.max(d.depth * 0 - 200 , y(d.y + d.dy) - 40) : Math.max(d.depth * 20 + 10, y(d.y + d.dy)) })
.cornerRadius(function(d) { return 3; });
var num = 0;
var arcTransition = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return d.depth > num + 2 ? Math.max(d.depth * 50, y(d.y)) : Math.max(d.depth * 20, y(d.y)) })
.outerRadius(function(d) { return d.depth > num + 2 ? Math.max(d.depth * 0 - (d.depth * 100) , y(d.y + d.dy) - 40) : Math.max(d.depth * 20 + 10, y(d.y + d.dy)) })
.cornerRadius(function(d) { return 3; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.append("g")
.classed("inner", true);
d3.json("https://dl.dropboxusercontent.com/u/76642081/flare.json", function(error, root) {
if (error) throw error;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g")
.attr('id', function(d,i) {
d.id = 'node' + i;
return d.id;
});
var scaleValue = 1;
path = g.append("path")
.classed('invisible', function(d) { return d.depth > 3 ? true : false; })
.attr("d", arcStart)
.attr('stroke', function(d) { return 'white'; })
.attr("fill", function(d) { return color((d.children ? d : d.parent).id); })
.attr("transform", function() { return "scale(" + scaleValue + ")"; })
.on("click", magnify)
.each(stash);
var text = g.append("text")
.classed('invisible', function(d) { return d.depth > 1 ? true : false; })
.text(function(d) { return d.labels ? d.labels[0] : d.depth <= 0 ? '' : 'Test Label'; })
.attr('font-size', function(d) { return '10px'; })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
if (d.depth > 0) { return "translate(" + ([arcTransition.centroid(d)[0] * scaleValue, arcTransition.centroid(d)[1] * scaleValue]) + ")" + "rotate(" + getStartAngle(d) + ")"; }
})
.attr('opacity', function(d) {
if (d.dx > 0.009) { return 1 } else { return 0 }
})
.on("click", magnify);
var text2 = g.append('text')
.text(function(d) {
return d.labels ? d.labels[1] : d.depth <= 0 ? '' : 'Test Label';
})
.attr('font-size', function(d) { return '8px'; })
.classed('invisible', true)
.classed('text2', true)
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + arcTransition.centroid(d) + ")" +
"rotate(" + getStartAngle(d) + ")";
}
});
var text3 = g.append('text')
.text(function(d) {
return d.labels ? d.labels[2] : d.depth <= 0 ? '' : 'Test Label';
})
.attr('font-size', function(d) { return '8px'; })
.classed('invisible', true)
.classed('text3', true)
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + arcTransition.centroid(d) + ")" +
"rotate(" + getStartAngle(d) + ")";
}
});
var innerG = d3.selectAll("g.inner");
// change color on radio button click
d3.selectAll("input").on("change", function changeColor() {
var setColor = this.value === "normal"
? function(d) { return color((d.children ? d : d.parent).id); }
: function(d) { return impairedColor((d.children ? d : d.parent).id); };
path
.attr('fill', setColor)
});
// Distort the specified node to 80% of its parent.
function magnify(node) {
// console.log(node)
// get and store parent sequence
var parentSequence = getAncestors(node)
text.transition().attr("opacity", 0);
text2.transition().attr("opacity", 0);
text3.transition().attr("opacity", 0);
spin(node);
var newScale = 1.0
// check if node has a parent. If so, iterate throught parentSequence and update the size of each node in the sequence
if (node.parent) {
for (var p = 0; p < parentSequence.length; p++) {
if (parent = parentSequence[p].parent) {
var parent,
x = parent.x,
k = 0.8,
kSide = 0.10,
k1 = 0.6
var sibs = node.parent.children
for (var i = 0; i < sibs.length; i ++) {
if (sibs[i] == node && i != 0 && i != sibs.length-1) {
// console.log('no edges')
var less = sibs[i - 1]
var more = sibs[i + 1]
} else if (sibs[i] == node && i == 0) {
// console.log('left edge case')
var more = sibs[i + 1]
} else if (sibs[i] == node && i == sibs.length-1) {
// console.log('right edge case')
var less = sibs[i - 1]
}
}
parent.children.forEach(function(sibling) {
if (parentSequence[p].depth <= 1) {
x += reposition(sibling, x, sibling === parentSequence[p]
? parent.dx * k1 / parentSequence[p].value
: parent.dx * (1 - k1) / (parent.value - parentSequence[p].value));
} else {
x += reposition(sibling, x, sibling === parentSequence[p]
? parent.dx * k / parentSequence[p].value
: sibling === more ? parent.dx * kSide / (sibling.value)
: sibling === less ? parent.dx * kSide / (sibling.value)
: more && less ? parent.dx * (1 - (k+kSide+kSide)) / (parent.value - (parentSequence[p].value + sibling.value))
: more && ! less ? parent.dx * (1 - (k+kSide)) / (parent.value - parentSequence[p].value)
: less && ! more ? parent.dx * (1 - (k+kSide)) / (parent.value - parentSequence[p].value)
: parent.dx * (1 - k) / (parent.value - parentSequence[p].value));
}
});
}
}
// if node does not have parent (center node) reset all values to original
} else {
reposition(node, 0, node.dx / node.value);
}
path.transition()
// .attr('stroke', 'white')
.duration(1100)
.attr('transform', function() {
return "scale(" + newScale + ")"
})
.attrTween("d", arcTween)
.tween("d", arcTweenZoom(node))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in node
if (e.depth <= node.depth + 3) {
var nodePath = d3.select(this.parentNode).select("path").classed('invisible', false);
nodePath.transition().duration(750)
} else {
var nodePath = d3.select(this.parentNode).select("path").classed('invisible', true);
nodePath.transition().duration(750)
}
// Render information for selected node
if (e.id == node.id) {
var arcText = d3.select(this.parentNode).select("text").attr("opacity", 1).classed('invisible', false);
var origText = arcText[0][0].textContent
var shortText = arcText[0][0].textContent.substring(0,5) + '...'
if (!(e.origText)) {
e.origText = origText
}
arcText
.text(function(d) {
return d.origText
})
.attr("transform", function(d) {
if (d.depth > 0) {
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + turn + ")" + 'scale(' + newScale + ')';
}
})
var arcText2 = d3.select(this.parentNode).select(".text2").attr("opacity", 1).classed('invisible', false);
arcText2
.attr("transform", function(d) {
if (d.depth > 0) {
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" +
"rotate(" + turn + ")" + 'scale(' + newScale + ')';
}
})
.attr('dy', 8)
.attr('dx', -20);
var arcText3 = d3.select(this.parentNode).select(".text3").attr("opacity", 1).classed('invisible', false);
arcText3
.attr("transform", function(d) {
if (d.depth > 0) {
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" +
"rotate(" + turn + ")" + 'scale(' + newScale + ')';
}
})
.attr('dy', 16)
.attr('dx', -20);
} else {
// hide secondary and tertiary labels for inactivated children nodes
var arcText2 = d3.select(this.parentNode).select(".text2");
arcText2.classed('invisible', true)
var arcText3 = d3.select(this.parentNode).select(".text3");
arcText3.classed('invisible', true)
// display secondary and tertiary labels for children nodes
if (node.children) {
for (var x = 0; x < node.children.length; x++) {
if (e == node.children[x] && node.depth >= 1) {
var childText2 = d3.select(this.parentNode).select('.text2').attr('opacity', 1).classed('invisible', false)
.attr("transform", function(d) {
if (d.depth > 0) {
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + turn + ")" + 'scale(' + newScale + ')';
}
})
.attr('dy', 8)
.attr('dx', -20);
var childText3 = d3.select(this.parentNode).select('.text3').attr('opacity', 1).classed('invisible', false)
.attr("transform", function(d) {
if (d.depth > 0) {
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + turn + ")" + 'scale(' + newScale + ')';
}
})
.attr('dy', 16)
.attr('dx', -20);
}
}
}
// check that node size is big enough and in proper path to display relevant information
if (e.x >= node.x && (e.x < (node.x + node.dx)) && e.depth >= node.depth || e.depth >= node.depth && e.parent == node.parent ) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
if (e.depth <= node.depth + 2) {
arcText.classed('invisible', false);
} else {
arcText.classed('invisible', true);
}
var origText = arcText[0][0].textContent
var shortText = arcText[0][0].textContent.substring(0,5) + '...'
if (!(e.origText)) {
e.origText = origText
}
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("x", function(d) { return d.x; })
.attr('text-anchor', 'middle')
.text(function(d) {
if (d.depth >= node.depth && d.parent == node.parent) {
return shortText
} else {
return d.origText
}
})
.attr("transform", function(d) {
// console.log(arcText)
if (node.depth > 0) {
if (d.depth > 0) {
var turn = e.depth == node.depth && e != node ? getNewAngle(d, node) : getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90;
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + turn + ")" + 'scale(' + newScale + ')';
}
} else {
if (d.depth > 0) {
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + (getNewAngle(d, node)) + ")" + 'scale(' + 0.7 + ')';
}
}
});
} else {
var arcText = d3.select(this.parentNode).select("text").classed('invisible', true);
arcText.transition().duration(750)
.attr("x", function(d) {
return d.x;
})
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + getNewAngle(d, node) + ")" + 'scale(' + newScale + ')';
}
});
}
}
});
}
});
var innerG = d3.selectAll("g.inner");
// rotate diagram to display selected node at top (zero degrees)
function spin(d) {
// console.log(d)
var slide = d.depth * 60
var spin1 = new Promise (function(resolve, reject) {
var newAngle = - x(d.x + d.dx / 2);
innerG
.transition()
.duration(1500)
.attr("transform", function() {
return d.depth == 0 ? "rotate(" + 0 + ")" + "scale(" + 1.6 + ")" : "translate( 0," + slide + ")" + "rotate(" + ((180 / Math.PI * newAngle)) + ")" + "scale(" + 1.4 + ")";
});
resolve("Success!");
});
spin1.then(function() {
var newerAngle = - x(d.x + d.dx / 2);
innerG
.transition()
.duration(1500)
.attr("transform", function() {
return d.depth == 0 ? "rotate(" + 0 + ")" + "scale(" + 1.6 + ")" : "translate( 0," + slide + ")" + "rotate(" + ((180 / Math.PI * newerAngle)) + ")" + "scale(" + 1.4 + ")";
});
});
path
.classed("selected", function (x) { return d.name == x.name; });
};
// Recursively reposition the node at position x with scale k.
function reposition(node, x, k) {
node.x = x;
if (node.children && (n = node.children.length)) {
var i = -1, n;
while (++i < n) x += reposition(node.children[i], x, k);
}
return node.dx = node.value * k;
};
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
};
// Interpolate the arcs in data space.
function arcTween(a) {
var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arcTransition(b);
};
};
// Interpolate the scales!
function arcTweenZoom(d) {
num = d.depth
var yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arcTransition(d); }
: function(t) {
y.domain(yd(t)).range(yr(t));
return arcTransition(d);
};
};
};
// rotate text around diagram to its appropriate location on first render
function getStartAngle(d) {
// Offset the angle by 90 deg since the '0' degree axis for arc is Y axis, while
// for text it is the X axis.
var thetaDeg = (180 / Math.PI * (arcTransition.startAngle()(d) + arcTransition.endAngle()(d)) / 2 - 90);
// If we are rotating the text by more than 90 deg, then "flip" it.
// This is why "text-anchor", "middle" is important, otherwise, this "flip" would
// be a little harder.
return (thetaDeg > 90) ? thetaDeg - 180 : thetaDeg;
}
// rotate text around diagram to correspond with its node after click event
function getNewAngle(d, node) {
var thetaDeg = (180 / Math.PI * (arcTransition.startAngle()(d) + arcTransition.endAngle()(d)) / 2 - 90);
if (node.depth == 0) {
return (thetaDeg > 90) ? thetaDeg - 180 : thetaDeg;
} else {
return (thetaDeg < 90) ? thetaDeg - 180 : thetaDeg;
}
}
// find parent path
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment