How To Enable Target Value and Actual Value In D3 Gauge Chart

Bala Murugan
 
Technology Specialist
January 15, 2017
 
Rate this article
 
Views
13050

This article is all about adding a target marker on a d3 gauge chart. You might have come across various JavaScript chart libraries with gauge charts, out of which, D3 Js is very popular. We also knew that D3 provides angular js directives (NVD3) which enable us to populate charts for Angular Js applications in an easy manner.

But the gauge charts available today comes with option only to show a single value (actual value) as shown below.

clip_image001

In this article we will see, how we can populate the gauge chart with two indicators that is Actual and Target Values like below.

clip_image002

To accomplishing this we have to create the following SVG items and include them to the gauge chart.

1. A SVG Text item to show the Actual value at the centre of the circle instead of the needle.

 actualText = chart.append('text')
                 .attr('id', "Value")
                     .attr("font-size", 16)
                     .attr("text-anchor", "middle")
                     .attr("dy", ".5em")
                     .style("fill", '#0000FF')
                     .attr('class', 'needle').attr('cx', 0).attr('cy', 0).attr('r', radiusValue);
 actualText.text(formatValue(actualValue))

The code above will create a text with actual value which will be rendered at the middle of the gauge chart instead of a needle.

2. A SVG path item (red strip) to move along with the radius to show the target value. To do that we can use the following code to draw it between inner and outer radius of the gauge chart.

 //To Create the path object in the chart
 chart.append('path').attr('class', "arc chart-target");
 
 //To Create attributes to the path(arc3)
 var margin = {
         top: 0,
         right: 0,
         bottom: 0,
         left: 0
     };
 
     var width = el[0][0].offsetWidth - margin.left - margin.right;
     var height = width;
     var radius = Math.min(width, height) / 3;
     var barWidth = 40 * width / 300;
 arc3 = d3.svg.arc().outerRadius(radius - 10).innerRadius(radius - 10 - barWidth)
 
         var next_start1 = totalPercent;
         var arcStartRad = percToRad(next_start1);
         var arcEndRad = arcStartRad + percToRad(targetPerc / 2);
         next_start1 += targetPerc / 2;
 
         arc3.startAngle(arcEndRad - padRad).endAngle(arcEndRad);
 //Adding the created object arc3 to the d3 chart.
 chart.select(".chart-target").attr('d', arc3);

3. Finally a SVG text which will also move along with the radius to show the target value (in this case 50 is the target value). The below code will create the text we want for the target value.

 var targetText = chart.append("text")
 .attr('id', "Value")
 .attr("font-size", 16)
 .attr("text-anchor", "middle")
 .attr("dy", ".5em")
 .style("fill", '#0000FF');
 var thetaRad = percToRad(targetValue / 2);
 var textX = -(self.len + 5) * Math.cos(thetaRad);
 var textY = -(self.len + 5) * Math.sin(thetaRad);
 targetText.text(formatValue(targetValue))
 .attr('transform', "translate(" + textX + "," + textY + ")")

textX and textY values that are mentioned in the above code are the calculated values to place the target text on the outer radius of the chart.

Find the Complete html file code and the JavaScript file code below. Happy coding !!!

HTML FILE

 <!DOCTYPE html>
 <html>
 	<head>
         <title>G C</title>
 		<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
 		<style type="text/css">
   			.chart-gauge
 			{
 			  width: 400px;
 			  margin: 100px auto  
 			 } 
 			.chart-filled
 			{
 				fill: steelblue;
 			}
 			.chart-empty
 			{
 				fill: #DFDFDF;
 			}
 
             .chart-target
 			{
 				fill: #ff0000;
 			}
 		
 			.needle, .needle-center
 			{
 				fill: #fff;
 			}
 
 			svg {
 			  font: 10px sans-serif;
 			}
 
 		</style>
 
 	</head>
 	<body>
 
 		<div class="chart-gauge"></div>
 		<script type="text/javascript" src="d3_gauge.js"></script>
 		<script type="text/javascript">
 			//setInterval(function(){
 			//    needle.moveTo(Math.random(), Math.random());
             //}, 5000);
             needle.moveTo(0.25, 0.5);
 		</script>
 	</body>
 
 </html>

