function clockFace(parentSvg, topology, data, colormap) { // settings var outerRadius = 380, innerRadius = 205, gap = 2, nCircle = 5, colors = colormap.range(), startYear = 1751, endYear = 2011, yearLapse = 300; //initial values this.ids = [], this.sortIds = [-1], this.layers = d3.map([]); this.data = data; this.colormap = colormap; var _this = this; // scales var angle = d3.scale.linear() .range([0, 2 * Math.PI]) .domain([startYear, endYear]) .clamp(true); var radius = d3.scale.linear() .clamp(true); var area = d3.svg.area.radial() .interpolate("cardinal-closed") .angle(function(d) {return angle(d.year); }) .outerRadius(function(d) {return radius(d.value)}); // groups; reserver place for clock var clock = parentSvg.append("g") .attr("id","clock-group") .attr("transform", "translate(" + parseInt(parentSvg.style("width").slice(0, -2)) / 2 + "," + parseInt(parentSvg.style("height").slice(0, -2)) / 2 + ")"); var refer = parentSvg.append("g") .attr("id","reference-group") .attr("transform", "translate(" + parseInt(parentSvg.style("width").slice(0, -2)) / 2 + "," + parseInt(parentSvg.style("height").slice(0, -2)) / 2 + ")"); // buld reference layer this.buildReference = function () { // invert mouseover position var invertCord = function (cord) { var a = Math.atan(-cord[0]/cord[1]); if (cord[1]>0) { return a + Math.PI; } else { if (cord[0]*cord[1]>0) { return a + 2*Math.PI; } else { return a; } } }; // add invisible arc for reference var baseArc = d3.svg.arc() .innerRadius(innerRadius) .outerRadius(outerRadius) .startAngle(0) .endAngle(2 * Math.PI)(outerRadius - innerRadius); var base = refer .append("path") .attr("d", baseArc) .attr("fill", "transparent"); //add ruler var ruler = refer .append("path") .attr("fill", "black") .attr('stroke', "black") .attr('stroke-width', 1) .style("opacity", 0); // add year var rulerYear = refer .append("text") .attr("text-anchor", "middle") .style("opacity", 0); base.on('mousemove', function () { // clean previous refer.selectAll(".clock-hover").remove(); // get mouse postion and angle cord = d3.mouse(this); r_angle = invertCord(cord); r_year = Math.round(angle.invert(r_angle)); ruler.style("opacity", 1) .attr("d", d3.svg.line.radial()([[innerRadius, angle(r_year)], [outerRadius, angle(r_year)]])); // add year rulerYear.style("opacity", 1) .attr("transform", function(d) { return "translate(" + Math.sin(angle(r_year))*outerRadius*1.01 + ", " + (- Math.cos(angle(r_year))*outerRadius*1.01) + ")" + "rotate(" + angle(r_year)*180/Math.PI + ")" }) .text(r_year); // add name and value _this.sortIds.forEach(function(id, index) { var bandwidth = (outerRadius - innerRadius - gap*_this.ids.length)/(_this.ids.length+1), r = innerRadius + index*bandwidth + index*gap; refer .append("text") .attr("class", "clock-hover") .attr("id", "hover-year") .attr("transform", function(d) { return "translate(" + Math.sin(angle(r_year))*r*1.01 + ", " + (- Math.cos(angle(r_year))*r*1.01) + ")" + "rotate(" + angle(r_year)*180/Math.PI + ")" }) .text(_this.data.get(id).name) .attr("text-anchor", "end"); refer .append("text") .attr("class", "clock-hover") .attr("id", "hover-year") .attr("transform", function(d) { return "translate(" + Math.sin(angle(r_year))*r*1.01 + ", " + (- Math.cos(angle(r_year))*r*1.01) + ")" + "rotate(" + angle(r_year)*180/Math.PI + ")" }) .text(get_value_str(_this.data, id, r_year)) .attr("text-anchor", "start"); }); }) .on('mouseout', function () { refer.selectAll(".clock-hover").remove(); ruler.style("opacity", 0); rulerYear.style("opacity", 0); }) .on('click', function () { cord = d3.mouse(this); r_angle = invertCord(cord); r_year = Math.round(angle.invert(r_angle)); startTime = Date.now(); timerStartYear = r_year; }); } // draw _this.data this.draw = function() { var bandwidth = (outerRadius - innerRadius - gap*_this.ids.length)/(_this.ids.length+1); _this.sortIds.forEach(function(id, index) { var d = _this.data.get(id), name = d.name, y0 = innerRadius + index*(bandwidth+gap); area.innerRadius(y0); // compute _this.data if not exists if (!_this.layers.has(id)) { var dataTemp = []; for (var y = startYear; y <= endYear; y++) { if (d.data.has(y)) { dataTemp.push({year: y, value: d.data.get(y).value}); } else { dataTemp.push({year: y, value: 0}); } }; _this.layers.set(id, {data: dataTemp, name: d.name}); } // create or update colors.forEach(function(color) { radius.range([y0, y0 + bandwidth]) .domain(_this.colormap.invertExtent(color)); node = d3.select("#c"+id+"-c"+color.slice(1)); if (node.empty()) { clock.append("path") .attr("id", "c"+id+"-c"+color.slice(1)) .attr("d", area(_this.layers.get(id).data)) .attr("class", "layers-"+id) .attr("fill", color); } else { node.transition(800).attr("d", area(_this.layers.get(id).data)); } }); }); } // handle id streamed in by map this.updateId = function(id) { // if duplicate if (_this.ids.indexOf(id)>-1) return; // add and sort new added id if (id!=-1) _this.ids.push(id); if (_this.ids.length>(nCircle-1)) { // delete layer dl = _this.ids.shift(); d3.selectAll(".layers-"+dl).remove(); }; _this.sortIds = $.extend([], _this.ids); _this.sortIds.push(-1); _this.sortIds.sort(function(id1, id2){ return _this.data.get(id1).data.get(endYear).value - _this.data.get(id2).data.get(endYear).value} ); _this.draw(); _this.buildReference(); } // update hand and map by year this.updateYear = function(year) { refer.selectAll(".clock-hand").remove(); // add ruler refer .append("path") .attr("id", "hand-ruler") .attr("class", "clock-hand") .attr("fill", "black") .attr('stroke', "black") .attr('stroke-width', 1) .attr("d", d3.svg.line.radial()([[innerRadius, angle(year)], [outerRadius, angle(year)]])); // add year refer .append("text") .attr("class", "clock-hand") .attr("id", "hand-year") .attr("transform", function(d) { return "translate(" + Math.sin(angle(year))*outerRadius*1.01 + ", " + (- Math.cos(angle(year))*outerRadius*1.01) + ")" + "rotate(" + angle(year)*180/Math.PI + ")" }) .text(year) .attr("text-anchor", "middle"); this.centerMap.updateYear(year); }; this.updateData = function(data, colormap) { _this.layers = d3.map([]); _this.data = data; _this.colormap = colormap; _this.centerMap.data = data; _this.centerMap.colormap = colormap; _this.centerMap.updateYear(_this.currentYear); _this.draw(); }; // draw initial state this.draw(); this.buildReference(); // construct map in the center this.centerMap = new clockMap(parentSvg, topology, _this.data, colormap, this.updateId); var startTime = Date.now(), timerStartYear = 1800; var makeCallback = function() { // returning a new callback function each time return function() { var dt = Date.now() - startTime; _this.currentYear = timerStartYear + Math.round(dt/yearLapse); if ( _this.currentYear<=endYear) { _this.updateYear( _this.currentYear); d3.timer(makeCallback(), yearLapse); return true; } else { return false; } }; }; d3.timer(makeCallback(), yearLapse); }