使用Path将SVG半圆弧转换为全圆弧

时间:2017-07-03 02:24:56

标签: angularjs svg progress-bar progress

我试图将这个半圆弧转换成一个完整的圆圈,或者至少是一个几乎完整的圆圈,底部有一个小的断开凹口。

我尝试将180更改为360。

我也尝试更改0,0,1参数来改变弧的旋转,但这似乎有旋转/翻转效果,而不是拉长弧。

Gauge widget

此代码在AngularJS中,但不应减损解决此问题所涉及的数学。

此示例是根据Pluralsight的可扩展动态图表& amp; amp;图表使用AngularJS和SVG课程。

Plunker Link

gauge.component.js

angular.module('app.gauge', []);
angular.module('app.gauge')
  .component('gauge', {
    require: {
      parent: '^appMain'
    },
    bindings: {
      centerX: '=',
      centerY: '=',
      radius: '<',
      maxValue: '<',
      gradientInterval: '<',
      currentValue: '<',
      gradientsOffset: '<'
    },
    controller: GaugeCtrl,
    controllerAs: 'gauge',
    templateUrl: 'gauge.html',
    bindToController: true
  });

function GaugeCtrl(d3, $scope) {
  var gauge = this;
  // preset defaults
  gauge.specs = {
    centerX: 0, // pass in 300
    centerY: 0, // pass in 300
    radius: 0, // pass in 200
    maxValue: 0, // pass in 180
    gradientInterval: 0,
    currentValue: 0, // 45 passed in
    gradients: [],
    gradientsOffset: 0, // 10 
    maxValueCoordinates: null
  };

  // pass in values from component passed-in values
  function initPassedInValues() {
    // grab all props from controller
    var keys = Object.keys(gauge);
    // if ctrl key is in gauge.specs object, copy over to specs
    keys.forEach(function(key,idx){
      if (gauge.specs.hasOwnProperty(key)) {
        gauge.specs[key] = gauge[key];
      }
    });   
  }

  // passedin padding
  gauge.$onInit = function() {
    initPassedInValues(); // process passed-in values from component
    initGauge();
    initGradients();
  }

  gauge.$postLink = function() {

  }

  // function defs
  var getCoordinatesForAngle = function(centerX, centerY, radius, angleInDegrees) {
    var angleInRadians = ((angleInDegrees - 180.0) * Math.PI / 180.0);
    return {
      x: parseInt(centerX + (radius * Math.cos(angleInRadians))),
      y: parseInt(centerY + (radius * Math.sin(angleInRadians)))
    };
  };

  // calc background and value arc
    // radius as param - diff for circle vs. text path 
  var getArcPathForAngle = function(startingAngle, endingAngle, radius){
    var startingPt = getCoordinatesForAngle(
        gauge.specs.centerX,
        gauge.specs.centerY,
        radius,
        startingAngle);

    var endingPt = getCoordinatesForAngle(
        gauge.specs.centerX,
        gauge.specs.centerY,
        radius,
        endingAngle);

    return ["M", startingPt.x, startingPt.y, "A", radius, radius, 0, 0, 1, endingPt.x, endingPt.y].join(' ');
  };

  // textPath ticks
  function initGradients() {
    // use < instead of <= so doesn't show last value, taken care of with fixLastGradientTextValue fn
    for (var value = 0, offset = 0; value < gauge.specs.maxValue; value += gauge.specs.gradientInterval, offset += 100/18) {
      gauge.specs.gradients.push({value: value, offset: offset});
    }
  }

  function initGauge() {
    // draw background
    gauge.background = getArcPathForAngle(0, gauge.specs.maxValue, gauge.specs.radius);
    // draw gauge value
    gauge.value = getArcPathForAngle(0, gauge.specs.currentValue, gauge.specs.radius);
    // draw gradient tick values
    gauge.gradients = getArcPathForAngle(0, gauge.specs.maxValue, gauge.specs.radius + gauge.specs.gradientsOffset);
    // fix last text value and rotate
    gauge.specs.maxValueCoordinates = getCoordinatesForAngle(
      gauge.specs.centerX,
      gauge.specs.centerY,
      gauge.specs.radius + gauge.specs.gradientsOffset,
      gauge.specs.maxValue);
  }

  // additional watcher for currentValue
  $scope.$watch('gauge.specs.currentValue', function(oldValue, newValue) {
    initGauge();
  }, true);
}

gauge.html

