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

。_饼干妹妹 提交于 2019-12-12 07:57:02

问题


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.


回答1:


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+")")



回答2:


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.



来源:https://stackoverflow.com/questions/36592648/gauge-d3-js-display-value-at-the-top-of-the-needle

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!