代码之家  ›  专栏  ›  技术社区  ›  Cmagelssen

为d3.drag()行为添加转换

  •  0
  • Cmagelssen  · 技术社区  · 4 年前

    我即将用d3.js完成我的第一个项目。它计算滑雪者可用的势能,如果所有这些能量都转化为动能,我想计算滑雪者的预期速度。我想做的最后一件事是在滑雪者摔倒时为他添加一个过渡。我希望滑雪者总是转到右边的黑圈(p2)。过渡的持续时间现在并不重要。有没有一种简单的方法来完成这项任务?

    example

    const height = 500;
    const width = 800;
    const skierIconSvg = 'https://image.flaticon.com/icons/svg/94/94150.svg';
    const gate =
      'https://docs.google.com/drawings/d/1k-mtzjW05U6KZKNt0kAI2pWqKsXFobptsa9kz6grYzA/edit?usp=sharing';
    
    const [p1, p2, p3] = [
      [80, 130],
      [600, 170],
      [750, 190],
    ];
    
    const svg = d3.select('svg');
    
    // Store a reference to the span we're going to update
    const skierHeight = d3.select('#skier-height');
    
    const vek = document.querySelector('#vekt');
    console.log(vek.value);
    
    const line = svg.append('line').attr('stroke', 'black');
    
    const connection = svg.append('line').attr('stroke', 'green');
    
    const projection = svg
      .append('circle')
      .attr('r', 5)
      .attr('stroke', 'red')
      .attr('fill', 'none');
    
    const g = svg
      .append('g')
      .attr('cursor', 'move')
      .attr('pointer-events', 'all')
      .attr('stroke', 'transparent')
      .attr('stroke-width', 30);
    
    const point = g
      .selectAll('image')
      .data([p1, p2])
      .enter()
      .append('circle')
      .attr('r', 10)
      .call(
        d3
        .drag()
        .subject(([x, y]) => ({
          x,
          y,
        }))
        .on('drag', dragged)
      );
    
    const skier = g
      .append('image')
      .attr('id', 'skier')
      .datum(p3)
      .attr('href', skierIconSvg)
      .attr('width', 100)
      .attr('height', 100)
      .attr('transform', 'translate(-50, -40)')
      .call(
        d3
        .drag()
        .subject(([x, y]) => ({
          x,
          y,
        }))
        .on('drag', dragged)
      );
    
    update();
    
    function dragged(d) {
      d[0] = d3.event.x;
      d[1] = d3.event.y;
      update();
      potensiellEnergi();
    }
    
    function update() {
      const t = (width + height) / distance(p1, p2);
    
      const l1 = interpolate(p1, p2, t);
    
      const l2 = interpolate(p2, p1, t);
      const p = interpolate(p1, p2, project(p1, p2, p3));
    
      connection.attr('x1', p3[0]).attr('y1', p3[1]);
      connection.attr('x2', p[0]).attr('y2', p[1]);
      projection.attr('cx', p[0]).attr('cy', p[1]);
      line.attr('x1', l1[0]).attr('y1', l1[1]);
      line.attr('x2', l2[0]).attr('y2', l2[1]);
      point.attr('cx', (d) => d[0]).attr('cy', (d) => d[1]);
      skier.attr('x', (d) => d[0]).attr('y', (d) => d[1]);
    
      skierHeight.text(`${getHeight(p, p1, p2).toFixed(2)} meter`);
    }
    
    function distance([x1, y1], [x2, y2]) {
      return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
    }
    
    function interpolate([x1, y1], [x2, y2], t) {
      return [x1 + (x2 - x1) * t, y1 + (y2 - y1) * t];
    }
    
    function project([x1, y1], [x2, y2], [x3, y3]) {
      const x21 = x2 - x1,
        y21 = y2 - y1;
      const x31 = x3 - x1,
        y31 = y3 - y1;
      return (x31 * x21 + y31 * y21) / (x21 * x21 + y21 * y21);
    }
    
    function getHeight([xp, yp], [x1, y1], [x2, y2]) {
      // Note that y is counted from top to bottom, so higher y means
      // a point is actually lower.
    
      // First, the total height is 100 metres.
      const pxPerMeter = (y2 - y1) / 100;
    
      // Calculate the height diff in pixels
      const heightDiffPx = y2 - yp;
    
      // Now transform it to meters
      return heightDiffPx / pxPerMeter;
    }
    
    function vektVelger() {
      const vektVerdi = document.querySelector('#vekt');
      const vektDisplay = document.querySelector('#skier-vekt');
    
      vektDisplay.innerHTML = vektVerdi.value;
    }
    
    function potensiellEnergi() {
      const vektVerdi = parseInt(document.querySelector('#skier-vekt').textContent);
      const skierHeight = parseInt(
        document.querySelector('#skier-height').textContent
      );
      const potEDisplay = document.querySelector('#skier-potentialenergi');
      const potEnergi = `${vektVerdi * skierHeight * 9.8}`;
      return (potEDisplay.innerHTML = `${potEnergi}`);
    }
    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8" />
      <script src="https://d3js.org/d3.v5.js"></script>
      <script src="https://d3js.org/d3-path.v1.min.js"></script>
      <script src="https://d3js.org/d3-shape.v1.min.js"></script>
      <script src="https://d3js.org/d3-scale.v3.min.js"></script>
      <script src="https://d3js.org/d3-axis.v1.min.js"></script>
      <script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
      <script src="https://d3js.org/d3-selection.v1.min.js"></script>
    
      <link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap" rel="stylesheet" />
      <link href="https://fonts.googleapis.com/css2?family=Amatic+SC:wght@700&display=swap" rel="stylesheet" />
    </head>
    
    <body>
      <h1>Forsøk på å lage en tutorial i JavaScript og D3.js</h1>
      <h2>Høydeforskjell: <span id="skier-height"></span></h2>
      <h2>Vekt: <span id="skier-vekt">70</span> kg</h2>
      <h2>
        Gravitasjonskraft (fg): <span id="skier-gravitasjon">9.8</span> m/sek/sek
      </h2>
      <h2>Potential Energy: <span id="skier-potentialenergi">0</span> KJOULE</h2>
    
      <svg width="800" height="300"></svg>
      <form>
        Velg utøverens vekt:
        <input type="number" id="vekt" placeholder="Velg utøverens vekt" />
      </form>
      <input type="button" value="velg" onclick="vektVelger()" />
    
      <script src="skier.js"></script>
    </body>
    
    </html>
    0 回复  |  直到 4 年前
        1
  •  2
  •   Ruben Helsloot Rez    4 年前

    我在下面添加了过渡。滑雪者首先降至地面,当这一过渡完成后,他们滑至终点。你甚至可以使用 one of these 过渡缓解方法,让他们一路提速!

    我在处理你的包裹时遇到了一些问题。我不明白为什么你的HTML中有这么多行,但它们破坏了d3.transition:

      <script src="https://d3js.org/d3-path.v1.min.js"></script>
      <script src="https://d3js.org/d3-shape.v1.min.js"></script>
      <script src="https://d3js.org/d3-scale.v3.min.js"></script>
      <script src="https://d3js.org/d3-axis.v1.min.js"></script>
      <script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
      <script src="https://d3js.org/d3-selection.v1.min.js"></script>
    

    将它们全部删除,只留下 <script src="https://d3js.org/d3.v5.js"></script> 完全没问题。

    const height = 500;
    const width = 800;
    const skierIconSvg = 'https://image.flaticon.com/icons/svg/94/94150.svg';
    const gate =
        'https://docs.google.com/drawings/d/1k-mtzjW05U6KZKNt0kAI2pWqKsXFobptsa9kz6grYzA/edit?usp=sharing';
    
    const [p1, p2, p3] = [
        [80, 130],
        [600, 170],
        [750, 190],
    ];
    
    const svg = d3.select('svg');
    
    // Store a reference to the span we're going to update
    const skierHeight = d3.select('#skier-height');
    
    const vek = document.querySelector('#vekt');
    console.log(vek.value);
    
    const line = svg.append('line').attr('stroke', 'black');
    
    const connection = svg.append('line').attr('stroke', 'green');
    
    const projection = svg
        .append('circle')
        .attr('r', 5)
        .attr('stroke', 'red')
        .attr('fill', 'none');
    
    const g = svg
        .append('g')
        .attr('cursor', 'move')
        .attr('pointer-events', 'all')
        .attr('stroke', 'transparent')
        .attr('stroke-width', 30);
    
    const point = g
        .selectAll('image')
        .data([p1, p2])
        .enter()
        .append('circle')
        .attr('r', 10)
        .call(
            d3
                .drag()
                .subject(([x, y]) => ({
                    x,
                    y,
                }))
                .on('start', () => {
                  // Interrupt all transitions
                  skier.interrupt();
                  connection.interrupt();
    
                  // Update the value of p3 to wherever the skier happened to be
                  // This will stop him dead in his tracks instead of snapping him back to the old position
                  p3[0] = Number(skier.attr('x'));
                  p3[1] = Number(skier.attr('y'));
                })
                .on('drag', dragged)
        );
    
    const skier = g
        .append('image')
        .attr('id', 'skier')
        .datum(p3)
        .attr('href', skierIconSvg)
        .attr('width', 100)
        .attr('height', 100)
        .attr('transform', 'translate(-50, -40)')
        .call(
            d3
                .drag()
                .subject(() => ({
                    // Use where the skier is, not where he's supposed to be
                    x: Number(skier.attr('x')),
                    y: Number(skier.attr('y')),
                }))
                .on('start', () => {
                  // Interrupt all transitions
                  skier.interrupt();
                  connection.interrupt();
                })
                .on('drag', dragged)
                .on('end', dropSkier)
        );
    
    update();
    
    function dragged(d) {
        d[0] = d3.event.x;
        d[1] = d3.event.y;
        update();
        potensiellEnergi();
    }
    
    function update() {
        const t = (width + height) / distance(p1, p2);
    
        const l1 = interpolate(p1, p2, t);
    
        const l2 = interpolate(p2, p1, t);
        const p = interpolate(p1, p2, project(p1, p2, p3));
    
        connection.attr('x1', p3[0]).attr('y1', p3[1]);
        connection.attr('x2', p[0]).attr('y2', p[1]);
        projection.attr('cx', p[0]).attr('cy', p[1]);
        line.attr('x1', l1[0]).attr('y1', l1[1]);
        line.attr('x2', l2[0]).attr('y2', l2[1]);
        point.attr('cx', (d) => d[0]).attr('cy', (d) => d[1]);
        skier.attr('x', (d) => d[0]).attr('y', (d) => d[1]);
    
        skierHeight.text(`${getHeight(p, p1, p2).toFixed(2)} meter`);
    }
    function distance([x1, y1], [x2, y2]) {
        return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
    }
    
    function interpolate([x1, y1], [x2, y2], t) {
        return [x1 + (x2 - x1) * t, y1 + (y2 - y1) * t];
    }
    
    function project([x1, y1], [x2, y2], [x3, y3]) {
        const x21 = x2 - x1,
            y21 = y2 - y1;
        const x31 = x3 - x1,
            y31 = y3 - y1;
        return (x31 * x21 + y31 * y21) / (x21 * x21 + y21 * y21);
    }
    
    function getHeight([xp, yp], [x1, y1], [x2, y2]) {
        // Note that y is counted from top to bottom, so higher y means
        // a point is actually lower.
    
        // First, the total height is 100 metres.
        const pxPerMeter = (y2 - y1) / 100;
    
        // Calculate the height diff in pixels
        const heightDiffPx = y2 - yp;
    
        // Now transform it to meters
        return heightDiffPx / pxPerMeter;
    }
    
    function vektVelger() {
        const vektVerdi = document.querySelector('#vekt');
        const vektDisplay = document.querySelector('#skier-vekt');
    
        vektDisplay.innerHTML = vektVerdi.value;
    }
    
    function potensiellEnergi() {
        const vektVerdi = parseInt(document.querySelector('#skier-vekt').textContent);
        const skierHeight = parseInt(
            document.querySelector('#skier-height').textContent
        );
        const potEDisplay = document.querySelector('#skier-potentialenergi');
        const potEnergi = `${vektVerdi * skierHeight * 9.8}`;
        return (potEDisplay.innerHTML = `${potEnergi}`);
    }
    
    function dropSkier(d) {
      const projection = interpolate(p1, p2, project(p1, p2, p3));
      skier
        .transition()
        .duration(500)
        // First down to the ground
        .attr('x', projection[0])
        .attr('y', projection[1])
        .on("end", () => {
          skier
            .transition()
            .duration(2500)
            // First down to the ground
            .attr('x', p2[0])
            .attr('y', p2[1]);
        });
        
        // Remove the line together with the skier
        connection
          .transition()
          .duration(500)
          // First down to the ground
          .attr('x1', projection[0])
          .attr('y1', projection[1]);
    }
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <script src="https://d3js.org/d3.v5.js"></script>
    
            <link
                href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap"
                rel="stylesheet"
            />
            <link
                href="https://fonts.googleapis.com/css2?family=Amatic+SC:wght@700&display=swap"
                rel="stylesheet"
            />
        </head>
    
        <body>
            <h1>Forsøk på å lage en tutorial i JavaScript og D3.js</h1>
            <h2>Høydeforskjell: <span id="skier-height"></span></h2>
            <h2>Vekt: <span id="skier-vekt">70</span> kg</h2>
            <h2>
                Gravitasjonskraft (fg): <span id="skier-gravitasjon">9.8</span> m/sek/sek
            </h2>
            <h2>Potential Energy: <span id="skier-potentialenergi">0</span> KJOULE</h2>
    
            <svg width="800" height="300"></svg>
            <form>
                Velg utøverens vekt:
                <input type="number" id="vekt" placeholder="Velg utøverens vekt" />
            </form>
            <input type="button" value="velg" onclick="vektVelger()" />
    
            <script src="skier.js"></script>
        </body>
    </html>