创建一个工具提示以在一行(d3.js)上显示Y位置

时间:2020-09-11 20:33:06

标签: javascript d3.js

我设法将滑雪者沿着一条线拖了。最后的重要步骤是创建一个工具提示,该提示会在我将其向上拖动时计算出滑雪者的Y位置。假设该行的顶部对应于100米,该行的底部为0。这可能吗?如果是这样,我该怎么做?

draganddrop skier

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 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 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)
    );
// create a tooltip

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

        <style>
            * {
                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;
            }
        </style>
    </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>

1 个答案:

答案 0 :(得分:1)

是的,这是很有可能的,因为当您即将更新^(?:(?<cn>CN=(?<name>.*?)),)?(?<parent>(?:(?<path>(?:CN|OU).*?),)?(?<domain>(?:DC=.*)+))$ (投影)和p(右下角)的属性时,这是完美的时刻显示它。

您似乎确实删除了其他点-与your last question相比,因此很难将它们用作参考点。从技术上讲,这意味着滑雪者可以超越100米高甚至低于0米。

我使用p2格式化数字,但是如果您愿意,可以轻松使用toFixed()

d3-format
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');

// Store a reference to the span we're going to update
const skierHeight = d3.select("#skier-height");

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 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)
  );
// create a tooltip

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));
  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]);
  skier.attr('x', (d) => d[0]).attr('y', (d) => d[1]);

  skierHeight.text(`${getHeight(p, p1, p2).toFixed(2)} metres`);
}

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;
}
* {
  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;
}