console.clear() var width = 500, height = 500, start = 0, end = 2.25, numSpirals = 3 margin = {top:50,bottom:50,left:50,right:50}; var theta = function(r) { return numSpirals * Math.PI * r; }; // used to assign nodes color by group var color = d3.scaleOrdinal(d3.schemeDark2); var r = d3.min([width, height]) / 2 - 40; var radius = d3.scaleLinear() .domain([start, end]) .range([40, r]); var svg = d3.select("#chart").append("svg") .attr("width", width + margin.right + margin.left) .attr("height", height + margin.left + margin.right) .append("g") .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); var points = d3.range(start, end + 0.001, (end - start) / 1000); var spiral = d3.radialLine() .curve(d3.curveCardinal) .angle(theta) .radius(radius); var rectG = svg.append("g") var pathG = svg.append("g") var path = pathG.append("path") .datum(points) .attr("class", "spiral") .attr("d", spiral) .style("fill", "none") .style("stroke", "grey"); var spiralLength = path.node().getTotalLength(), N = 365, barWidth = (spiralLength / N) - 1; var someData = []; for (var i = 0; i < N; i++) { var currentDate = new Date(); currentDate.setDate(currentDate.getDate() + i); someData.push({ date: currentDate, value: Math.random(), group: currentDate.getMonth() }); } var timeScale = d3.scaleTime() .domain(d3.extent(someData, function(d){ return d.date; })) .range([0, spiralLength]); someData.forEach(function(d){ var linePer = timeScale(d.date), posOnLine = path.node().getPointAtLength(linePer), angleOnLine = path.node().getPointAtLength(linePer - barWidth); d.linePer = linePer; // % distance are on the spiral d.x = posOnLine.x; // x postion on the spiral d.y = posOnLine.y; // y position on the spiral d.a = (Math.atan2(angleOnLine.y, angleOnLine.x) * 180 / Math.PI) - 90; //angle at the spiral position }) // yScale for the bar height var yScale = d3.scaleLinear() .domain([0, d3.max(someData, function(d){ return d.value; })]) .range([0, (r / numSpirals) - 30]); var rects = rectG.selectAll("rect") .data(someData) .enter() .append("g") .attr("id", (d, i) => "rect-" + i ) .attr("transform", function(d, i){ return "translate(" + d.x + ", "+ d.y +")" }) rects.append("rect") .attr("class", "hover-rect") .attr("width", barWidth) .attr("height", yScale.range()[1]) .style("fill", "white") .style("stroke", "none") .attr("transform", function(d){ return "rotate(" + d.a + ")"; // rotate the bar }); rects.append("rect") .attr("class", "visible-rect") .attr("width", barWidth) .attr("height", function(d){ return yScale(d.value); }) .style("fill", function(d){return color(d.group);}) .style("stroke", "none") .attr("transform", function(d){ return "rotate(" + d.a + ")"; // rotate the bar }); // add date labels var tF = d3.timeFormat("%b %Y"), firstInMonth = {}; rectG.selectAll("text") .data(someData) .enter() .append("text") .attr("dy", 10) .style("text-anchor", "start") .style("font", "10px arial") .append("textPath") // only add for the first of each month .filter(function(d){ var sd = tF(d.date); if (!firstInMonth[sd]){ firstInMonth[sd] = 1; return true; } return false; }) .text(function(d){ return tF(d.date); }) // place text along spiral .attr("xlink:href", "#spiral") .style("fill", function(d){ return color(d.date.getMonth()) }) .attr("startOffset", function(d){ return ((d.linePer / spiralLength) * 100) + "%"; }) var tooltip = d3.select("#chart") .append('div') .attr('class', 'tooltip'); tooltip.append('div').attr('class', 'date'); tooltip.append('div').attr('class', 'value'); svg.selectAll(".hover-rect") .on('mouseover', function(d, i) { tooltip.select('.date').html("Date: " + d.date.toDateString() + ""); tooltip.select('.value').html("Value: " + Math.round(d.value*100)/100 + ""); let selectedID = "#rect-" + i d3.select(selectedID).select(".visible-rect") .style("fill","#FFFFFF") .style("stroke","#000000") .style("stroke-width","1px"); tooltip.style('display', 'block'); tooltip.style('opacity',2); }) .on('mousemove', function(d) { tooltip.style('top', (d3.event.layerY + 10) + 'px') .style('left', (d3.event.layerX - 25) + 'px'); }) .on('mouseout', function(d) { d3.selectAll(".visible-rect") .style("fill", function(d){return color(d.group);}) .style("stroke", "none") tooltip.style('display', 'none'); tooltip.style('opacity',0); });