JAVASCRIPT FILE (d3_gauge.js)

 var needle;
 
 (function () {
     var barWidth, chart, chartInset, degToRad, repaintGauge,
         height, margin, numSections, padRad, percToDeg, percToRad,
         percent, radius, sectionIndx, svg, totalPercent, width,
             targetText, actualText, formatValue, k;
 
     percent = .65;
     numSections = 1;
     sectionPerc = 1 / numSections / 2;
     padRad = 0.025;
     chartInset = 10;
 
     // Orientation of gauge:
     totalPercent = .75;
 
     el = d3.select('.chart-gauge');
 
     margin = {
         top: 0,
         right: 0,
         bottom: 0,
         left: 0
     };
 
     width = el[0][0].offsetWidth - margin.left - margin.right;
     height = width;
     radius = Math.min(width, height) / 3;
     barWidth = 40 * width / 300;
 
 
     /*
       Utility methods 
     */
     percToDeg = function (perc) {
         return perc * 360;
     };
 
     percToRad = function (perc) {
         return degToRad(percToDeg(perc));
     };
 
     degToRad = function (deg) {
         return deg * Math.PI / 180;
     };
 
     // Create SVG element
     svg = el.append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom);
 
     // Add layer for the panel
     chart = svg.append('g').attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 2) + ")");
     chart.append('path').attr('class', "arc chart-filled");
     chart.append('path').attr('class', "arc chart-empty");
     chart.append('path').attr('class', "arc chart-target");
 
     targetText = chart.append("text")
                     .attr('id', "Value")
                     .attr("font-size", 16)
                     .attr("text-anchor", "middle")
                     .attr("dy", ".5em")
                     .style("fill", '#0000FF');
 
     actualText = chart.append('text')
                 .attr('id', "Value")
                     .attr("font-size", 16)
                     .attr("text-anchor", "middle")
                     .attr("dy", ".5em")
                     .style("fill", '#0000FF')
                     .attr('class', 'needle').attr('cx', 0).attr('cy', 0).attr('r', this.radius);
 
 
     formatValue = d3.format('1%');
 
     arc3 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
     arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
     arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
 
     repaintGauge = function (actualPerc, targetPerc) {
         
         var next_start = totalPercent;
         arcStartRad = percToRad(next_start);
         arcEndRad = arcStartRad + percToRad(actualPerc / 2);
         next_start += actualPerc / 2;
 
         arc1.startAngle(arcStartRad).endAngle(arcEndRad);
 
         var next_start1 = totalPercent;
         arcStartRad = percToRad(next_start1);
         arcEndRad = arcStartRad + percToRad(targetPerc / 2);
         next_start1 += targetPerc / 2;
 
         arc3.startAngle(arcEndRad - padRad).endAngle(arcEndRad);
 
 
 
         arcStartRad = percToRad(next_start);
         arcEndRad = arcStartRad + percToRad((1 - actualPerc) / 2);
 
         arc2.startAngle(arcStartRad).endAngle(arcEndRad);
         var fillColor = "#CD6660";
         if (actualPerc >= targetPerc) {
             fillColor = "#94BFF5";
         }
 
         chart.select(".chart-filled").style("fill", fillColor).attr('d', arc1);
         chart.select(".chart-empty").attr('d', arc2);
         chart.select(".chart-target").attr('d', arc3);
 
     }
 
 
     var Needle = (function () {
         /** 
           * Helper function that returns the `d` value
           * for moving the needle
         **/
         var recalcPointerPos = function (perc) {
             var centerX, centerY, leftX, leftY, rightX, rightY, thetaRad, topX, topY;
             thetaRad = percToRad(perc / 2);
             centerX = 0;
             centerY = 0;
             topX = centerX - this.len * Math.cos(thetaRad);
             topY = centerY - this.len * Math.sin(thetaRad);
             leftX = centerX - this.radius * Math.cos(thetaRad - Math.PI / 2);
             leftY = centerY - this.radius * Math.sin(thetaRad - Math.PI / 2);
             rightX = centerX - this.radius * Math.cos(thetaRad + Math.PI / 2);
             rightY = centerY - this.radius * Math.sin(thetaRad + Math.PI / 2);
             return "M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;
         };
 
         function Needle(el) {
             this.el = el;
             this.len = width / 3;
             this.radius = this.len / 6;
         }
 
         Needle.prototype.render = function () {
             return this.el;
         };
 
         Needle.prototype.moveTo = function (perc, perc2) {
             var self,
                 oldValue = this.perc || 0;
 
             this.perc = perc;
             self = this;
 
             // Reset pointer position
             this.el.transition().delay(100).ease('quad').duration(200).select('.needle').tween('reset-progress', function () {
                 return function (percentOfPercent) {
                     var progress = (1 - percentOfPercent) * oldValue;
 
                     repaintGauge(progress, perc2);
                     return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
                 };
             });
 
             this.el.transition().delay(300).ease('bounce').duration(1500).select('.needle').tween('progress', function () {
                 return function (percentOfPercent) {
                     var progress = percentOfPercent * perc;
 
                     repaintGauge(progress, perc2);
 
                     var thetaRad = percToRad(perc2 / 2);
                     var textX = -(self.len + 5) * Math.cos(thetaRad);
                     var textY = -(self.len + 5) * Math.sin(thetaRad);
 
                     actualText.text(formatValue(perc))
                     
 
                     targetText.text(formatValue(perc2))
                     .attr('transform', "translate(" + textX + "," + textY + ")")
 
                     return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
                 };
             });
 
         };
 
         return Needle;
 
     })();
 
     needle = new Needle(chart);
     needle.render();
 
     needle.moveTo(percent);
 
 })();

Author Info

Bala Murugan
 
Technology Specialist
 
Rate this article
 
Bala has been working in IT industry for over 10+ years. He has completed B.Tech in Information Technology. He has hands on experience in developing Asp.net, Angular Js applications. Specialties ...read more
 

Leave a comment