Javascript SVG, управляющий XY на основе направления пути - PullRequest
0 голосов
/ 31 января 2019

У меня есть некоторый код JavaScript, который генерирует SVG-пути для подключения HTML-элементов на странице.Это показывает, какой блок подключен к другому блоку.

В путях используются маркеры, поэтому у меня может быть стрелка на конце, чтобы показать его направление.

В целом, это работает хорошо.Я пытался добавить circle к этой настройке сейчас, который находится прямо в центре начального и конечного элемента.

Моя цель состоит в том, чтобы путь останавливался чуть ниже этого круга, так что стрелкауказывая на него, а не поверх него.

Пример снимка экрана: enter image description here

Пример кода:

//helper functions, it turned out chrome doesn't support Math.sgn() 
function signum(x) {
  return (x < 0) ? -1 : 1;
}

function absolute(x) {
  return (x < 0) ? -x : x;
}

/**
 * Get the offsets of page elements
 * @param el
 */
function getOffset(el) {
  const rect = el.getBoundingClientRect();
  return {
    left: rect.left + window.pageXOffset,
    top: rect.top + window.pageYOffset,
    bottom: rect.bottom - window.pageYOffset,
    width: rect.width || el.offsetWidth,
    height: rect.height || el.offsetHeight
  };
}

/**
 * Draw the path on the SVG using proided coords
 * @param svg
 * @param path
 * @param startX
 * @param startY
 * @param endX
 * @param endY
 */
function drawPath(svg, path, startX, startY, endX, endY, circle) {

  // Get the path's stroke width (if one wanted to be  really precize, one could use half the stroke size)
  const style = getComputedStyle(path);
  const stroke = parseFloat(style.strokeWidth);

  // Check if the svg is big enough to draw the path, if not, set height/width
  if (svg.getAttribute("height") < startY) {
    svg.setAttribute("height", startY + 20);
  }

  if (svg.getAttribute("width") < startX + stroke) {
    svg.setAttribute("width", startX + stroke + 20);
  }
  if (svg.getAttribute("width") < endX + stroke) {
    svg.setAttribute("width", endX + stroke + 20);
  }

  /**
            M = moveto
            L = lineto
            H = horizontal lineto
            V = vertical lineto
            C = curveto
            S = smooth curveto
            Q = quadratic Bézier curve
            T = smooth quadratic Bézier curveto
            A = elliptical Arc
            Z = closepath
         */

  // Straight line from XY Start to XY End
  path.setAttribute(
    "d",
    "M" + startX + " " + startY + " L" + endX + " " + endY
  );

  // Show the starting and ending circle
  if (circle) {
    circle.setAttribute("cx", startX);
    circle.setAttribute("cy", startY);
    circle.setAttribute("cx", endX);
    circle.setAttribute("cy", endY);
  }
}

/**
 * Calculate the coords for where the line will be drawn
 * @param svg
 * @param path
 * @param startElem
 * @param endElem
 * @param type
 */
function connectElements(svg, path, startElem, endElem, circle) {

  // Define our container
  const svgContainer = document.getElementById("svgContainer"),
    svgTop = getOffset(svgContainer).top,
    svgLeft = getOffset(svgContainer).left,
    startCoord = startElem,
    endCoord = endElem;

  let startX, startY, endX, endY;

  // Calculate path's start (x,y)  coords
  // We want the x coordinate to visually result in the element's mid point
  startX =
    getOffset(startCoord).left +
    getOffset(startCoord).width / 2 -
    svgLeft;
  startY =
    getOffset(startCoord).top +
    getOffset(startCoord).height / 2 -
    svgTop;

  // Calculate path's start (x,y)  coords
  // We want the x coordinate to visually result in the element's mid point
  endX =
    getOffset(endCoord).left +
    0.5 * getOffset(endCoord).width -
    svgLeft;
  endY =
    getOffset(endCoord).top +
    getOffset(endCoord).height / 2 -
    svgTop;

  // Call function for drawing the path
  drawPath(svg, path, startX, startY, endX, endY, circle);
}

