Javascript创建SVG路径自上而下与自上而下

时间:2019-01-24 16:55:31

标签: javascript svg

我有一个页面显示工作位置的网格,并通过使用SVG +路径绘制框之间的连接来显示从一个位置到另一个位置的进度。

当我将顶部的元素连接到底部的元素时,我的代码工作正常。它正在查找顶部框的XY和底部框的XY并将两者连接起来。

我的问题是我想翻转这段代码,然后从头开始。这意味着我需要底部元素的顶部XY和顶部元素的底部XY并绘制路径。

我一直在尝试偏移量,并且基本上做与工作相反的操作,但是我认为我的数学在某些地方是错误的。

这是自上而下的方法。效果很好。 enter image description here

但是,自下而上的方法是不正确的。某处存在一些数学错误,并且计算导致SVG被切断。 enter image description here

我相信答案就在connectElements()函数之内,因为这是确定坐标的地方。

是否有关于如何纠正这些计算的想法?

小提琴: http://jsfiddle.net/Ly59a2hf/2/

JS代码:

function getOffset(el) {
  var rect = el.getBoundingClientRect();
  return {
    left: rect.left + window.pageXOffset,
    top: rect.top + window.pageYOffset,
    width: rect.width || el.offsetWidth,
    height: rect.height || el.offsetHeight
  };
}

function drawPath(svg, path, startX, startY, endX, endY) {

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

  // check if the svg is big enough to draw the path, if not, set heigh/width
  if (svg.getAttribute("height") < endY) svg.setAttribute("height", endY);
  if (svg.getAttribute("width") < (startX + stroke)) svg.setAttribute("width", (startX + stroke));
  if (svg.getAttribute("width") < (endX + stroke * 3)) svg.setAttribute("width", (endX + stroke * 3));

  var deltaX = (endX - startX) * 0.15;
  var deltaY = (endY - startY) * 0.15;
  // for further calculations which ever is the shortest distance
  var delta = deltaY < absolute(deltaX) ? deltaY : absolute(deltaX);

  // set sweep-flag (counter/clock-wise)
  // if start element is closer to the left edge,
  // draw the first arc counter-clockwise, and the second one clock-wise
  var arc1 = 0;
  var arc2 = 1;
  if (startX > endX) {
    arc1 = 1;
    arc2 = 0;
  }
  // draw tha pipe-like path
  // 1. move a bit down, 2. arch,  3. move a bit to the right, 4.arch, 5. move down to the end 
  path.setAttribute("d", "M" + startX + " " + startY +
    " V" + (startY + delta) +
    " A" + delta + " " + delta + " 0 0 " + arc1 + " " + (startX + delta * signum(deltaX)) + " " + (startY + 2 * delta) +
    " H" + (endX - delta * signum(deltaX)) +
    " A" + delta + " " + delta + " 0 0 " + arc2 + " " + endX + " " + (startY + 3 * delta) +
    " V" + (endY - 30));
}

function connectElements(svg, path, startElem, endElem, type, direction) {

  // Define our container
  var svgContainer = document.getElementById('svgContainer'),
    svgTop = getOffset(svgContainer).top,
    svgLeft = getOffset(svgContainer).left,
    startX,
    startY,
    endX,
    endY,
    startCoord = startElem,
    endCoord = endElem;

  console.log(svg, path, startElem, endElem, type, direction)

  /** 
   * bottomUp - This means we need the top XY of the starting box and the bottom XY of the destination box
   * topDown - This means we need the bottom XY of the starting box and the top XY of the destination box
   */
  switch (direction) {

    case 'bottomUp': // Not Working

      // 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 + 0.5 * getOffset(startElem).width - svgLeft; // x = left offset + 0.5*width - svg's left offset
      startY = getOffset(startCoord).top + getOffset(startElem).height - svgTop; // y = top offset + height - svg's top offset

      // Calculate path's end (x,y) coords
      endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
      endY = endCoord.getBoundingClientRect().top - svgTop;

      break;

    case 'topDown': // Working

      // If first element is lower than the second, swap!
      if (startElem.offsetTop > endElem.offsetTop) {
        var temp = startElem;
        startElem = endElem;
        endElem = temp;
      }

      // 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 + 0.5 * getOffset(startElem).width - svgLeft; // x = left offset + 0.5*width - svg's left offset
      startY = getOffset(startCoord).top + getOffset(startElem).height - svgTop; // y = top offset + height - svg's top offset

      // Calculate path's end (x,y) coords
      endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
      endY = endCoord.getBoundingClientRect().top - svgTop;

      break;
  }

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



}

