具有动态笔划的 SVG 仪表
SVG gauge meter with dynamic strokes
我正在尝试构建类似于以下内容的 SVG:
笔划完全是动态的,因为它们来自 API。我想把笔画放在收到的点上(作为百分比值的数组)。不必按顺序且2笔画之间的距离不必相等
我正在尝试类似下面的内容,但无法提出笔划放置的逻辑。我试着按照这里的答案: 但是这里的笔画之间的距离是相等的。在我的场景中,它们不一致。
Fiddle Link: https://jsfiddle.net/puq8v594/2/
let divisionsElement = document.getElementById('divisions');
let length = parseInt(divisionsElement.getAttribute("r")) * Math.PI;
let divisionsArray = [20,30,80,90]; //stroke at 20%, 30%,80%,90% position
let METER_DIVISIONS_GAP = 0.2;
divisionsArray.map(ele => {
setupPath(ele);
})
function setupPath(val) {
divisionsElement.setAttribute("stroke-dasharray", val + ' ' +METER_DIVISIONS_GAP);
}
circle {
fill: none;
}
.circle-back {
stroke: #F8F6F0;
stroke-width:9px;
}
.circle-fill {
stroke: grey;
stroke-width: 10px;
}
.circle-progress {
stroke: blue;
stroke-width:9px;
}
<svg viewBox="0 0 126 126" preserveAspectRatio="xMinYMin meet">
<clipPath id="cut-off">
<rect x="0" y="0" width="100%" height="50" />
</clipPath>
<clipPath id="progress-percent">
<path x="0" y="0" width="12%" height="50" />
</clipPath>
<g>
<circle r="35%" cx="40%" cy="40%" class="circle-fill" clip-path="url(#cut-off)" />
<circle id="divisions" r="35%" cx="40%" cy="40%" class="circle-back" clip-path="url(#cut-off)"
stroke-dasharray="20 0.5" stroke-dashoffset="25" />
<circle r="35%" cx="40%" cy="40%" class="circle-progress" clip-path="url(#progress-percent)" />
</g>
</svg>
------------编辑------------
对于想要在支持 IE 的情况下实现此功能的人,我可以通过 Michael 对代码进行一些更改来实现它。
var data = [10,20,10,10,25,25];
var dataSum = 0;
for (let i = 0; i < data.length - 1; i++) { dataSum += data[i]; }
var drawArray = "";
var indexOfFill = 2;
var arcPath = document.getElementById("meter-back");
var filledPath = document.getElementById("meter-fill");
var totalLength = arcPath.getTotalLength();
function drawPath() {
// Draw the background strokes
let test='';
for (i = 0; i < data.length; i++) {
let barLen = ((data[i]) * totalLength) / 100;
test = test + ((barLen) - (0.2 * (dataSum / 100)) + " " + 0.2 * (dataSum / 100) + " ")
}
arcPath.setAttribute("stroke-dasharray", test);
animate(filledPath, totalLength, 0,50);
}drawPath();
function animate(el,length,end,percentage) {
el.style.visibility = 'visible';
if (end <= percentage) {
el.style.strokeOpacity = 5;
let gap = 100 - end;
let offset = 100;
el.style.strokeDasharray = ((end / 100) * length) + ' ' + ((gap / 100) * length);
el.style.strokeDashoffset = (offset / 100) * length;
animate(el, length, ++end, percentage);
}
}
<svg width="100%" height="600px" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet">
<defs>
<filter id="outline" y="-50%" height="200%">
<feMorphology operator="erode" radius="0.2" />
<feComposite operator="out" in="SourceGraphic" result="outline" />
<feFlood flood-color="grey" />
<feComposite operator="in" in2="outline" />
<feComposite operator="atop" in2="SourceGraphic" />
</filter>
</defs>
<g filter="url(#outline)">
<path fill="none" stroke-dasharray="" stroke-width="20" stroke="grey" d="M30 100 A 40 40 0 0 1 170 100" />
<path id="meter-back" fill="none" stroke-dasharray="" stroke-width="20" stroke="white" d="M30 100 A 40 40 0 0 1 170 100" />
<path id="meter-fill" fill="none" stroke-dasharray="" stroke="rgba(128,128,128,0.5)" stroke-width="20" d="M30 100 A 40 40 0 0 1 170 100" style="visibility: hidden;" ></path>
</g>
</svg>
将 pathLength 设置为 100(或接近 100)的弧形路径可能更容易做到这一点。
let data =[5 , 10 , 25 ,25 , 6 , 9 , 20];
let i=0;
let drawArray = "";
let arcPath = document.getElementById("meter");
function setupPath() {
for (i=0; i < data.length; i++) {
drawArray = drawArray + (data[i] - 0.5) + " 0.5 ";
}
arcPath.setAttribute("stroke-dasharray", drawArray);
}; setupPath();
<svg width="800px" height="600px" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet">
<path id="meter" fill="none" stroke-dasharray="" stroke-width="20" stroke="blue" pathLength="99.5" d="M30 100 A 40 40 0 0 1 170 100" />
</svg>
更新:
我从问题中想到,您期望输入的百分比值数组加起来为 100。但是,如果您不知道值的数量或它们的总和,则可以按如下方式对数据进行归一化。此外,您可以使用过滤器添加轮廓 - 这样您就不必进行大量数学运算来明确绘制弧线段。
我还意识到您想要部分填充 - 因此您可以通过更改变量 - indexOfFill 来控制填充量表的位置。
var data = [5, 10 ,25 ,25 ,6 ,9 ,20 ,16 ,33 ,45 ,67];
var dataSum = data.reduce((partial_sum, a) => partial_sum + a, 0);
var indexOfFill = 3;
var i=0;
var drawArray = "";
var arcPath = document.getElementById("meter-back");
var filledPath = document.getElementById("meter-fill");
function setupPath() {
// Draw the background
for (i=0; i < data.length; i++) {
drawArray = drawArray + (data[i] - (0.5 * (dataSum/100)) + " " + 0.5*(dataSum/100) +" ")};
arcPath.setAttribute("stroke-dasharray", drawArray);
arcPath.setAttribute("pathLength", dataSum - (0.5 * (dataSum/100)));
//Draw the fill
drawArray="";
for (i=0; i < indexOfFill; i++) {
if ( (i + 1) === indexOfFill ) {
drawArray = drawArray + (data[i] - (0.5 * (dataSum/100)) + " " + dataSum)
} else {
drawArray = drawArray + (data[i] - (0.5 * (dataSum/100)) + " " + 0.5*(dataSum/100) +" ")}};
filledPath.setAttribute("stroke-dasharray", drawArray);
filledPath.setAttribute("pathLength", dataSum - (0.5 * (dataSum/100)));
}; setupPath();
<svg width="800px" height="600px" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet">
<defs>
<filter id="outline" y="-50%" height="200%">
<feMorphology operator="erode" radius="0.2"/>
<feComposite operator="out" in="SourceGraphic" result="outline"/>
<feFlood flood-color="black"/>
<feComposite operator="in" in2="outline"/>
<feComposite operator="atop" in2="SourceGraphic"/>
</filter>
</defs>
<g filter="url(#outline)">
<path id="meter-back" fill="none" stroke-dasharray="" stroke-width="20" stroke="white" pathLength="99.5" d="M30 100 A 40 40 0 0 1 170 100" />
<path id="meter-fill" fill="none" stroke-dasharray="" stroke-width="20" stroke="rgb(90,90,220)" pathLength="99.5" d="M30 100 A 40 40 0 0 1 170 100" />
</g>
</svg>
我正在尝试构建类似于以下内容的 SVG:
笔划完全是动态的,因为它们来自 API。我想把笔画放在收到的点上(作为百分比值的数组)。不必按顺序且2笔画之间的距离不必相等
我正在尝试类似下面的内容,但无法提出笔划放置的逻辑。我试着按照这里的答案:
Fiddle Link: https://jsfiddle.net/puq8v594/2/
let divisionsElement = document.getElementById('divisions');
let length = parseInt(divisionsElement.getAttribute("r")) * Math.PI;
let divisionsArray = [20,30,80,90]; //stroke at 20%, 30%,80%,90% position
let METER_DIVISIONS_GAP = 0.2;
divisionsArray.map(ele => {
setupPath(ele);
})
function setupPath(val) {
divisionsElement.setAttribute("stroke-dasharray", val + ' ' +METER_DIVISIONS_GAP);
}
circle {
fill: none;
}
.circle-back {
stroke: #F8F6F0;
stroke-width:9px;
}
.circle-fill {
stroke: grey;
stroke-width: 10px;
}
.circle-progress {
stroke: blue;
stroke-width:9px;
}
<svg viewBox="0 0 126 126" preserveAspectRatio="xMinYMin meet">
<clipPath id="cut-off">
<rect x="0" y="0" width="100%" height="50" />
</clipPath>
<clipPath id="progress-percent">
<path x="0" y="0" width="12%" height="50" />
</clipPath>
<g>
<circle r="35%" cx="40%" cy="40%" class="circle-fill" clip-path="url(#cut-off)" />
<circle id="divisions" r="35%" cx="40%" cy="40%" class="circle-back" clip-path="url(#cut-off)"
stroke-dasharray="20 0.5" stroke-dashoffset="25" />
<circle r="35%" cx="40%" cy="40%" class="circle-progress" clip-path="url(#progress-percent)" />
</g>
</svg>
------------编辑------------
对于想要在支持 IE 的情况下实现此功能的人,我可以通过 Michael 对代码进行一些更改来实现它。
var data = [10,20,10,10,25,25];
var dataSum = 0;
for (let i = 0; i < data.length - 1; i++) { dataSum += data[i]; }
var drawArray = "";
var indexOfFill = 2;
var arcPath = document.getElementById("meter-back");
var filledPath = document.getElementById("meter-fill");
var totalLength = arcPath.getTotalLength();
function drawPath() {
// Draw the background strokes
let test='';
for (i = 0; i < data.length; i++) {
let barLen = ((data[i]) * totalLength) / 100;
test = test + ((barLen) - (0.2 * (dataSum / 100)) + " " + 0.2 * (dataSum / 100) + " ")
}
arcPath.setAttribute("stroke-dasharray", test);
animate(filledPath, totalLength, 0,50);
}drawPath();
function animate(el,length,end,percentage) {
el.style.visibility = 'visible';
if (end <= percentage) {
el.style.strokeOpacity = 5;
let gap = 100 - end;
let offset = 100;
el.style.strokeDasharray = ((end / 100) * length) + ' ' + ((gap / 100) * length);
el.style.strokeDashoffset = (offset / 100) * length;
animate(el, length, ++end, percentage);
}
}
<svg width="100%" height="600px" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet">
<defs>
<filter id="outline" y="-50%" height="200%">
<feMorphology operator="erode" radius="0.2" />
<feComposite operator="out" in="SourceGraphic" result="outline" />
<feFlood flood-color="grey" />
<feComposite operator="in" in2="outline" />
<feComposite operator="atop" in2="SourceGraphic" />
</filter>
</defs>
<g filter="url(#outline)">
<path fill="none" stroke-dasharray="" stroke-width="20" stroke="grey" d="M30 100 A 40 40 0 0 1 170 100" />
<path id="meter-back" fill="none" stroke-dasharray="" stroke-width="20" stroke="white" d="M30 100 A 40 40 0 0 1 170 100" />
<path id="meter-fill" fill="none" stroke-dasharray="" stroke="rgba(128,128,128,0.5)" stroke-width="20" d="M30 100 A 40 40 0 0 1 170 100" style="visibility: hidden;" ></path>
</g>
</svg>
将 pathLength 设置为 100(或接近 100)的弧形路径可能更容易做到这一点。
let data =[5 , 10 , 25 ,25 , 6 , 9 , 20];
let i=0;
let drawArray = "";
let arcPath = document.getElementById("meter");
function setupPath() {
for (i=0; i < data.length; i++) {
drawArray = drawArray + (data[i] - 0.5) + " 0.5 ";
}
arcPath.setAttribute("stroke-dasharray", drawArray);
}; setupPath();
<svg width="800px" height="600px" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet">
<path id="meter" fill="none" stroke-dasharray="" stroke-width="20" stroke="blue" pathLength="99.5" d="M30 100 A 40 40 0 0 1 170 100" />
</svg>
更新:
我从问题中想到,您期望输入的百分比值数组加起来为 100。但是,如果您不知道值的数量或它们的总和,则可以按如下方式对数据进行归一化。此外,您可以使用过滤器添加轮廓 - 这样您就不必进行大量数学运算来明确绘制弧线段。
我还意识到您想要部分填充 - 因此您可以通过更改变量 - indexOfFill 来控制填充量表的位置。
var data = [5, 10 ,25 ,25 ,6 ,9 ,20 ,16 ,33 ,45 ,67];
var dataSum = data.reduce((partial_sum, a) => partial_sum + a, 0);
var indexOfFill = 3;
var i=0;
var drawArray = "";
var arcPath = document.getElementById("meter-back");
var filledPath = document.getElementById("meter-fill");
function setupPath() {
// Draw the background
for (i=0; i < data.length; i++) {
drawArray = drawArray + (data[i] - (0.5 * (dataSum/100)) + " " + 0.5*(dataSum/100) +" ")};
arcPath.setAttribute("stroke-dasharray", drawArray);
arcPath.setAttribute("pathLength", dataSum - (0.5 * (dataSum/100)));
//Draw the fill
drawArray="";
for (i=0; i < indexOfFill; i++) {
if ( (i + 1) === indexOfFill ) {
drawArray = drawArray + (data[i] - (0.5 * (dataSum/100)) + " " + dataSum)
} else {
drawArray = drawArray + (data[i] - (0.5 * (dataSum/100)) + " " + 0.5*(dataSum/100) +" ")}};
filledPath.setAttribute("stroke-dasharray", drawArray);
filledPath.setAttribute("pathLength", dataSum - (0.5 * (dataSum/100)));
}; setupPath();
<svg width="800px" height="600px" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet">
<defs>
<filter id="outline" y="-50%" height="200%">
<feMorphology operator="erode" radius="0.2"/>
<feComposite operator="out" in="SourceGraphic" result="outline"/>
<feFlood flood-color="black"/>
<feComposite operator="in" in2="outline"/>
<feComposite operator="atop" in2="SourceGraphic"/>
</filter>
</defs>
<g filter="url(#outline)">
<path id="meter-back" fill="none" stroke-dasharray="" stroke-width="20" stroke="white" pathLength="99.5" d="M30 100 A 40 40 0 0 1 170 100" />
<path id="meter-fill" fill="none" stroke-dasharray="" stroke-width="20" stroke="rgb(90,90,220)" pathLength="99.5" d="M30 100 A 40 40 0 0 1 170 100" />
</g>
</svg>