使用svg图像沿d3.js中的一条线拖动 [英] Use a svg image to drag along a line in d3.js

查看:78
本文介绍了使用svg图像沿d3.js中的一条线拖动的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在D3.js中建立一个可视化的图像,为学生说明势能和动能的概念.本质上,我希望学生能够将滑雪者拖到斜坡上(即一条直线),当他们放下滑雪者时,他会再次滑下.我确信这在D3中是一项容易的任务,但是我很难理解我如何才能使滑雪者图标只能沿直线路径拖动?

I want to build a visualization in D3.js that illustrates the concept of potential energy and kinetic energy for my students. In essence, I want the students to be able to drag a skier up a slope (i.e. a Line) and when they drop him, he transits down again. I'm sure it is an easy task in D3 but I'm struggling to understand how I can get the skier icon to be draggable only along the line path?

这是一张图片供说明:

我希望滑雪者图标通过绿色连接器与球切换位置.我用过 https://observablehq.com/@mbostock/closest-point-on 作为该项目的灵感.在这里,您可以上下拖动圆,但是小球将始终在该线上.我想和滑雪者做同样的事情,但我一直在努力了解如何才能做到这一点?

I want the skier icon to switch place with the ball with the green connector. I have used https://observablehq.com/@mbostock/closest-point-on-line as inspiration for this project. Here, you can drag the circle up and down but the small ball will always be on that line. I want the same thing with my skier but I'm struggling to understand how I can accomplish this?

这是小提琴.

const height = 500;
const width = 960;
const skierIconSvg = "https://image.flaticon.com/icons/svg/94/94150.svg";


const [p1, p2, p3] = [
  [width / 3, 213],
  [(2 * width) / 3, 300],
  [width / 2, 132],
];

const svg = d3.select('svg');

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 skier = svg
  .append('image')
  .attr('id', 'skier')
  .attr('href', skierIconSvg)
  .attr('x', -40)
  .attr('y', -80)
  .attr('width', 100)
  .attr('height', 100);

const point = svg
  .append('g')
  .attr('cursor', 'move')
  .attr('pointer-events', 'all')
  .attr('stroke', 'transparent')
  .attr('stroke-width', 30)
  .selectAll('circle')
  .data([p1, p2, p3])
  .enter()
  .append('circle')
  .attr('r', 10)
  .attr('fill', (d, i) => (i === 2 ? 'red' : null))
  .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();
}

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]);
}

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);
}

* {
  font-family: 'Amatic SC', cursive;
  text-align: center;
}

h1 {
  font-size: 50px;
}

p {
  font-size: 20px;
}

path {
  fill: none;
  stroke: #000;
  stroke-width: 4px;
}

circle {
  fill: steelblue;
  stroke: #fff;
  stroke-width: 3px;
}

<!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>
  <svg width="960" height="500"></svg>

  <script src="main.js"></script>
</body>

</html>

推荐答案

您非常亲密!我唯一要做的是绘制两个圆圈而不是三个圆圈,并为滑雪者提供最后一个圆圈的坐标.然后,我将 transform 应用于滑雪者,因为否则他将被锚定在图片的左上方,而不是底部中心.

You were very close! The only thing I did was draw two circles instead of three, and give the skier the coordinates of the last circle. Then I applied transform to the skier, because otherwise he would be anchored at the top left of the image, instead of the bottom centre.

我使用了 datum 而不是 data ,因为 datum 只需要一个值,而 data 需要一个数组.请参阅此bl.ock ,以获取有关其的良好教程.

I used datum instead of data, since datum only expects one value, while data expects an array. See this bl.ock for a good tutorial on it.

const height = 500;
const width = 960;
const skierIconSvg = "https://image.flaticon.com/icons/svg/94/94150.svg";


const [p1, p2, p3] = [
  [width / 3, 213],
  [(2 * width) / 3, 300],
  [width / 2, 132],
];

const svg = d3.select('svg');

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('circle')
  .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, -100)")
  .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();
}

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]);
}

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);
}

* {
  font-family: 'Amatic SC', cursive;
  text-align: center;
}

h1 {
  font-size: 50px;
}

p {
  font-size: 20px;
}

path {
  fill: none;
  stroke: #000;
  stroke-width: 4px;
}

circle {
  fill: steelblue;
  stroke: #fff;
  stroke-width: 3px;
}

<!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>
  <svg width="960" height="500"></svg>

  <script src="main.js"></script>
</body>

</html>

这篇关于使用svg图像沿d3.js中的一条线拖动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