将箭头附加到SVG弧路径上

4
我正在使用SVG创建一个仪表盘。
有没有办法在动画仪表盘的末尾添加一个箭头来指示输入的值?
由于仪表盘是使用路径的stroke-dasharray显示的,根据SVG规范,似乎无法应用标记。

enter image description here

class arcGauge {
    constructor(targetEl) {
        this.el = targetEl;
        this.minmax = [];
        this.arcCoordinate = [];
        this.valueArcDataValue = 0;
        this.gaugeArc = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    }

    // draw gauge
    init(data) {
        // set data
        this.viewBox = [0, 0, 110, 100];
        this.minmax = data.minmax || [];
        this.arcCoordinate = data.arcCoordinate || [120, 60];
        this.threshold = data.threshold || [];
        this.valueArcData = data.valueArcData;
        this.gaugeRadius = 42;
        this.gaugeStrokeWidth = 6;

        this._makeGauge();

    }

    _makeGauge() {
        const radius = this.gaugeRadius;

        let arcCoord = [];
        // coordinate
        this.arcCoordinate.forEach((ang) => {
            const radian = this._degreesToRadians(ang);
            const x = this.viewBox[2] / 2 + Math.cos(radian) * radius;
            const y = this.viewBox[2] / 2 + Math.sin(radian) * radius;
            arcCoord.push([x.toFixed(2), y.toFixed(2)]);
        });

        //arc
        this.gaugeArc.setAttribute('id', 'gaugeArc');
        this.gaugeArc.setAttribute('d', `M ${arcCoord[0][0]} ${arcCoord[0][1]} A ${radius} ${radius} 0 1 1  ${arcCoord[1][0]} ${arcCoord[1][1]}`);
        this.gaugeArc.setAttribute('fill', 'none');
        this.gaugeArc.setAttribute('stroke', this.valueArcData.color);
        this.gaugeArc.setAttribute('stroke-width', this.gaugeStrokeWidth);
        this.gaugeArc.setAttribute('transform', 'scale(-1, 1) translate(-110, 0)');

        let percentage = 0;
    percentage = this.valueArcData.value;

        this.gaugeArc.style.strokeDasharray = this._getArcLength(radius, 300, percentage);

        this.el.appendChild(this.gaugeArc);
    }

    // degree
    _degreesToRadians(degrees) {
        const pi = Math.PI;
        return degrees * (pi / 180);
    }
    // arc length
    _getArcLength(radius, degrees, val) {
        const radian = this._degreesToRadians(degrees);
        const arcLength = 2 * Math.PI * radius * (degrees / 360);
        const pathLength = arcLength * (val / 100);
        const dasharray = `${pathLength.toFixed(2)} ${arcLength.toFixed(2)}`;
        return dasharray;
    }



    // set gauge value
    setValue(v) {
            const baseValue = this.minmax[1] - this.minmax[0];
            const percentage = (v / baseValue) * 100;
            this.valueArcData.value = v;

            this.gaugeArc.style.strokeDasharray = this._getArcLength(this.gaugeRadius, 300, percentage);
            //gauge animation
            this.gaugeArc.style.transition = 'stroke-dasharray 1s ease-in-out';
    }

}


const arcGaugeData = {
  minmax: [0, 100],
  thresholdColor: ['#22b050', '#f4c141', '#e73621'],
  valueArcData: { type: 'arrow', color: '#3e3eff', value: 80 },
};
const arcGaugeIns = new arcGauge(document.querySelector('#chart'));
arcGaugeIns.init(arcGaugeData);

function changeArc(ipt) {
  arcGaugeIns.setValue(ipt.value);
}
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
<div class="container">
  <svg id="chart" xmlns="http://www.w3.org/2000/svg" width="180px" height="auto" viewBox="0 0 110 100"></svg>
  <input type="range"  min="0" max="100" step="1" value="80" onchange="changeArc(this)" />
</div>

如果这不可能,请告知是否有其他的选择。

使用一个<marker>标记 https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/marker - undefined
2个回答

2
在这个例子中,我只是使用一个简单的多边形作为箭头。箭头是从中心点旋转的。

var svg = document.getElementById('svg01');

document.forms.form01.range.addEventListener('change', e => {
  let rotate = 240 * e.target.value / 100;
  svg.querySelector('circle').setAttribute('stroke-dashoffset', rotate - 360);
  svg.querySelector('.polygon').setAttribute('transform', `rotate(-${rotate})`);
});
body {
  display: flex;
}

svg {
  border: solid;
  height: 90vh;
}
<svg id="svg01" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <g transform="translate(50 50) rotate(30)">
    <circle r="40" fill="none" stroke="#3e3eff" stroke-width="6" pathLength="360" stroke-dasharray="240 360" stroke-dashoffset="-200" />
    <g class="polygon" transform="rotate(-160)">
      <polygon transform="translate(40 0)" points="0,-7 10,7 -10,7 0,-7" fill="red" />
    </g>
  </g>
</svg>
<form name="form01">
  <input type="range" name="range" min="0" max="100" step="1" value="66" />
</form>


我喜欢简洁的代码。 很抱歉不能选择你的答案。希望你能理解我选择了enxaneta的答案。 谢谢你的帮助。 - undefined

2
主要思想是这样的: 使用getPointAtLength计算出量表的终点,然后将箭头移动到该点。
同时,还要计算出终点处路径的角度,以便相应地旋转箭头。
为了计算角度,您需要两个点:
point1,即量表的终点
point2,即稍微偏移的点(val + 0.1)。
这样做是有效的,除非滑块的值为100。在这种情况下,我将箭头用作标记。

let length = chart.getTotalLength();
chart.style.strokeDasharray = length;
chart.style.strokeDashoffset = length;

function gauge(rangeValue) {
  let val = length - (length * rangeValue) / 100;
  if (rangeValue < 100 && rangeValue > 0) {
    let point1 = chart.getPointAtLength(val);
    let point2 = chart.getPointAtLength(val + 0.1);
    let angle = Math.atan2(point2.y - point1.y, point2.x - point1.x);
    arrow.setAttribute(
      "transform",
      "translate(" +
        [point1.x, point1.y] +
        ")" +
        "rotate(" +
        (angle * 180) / Math.PI +
        ")"
    );
  } else {
    chart.setAttribute("marker", "url(#m)");
  }
  chart.style.strokeDashoffset = val;
}

gauge(Number(range.value));

range.addEventListener("input", () => {
  let rangeValue = Number(range.value);
  gauge(rangeValue);
});
svg{border:solid;}
<svg xmlns="http://www.w3.org/2000/svg" width="180px" height="auto" viewBox="0 0 110 100">

  <path id="chart" d="M 34.00 91.37 A 42 42 0 1 1  76.00 91.37" fill="none" stroke="#3e3eff" stroke-width="6" transform="scale(-1, 1) translate(-110, 0)"  marker="url(#m)"></path>
  <marker id="m">
    <path id="markerarrow" fill="red" d="M0,0L0,-10L-14,0L0,10" />
  </marker>
  <use href="#markerarrow" id="arrow"/>
</svg>
</br>
<p><input type="range" id="range" min="0" max="100" step="1" value="80" /></p>

观察:这个解决方案并不完美。如果你快速移动滑块,可能会出现箭头偏移的情况。

1
我知道原理,但是对于如何实施它感到困惑,但这正是我想要的代码。 谢谢你的帮助。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接