【问题标题】:How to draw sine waves with SVG (+JS)?如何使用 SVG (+JS) 绘制正弦波?
【发布时间】:2012-12-05 15:26:38
【问题描述】:

在 SVG 中绘制正弦波最简单的解决方案是什么? 我猜应该用 JavaScript 在一个简单的循环中重复正弦波...... :)

这里是 X-Y 坐标作为一个好的开始... :)

http://jsbin.com/adaxuy/1/edit

<svg>
  <line x1="0" y1="250" x2="500" y2="250"
        style="stroke:black;stroke-width:1"/>
  <line x1="250" y1="0" x2="250" y2="500"
        style="stroke:black;stroke-width:1"/>
</svg>

【问题讨论】:

  • @FrankvanPuffelen:是的,我看到了,但这远不是一个好的简单的解决方案...... :) 我认为这是由外部软件生成的。
  • 大概是这样的吧? jsbin.com/adaxuy/4
  • @Sk8erPeter:是的,它是由 GNUPLOT 生成的。看看它的来源!您只是在寻找文档中的最后一个 &lt;path&gt; 元素
  • @Bergi:感谢您提供的信息! :)

标签: javascript html svg


【解决方案1】:

直线近似的替代方法是贝塞尔近似。一个周期的第一季度的一个非常好的近似值是具有以下控制点的三次贝塞尔曲线:

 0   0
1/2 1/2
 1   1
π/2  1

编辑: 使用以下控制点可以获得更精确的近似值:

0                    0
0.512286623256592433 0.512286623256592433
1.002313685767898599 1
1.570796326794896619 1

(参见 cmets 中 NominalAnimal 的解释)

Demo comparing line elements (gray) and "good" Bézier (red) and "better" Bézier (green).

在样条线端点精确插入斜率和曲率的近似值是

       0                0 
(6−(3/2π−3)²)/6  (6−(3/2π−3)²)/6
       1                1
      π/2               1

(See derivation)

【讨论】:

  • +1,哇,非常感谢您提供的这种方法,这也是一个非常好的解决方案!
  • 使用0, 0、0.512286623256592433,0.512286623256592433、1.002313685767898599,1、1.570796326794896619,1 在 0.0000584414 ≃ 1/17111 下可以获得绝对误差(将结果图与 y = sin x 进行比较)正弦波的四分之一周期。 @broofa:请注意,这是三次贝塞尔曲线,而不是二次贝塞尔曲线,而且贝塞尔曲线通常比类似的折线渲染得更快、更平滑。
  • 对我上面的cmets的更正:最大绝对误差约为0.000058442(max(p(t))约为0.0000000034155),当然有四个点(端点和两个控制点,八个系数) 2D 中的每立方贝塞尔曲线。
  • @broofa:它实际上是简单概括。使用线性 x,并使用分段三次插值 y。因此,使用由x[n], y[n]、x[n]*2/3+x[n+1]/3, y[n]+dy[n]*(x[n+1]-x[n])/3、x[n]/3+x[n+1]*2/3, y[n+1]-dy[n+1]*(x[n+1]-x[n])/3 和x[n+1], y[n+1] 定义的贝塞尔曲线段代替线段,其中x[n] 是采样点,y[n] = f(x[n])(即 f 在 x[n ]) 和dy[n] = d(f(t)/dt)[t=x[n]](即 f 在 x[n] 处的导数或斜率)。 最佳曲线更难找到,但这会产生一个可接受的分段三次近似。
  • @ThomasW:对!与 sin() 相比,这些点的绝对误差要大得多,介于零和 0.000582 (≃ 1/1700) 之间;与我列出的相比,大约是 10 倍,但实际上仍然是不可见的。寻找最优分段曲线的主要问题是首先定义最优的实际要求和标准。例如,使用我上面列出的点,x[n] 不需要是正则的。如果您编写代码以将 C2 连续 Bézier 样条拟合到函数,则大多数代码通常会找到最佳的 x[n] 集合;它们对结果的影响最大。
【解决方案2】:

这是一个概念证明,它向 SVG 元素添加了多个 line 元素:

var svg = document.getElementById('sine_wave').children[0];
var origin = { //origin of axes
    x: 100,
    y: 100
};
var amplitude = 10; // wave amplitude
var rarity = 1; // point spacing
var freq = 0.1; // angular frequency
var phase = 0; // phase angle

