Skip to content

Instantly share code, notes, and snippets.

@GitNoise
Last active January 12, 2016 20:53
Show Gist options
  • Save GitNoise/5fa0aa2e319869321560 to your computer and use it in GitHub Desktop.
Save GitNoise/5fa0aa2e319869321560 to your computer and use it in GitHub Desktop.
Circular hours per week

Built with blockbuilder.org

Experiment on how to display all hours of a (some) weeks in a circular manner using d3.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
svg { width:100%; height: 100% }
</style>
</head>
<body>
<svg></svg>
<script>
// magic numbers
var t = 2 * Math.PI;
var depth = 3;
var arcHeight = 50;
var innerRadius = 50;
var hours = 24 * 7;
var intensityThreshold = 0.5;
// arc generators
var arc = d3.svg.arc()
.innerRadius(function(d,i) { return innerRadius + (d.depth+1) * arcHeight; })
.outerRadius(function(d,i) { return innerRadius + d.depth * arcHeight + arcHeight*2; })
.startAngle(function(d, i){ return d.start;})
.endAngle(function(d, i){ return d.end;});
var overlayArc = d3.svg.arc()
.innerRadius(arcHeight + innerRadius)
.outerRadius(innerRadius + depth * arcHeight + arcHeight)
.startAngle(function(d){ return d.start;})
.endAngle(function(d){ return d.end;});
function postion(i,offset) {
offset = offset || 0;
return (i*24)*((2*Math.PI) / hours) + (offset)*((2*Math.PI) / hours)
}
var dayArc = d3.svg.arc()
.innerRadius( innerRadius + arcHeight)
.outerRadius(innerRadius + (depth+1) * arcHeight)
.startAngle(function(d,i){ return postion(i) ;})
.endAngle(function(d,i){ return postion(i,24);});
var labelArc = d3.svg.arc()
.innerRadius( innerRadius + (depth+1) * arcHeight + 10)
.outerRadius(innerRadius + (depth+2) * arcHeight)
.startAngle(function(d,i){ return postion(i) ;})
.endAngle(function(d,i){ return postion(i,24);})
.padAngle(.01);
// get svg object
var svg = d3.select("svg")
.append("g")
.attr("transform", "translate(310, 310)");
// data
var data = d3.range(0,hours*depth);
data = data.map(function(d) {
var intensity = Math.random();
return {
hoursIndex: d % hours,
hour: d%24,
start: d*((2*Math.PI) / hours),
end: d*((2*Math.PI) / hours) + ((2*Math.PI) / hours),
color: intensity < intensityThreshold ? 0 : intensity,
depth: Math.floor(d/hours)
}
});
var days = ["Monday","Tuesday", "Wednesday","Thursday","Friday","Saturday","Sunday"];
var dataOverlay = d3.range(0,hours)
dataOverlay = dataOverlay.map(function(d) {
return {
start: d*((2*Math.PI) / hours),
end: d*((2*Math.PI) / hours) + ((2*Math.PI) / hours),
data: d
}
});
// Scales
var colorScaleDay = d3.scale.linear()
.domain([0, 1])
.interpolate(d3.interpolateRgb)
.range(["#ffffff", "#ffba19"]);
var colorScaleNight = d3.scale.linear()
.domain([0, 1])
.interpolate(d3.interpolateRgb)
.range(["#ffffff", "#2d73fe"]);
var opacityScale = d3.scale.pow().exponent(1.2)
.domain([0, 1])
.range([0,1]);
// render viz
var select = svg.selectAll(".fore").data(data).enter();
select.append("path")
.classed("fore", true)
.style({
"fill-opacity": function(d) { return opacityScale(d.color); },
"stroke": "black",
"stroke-width": 0.05,
"fill": function(d) { return (5 >= d.hour) || (18 <= d.hour) ? colorScaleNight(d.color) : colorScaleDay(d.color); }
})
.attr("d", arc)
.transition();
var opacity = 0;
svg.selectAll(".overlay").data(dataOverlay).enter()
.append("path")
.classed("overlay", true)
.style({
"stroke": "black",
"stroke-opacity": opacity,
"stroke-width": 2,
"fill": "white",
"fill-opacity": 0
})
.attr("d", overlayArc)
.on('mouseover', function(d) {
d3.select(this).style("stroke-opacity", 1);
var dataindex = d3.select(this).data()[0].data;
var object = data.filter(function(d) { return d.hoursIndex == dataindex; });
var result = object.map(function(d) { return d.color; });
/*svg.select(".txt").data(result).enter()
.append("text").classed("txt",true)
.text(function(d) {return d[0];});*/
})
.on('mouseout', function(d) {
d3.select(this).style("stroke-opacity", opacity);
});
// labeling
svg.append("circle")
.attr({
cx: 0,
cy: 0,
r: innerRadius + arcHeight - 5
})
.style({
"stroke-width": "1",
"stroke": "#606060",
"fill": "white"
});
svg.append("text").text(depth + " weeks (in hours)")
.attr({
x: 0,
y: 3
})
.style({
"text-anchor": "middle",
"font-weight": "bold"
});
// day label text on arch
svg.selectAll(".daySlice")
.data(days)
.enter().append("path")
.classed("daySlice",true)
.attr("d", dayArc)
.style("fill", "none")
.style("stroke", "#494949")
.style("stroke-width", 2);
svg.selectAll(".donutArcSlices")
.data(days)
.enter().append("path")
.classed("donutArcSlices",true)
.attr("id", function(d,i) { return "monthArc_"+i; })
.attr("d", labelArc)
.style("fill", "none")
.style("stroke", "black")
.each(function(d,i) {
//A regular expression that captures all in between the start of a string (denoted by ^)
//and the first capital letter L
var firstArcSection = /(^.+?)L/;
//The [1] gives back the expression between the () (thus not the L as well)
//which is exactly the arc statement
var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
//Replace all the comma's so that IE can handle it -_-
//The g after the / is a modifier that "find all matches rather than stopping after the first match"
newArc = newArc.replace(/,/g , " ");
//Create a new invisible arc that the text can flow along
svg.append("path")
.attr("class", "hiddenDonutArcs")
.attr("id", "donutArc"+i)
.attr("d", newArc)
.style("fill", "none");
});
svg.selectAll(".donutText")
.data(days)
.enter().append("text")
.attr("class", "donutText")
.attr("dy", 17)
.append("textPath")
.attr("startOffset","50%")
.style("text-anchor","middle")
.attr("xlink:href",function(d,i){return "#donutArc"+i;})
.text(function(d){return d;});
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment