javascript - 添加过渡到 d3.drag() 行为

标签 javascript d3.js

我即将完成我的第一个 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>

最佳答案

我在下面添加了过渡。滑雪者首先落到地面,当过渡完成后,他们滑到终点。您甚至可以使用 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>

关于javascript - 添加过渡到 d3.drag() 行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63859153/

相关文章:

javascript - 文本区域选择器的自动完成中间 javascript jquery

javascript - D3 : incorporate two datasets in bin - histogram layout

d3.js - 如何根据形状场值向 D3 力有向图添加两种不同的形状?

javascript - 在 Javascript 中读取复杂的 JSON

javascript - AngularJs - 我可以从另一个元素的一个元素上打开弹出窗口吗?

javascript - Css 缩放固定横幅

javascript - 在 D3 中思考 - 选择与追加

javascript - 为什么CSSOM构建不等待Javascript执行?

javascript - 使用 HTML 输入在 D3 中进行动态过滤

javascript - d3.transition().attr ('x' , y) 在 d3.attr ('x' , y) 有效时无效