¿Cómo dibujar ondas sinusoidales con SVG (+JS)?

10 minutos de lectura

¿Cuál sería la solución más sencilla para dibujar ondas sinusoidales en SVG? Supongo que las ondas sinusoidales deberían repetirse en un bucle simple con JavaScript… 🙂

Aquí están las coordenadas XY como un buen comienzo… 🙂

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

<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>

  • en.wikipedia.org/wiki/File:Simple_sine_wave.svg

    –Frank van Puffelen

    18 de diciembre de 2012 a las 12:33

  • @FrankvanPuffelen: sí, vi eso, pero esto está lejos de ser una solución agradable y simple… 🙂 Creo que esto ha sido generado por un software externo.

    – Sk8erPeter

    18 de diciembre de 2012 a las 12:48

  • ¿Algo así, presumiblemente? jsbin.com/adaxuy/4

    – Asad Saeeduddin

    18 de diciembre de 2012 a las 13:14

  • @Sk8erPeter: Sí, fue generado por GNUPLOT. ¡Echa un vistazo a su origen! Solo estás buscando lo último <path> elemento en el documento

    – Bergi

    18 de diciembre de 2012 a las 13:18

  • @Bergi: ¡gracias por la información! 🙂

    – Sk8erPeter

    19 de diciembre de 2012 a las 16:30

Avatar de usuario de Thomas W.
Tomas W.

Una alternativa a las aproximaciones de línea recta sería una aproximación de Bézier. Una aproximación bastante buena del primer trimestre de un período es una curva de Bézier cúbica con los siguientes puntos de control:

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

Editar:
Son posibles aproximaciones aún más exactas con los siguientes puntos de control:

0                    0
0.512286623256592433 0.512286623256592433
1.002313685767898599 1
1.570796326794896619 1

(Ver las explicaciones de NominalAnimal en los comentarios)

Demostración que compara elementos de línea (gris) y Bézier “bueno” (rojo) y Bézier “mejor” (verde).

Una aproximación que interpola exactamente la pendiente y la curvatura en los extremos de la spline es

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

(Ver derivación)

  • +1, guau, muchas gracias por este enfoque, ¡esta también es una muy buena solución!

    – Sk8erPeter

    18 de diciembre de 2012 a las 15:19

  • Puede obtener un error absoluto (al comparar el gráfico resultante con y = sen x) por debajo de 0.0000584414 ≃ 1/17111 usando 0, 0, 0.512286623256592433,0.512286623256592433, 1.002313685767898599,1, 1.570796326794896619,1 para el primer cuarto de período de la onda sinusoidal. @broofa: tenga en cuenta que este es un Bézier cúbico, no uno cuadrático, y que los Béziers a menudo se procesan más rápido y más suave que una polilínea similar.

    – Animal nominal

    18 de diciembre de 2012 a las 19:47

  • Correcciones a mis comentarios anteriores: el error absoluto máximo es aproximadamente 0.000058442 (max(p

    – Animal nominal

    18 de diciembre de 2012 a las 20:44


  • @broofa: En realidad es sencillo de generalizar. Usa x lineal e interpola y usando cúbicas por partes. Entonces, en lugar de segmentos de línea, use segmentos de curva de Bézier definidos por 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])/3y x[n+1], y[n+1]dónde x[n] son los puntos de muestreo, y[n] = f(x[n]) (es decir, f en x[n]), y dy[n] = d(f

    – Animal nominal

    18 de diciembre de 2012 a las 23:42

  • @ThomasW: ¡Correcto! El error absoluto comparado con sin() con esos puntos es mucho mayor, entre cero y 0.000582 (≃ 1/1700); alrededor de 10 veces en comparación con el que enumeré, pero aún prácticamente invisible. El principal problema en la búsqueda de óptimo curvas por partes es definir los requisitos y criterios reales para óptimo primero. Por ejemplo, usando los puntos que enumeré anteriormente, x[n] no necesita ser regular. Si escribe código para ajustar una spline de Bézier continua C2 a una función, la mayor parte del código generalmente será para encontrar el mejor conjunto de x[n]; tienen el mayor impacto en el resultado.

    – Animal nominal

    19 de diciembre de 2012 a las 11:45

Avatar de usuario de Asad Saeeduddin
Asad Saeeduddin

Aquí hay una prueba de concepto que agrega múltiples line elementos al elemento SVG:

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>

  • ¡Muchas gracias por su respuesta! Fue muy difícil decidir qué respuesta aceptar, pero finalmente acepté la tuya, porque es muy flexible y no "imprimiste" solo un "ciclo". (Y por cierto, mi primer enfoque fue usar line elementos también.) ¡Gracias de nuevo!

    – Sk8erPeter

    18 de diciembre de 2012 a las 15:16

avatar de usuario de broofa
Broofa

Lo siguiente agregará una onda sinusoidal de un ciclo a su gráfico SVG:

const XMAX = 500;
const YMAX = 100;

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

// Create PATH element
const 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);
<svg width="500" height="100"/>

