D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
noblemillie
Full window
Github gist
rotating radial chart
forked from
tezzutezzu
's block:
rotating radial chart
<!DOCTYPE html> <meta charset="utf-8"> <script src="https://d3js.org/d3.v4.min.js"></script> <style type="text/css"> body { font-family: 'Open Sans', sans-serif; font-size: 11px; font-weight: 300; fill: #242424; text-align: center; text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff; cursor: default; } .legend { font-family: 'Raleway', sans-serif; fill: #333333; } .tooltip { fill: #333333; } </style> <div class="radarChart"></div> <script type="text/javascript"> var margin = {top: 100, right: 100, bottom: 100, left: 100}, width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right, height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20); var offset = 0; var data = [ [{axis:"Jan",value: 700}, {axis:"Feb",value: 1453}, {axis:"March",value: 1300}, {axis:"April",value: 1534}, {axis:"May",value: 1534}, {axis:"June",value: 1547}, {axis:"July",value: 1100}, {axis:"August",value: 1800}, {axis:"September",value: 1700}, {axis:"October",value: 1500}, {axis:"November",value: 1000}, {axis:"December",value: 1200} ] ]; var color = d3.scaleOrdinal() .range(["#58D6C7","#CC333F","#00A0B0"]); var radarChartOptions = { w: width, h: height, margin: margin, maxValue: 0.5, levels: 5, roundStrokes: true, color: color, opacityCircles: 0.1 }; //Call function to draw the Radar chart RadarChart(".radarChart", data, radarChartOptions); /////////// Inspired by the code of alangrafu /////////// function RadarChart(id, data, options) { var cfg = { w: 600, //Width of the circle h: 600, //Height of the circle margin: {top: 20, right: 20, bottom: 20, left: 20}, //The margins of the SVG levels: 3, //How many levels or inner circles should there be drawn maxValue: 0, //What is the value that the biggest circle will represent labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed wrapWidth: 60, //The number of pixels after which a label needs to be given a new line opacityArea: 0.35, //The opacity of the area of the blob dotRadius: 4, //The size of the colored circles of each blog opacityCircles: 0.1, //The opacity of the circles of each blob strokeWidth: 2, //The width of the stroke around each blob roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed) color: d3.scaleOrdinal(d3.schemeCategory10) //Color function }; //Put all of the options into a variable called cfg if('undefined' !== typeof options){ for(var i in options){ if('undefined' !== typeof options[i]){ cfg[i] = options[i]; } }//for i }//if //If the supplied maxValue is smaller than the actual one, replace by the max in the data var maxValue = Math.max(cfg.maxValue, d3.max(data, function(i){return d3.max(i.map(function(o){return o.value;}))})); var allAxis = (data[0].map(function(i, j){return i.axis})), //Names of each axis total = allAxis.length, //The number of different axes radius = Math.min(cfg.w/2, cfg.h/2), //Radius of the outermost circle Format = d3.format(''), //Percentage formatting angleSlice = Math.PI * 2 / total; //The width in radians of each "slice" //Scale for the radius var rScale = d3.scaleLinear() .range([0, radius]) .domain([0, maxValue]); //Remove whatever chart with the same id/class was present before d3.select(id).select("svg").remove(); //Initiate the radar chart SVG var svg = d3.select(id).append("svg") .attr("width", cfg.w + cfg.margin.left + cfg.margin.right) .attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom) .attr("class", "radar"+id); //Append a g element var g = svg.append("g") .attr("transform", "translate(" + (cfg.w/2 + cfg.margin.left) + "," + (cfg.h/2 + cfg.margin.top) + ")"); ////////// Glow filter for some extra pizzazz /////////// //Filter for the outside glow var filter = g.append('defs').append('filter').attr('id','glow'), feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation','2.5').attr('result','coloredBlur'), feMerge = filter.append('feMerge'), feMergeNode_1 = feMerge.append('feMergeNode').attr('in','coloredBlur'), feMergeNode_2 = feMerge.append('feMergeNode').attr('in','SourceGraphic'); //Wrapper for the grid & axes var axisGrid = g.append("g").attr("class", "axisWrapper"); //Draw the background circles axisGrid.selectAll(".levels") .data(d3.range(1,(cfg.levels+1)).reverse()) .enter() .append("circle") .attr("class", "gridCircle") .attr("r", function(d, i){return radius/cfg.levels*d;}) .style("fill", "#CDCDCD") .style("stroke", "#CDCDCD") .style("fill-opacity", cfg.opacityCircles) .style("filter" , "url(#glow)"); //Text indicating at what % each level is axisGrid.selectAll(".axisLabel") .data(d3.range(1,(cfg.levels+1)).reverse()) .enter().append("text") .attr("class", "axisLabel") .attr("x", 4) .attr("y", function(d){return -d*radius/cfg.levels;}) .attr("dy", "0.4em") .style("font-size", "10px") .attr("fill", "#737373") .text(function(d,i) { return Format(maxValue * d/cfg.levels); }); //Create the straight lines radiating outward from the center var axis = axisGrid.selectAll(".axis") .data(allAxis) .enter() .append("g") .attr("class", "axis"); //Append the lines axis.append("line") .attr("x1", 0) .attr("y1", 0) .attr("x2", function(d, i){ return rScale(maxValue*1.1) * Math.cos(angleSlice*i - Math.PI/2 - offset); }) .attr("y2", function(d, i){ return rScale(maxValue*1.1) * Math.sin(angleSlice*i - Math.PI/2 - offset); }) .attr("class", "line") .style("stroke", "white") .style("stroke-width", "2px"); //Append the labels at each axis axis.append("text") .attr("class", "legend") .style("font-size", "11px") .attr("text-anchor", "middle") .attr("dy", "0.35em") .attr("x", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.cos(angleSlice*i - Math.PI/2 - offset); }) .attr("y", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.sin(angleSlice*i - Math.PI/2 - offset); }) .text(function(d){return d}) .call(wrap, cfg.wrapWidth); //The radial line function var radarLine = d3.radialLine() .curve(d3.curveLinearClosed) .radius(function(d) { return rScale(d.value); }) .angle(function(d,i) { return i*angleSlice - offset; }); if(cfg.roundStrokes) { radarLine.curve(d3.curveCardinalClosed); } //Create a wrapper for the blobs var blobWrapper = g.selectAll(".radarWrapper") .data(data) .enter().append("g") .attr("class", "radarWrapper"); //Append the backgrounds blobWrapper .append("path") .attr("class", "radarArea") .attr("d", function(d,i) { return radarLine(d); }) .style("fill", function(d,i) { return cfg.color(i); }) .style("fill-opacity", cfg.opacityArea) .on('mouseover', function (d,i){ //Dim all blobs d3.selectAll(".radarArea") .transition().duration(200) .style("fill-opacity", 0.1); //Bring back the hovered over blob d3.select(this) .transition().duration(200) .style("fill-opacity", 0.7); }) .on('mouseout', function(){ //Bring back all blobs d3.selectAll(".radarArea") .transition().duration(200) .style("fill-opacity", cfg.opacityArea); }); //Create the outlines blobWrapper.append("path") .attr("class", "radarStroke") .attr("d", function(d,i) { return radarLine(d); }) .style("stroke-width", cfg.strokeWidth + "px") .style("stroke", function(d,i) { return cfg.color(i); }) .style("fill", "none") .style("filter" , "url(#glow)"); //Append the circles blobWrapper.selectAll(".radarCircle") .data(function(d,i) { return d; }) .enter().append("circle") .attr("class", "radarCircle") .attr("r", cfg.dotRadius) .attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2 - offset); }) .attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2 - offset); }) .style("fill", function(d,i,j) { return cfg.color(j); }) .style("fill-opacity", 0.8); blobWrapper .append("line") .attr("stroke","red") .attr("stroke-width",3) .attr("y2", -radius) //////// Append invisible circles for tooltip /////////// //Wrapper for the invisible circles on top var blobCircleWrapper = g.selectAll(".radarCircleWrapper") .data(data) .enter().append("g") .attr("class", "radarCircleWrapper"); //Append a set of invisible circles on top for the mouseover pop-up blobCircleWrapper.selectAll(".radarInvisibleCircle") .data(function(d,i) { return d; }) .enter().append("circle") .attr("class", "radarInvisibleCircle") .attr("r", cfg.dotRadius*1.5) .attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2 - offset); }) .attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2 - offset); }) .style("fill", "none") .style("pointer-events", "all") .on("mouseover", function(d,i) { newX = parseFloat(d3.select(this).attr('cx')) - 10; newY = parseFloat(d3.select(this).attr('cy')) - 10; tooltip .attr('x', newX) .attr('y', newY) .text(Format(d.value)) .transition().duration(200) .style('opacity', 1); }) .on("mouseout", function(){ tooltip.transition().duration(200) .style("opacity", 0); }); //Set up the small tooltip for when you hover over a circle var tooltip = g.append("text") .attr("class", "tooltip") .style("opacity", 0); function animate() { offset+=angleSlice; blobWrapper.select(".radarArea").transition().attr('transform', 'rotate('+(-offset*180/Math.PI) + ')') blobWrapper.select(".radarStroke").transition().attr('transform', 'rotate('+(-offset*180/Math.PI) + ')') d3.selectAll(".radarInvisibleCircle").transition().attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2 - offset); }).transition().attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2 - offset); }) d3.selectAll(".radarCircle").transition().attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2 - offset); }).attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2 - offset); }) d3.selectAll(".legend tspan").transition().attr("x", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.cos(angleSlice*i - Math.PI/2 - offset); }).attr("y", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.sin(angleSlice*i - Math.PI/2 - offset); }) // current index var currentAngle = (offset) % (Math.PI*2); var currentIndex = Math.floor( (currentAngle/(Math.PI*2)) * data[0].length); var currentValue = data[0][currentIndex].value; console.log("currentValue",currentValue); } // setInterval(animate, 1000) //Taken from https://bl.ocks.org/mbostock/7555321 //Wraps SVG text function wrap(text, width) { text.each(function() { var text = d3.select(this), words = text.text().split(/\s+/).reverse(), word, line = [], lineNumber = 0, lineHeight = 1.4, // ems y = text.attr("y"), x = text.attr("x"), dy = parseFloat(text.attr("dy")), tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em"); while (word = words.pop()) { line.push(word); tspan.text(line.join(" ")); if (tspan.node().getComputedTextLength() > width) { line.pop(); tspan.text(line.join(" ")); line = [word]; tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); } } }); }//wrap }//RadarChart </script>
https://d3js.org/d3.v4.min.js