代码之家  ›  专栏  ›  技术社区  ›  curtybear

使用路径将SVG半圆弧转换为全圆弧

  •  2
  • curtybear  · 技术社区  · 8 年前

    我试着把180改成360。

    Gauge widget

    这段代码是用AngularJS编写的,但不应影响解决这一问题所涉及的数学。

    这个例子是从Pluralsight的可扩展动态图&使用AngularJS和SVG课程的图表。

    Plunker Link

    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>
    

    ...
    <!-- 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 回复  |  直到 5 年前
        1
  •  2
  •   Paul LeBeau    8 年前

    好吧,我让步了,为你做了修改:)

    原始仪表的“扫描角度”和最大仪表值均硬连线为180。如果您尝试更改 max-value

    我的版本修复了这个问题,并引入了一个新属性 gauge-sweep 设置仪表覆盖的角度(最高360度)。您还可以设置 最大值 独立(如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

      // 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

    也有一些小的变化 app-main.html gauge.html

    My updated plunkr is here