Esto usa un elemento RUTA compuesto por comandos 'lineto' (línea recta). Esto funciona porque, como era de esperar, contiene muchos (500) pequeños segmentos de línea. Podría simplificar la ruta para tener menos puntos usando curvas Bézier para dibujar los segmentos, pero esto complica el código. Y usted pidió simple. 🙂

  • ¡Muchos gracias! Es una muy buena solución, también. Lo voté a favor, desearía poder aceptar múltiples respuestas... La razón por la que decidí aceptar la otra es que tal vez era un poco más flexible, pero la tuya también es una muy buena solución. ¡Gracias de nuevo, y lamento no haber podido aceptar el tuyo también!

    – Sk8erPeter

    18 de diciembre de 2012 a las 15:18

  • @Sk8erPeter Para más ciclos, simplemente cambie Math.sin(angle) a Math.sin(angle * X), donde X = número de ciclos. Además, en mi humilde opinión, un solo path El elemento (como el anterior) es una solución más elegante que la respuesta del elemento de muchas líneas de @Asad, por varias razones. 1. Una ruta solo agrega un único elemento DOM. Esto mejora el rendimiento del diseño DOM y la representación del lienzo (importante para la animación) y se puede 'rellenar' una ruta en caso de que desee rellenar el área debajo de su línea. No puede llenar segmentos de línea separados.

    – Broofa

    18 de diciembre de 2012 a las 17:10

  • Gracias por esto. Tenga en cuenta que puede simplificarlo, x = i a menos que me haya perdido algo.

    – Martín Fido

    12 de marzo de 2013 a las 11:57

  • @MartinFido: ¡buena captura! editado para eliminar innecesario i variedad gracias.

    – Broofa

    12/03/2013 a las 17:20

  • Alternativamente, si desea mantener la longitud de onda, puede cambiar la for bucle para contar el número de ciclos, es decir for (var x = 0; x <= (XMAX * cylces); x++) {

    - Gawpertron

    27/11/2017 a las 13:30

avatar de usuario de sffc
sffc

En caso de que sea útil para alguien: aquí hay un SVG de una línea que se aproxima mucho a la mitad de una onda sinusoidal usando una aproximación cúbica de bezier.

<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>

Ajusté el parámetro 36.42 minimizando la distancia de suma cuadrada (l2) entre la curva de Bézier y la curva del coseno verdadero. https://octave-online.net/bucket~AN33qHTHk7eARgoSe7xpYg

Mi respuesta se basa en parte en ¿Cómo aproximar una curva de medio coseno con rutas bezier en SVG?

Avatar de usuario de Ted Shaneyfelt
Ted Shaneyfelt

Para usar en ilustraciones, esta aproximación funciona bien.

<path d="M0,10 q5,20,10,0 t 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10 0 10,0" stroke="black" fill="none"/>

Es compacto, relativamente fácil de trabajar.

  • M0,10

    • Los primeros 10 después de M lo mueve hacia abajo 10 (a la vista si usa el cuadro de vista predeterminado)
    • El cero que sigue podría usarse para mover la onda hacia la derecha o hacia la izquierda.
    • Mueva una fracción de longitud de onda para mostrar el cambio de fase
  • q5,20,10,0

    • dibuja la primera media onda
    • 5 y 10 hacen que la media onda tenga 10 de ancho, por lo que la de una onda completa será 20
      • Puede escalarlos juntos junto con todos los 10 después de la t
      • Puede ilustrar FM ajustando el período (ver más abajo)
    • 20 hace la amplitud de la onda 10. (escala al gusto)
  • t 10 0

    • repite la media onda anterior invertida.
    • Cada 10 0 10 0 adicional produce una onda completa adicional
    • También puedes modular la frecuencia.
      • por ejemplo, 10 0 10 0 7,5 0 5 0 5 0 5 0 7,5 0 10 0 10 0 10 0
      • Al cambiar de frecuencia, use un valor intermedio como 7.5
      • El valor intermedio evita sesgar la onda.

Encuentro esto útil para ilustrar la modulación en la comunicación de datos. Para ilustrar AM (o incluso QAM), simplemente repita el comando q con los nuevos parámetros. Es posible que deba ajustar el comando M para cambiarlo a la vista si aumenta la amplitud

Para usar esto en HTML5, simplemente colóquelo en un elemento svg

<h1>FM and QAM Examples</h1>
<svg>
  <path d=" 
M0,20
q 5 20 10 0 t 10 0 10 0 10 0 10 0 
7.5 0 5 0 5 0 5 0 5 0 5 0 5 0 7.5 0 
10 0 10 0 10 0 10 0 10 0 10 0 
10 0 10 0 10 0 10 0 10 0 10 0 

M0,60
q 5 20 10 0 t 10 0 10 0 10 0 10 0 
q 5 20 10 0 t 10 0 10 0 10 0 10 0 
q 5 40 10 0 t 10 0 10 0 10 0 10 0
q 5 -20 10 0 t 10 0 10 0 10 0 10 0 10 

" stroke="black" fill="none"/>
</svg>

ingrese la descripción de la imagen aquí

avatar de usuario de broofa
Broofa

Recorra el eje X y para cada iteración calcule la posición Y usando una función de seno en el valor X actual.

Avatar de usuario de Ted Shaneyfelt
Ted Shaneyfelt

Aquí hay una ilustración de CDMA donde he usado splines cúbicos para ilustrar conceptos de CDMA. Primero define estas funciones:

<script>
function go(x,y) {return(`M ${x},${y}`)}
function to(y) {return(`c 5 0 5 ${y} 10 ${y}`)}
function dn(y=10) {return to(y)}
function up(y=10) {return to(-y)}
function path(d,color="black") {return `<path d="${d}" stroke=${color} fill="none"/>`}
function svg
function bits(n) {
  let s="", n0=(n>>1)
  for (m=0x80;m;m>>=1) s+= up(10* (!(m&n0)-!(m&n))  )
  return s;
}
function plot(a) {
  let s="", y0=0
  for (let y of a) {
    s += up(y-y0); y0=y
  }
  return s
}
function add(a) {
  let s=""
  if typeof y0 == 'undefined' var y0=0
  for (m=0x80;m;m>>=1) {
    let y=0; for (let e of a) y+= 5-10*!(e&m)
    s += up(y-y0); y0=y   
  }
  return s
}
</script>

Entonces puedes ilustrar aproximadamente ondas como esta:

<script>
  document.write(svg(path(go(0,25)+(up()+dn()).repeat(10))))
</script>

Onda de línea de coseno simple con fines ilustrativos

Aquí hay una ilustración de CDMA usando esta técnica

<h1>CDMA Example</h1>
<script>
a=0b00010010 
b=0b00010101 
document.write(svg(
  path(go(0,40)+bits(a)+bits(~a)+bits(a)+bits(~a)+bits(a),'red')+
  path(go(0,80)+bits(b)+bits(b)+bits(~b)+bits(~b)+bits(~b),'orange')+
  path(go(0,100+add([a,b])+add([~a,b])+add([a,~b])+add([~a,~b])+add([a,b])+add([~a,~b])))
))
</script>

Multiplexación de dos señales utilizando la técnica de estilo CDMA (simplificado)

NOTA: las señales CDMA reales no estarían alineadas con bits o incluso con chip

¿Ha sido útil esta solución?