function connectAll() {

  // Loop over our destinations
  for (let i = 0; i < dest.length; i++) {

    // Define
    const marker = document.createElementNS(
      "http://www.w3.org/2000/svg",
      "marker"
    );
    const path = document.createElementNS(
      "http://www.w3.org/2000/svg",
      "path"
    );
    const markerPath = document.createElementNS(
      "http://www.w3.org/2000/svg",
      "path"
    );
    const defs = document.createElementNS(
      "http://www.w3.org/2000/svg",
      "defs"
    );

    const circle = document.createElementNS(
      "http://www.w3.org/2000/svg",
      "circle"
    );

    // Set definitions attribute
    defs.setAttribute("id", "defs");

    // Create our center circle
    circle.setAttribute("id", "circle_" + dest[i].linkID + "_" + dest[i].boxID);
    circle.setAttribute("cx", "0");
    circle.setAttribute("cy", "0");
    circle.setAttribute("r", "15");
    circle.setAttribute("fill", "red");

    // Append our circle
    document.getElementById('svg1').appendChild(circle);

    // Set up marker (Arrow)
    marker.setAttribute("id", "Triangle");
    marker.setAttribute("viewBox", "0 0 10 10");
    marker.setAttribute("refX", "0");
    marker.setAttribute("refY", "5");
    marker.setAttribute("markerUnits", "strokeWidth");
    marker.setAttribute("markerWidth", "4");
    marker.setAttribute("markerHeight", "3");
    marker.setAttribute("orient", "auto");

    // Append our marker (Arrow)
    marker.appendChild(markerPath);
    markerPath.setAttribute("d", "M 0 0 L 10 5 L 0 10 z");
    markerPath.setAttribute("fill", "#428bca");

    // Create our main path
    path.setAttribute(
      "id",
      "path_" + dest[i].linkID + "_" + dest[i].boxID
    );
    path.setAttribute("class", "path");
    path.setAttribute(
      "marker-end",
      "url(" + window.location + "#Triangle)"
    );

    // Only create one set of definitions
    if (i === 0) {
      document.getElementById('svg1').appendChild(defs);
      document.getElementById('defs').appendChild(marker);
    }

    // Append our path to the SVG
    document.getElementById('svg1').appendChild(path);

    const svg = document.getElementById("svg1"),
      p = document.getElementById(
        "path_" + dest[i].linkID + "_" + dest[i].boxID
      ),
      startingBox = document.getElementById("box_" + dest[i].boxID),
      destinationBox = document.getElementById(
        "box_" + dest[i].destinationBoxID
      );

    // Connect paths
    connectElements(
      svg,
      p,
      startingBox,
      destinationBox,
      circle
    );
  }


}

// Define our boxes to connect
var dest = [{
    "boxID": "16",
    "destinationBoxID": "5",
    "linkID": "1"
  },
  {
    "boxID": "18",
    "destinationBoxID": "1",
    "linkID": "9"
  },
  {
    "boxID": "2",
    "destinationBoxID": "5",
    "linkID": "8"
  }
]

// Run
connectAll()

Моя попытка:

Одна из вещей, которые я пробовал, заключалась в корректировке конечной X координаты для пути следующим образом:

// Straight line from XY Start to XY End
path.setAttribute(
  "d",
  "M" + startX + " " + startY + " L" + (endX-30) + " " + endY
);

Говоря об окончании короткого 30, я ожидал, что стрелка не совсем доходит до середины.

Ну, это прекрасно работает для горизонтальных линий, но когда у меня диагональ,помещает его -30 слева от средней точки, как ему было сказано, когда действительно нужно учитывать направление и корректировать Y.

Горизонтальная линия в порядке с-30, но диагональная не там, где я хочу (понимая, где я СКАЗАЛ, чтобы она была).

enter image description here

Вот пример того, где я мог бы ожидать, что диагональная линия заканчивается в верхнем левом углу:

enter image description here

Как можно настроить XY в зависимости от направления пути или есть что-то большее?

JS Fiddle: http://jsfiddle.net/m4fupk7g/5/

1 Ответ

0 голосов
/ 31 января 2019

Вам необходимо преобразовать ваши линии в углы и расстояния, уменьшить расстояния и затем преобразовать их обратно.Например:

let dx = endX - startX;
let dy = endY - startY;

let angle = Math.atan2(dy, dx);
let distance = Math.sqrt(dx * dx + dy * dy) - 20;

let offsetX = Math.cos(angle) * distance + startX;
let offsetY = Math.sin(angle) * distance + startY;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...