derOtterDieb
derOtterDieb

Reputation: 555

Gauge D3.js display value at the top of the needle

I'm kinda new to D3.js, and I managed to create a gauge with what I found on the Internet. But I couldn't find any gauge showing the current value at the top of the needle. Something like this : what I want

Obviously I'd like the value to follow the needle. I tried to add "text" attribute to the needle itself, but it didn't work.

Here's a codepen link : http://codepen.io/kazu_codepen/pen/wGmGjv?editors=1010

Here's my js code :

// data which need to be fetched

var name = "azerty";

var value = 17;


var gaugeMaxValue = 100; 

// data to calculate 
var percentValue = value / gaugeMaxValue; 

////////////////////////

var needleClient;



(function(){

var barWidth, chart, chartInset, degToRad, repaintGauge,
    height, margin, numSections, padRad, percToDeg, percToRad, 
    percent, radius, sectionIndx, svg, totalPercent, width;



  percent = percentValue;

  numSections = 1;
  sectionPerc = 1 / numSections / 2;
  padRad = 0.025;
  chartInset = 10;

  // Orientation of gauge:
  totalPercent = .75;

  el = d3.select('.chart-gauge');

  margin = {
    top: 20,
    right: 20,
    bottom: 30,
    left: 20
  };

  width = el[0][0].offsetWidth - margin.left - margin.right;
  height = width;
  radius = Math.min(width, height) / 2;
  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-first");
  chart.append('path').attr('class', "arc chart-second");
  chart.append('path').attr('class', "arc chart-third");


  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 () 
  {
    perc = 0.5;
    var next_start = totalPercent;
    arcStartRad = percToRad(next_start);
    arcEndRad = arcStartRad + percToRad(perc / 3);
    next_start += perc / 3;


    arc1.startAngle(arcStartRad).endAngle(arcEndRad);

    arcStartRad = percToRad(next_start);
    arcEndRad = arcStartRad + percToRad(perc / 3);
    next_start += perc / 3;

    arc2.startAngle(arcStartRad + padRad).endAngle(arcEndRad);

    arcStartRad = percToRad(next_start);
    arcEndRad = arcStartRad + percToRad(perc / 3);

    arc3.startAngle(arcStartRad + padRad).endAngle(arcEndRad);

    chart.select(".chart-first").attr('d', arc1);
    chart.select(".chart-second").attr('d', arc2);
    chart.select(".chart-third").attr('d', arc3);


  }
/////////

    var dataset = [{metric:name, value: value}]

    var texts = svg.selectAll("text")
                .data(dataset)
                .enter();

    texts.append("text")
         .text(function(){
              return dataset[0].metric;
         })
         .attr('id', "Name")
         .attr('transform', "translate(" + ((width + margin.left) / 6) + ", " + ((height + margin.top) / 1.5) + ")")
         .attr("font-size",25)
         .style("fill", "#000000");


    texts.append("text")
         .text(function(){
            return dataset[0].value;
         })
         .attr('id', "Value")
         .attr('transform', "translate(" + ((width + margin.left) / 1.4) + ", " + ((height + margin.top) / 1.5) + ")")
         .attr("font-size",25)
         .style("fill", "#000000");




    texts.append("text")
        .text(function(){
            return 0;
        })
        .attr('id', 'scale0')
        .attr('transform', "translate(" + ((width + margin.left) / 100 ) + ", " + ((height + margin.top) / 2) + ")")
        .attr("font-size", 15)
        .style("fill", "#000000");

    texts.append("text")
        .text(function(){
            return gaugeMaxValue/2;
        })
        .attr('id', 'scale10')
        .attr('transform', "translate(" + ((width + margin.left) / 2.15 ) + ", " + ((height + margin.top) / 30) + ")")
        .attr("font-size", 15)
        .style("fill", "#000000");


    texts.append("text")
        .text(function(){
            return gaugeMaxValue;
        })
        .attr('id', 'scale20')
        .attr('transform', "translate(" + ((width + margin.left) / 1.03 ) + ", " + ((height + margin.top) / 2) + ")")
        .attr("font-size", 15)
        .style("fill", "#000000");

  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 / 2.5;
      this.radius = this.len / 8;
    }

    Needle.prototype.render = function() {
      this.el.append('circle').attr('class', 'needle-center').attr('cx', 0).attr('cy', 0).attr('r', this.radius);

        ///////
        /**
        *
        * I tried to add text here
        *
        */
        ///////

      return this.el.append('path').attr('class', 'needle').attr('id', 'client-needle').attr('d', recalcPointerPos.call(this, 0));


    };

    Needle.prototype.moveTo = function(perc) {
      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);
          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);
          return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
        };
      });

    };


    return Needle;

  })();



  needle = new Needle(chart);
  needle.render();
  needle.moveTo(percent);   

})();