for (var i = -100; i < 1000; i++) {
    var line = document.createElementNS("http://www.w3.org/2000/svg", "line");

    line.setAttribute('x1', (i - 1) * rarity + origin.x);
    line.setAttribute('y1', Math.sin(freq*(i - 1 + phase)) * amplitude + origin.y);

    line.setAttribute('x2', i * rarity + origin.x);
    line.setAttribute('y2', Math.sin(freq*(i + phase)) * amplitude + origin.y);

    line.setAttribute('style', "stroke:black;stroke-width:1");

    svg.appendChild(line);
}
html, body, div{
    height:100%;
}
<div id="sine_wave">

  <svg width="1000" height="1000">
    <line x1="100" y1="0" x2="100" y2="200"
          style="stroke:black;stroke-width:1"/>
    <line x1="0" y1="100" x2="1000" y2="100"
          style="stroke:black;stroke-width:1"/>
  </svg>

</div>

【讨论】:

  • 非常感谢您的回答!很难决定接受哪个答案,但最后我接受了你的答案,因为它非常灵活,而且你没有“打印”只有一个“循环”。 (顺便说一句,我的第一种方法也是使用 line 元素。)再次感谢!
【解决方案3】:

以下将为您的 SVG 图形添加一个单周期正弦波:

var XMAX = 500;
var YMAX = 500;

// Create path instructions
var path = [];
for (var x = 0; x <= XMAX; x++) {
    var angle = (x / XMAX) * Math.PI * 2;  // angle = 0 -> 2π
    var y = Math.sin(angle) * (YMAX / 2) + (YMAX / 2);
    // M = move to, L = line to
    path.push((x == 0 ? 'M' : 'L') + x + ',' + y);
}

// Create PATH element
var pathEl = document.createElementNS("http://www.w3.org/2000/svg", "path");
pathEl.setAttribute('d', path.join(' ') );
pathEl.style.stroke = 'blue';
pathEl.style.fill = 'none';

// Add it to svg element
document.querySelector('svg').appendChild(pathEl);

​    ​

这是jsfiddle you can run。

这使用由“lineto”(直线)命令组成的PATH element。这是因为,毫不奇怪,它包含许多 (500) 个小线段。您可以通过使用贝塞尔曲线绘制线段来简化路径以减少点,但这会使代码复杂化。你要求简单。 :)

【讨论】:

  • 非常感谢!这也是一个非常好的解决方案。我赞成它,我希望我能接受多个答案......我决定接受另一个答案的原因是也许这更灵活一点,但你的也是一个很好的解决方案。再次感谢,很抱歉我也不能接受你的!
  • @Sk8erPeter 要获得更多周期,只需将 Math.sin(angle) 更改为 Math.sin(angle * X),其中 X = 周期数。此外,恕我直言,单个path 元素(如上)是比@Asad 的多行元素答案更优雅的解决方案,原因有几个。 1. 一个路径只添加一个 DOM 元素。这提高了 DOM 布局和画布渲染(对动画很重要)的性能,并且可以在您想要填充线条下方区域的事件中“填充”路径。您不能填充单独的线段。
  • 谢谢。注意你可以简化它,x = i 除非我错过了什么。
  • @MartinFido:好收获!编辑以删除不必要的i var。谢谢。
  • 或者,如果您想保持波长,您可以更改 for 循环以计算循环数,即 for (var x = 0; x &lt;= (XMAX * cylces); x++) {
【解决方案4】:

如果它对任何人有用:这是一个单线 SVG,它使用三次贝塞尔近似近似接近正弦波的一半。

<svg width="100px" height="100px" viewBox="0 0 100 100">
    <path stroke="#000000" fill="none" d="M0,0 C36.42,0,63.58,100,100,100" />
</svg>

我通过最小化贝塞尔曲线和真实余弦曲线之间的平方和 (l2) 距离来拟合参数 36.42。 https://octave-online.net/bucket~AN33qHTHk7eARgoSe7xpYg

我的回答部分基于How to approximate a half-cosine curve with bezier paths in SVG?

【讨论】:

  • 粗略近似另一种绘制半波的方法,下/上“杯子”:&lt;path d="M0,0 c1.29,1.334 1.8516,1.334 3.1416,0" stroke-width=".1" /&gt;
【解决方案5】:

在 X 轴上循环,每次迭代都使用当前 X 值的正弦函数计算 Y 位置。

【讨论】:

  • 是的,理论上没问题,但我想要一个可行的示例解决方案(例如,通过编辑 this one 或仅发布示例代码 :))。谢谢。
  • @Sk8erPeter 好吧,我看到 stackoverflow 有点不同。我使用 SO 来获得我的问题的答案——而不是找到一个可以完成我的工作并实现整个事情的人。
  • 我认为这是一个不好的方法。如果我想问这个问题的数学背景,我会发到math.stackexchange.com。但这是一个提供问题编程解决方案的编程论坛。更不用说你错了,这绝对是不是我的工作,我只是觉得使用 SVG 更高级一点的专业兴趣,我还没有见过这样的一个有解决方案的问题。 BTW,当多个代码共享时,可以看到多种方法,我们可以从中学到很多东西。想象一下没有不同代码的互联网,这将非常困难。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多