<div class="svg-container gauge">
  <!-- gauge -->
  <svg class="svg-scalable" viewBox="0 0 600 400" preserveAspectRation="xMidYMid meet">

    <g>
      <!-- background -->
      <path id="gaugeBackground" ng-attr-d="{{ gauge.background }}" stroke-width="10" stroke="black" fill="none"/>

      <!-- gauge value -->
      <path ng-attr-d="{{ gauge.value }}" stroke-width="10" stroke="#2a9fbc" fill="none"/>

      <!-- invisible arc for textPath to follow, slightly larger -->
      <path id="gradients" ng-attr-d="{{ gauge.gradients }}" stroke width="0" fill="none" />

      <!-- gradient ticks -->
      <text ng-repeat="gradient in gauge.specs.gradients" dx="0" dy="0" text-anchor="middle" style="font: bold large arial">
        <textPath xlink:href="#gradients" startOffset="{{ gradient.offset }}%">
          {{ gradient.value }}
        </textPath>
      </text>

      <!-- Fix for last tick-->
      <text dx="{{ gauge.specs.maxValueCoordinates.x }}" dy="{{ gauge.specs.maxValueCoordinates.y }}" text-anchor="middle" style="font: bold large arial" transform="rotate(90, {{ gauge.specs.maxValueCoordinates.x}}, {{ gauge.specs.maxValueCoordinates.y }} )">
       {{ gauge.specs.maxValue }}
      </text>

      <text dx="50%" dy="50%" text-anchor="middle" 
    alignment-baseline="hanging" style="font-size: 7rem">
        {{ gauge.specs.currentValue }}
      </text> 
    </g>
  </svg>
</div>

app-main.html - 传入默认值

...
<!-- Gauge component -->
<gauge center-x="300"
  center-y="300"
  radius="200"
  max-value="180"
  gradient-interval="10"
  current-value="45"
  gradients-offset="10"> 
</gauge>
...

1 个答案:

答案 0 :(得分:2)

好的,我心软了,并为你做了修改:)

原始测量仪的“扫描角度”和最大测量值都硬连线到180.如果您尝试更改max-value属性,它会中断。

我的版本修复了该问题,并引入了一个新属性gauge-sweep,用于设置仪表覆盖的角度(最高360度)。您也可以单独设置max-value(例如100)。

代码的主要更改包括以下三个功能:

  var getCoordinatesForAngle = function(centerX, centerY, radius, angleInDegrees) {
    var angleInRadians = ((angleInDegrees - 90 - gauge.specs.gaugeSweep/2) * Math.PI / 180.0);
    return {
      x: parseInt(centerX + (radius * Math.cos(angleInRadians))),
      y: parseInt(centerY + (radius * Math.sin(angleInRadians)))
    };
  };

这需要从仪表的起始角度在左侧(西侧)进行修改。我们现在根据gaugeSweep值更改它。

  // calc background and value arc
  // radius as param - diff for circle vs. text path 
  // Divided into three arcs to ensure accuracy over the largest possible range (360deg)
  var getArcPathForAngle = function(startingAngle, endingAngle, radius, maxAngle) {
    var startingPt = getCoordinatesForAngle(
        gauge.specs.centerX,
        gauge.specs.centerY,
        radius,
        startingAngle);
    var midPt1 = getCoordinatesForAngle(
        gauge.specs.centerX,
        gauge.specs.centerY,
        radius,
        (startingAngle + endingAngle)/3);
    var midPt2 = getCoordinatesForAngle(
        gauge.specs.centerX,
        gauge.specs.centerY,
        radius,
        (startingAngle + endingAngle)*2/3);
    var endingPt = getCoordinatesForAngle(
        gauge.specs.centerX,
        gauge.specs.centerY,
        radius,
        endingAngle);

    return ["M", startingPt.x, startingPt.y,
            "A", radius, radius, 0, 0, 1, midPt1.x, midPt1.y,
            "A", radius, radius, 0, 0, 1, midPt2.x, midPt2.y,
            "A", radius, radius, 0, 0, 1, endingPt.x, endingPt.y].join(' ');
  };

路径弧(A)命令如果覆盖180度或更多度,则会有一些不准确的倾向。为了避免这种情况,我们现在使用三个圆弧作为测量仪,这样我们就可以安全地覆盖任何高达360度的扫描。

  // textPath ticks
  function initGradients() {
    // use < instead of <= so doesn't show last value, taken care of with fixLastGradientTextValue fn
    var offsetStep = (gauge.specs.gradientInterval * 100) / gauge.specs.maxValue;
    for (var value = 0, offset = 0; value < gauge.specs.maxValue; value += gauge.specs.gradientInterval, offset += offsetStep) {
      gauge.specs.gradients.push({value: value, offset: offset});
    }
  }

此功能计算仪表的刻度位置。期望maxValue 180度是硬连线的。它需要修复。

app-main.htmlgauge.html也有一些细微的变化。

My updated plunkr is here