Существует (насколько мне известно), никаких встроенных способов сделать это нет ...
Теперь вы можете самостоятельно настроить алгоритм для выполнения проверки, но он может оказаться немного вычислительным,
В качестве первого шага вы будете проверять только ограничивающие рамки ваших путей, исключая все те, которые никак не попадут на ваш путь:
function intersectBoxes(bb1, bb2, padding = 0) {
// half padding (we'll add it in every direction of our rects)
const pad = padding / 2;
// helper function to get clean labels
const getCorners = (bb) => ({
left: bb.x - pad,
top: bb.y - pad,
right: bb.x + bb.width + pad,
bottom: bb.x + bb.width + pad,
});
const r1 = getCorners(bb1);
const r2 = getCorners(bb2);
// check intersection
return r1.left <= r2.right &&
r1.right >= r2.left &&
r1.top <= r2.bottom &&
r1.bottom >= r2.top;
}
// usage
intersectBoxes(path1.getBBox(), path2.getBBox(), 2);
// @return Boolean
Как только это будет сделано, вы можете начать более тяжелую проверку.SVG2 представляет новый метод isPointInStroke , но в настоящее время он поддерживается только в нескольких браузерах.Так что вам может понадобиться его заполнить.Я не нашел ни одного, поэтому быстро сделал патч для обезьяны, используя эквивалент 2DContext.
С помощью этого метода нам нужно только захватить координаты x, y одного из наших путей по всей его длине и повторно вызывать этот метод.
function slowHitCheck(p1, p2) {
// we will walk along the smallest of both paths
const smallest = p1.getTotalLength() < p2.getTotalLength() ? p1 : p2;
const longest = smallest === p1 ? p2 : p1;
const length = smallest.getTotalLength();
let pos = 0;
while(pos < length) {
const pt = smallest.getPointAtLength(pos);
if(longest.isPointInStroke(pt)) return true;
pos += stroke_width;
}
return false;
}
$(".map").click(function(event) {
var $mapTarget = $(event.target);
if ($mapTarget.is("path")) {
$(".region").attr("class", "region");
const neighbors = getNeighbors($mapTarget[0], $('#Map')[0]);
neighbors.forEach(node => {
node.classList.add('neighbour');
})
$mapTarget.addClass("active");
} else {
return;
}
})
function getNeighbors(target, root, stroke_width = 1) {
const targetBB = target.getBBox();
return [...root.querySelectorAll('path')]
.filter(path =>
path !== target && // not the target
// fast check BBoxes
intersectBoxes(path.getBBox(), targetBB, stroke_width / 2) &&
// walk the path
slowHitCheck(path, target, stroke_width)
);
}
function intersectBoxes(bb1, bb2, padding) {
const pad = padding / 2;
const getCorners = (bb) => ({
left: bb.x - pad,
top: bb.y - pad,
right: bb.x + bb.width + pad,
bottom: bb.x + bb.width + pad,
});
const r1 = getCorners(bb1);
const r2 = getCorners(bb2);
return r1.left <= r2.right &&
r1.right >= r2.left &&
r1.top <= r2.bottom &&
r1.bottom >= r2.top;
}
function slowHitCheck(p1, p2, stroke_width) {
const smallest = p1.getTotalLength() < p2.getTotalLength() ? p1 : p2;
const longest = smallest === p1 ? p2 : p1;
const length = smallest.getTotalLength();
let pos = 0;
while (pos < length) {
const pt = smallest.getPointAtLength(pos);
if (longest.isPointInStroke(pt)) return true;
pos += stroke_width;
}
return false;
}
/* Half related code below:
* Monkey Patches SVGGeometryElement's isPointInStroke
* and is isPointInFill.
* You can check the revision history
* for a simpler version that only worked for SVGPathElements
*/
// Beware untested code below
// There may very well be a lot of cases where it will not work at all
if (window.SVGGeometryElement && !window.SVGGeometryElement.prototype.isPointInStroke) {
monkeyPatchSVGIsPointIn();
}
function monkeyPatchSVGIsPointIn() {
const ctx = get2DContext(0, 0);
const default_ctx = get2DContext(0, 0);
Object.defineProperty(SVGGeometryElement.prototype, 'isPointInStroke', {
value: function isPointInStroke(point) {
returnIfAbrupt(point);
const path = generatePath2DFromSVGElement(this);
setUpContextToSVGElement(ctx, this);
ctx.stroke(path);
return ctx.isPointInStroke(path, point.x, point.y);
}
});
Object.defineProperty(SVGGeometryElement.prototype, 'isPointInFill', {
value: function isPointInFill(point) {
returnIfAbrupt(point);
const path = generatePath2DFromSVGElement(this);
setUpContextToSVGElement(ctx, this);
ctx.fill(path, this.getAttribute('fill-rule') || "nonzero")
return ctx.isPointInPath(path, point.x, point.y, this.getAttribute('fill-rule') || 'nonzero');
}
});
function returnIfAbrupt(svgPoint) {
if (svgPoint instanceof SVGPoint === false) {
throw new TypeError("Failed to execute 'isPointInStroke' on 'SVGGeometryElement':" +
"parameter 1 is not of type 'SVGPoint'.")
}
}
function generatePath2DFromSVGElement(el) {
const def = el instanceof SVGPathElement ?
el.getAttribute('d') :
(el instanceof SVGPolygonElement ||
el instanceof SVGPolylineElement) ?
("M" + el.getAttribute('points').split(' ').filter(Boolean).join('L')) :
"";
const path = new Path2D(def);
if (!def) {
if (el instanceof SVGLineElement) {
path.lineTo(el.getAttribute('x1'), el.getAttribute('y1'))
path.lineTo(el.getAttribute('x2'), el.getAttribute('y2'))
}
if (el instanceof SVGRectElement) {
path.rect(el.getAttribute('x'), el.getAttribute('y'), el.getAttribute('width'), el.getAttribute('height'));
} else if (el instanceof SVGCircleElement) {
path.arc(el.getAttribute('cx'), el.getAttribute('cy'), el.getAttribute('r'), Math.PI * 2, 0);
} else if (el instanceof SVGEllipseElement) {
path.ellipse(el.getAttribute('cx'), el.getAttribute('cy'), el.getAttribute('rx'), el.getAttribute('ry'), 0, Math.PI * 2, 0);
}
}
return path;
}
function setUpContextToSVGElement(ctx, svgEl) {
const default_ctx = get2DContext();
const dict = {
"stroke-width": "lineWidth",
"stroke-linecap": "lineCap",
"stroke-linejoin": "lineJoin",
"stroke-miterlimit": "miterLimit",
"stroke-dashoffset": "lineDashOffset"
};
for (const [key, value] of Object.entries(dict)) {
ctx[value] = svgEl.getAttribute(key) || default_ctx[value];
}
ctx.setLineDash((svgEl.getAttribute("stroke-dasharray") || "").split(' '));
}
function get2DContext(width = 0, height = 0) {
return Object.assign(
document.createElement("canvas"),
{ width, height }
).getContext('2d');
}
}
body {
background: lightblue;
}
.region {
fill: green;
stroke: white;
stroke-width: 0.5;
}
.region:hover {
fill: lightgreen;
}
.active {
fill: red;
}
.neighbour {
fill: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg id="Map" class="map" viewBox="0 0 125 75">
<g>
<path id="Region01" class="region" d="M0,0 L 0,50 L 50,50Z"/>
<path id="Region02" class="region" d="M25,25 L 75,25 L 75,75Z"/>
<path id="Region03" class="region" d="M0,50 L 25,50 L 25,75 L 0,75Z"/>
<path id="Region04" class="region" d="M100,0 L 125,0 L 125,25 L 100,25Z"/>
</g>
</svg>