function connectAll(direction) {

  var svg = document.getElementById('svg1'),
    path = document.getElementById('path1');

  // This is just to help with example. 
  if (direction == 'topDown') {
    var div1 = document.getElementById('box_1'),
      div2 = document.getElementById('box_20');
  } else {
    var div1 = document.getElementById('box_20'),
      div2 = document.getElementById('box_1');
  }

  // connect all the paths you want!
  connectElements(svg, path, div1, div2, 'line', direction);


}

//connectAll('topDown'); // Works fine. Path goes from the bottom of box_1 to the top of box_20
connectAll('bottomUp'); // Doesn't work. I expect path to go from top of box_20 to the bottom of box_1

1 个答案:

答案 0 :(得分:1)

IMO,您可以通过将SVG设置为正确的大小来简化操作。就是将其垂直放置在两个元素之间,并使其从最左侧的X坐标开始。

如果执行此操作,则路径将从以下任一位置开始和结束:

  • X:0或svgWidth
  • Y:0或svgHeight。

然后就绘制路径而言,只需在计算中使用相对方向(startX-> endX和startY-> endY)即可。我将这些变量称为xSignySign。如果您与这些保持一致,那么一切都会正常进行。

最后剩下的并发症是确定圆角弧线必须沿着哪个方向-顺时针或逆时针旋转。您只需要计算出第一个,而另一个则相反。

function getOffset(el) {
  var rect = el.getBoundingClientRect();
  return {
    left: rect.left + window.pageXOffset,
    top: rect.top + window.pageYOffset,
    width: rect.width || el.offsetWidth,
    height: rect.height || el.offsetHeight
  };
}


function drawPath(svg, path, start, end) {

  // get the path's stroke width (if one wanted to be  really precise, one could use half the stroke size)
  var style = getComputedStyle(path)
  var stroke = parseFloat(style.strokeWidth);
  var arrowHeadLength = stroke * 3;

  var deltaX = (end.x - start.x) * 0.15;
  var deltaY = (end.y - start.y) * 0.15;
  // for further calculations which ever is the shortest distance
  var delta = Math.min(Math.abs(deltaX), Math.abs(deltaY));
  var xSign = Math.sign(deltaX);
  var ySign = Math.sign(deltaY);

  // set sweep-flag (counter/clock-wise)
  // If xSign and ySign are opposite, then the first turn is clockwise
  var arc1 = (xSign !== ySign) ? 1 : 0;
  var arc2 = 1 - arc1;

  // draw tha pipe-like path
  // 1. move a bit vertically, 2. arc,  3. move a bit to the horizontally, 4.arc, 5. move vertically to the end 
  path.setAttribute("d", ["M", start.x, start.y,
                          "V", start.y + delta * ySign,
                          "A", delta, delta, 0, 0, arc1, start.x + delta * xSign, start.y + 2 * delta * ySign,
                          "H", end.x - delta * xSign,
                          "A", delta, delta, 0, 0, arc2, end.x, start.y + 3 * delta * ySign,
                          "V", end.y - arrowHeadLength * ySign].join(" "));
}


function connectElements(svg, path, startElem, endElem, type, direction) {

  // Define our container
  var svgContainer = document.getElementById('svgContainer');

  // Calculate SVG size and position
  // SVG is sized to fit between the elements vertically, start at the left edge of the leftmost
  // element and end at the right edge of the rightmost element
  var startRect = getOffset(startElem),
      endRect = getOffset(endElem),
      pathStartX = startRect.left + startRect.width / 2,
      pathEndX = endRect.left + endRect.width / 2,
      startElemBottom = startRect.top + startRect.height,
      svgTop = Math.min(startElemBottom, endRect.top + endRect.height),
      svgBottom = Math.max(startRect.top, endRect.top),
      svgLeft = Math.min(pathStartX, pathEndX),
      svgHeight = svgBottom - svgTop;

  // Position the SVG
  svg.style.left = svgLeft + 'px';
  svg.style.top = svgTop + 'px';
  svg.style.width = Math.abs(pathEndX - pathStartX) + 'px';
  svg.style.height = svgHeight + 'px';

  // Call function for drawing the path
  var pathStart = {x: pathStartX - svgLeft, y: (svgTop === startElemBottom) ? 0 : svgHeight};
  var pathEnd   = {x: pathEndX - svgLeft,   y: (svgTop === startElemBottom) ? svgHeight : 0};
  drawPath(svg, path, pathStart, pathEnd);

}

function connectAll(direction) {

  var svg = document.getElementById('svg1'),
      path = document.getElementById('path1');

  // This is just to help with example. 
  if (direction == 'topDown') {
    var div1 = document.getElementById('box_1'),
      div2 = document.getElementById('box_20');
  } else {
    var div1 = document.getElementById('box_20'),
      div2 = document.getElementById('box_1');
  }

  // connect all the paths you want!
  connectElements(svg, path, div1, div2, 'line');

}

//connectAll('topDown');
connectAll('bottomUp');

http://jsfiddle.net/93Le85tk/3/