Reputation: 33
This is my first D3 project (I'm not a programmer, so learning everything at once).
I'm trying to make a circular calendar that I've been drawing by hand for several years, which is a huge pain.
Most of the bits are now working, but I can't figure out how to get the text turned 90 degrees on this arc.
Once I get the text turned, then I'll need to figure out how to get it spaced equally around the circle.
My goal is to get it to look like this hand drawn example:
Here's what it looks like currently:
I may have gone down a dead end by placing the text on a path, so any and all suggestions welcome.
Edit: I created a jsFiddle here jsfiddle.net/LMF9h/1/
Here is the code I have so far:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body { font: 12px Arial;}
path {
stroke: red;
stroke-width: 1;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script type="text/javascript">
var margin = {top: 25, right: 25, bottom: 25, left: 25},
width = 950 - margin.left - margin.right,
height = 950 - margin.top - margin.bottom;
// Build Day Numbers
function pad(n) {
return (n < 10) ? ("0" + n) : n;
}
function getDaysArray(year) {
var numDaysInMonth, daysInWeek, daysIndex, index, i, l, daysArray;
numDaysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
daysInWeek = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
daysIndex = { 'Sun': 0, 'Mon': 1, 'Tue': 2, 'Wed': 3, 'Thu': 4, 'Fri': 5, 'Sat': 6 };
daysArray = [];
for (m=0; m < 12; m++) {
index = daysIndex[(new Date(year, m, 1)).toString().split(' ')[0]];
for (i = 0, l = numDaysInMonth[m]; i < l; i++) {
daysArray.push(pad((i + 1)) + ' ' + daysInWeek[index++]);
if (index == 7) index = 0;
}
}
return daysArray;
}
var year = 2014
var days = getDaysArray(year)
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g"),
pi = Math.PI;
var arc = d3.svg.arc()
.innerRadius(850)
.outerRadius(851)
.startAngle(0)
.endAngle(-pi*1.99999)
var path = svg.append("path")
.attr("d", arc)
.attr("id", "path1")
.attr("transform", "translate(900,900)")
.attr("fill","#f00")
// Draw lines
function daysInYear(year) {
if(year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)) {
// Leap year
return 366;
} else {
// Not a leap year
return 365;
}
}
var dt = new Date(year+"/01/01");
var day = dt.getDay();
var startrads = (2*pi / daysInYear(year)) * day;
//---center point(width,height)---
var cx=width
var cy=height
var r=850 //--radius--
var radialLines=52 //---create 52 lines---
var angle=2*Math.PI/radialLines //---angle between each line---
var radialPoints=[]
for(var k=startrads;k<radialLines;k++)
{
var x2=r*Math.cos(angle*k)+cx
var y2=r*Math.sin(angle*k)+cy
radialPoints.push([x2,y2])
}
//---your d3 SVG parent element---
svg.selectAll("line")//---an empty selection---
.data(radialPoints)
.enter().append("svg:line")
.attr("x1",cx)
.attr("y1",cy)
.attr("x2",function(p){return p[0]})
.attr("y2",function(p){return p[1]})
.attr("stroke","red");
// Add day labels.
var text1 = svg.append("text");
text1
.selectAll("body")
.data(days)
.enter()
.append("textPath")
.attr("xlink:href","#path1")
.text(function(d) { return d; });
</script>
</body>
Upvotes: 3
Views: 5828
Reputation: 109272
To get the text right, you simply need to calculate its position and rotation in the same way that you're calculating the end of the radial lines. The code would be something like
svg.selectAll("text").data(days).enter()
.append("text")
.attr("transform", function(d, i) {
return "translate(" + (850*Math.cos(i*2*Math.PI/365)+width) +
"," + (850*Math.sin(i*2*Math.PI/365)+height) + ")" +
"rotate(" + (i*360/365) + ")";
})
.text(function(d) { return d; });
Note that on the side of the circle that is visible in your jsfiddle, the text would be upside down. You can fix this by adding 180 to the rotate
value. Modified jsfiddle here.
Upvotes: 1