xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { position: absolute; overflow: scroll; }
#container { margin-left: 905px; }
#article { display:inline-block; margin: auto; min-width: 40%; max-width: 80%; margin-top: 340px; }
#chart { display: inline-block; float:left; margin: 10px; }
#container p { font-size: 20px; }
text { fill: #333}
.axis circle { fill: none; stroke:#eee; }
.axis line, .axis circle:nth-of-type(5n), .axis circle:first-of-type { stroke:#aaa; }
.r text, .r text:first-of-type { display:none; }
.r text:nth-of-type(10n) {display:inline;}
.label text { font: bold; }
.wedge { fill: rgba(115,190,220,0.30); z-index: -1;}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #aca;
padding: 14px;
text-align: center;
}
.dot{ fill: #333}
</style>
</head>
<body>
<div id="chart"></div>
<div id="container">
<div id="article">
<h2>A Family Calendar</h2>
<p>
<em>by Frank Johnson. 16 June 2017.</em><br><br>
My grandma loves genealogy. I remember stopping at cemeteries on road trips, trying to piece together the long story of our history from charcoal rubbings of old headstones. The old family joke was that she'd rather go there instead of the beach.<br><br>
This diagram shows all of my grandparents' descendants, with their <em>birth date</em> represented by the angle from 12 o'clock, and their <em>age</em> represented by proximity to the circle's center. The blue sweep represents the days elapsed in the current year<br><br>
<em>Hover over each dot</em> to see the name, birth date, and current age of each family member.
</p>
</div>
</div>
<script>
// SVG Setup //
var size = 745,
margin = {top:50, left:80};
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const era = 100;
//toolTip framework
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
var svg = d3.select("#chart").append("svg")
.attr("width", size + margin.left*2)
.attr("height", size + margin.top*2)
.append("g")
.attr("transform", "translate(" + (size + margin.left*2) / 2 + "," + (size + margin.left*2) / 2 + ")");
// d3.select("#container")
// .style("margin-left", size + margin.left*2);
//age axis setup
var outerRadius = .5 * size - 27,
innerRadius = 50;
var r = d3.scaleLinear()
.domain([era, 0])
.range([innerRadius, outerRadius]);
var gr = svg.append("g")
.attr("class", "r axis")
.selectAll("g")
.data(r.ticks(96).slice(1))
.enter();
gr.append("circle")
.attr("r", r)
.attr("cx", size / 2 + "\"")
.attr("cy", "\"" + size / 2 + "\"");
gr.append("text")
.attr("y", function(d) { return -r(d) - 1; })
.attr("transform", "rotate(15)")
.attr("text-anchor", "middle")
.text(function(d) { return d; });
gr.append("text")
.attr("class", "label")
.attr("y", -outerRadius - 20)
.attr("transform", "rotate(15)")
.attr("text-anchor", "middle")
.text("Age");
//calendar axis setup
var ga = svg.append("g")
.attr("class", "a axis")
.selectAll("g")
.data(months)
.enter().append("g")
.attr("transform", function(d,i) { return "rotate(" + i*30 + ")"; });
ga.append("line")
.attr("y1", -innerRadius)
.attr("y2", -outerRadius);
ga.append("text")
.attr("y", -outerRadius - 8)
.style("text-anchor", function(d,i) { return i*30 < 270 && i*30 > 90 ? "end" : null; })
.attr("transform", function(d,i) { return i*30 < 270 && i*30 > 90 ? "rotate(180, 0," +(-outerRadius - 12) + ")" : null; })
.text(function(d) { return d; });
//JSON setup
var parseDate = d3.timeParse("%Y-%m-%d");
d3.json("birthdays.json", function(error, json) {
if (error) throw error;
var data = json;
var now = new Date();
var then = new Date();
then.setFullYear(now.getFullYear()-era);
console.log("NOW", now);
console.log("THEN", then);
data.forEach(function(d) {
d.name = d.name;
d.birthday = parseDate(d.birthday);
});
console.log("DATA", data);
var timeScale = d3.scaleTime()
.domain([now, then])
.range([outerRadius, innerRadius]);
var timeForm = d3.timeFormat("%Y-%m-%d");
var minutes = 1000 * 60;
var hours = minutes * 60;
var days = hours * 24;
var years = days * 365;
var endAngle = d3.timeDay.count(d3.timeYear(now), now)*360/365 * Math.PI/180;
var arc = d3.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius)
.startAngle(0)
.endAngle(endAngle);
console.log("ENDANGLE", d3.timeDay.count(d3.timeYear(now), now)*360.0/365);
var wedge = svg.append("g")
.attr("class", "wedge");
wedge.append("path")
.attr("d", arc);
var dots = svg.append("g")
.attr("class", "dots")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "rotate(" + (d.birthday.getMonth()*30 + d.birthday.getDate()/31*30) + ")"; });
dots.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cy", function(d) {
console.log("BIRTHDAY", timeScale(d.birthday));
return -timeScale(d.birthday);
})
.on("mouseenter", function(d){
tooltip
.style("left", d3.event.pageX - 75 + "px")
.style("top", d3.event.pageY - 90 + "px")
.style("display", "inline-block")
.html((d.name) + "<br>Birthdate: " + timeForm(d.birthday) + "<br>Age: " + (Math.floor((now-d.birthday)/years)));
})
.on("mouseexit", function(d){
tooltip
.style("display","none")
});
//trying out some Voronoi stuff
// v = d3.voronoi();
// voronoi(data);
// console.log(voronoi);
// var voronoiTooltip = svg.on("mouseenter", function(d){
// var mouse = d3.mouse(svg);
// voronoi.find(mouse[1],mouse[2]);
// });
}); //JSON
</script>
</body>
https://d3js.org/d3.v4.min.js