And here's my html code :

<!DOCTYPE html>
<html>
    <head>
        <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
        <style type="text/css" src="gauge.css">
            .chart-gauge
            {
              width: 400px;
              margin: 100px auto  
             } 
            .chart-first
            {
                fill: #9FBD35;
            }
            .chart-second
            {
                fill: #F2BA3A;
            }
            .chart-third
            {
                fill: #FB3033;
            }

            .needle, .needle-center
            {
                fill: #000000;
            }
            .text {
                color: "#112864";
                font-size: 16px;
            }


            svg {
              font: 10px sans-serif;
            }


        </style>

    </head>
    <body>


        <div class="chart-gauge"></div>



        <script type="text/javascript" src="./gaugeClient.js"></script>
        <script type="text/javascript" src="./labels.js"></script>
        <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    </body>

</html>

If anyone could help, I've spent few days trying unsuccessfully. Thanks.

Upvotes: 7

Views: 12720

Answers (2)

meetamit
meetamit

Reputation: 25157

Here's an updated codepen

Changes made:

1) As part of initialization, create element valueText

valueText = chart.append("text")
                .attr('id', "Value")
                .attr("font-size",16)
                .attr("text-anchor","middle")
                .attr("dy",".5em")
                .style("fill", '#000000');

2) Also at initialization, create formatValue, to format percent

formatValue = d3.format('1%');

3) At each frame of the transition, compute the position of the text. It adds an offset of 45 units to self.len (the needle's length) to move the text outwards.

var thetaRad = percToRad(progress / 2);
var textX = - (self.len + 45) * Math.cos(thetaRad);
var textY = - (self.len + 45) * Math.sin(thetaRad);

4) Translate valueText based on the computed position and update its text from current progress

valueText.text(formatValue(progress))
  .attr('transform', "translate("+textX+","+textY+")")

Upvotes: 8

derOtterDieb
derOtterDieb

Reputation: 555

Well, I found out something waiting for a better solution.

I'm using this :

var trX = 180 - 210 * Math.cos(percToRad(percent / 2)); 
var trY = 195 - 210 * Math.sin(percToRad(percent / 2));
// (180, 195) are the coordinates of the center of the gauge.

displayValue = function() {
                texts.append("text")
                    .text(function(){
                        return dataset[0].value;
                    })
                    .attr('id', "Value")
                    .attr('transform', "translate(" + trX + ", " + trY+ ")")
                    .attr("font-size",18)
                    .style("fill", '#FB0006');
            }

[...] then calling this :

setTimeout(displayValue, 1350);

So that it doesn't appear right away, but when the needle has stopped at its position.

It works, in the way that the value appears on the top of the needle, but I still have two problems. I'd really like it to follow the needle all the way long during the needle animation when it goes to its value, and I'd like something more general than using the coordinates I found out by calculating, is there a function which can give me the center of the gauge for example ?

I edited the pen given above to that version of the gauge. (http://codepen.io/kazu_codepen/pen/wGmGjv?editors=1010)

Anyway, this solution doesn't really fit my expectations, but I'll use it till I can find something better.

Upvotes: 0

Related Questions