Как определить, находится ли точка внутри SVG-элемента проанализированного, но не отображенного SVG-файла? - PullRequest
0 голосов
/ 31 октября 2018

Проблема заключается в том, как определить, находится ли точка (x,y) внутри общего графического элемента SVG (например, rect, ellipse, path) до ее отображения на экране. Это означает, что любые методы получения позиции в экранных координатах недоступны - могут использоваться только данные, указанные в файле SVG.

Исходным фоном является то, что моей компании нужна система для маркировки SVG-элементов из Adobe Illustrator, и, поскольку AI не допускает значительных модификаций вывода SVG, мы решили основать это на создании отдельного слоя tags с текстовыми объектами. содержащий текст тега. Затем на стороне клиента файлы анализируются и теги добавляются в соответствующие элементы SVG, а затем удаляется группа tags.

Получить основные координаты тегов легко (с помощью ES5 - извините). Для данной svg строки:

var parser = new DOMParser();

var svgDom = parser.parseFromString(svg, "image/svg+xml");
var tagsElement = svgDom.getElementById("tags");

if (tagsElement) {
    var textElementHtmlCollection = tagsElement.getElementsByTagName("text");
    var textElementArray = Array.prototype.slice.call(textElementHtmlCollection);
    var tagDataArray = textElementArray.map(function (el) {
        return {
            text: el.textContent,
            pos: {
                x: el.transform.baseVal[0].matrix.e,
                y: el.transform.baseVal[0].matrix.f
            }
        };
    });

Прямо сейчас работает ОЧЕНЬ базовая версия системы тегов, но каждый тип узла должен обрабатываться отдельно, и я дошел до реализации модифицированного алгоритма числового числа обмоток (https://en.wikipedia.org/wiki/Winding_number) для path узлов (проблемы, возникающие в случае «близкого вызова», не учитываются для варианта использования):

function windingNumber (element,x,y) {
    var isInside, p1 = undefined, p2 = undefined, px, py, R, wn, sum=0;
    var totalLength = element.getTotalLength();

    var resolution = 5;
    var threshold = 0.1;

    for (var len = 0; len <= totalLength; len += resolution) {
        p1 = p2;
        p2 = element.getPointAtLength(len);
        if (p1) {
            px = p1.x - x;
            py = p1.y - y;
            R = px*px + py*py;
            sum += (px*(p2.y-p1.y)/R - py*(p2.x-p1.x)/R);
        }
        wn = sum/(2*Math.PI);
        if (Math.abs(wn) > 1-threshold) {
            isInside = true;
            break;
        }
    }
    return isInside;
}

Теперь я решил, что должен быть другой путь, так как система становится чрезвычайно сложной, принимая во внимание, что к элементам могут применяться все виды transform.

Есть ли более простой способ сделать это? Идеи для разных подходов также приветствуются.

ОБНОВЛЕНИЕ Мой ответ ужасен , но это тот, который я в конечном итоге использовал. Инфраструктура, окружающая приложение, и проблема усложняли работу «правильного» решения (отображение SVG скрыто, используйте document.elementsFromPoint()).

Тем не менее, чем больше математики в коде, тем веселее.

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

Предложение Роберта должно быть самым простым подходом.

Вы должны просто иметь возможность добавить свой тестовый элемент в SVG, проверить, не закончена ли точка, используя elementFromPoint(), затем снова удалить элемент.

function testPointInElement(elem, x, y) {
  svg.appendChild(elem);
  var hitElem = document.elementFromPoint(x,y);
  // If you remove it again before the browser refreshes, it should not ever be visible
  svg.removeChild(elem);
  return hitElem === elem;
}

var svg = document.getElementById("mysvg");

// Generates a sample test element
function sampleElement() {
  var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  path.setAttribute("d", "M 200,300 L 300,100 L 350,350 L 200,200 L 400,200 Z");
  path.setAttribute("fill-rule", "evenodd");
  return path;
}

function testPointInElement(elem, x, y) {
  svg.appendChild(elem);
  var hitElem = document.elementFromPoint(x,y);
  // If you remove it again before the browser refreshes, it should not ever be visible
  svg.removeChild(elem);
  return hitElem === elem;
}



var testElem = sampleElement();

svg.addEventListener("click", function(evt) {
  var x = evt.clientX;
  var y = evt.clientY;
  
  // Find out if any element is at x,y
  if (testPointInElement(testElem, x,y)) {
    alert("Hit!");
  }
});
svg {
  background-color: linen;
}
<svg id="mysvg" width="400" height="400">
  <!-- ghost path showing where our test path is -->
  <path d="M 200,300 L 300,100 L 350,350 L 200,200 L 400,200 Z" fill-rule="evenodd" opacity="0.1"/>
</svg>

В SVG2 это даже проще. Вы сможете позвонить isPointInFill() на вашем элементе. Но поддержка браузера для этого плохая банкомат.

0 голосов
/ 31 октября 2018

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

// Instead of taking into account transformations on the element,
// invert the transformations and apply them to the point.
if (element.transform && element.transform.baseVal) {
    // Get the transformation matrix information.
    var transform = element.transform.baseVal.consolidate();
    if (transform) {
        // Calculate the inverse of the transformation matrix.
        var transformMatrix = transform.matrix;
        var inverseTM = transformMatrix.inverse();

        // Calculate the new x and y values as a matrix multiplication of
        // the inverse transformation matrix and the vector (x,y,1).
        //
        //   a c e       x       a*x + c*y + e       newX
        // ( b d f ) * ( y ) = ( b*x + d*y + f ) = ( newY )
        //   0 0 1       1             1              1
        //
        var newX, newY;
        newX = inverseTM.a*x + inverseTM.c*y + inverseTM.e;
        newY = inverseTM.b*x + inverseTM.d*y + inverseTM.f;
        x = newX;
        y = newY;
    }
}

Я обновлюсь, если у меня возникнут какие-либо проблемы, но, похоже, это работает на удивление хорошо.

Остерегайтесь любого, кто делает это: число обмоток на элементах SVG не простое и может сломаться для более сложных форм. Тем не менее, поскольку формы в нашем случае довольно просты, это решение подойдет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...