D3.js - добавить метку вдоль дуги - PullRequest
0 голосов
/ 17 сентября 2018

Я пытаюсь реализовать этот датчик , чтобы показать целевые и фактические значения.

Здесь позиция целевого значения '45% 'задается фиксированным числом, так чтовсегда остается в верхней части шкалы, как показано на изображении ниже:

enter image description here

Как сделать так, чтобы этот ярлык динамически привязывался к началу второй дуги,примерно так:

enter image description here

Ниже приведен фрагмент текущего кода, который я использую с жестко закодированными значениями перевода:

  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('#HSFO');

  margin = {
    top: 12,
    right: 12,
    bottom: 0,
    left: 12
  };

  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");
  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() {
      perc = 17 / 20;
      var next_start = totalPercent;
      arcStartRad = percToRad(next_start);
      arcEndRad = arcStartRad + percToRad(perc / 2);
      next_start += perc / 2;


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

      perc = 1 - perc;
      arcStartRad = percToRad(next_start);
      arcEndRad = arcStartRad + percToRad(perc / 2);
      next_start += perc / 2;

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

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

      svg.append("text").attr("transform", "translate("+(width + margin.left-35) +","+ (radius - chartInset - barWidth/4.5) +")" + 'rotate('+'70'+')') 
            .attr("text-anchor", "middle").style("font-size", "12").style("font-family", "Helvetica").text('17')

    } 

1 Ответ

0 голосов
/ 19 сентября 2018

Я бы использовал текстовый путь для процентного текста и использовал бы дугу в качестве шаблона, чтобы вам не пришлось беспокоиться о ручном преобразовании текста и вычислении угла.Это означает небольшую реорганизацию ваших элементов и использование arc3 (в настоящее время не используется) в качестве пути для текста.

Общий формат для текста в пути:

<path id="path_for_text" d="M-150,1.8369701987210297e-14A150,150 0 0,1 18.799985034645633,-148.8172051971717L13.45243373590199,-106.4869779410873A107.33333333333334,107.33333333333334 0 0,0 -107.33333333333334,1.3144542310848258e-14Z"></path>
<text>
    <textPath xlink:href="#path_for_text">my text here</textPath>
</text>

, поэтому основнойИзменения, которые нам нужно будет сделать в вашем коде, - это добавление новой дуги для текста и добавление в элемент пути текста.Итак, давайте создадим соответствующий генератор дуги:

 // we want the text to be offset slightly from the outer edge of the arc, and the arc
 // itself can have identical inner and outer radius measurements
 var arc3 = d3.svg.arc()
   .outerRadius(radius - chartInset + 10)
   .innerRadius(radius - chartInset + 10)

 // add the text element and give it a `textPath` element as a child
 var arc_text = chart.append('text')
    .attr('id', 'scale10')
    .attr("font-size", 15)
    .style("fill", "#000000")

 // the textPath element will use an element with ID `text_arc` to provide its shape
 arc_text.append('textPath')
    .attr('startOffset','0%')
    .attr('xlink:href', '#text_arc' )

 // add the path with the ID `text_arc`
 chart.append('path').attr('class', "arc chart-third")
    .attr('id', 'text_arc')

В repaintGauge, рассчитаем соответствующую дугу:

 // append the path to the chart, using the arc3 constructor to generate the arc
 // these numbers will be the same as those for arc2, although I would add a little
 // padding to both start and end angles to ensure that the text doesn't wrap if it
 // is at 0% or 100%
 arc3.startAngle(arcStartRad - 0.15).endAngle(arcEndRad + 0.15);

 chart.select('id', 'text_arc')
  .attr('d', arc3)

и обновим текст:

arc_text.select('textPath')
   .text( percent + '%')

Вы можете реорганизовать свою функцию repaintGauge, чтобы сделать ее значительно проще, поскольку некоторые из фигур дуги не меняются;StartAngle arc1 всегда будет иметь значение 1,5 пи радиана, а EndAngle arc2 всегда будет 2,5 пи радиана.Это означает, что вам нужно только определить, каков ваш процент в радианах, что довольно просто: если 0% - это 1,5 Пи, а 100% - это 2,5 Пи, и вы хотите представить perc процентов, это будет p / 100 * Math.PI + 1.5 * Math.PI.

