使用 Path 将 SVG 半圆弧转换为全圆弧
Convert SVG semi-circle arc to full-circle arc using Path
我正在尝试将这个半圆弧转换成一个完整的圆,或者至少是一个几乎完整的圆,底部有一个不相连的小缺口。
我试过将 180 度更改为 360 度。
我也试过改变 0, 0, 1 参数来改变圆弧的旋转,但是那似乎有旋转/翻转效果,而不是拉长圆弧。
此代码在 AngularJS 中,但不应影响解决此问题所涉及的数学。
此示例修改自 Pluralsight 的使用 AngularJS 和 SVG 课程的可扩展动态图形和图表。
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>
...
好吧,我心软了,给你修改了:)
原始量表的 "sweep angle" 和最大量表值都硬连线到 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.html
和 gauge.html
也有细微的变化。
我正在尝试将这个半圆弧转换成一个完整的圆,或者至少是一个几乎完整的圆,底部有一个不相连的小缺口。
我试过将 180 度更改为 360 度。
我也试过改变 0, 0, 1 参数来改变圆弧的旋转,但是那似乎有旋转/翻转效果,而不是拉长圆弧。
此代码在 AngularJS 中,但不应影响解决此问题所涉及的数学。
此示例修改自 Pluralsight 的使用 AngularJS 和 SVG 课程的可扩展动态图形和图表。
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>
...
好吧,我心软了,给你修改了:)
原始量表的 "sweep angle" 和最大量表值都硬连线到 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.html
和 gauge.html
也有细微的变化。