我试图弄清楚如何在任意SVG path
元素上绘制一条波浪线。路径由React组件生成。例如,我尝试复制行in this question:
在SVG和/或JavaScript生成的路径中有一种简单的方法吗?
我考虑过使用s
路径命令连接一系列曲线,但我需要计算曲线上的点数。我也考虑过某种位移滤波器,但我不确定从哪里开始。
答案 0 :(得分:7)
对我来说,最简单的方法就是走这条路。然后,在每个步骤中,插入一个二次贝塞尔曲线,其控制点位于它们之间并垂直于曲线。然后,为下一步切换控制点所在的一侧。
function makeSquiggle(squigglePathId, followPathId, squiggleStep, squiggleAmplitude)
{
var followPath = document.getElementById(followPathId);
var pathLen = followPath.getTotalLength();
// Adjust step so that there are a whole number of steps along the path
var numSteps = Math.round(pathLen / squiggleStep);
var pos = followPath.getPointAtLength(0);
var newPath = "M" + [pos.x, pos.y].join(',');
var side = -1;
for (var i=1; i<=numSteps; i++)
{
var last = pos;
var pos = followPath.getPointAtLength(i * pathLen / numSteps);
// Find a point halfway between last and pos. Then find the point that is
// perpendicular to that line segment, and is squiggleAmplitude away from
// it on the side of the line designated by 'side' (-1 or +1).
// This point will be the control point of the quadratic curve forming the
// squiggle step.
// The vector from the last point to this one
var vector = {x: (pos.x - last.x),
y: (pos.y - last.y)};
// The length of this vector
var vectorLen = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
// The point halfwasy between last point and tis one
var half = {x: (last.x + vector.x/2),
y: (last.y + vector.y/2)};
// The vector that is perpendicular to 'vector'
var perpVector = {x: -(squiggleAmplitude * vector.y / vectorLen),
y: (squiggleAmplitude * vector.x / vectorLen)};
// No calculate the control point position
var controlPoint = {x: (half.x + perpVector.x * side),
y: (half.y + perpVector.y * side)};
newPath += ("Q" + [controlPoint.x, controlPoint.y, pos.x, pos.y].join(','));
// Switch the side (for next step)
side = -side;
}
var squigglePath = document.getElementById(squigglePathId);
squigglePath.setAttribute("d", newPath);
}
makeSquiggle("squiggle", "follow", 25, 20);
&#13;
#follow {
fill: none;
stroke: grey;
stroke-width: 2;
}
#squiggle {
fill: none;
stroke: red;
stroke-width: 2;
}
&#13;
<svg width="500" height="400">
<path id="follow" d="M 50,300 C 100,100 300,0, 350,250 L 450,200"/>
<path id="squiggle" d="M0,0"/>
</svg>
&#13;
答案 1 :(得分:2)
最好的方法可能只是加入Bézier曲线,然后偏移并反转您想要创建的每个后续曲线的值,直到达到所需的长度。
答案 2 :(得分:1)
<强>注意强>: f(x)是你的波浪线应该遵循的曲线,x0是你开始绘制波浪线的地方,xn是你结束这个例子的地方假设x0和xn之间的f(x)不断增长
如果您有光栅化曲线并且要绘制的波浪线是cos(x),则可以找到绘制线的点,如下所示:
'use strict'
//
// point = [int, int], point[0] = x, point[1] = y
// rasterizedCurve = [point0, ...,pointN]
//
// int -> [int,...,int]
function rangeFrom1ToN(N) {
return Array(N).fill(0).map((x, index) => index).slice(1);
}
// [int, ...,int] -> [float, ..., float]
function expandRange(Range, precision) {
return Range.map(x => rangeFrom1ToN(precision).map((y, index) => x + 1/precision * index))
.reduce((acc, val) => acc.concat(val));
}
function formatForSvg(points) {
return points.map(x => x.toString()).reduce((acc, val) => {return acc + ' ' + val})
}
// rasterizedCurve, index -> int
function derivative(curve, index){
//
// return dx' curve(x)
//
if (index === 0) {
return 0;
}
const point1 = curve[index - 1];
const point2 = curve[index];
return (point2[1] - point1[1]) / (point2[0] - point1[0]);
}
// rasterizedCurve -> rasterizedCurve
function squiggleAroundCurve(x, y, curve, index) {
const len = lenCurve(curve, index);
const h = Math.sin(len);
const b = Math.sin(Math.atan2(1, derivative(curve, index))) * h;
const a = Math.cos(Math.atan2(1, derivative(curve, index))) * h;
x -= a;
y += b;
return [x, y];
}
function pow2(x) {
return Math.pow(x,2);
}
function dist(point1, point2) {
return Math.sqrt(pow2(point2[0] - point1[0]) + pow2(point2[1] - point1[1]))
}
// rasterizedCurve, int -> int
function lenCurve(rasterizedCurve, index) {
const curve = rasterizedCurve.slice(0, index);
return curve.reduce((sum, point, index) => {
let len = 0;
if (index > 0) {
len = dist(point, curve[index - 1]);
}
return sum + len;
}, 0);
}
const Curve = expandRange(rangeFrom1ToN(90),50).map(x => [x, (Math.log(x) * 15)]);
const SquiggledCurve = Curve.map((point, index) => squiggleAroundCurve(point[0], point[1], Curve, index))
function zoom(curve, w) {
return curve.map(point => [point[0] * w, point[1] * w]);
}
function getNode(n, v) {
n = document.createElementNS("http://www.w3.org/2000/svg", n);
for (var p in v)
n.setAttributeNS(null, p.replace(/[A-Z]/g, function(m, p, o, s) { return "-" + m.toLowerCase(); }), v[p]);
return n
}
var svg = getNode("svg");
setTimeout(function() {
document.body.appendChild(svg);
const r = getNode('polyline', { points:formatForSvg(zoom(SquiggledCurve, 10)), fill:'none', stroke:'black'});
const c = getNode('polyline', { points:formatForSvg(zoom(Curve, 10)), fill:'none', stroke:'black'});
svg.appendChild(r);
svg.appendChild(c);
}, 1000);
&#13;
svg {
width: 1100px;
height: 900px;
}
&#13;