repaintGauge = function(perc) {
  var arcOffset = Math.PI * 1.5
  var current = Math.PI * perc / 100 + arcOffset

  // arc1's endAngle and arc2, arc3's endAngle can be set to `current`
  arc1.startAngle(arcOffset).endAngle(current)

  arc2.startAngle(current + padRad).endAngle(arcOffset + Math.PI)
  arc3.startAngle(current - 0.15).endAngle(arcOffset + Math.PI + 0.15)

  chart.select(".chart-first").attr('d', arc1);
  chart.select(".chart-second").attr('d', arc2);
  chart.select(".chart-third").attr('d', arc3);
  arc_text.select('textPath').text(perc + '%');
};

Вот демонстрация, показывающая текст в разных позициях и с разными значениями:

var name = "Value";

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;
  
  var percStart = 0;
  var arcOffset = Math.PI * 1.5

  // Orientation of gauge:
  totalPercent = .75;

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

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

  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) + ")");
  
  formatValue = d3.format('1%');

  var arc3 = d3.svg.arc().outerRadius(radius - chartInset + 10).innerRadius(radius - chartInset + 10),
  arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth),
  arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)

  // bind angle data directly to the chart elements
  chart.append('path').attr('class', "arc chart-first")
    .datum({ startAngle: arcOffset, endAngle: arcOffset })
    .attr('d', arc1)
  chart.append('path').attr('class', "arc chart-second")
    .datum({ startAngle: arcOffset, endAngle: arcOffset + padRad + Math.PI })
    .attr('d', arc2)
  chart.append('path').attr('class', "arc chart-third")
    .attr('id', 'text_arc')
    .datum({ startAngle: arcOffset - 0.15, endAngle: arcOffset + Math.PI + 0.15 })
    .attr('d', arc3)

  var arc_text = chart.append('text')
    .attr('id', 'scale10')
    .attr("font-size", 15)
    .style("fill", "#000000")
    .attr('text-anchor', 'start')
    
  arc_text.append('textPath')
    .attr('startOffset','0%')
    .attr('xlink:href', '#text_arc' )

  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) / 2) + ", " + ((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 + "%";
    })
    .attr('id', 'scale20')
    .attr('transform', "translate(" + ((width + margin.left) / 1.08) + ", " + ((height + margin.top) / 2) + ")")
    .attr("font-size", 15)
    .style("fill", "#000000");

  repaintGauge = function(perc) {
      var current = Math.PI * perc / 100 + arcOffset
      var t = d3.transition().duration(500)
      
      chart.select(".chart-first")
        .transition(t)
        .attrTween('d', arcEndTween(current, arc1));

      chart.select(".chart-second")
        .transition(t)
        .attrTween('d', arcStartTween(current, arc2));

      chart.select(".chart-third")
        .transition(t)
        .attrTween('d', arcStartTween(current, arc3) );

      arc_text.select('textPath')
        .text( perc.toFixed(1) + '%')


  }

  function arcStartTween(newAngle, arc) {
    return function(d) {
      var interpolate = d3.interpolate(d.startAngle, newAngle);
      return function(t) {
        d.startAngle = interpolate(t);
        return arc(d);
      };
    };
  }
  function arcEndTween(newAngle, arc) {
    return function(d) {
      var interpolate = d3.interpolate(d.endAngle, newAngle);
      return function(t) {
        d.endAngle = interpolate(t);
        return arc(d);
      };
    };
  }

    /////////


  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);

      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;
          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;
          return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
        };
      });

    };


    return Needle;

  })();

  setInterval(function() {
    repaintGauge( Math.floor(Math.random() * 100) )
  }, 1500);

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


})();
  .chart-gauge
            {
              width: 400px;
              margin: 100px auto  
             } 
            .chart-first
            {
                fill: #66AB8C;
            }
            .chart-second
            {
                fill: #ff533d;
            }
  

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


            svg {
              font: 10px sans-serif;
            }
<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    
  </head>

  <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.min.js"></script>
<div class="chart-gauge"></div>
  </body>

</html>
...