Reputation: 2617
I have used polygonContains(polygon, [x,y])
in the past and I notice that the polygon
parameter takes coordinates. I do not have that kind of data structure, I have more of a "pre-fab" SVG shape if you will. Specifically SVG rects.
Question: What are some options for implementing the polygongContains()
call when you actually only have SVG rects?
My initial thought was to reverse engineer the SVG rect by doing this at rect creating:
.on('click', function(d) {
var containing_rect = d3.select(this);
console.log(containing_rect)
console.log(containing_rect.x1)
var points_contained = d3.selectAll('circle').filter(function(d) {
return !d3.polygonContains(containing_rect, [x(d.x),y(d.y)]);
})
})
But containing_rect.x1
appeared as undefined
in the log, which leads me to believe my reverse engineer approach will not be able to get the end points of the rect.
Perhaps there is a better way to interface with polygonContains()
in terms of passing arguments to have an SVG rect be used as a 4 sided bounding polygon (that the call expects).
Upvotes: 1
Views: 1283
Reputation: 102174
You can create your own function to check if the circle is inside a rectangle.
For instance, this function I just wrote:
function containing(container, point) {
var border = container.node().getBBox();
var coordinates = {
left: border.x,
right: border.x + border.width,
top: border.y,
bottom: border.y + border.height
};
if (point.attr("cx") > coordinates.left &&
point.attr("cx") < coordinates.right &&
point.attr("cy") > coordinates.top &&
point.attr("cy") < coordinates.bottom) {
return true
} else {
return false
}
}
It takes two arguments, container
(the rectangle) and points
(the circles), both as D3 selections.
Here is a demo of the function, I'm creating 100 circles and positioning them randomly. Those that fall outside the rectangle are coloured yellow, those that fall inside are coloured blue:
var w = 400,
h = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var rectangle = svg.append("rect")
.attr("x", 100)
.attr("y", 50)
.attr("width", 150)
.attr("height", 100)
.attr("fill", "white")
.attr("stroke", "teal");
var circles = svg.selectAll("schrodinger")
.data(d3.range(100))
.enter()
.append("circle")
.attr("cx", function(d) {
return Math.random() * w
})
.attr("cy", function(d) {
return Math.random() * h
})
.attr("r", 4);
circles.each(function() {
var circle = d3.select(this);
circle.attr("fill", function() {
return containing(rectangle, circle) ? "royalblue" : "goldenrod";
})
});
function containing(container, point) {
var border = container.node().getBBox();
var coordinates = {
left: border.x,
right: border.x + border.width,
top: border.y,
bottom: border.y + border.height
};
if (point.attr("cx") > coordinates.left && point.attr("cx") < coordinates.right && point.attr("cy") > coordinates.top && point.attr("cy") < coordinates.bottom) {
return true
} else {
return false
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
Bonus: using a D3 drag, you can check the position of the circles inside the drag function. Here is a demo, drag the rectangle:
var w = 400,
h = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var rectangle = svg.append("rect")
.datum({
x: 100,
y: 50
})
.attr("x", function(d) {
return d.x
})
.attr("y", function(d) {
return d.y
})
.attr("width", 150)
.attr("height", 100)
.attr("fill", "white")
.attr("stroke", "teal")
.call(d3.drag().on("drag", dragged));
var circles = svg.selectAll("schrodinger")
.data(d3.range(100))
.enter()
.append("circle")
.attr("cx", function(d) {
return Math.random() * w
})
.attr("cy", function(d) {
return Math.random() * h
})
.attr("r", 4)
.attr("pointer-events", "none");
circles.each(function() {
var circle = d3.select(this);
circle.attr("fill", function() {
return containing(rectangle, circle) ? "royalblue" : "goldenrod";
})
});
function containing(container, point) {
var border = container.node().getBBox();
var coordinates = {
left: border.x,
right: border.x + border.width,
top: border.y,
bottom: border.y + border.height
};
if (point.attr("cx") > coordinates.left && point.attr("cx") < coordinates.right && point.attr("cy") > coordinates.top && point.attr("cy") < coordinates.bottom) {
return true
} else {
return false
}
}
function dragged(d) {
d3.select(this).attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
circles.each(function() {
var circle = d3.select(this);
circle.attr("fill", function() {
return containing(rectangle, circle) ? "royalblue" : "goldenrod";
})
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
EDIT: In case you want to use polygonContains
, we first get the rectangle's corners the same way we did in the last snippet, and populate an array named polygon
. Then, we use it in your selection:
circles.each(function() {
var points = [+d3.select(this).attr("cx"), +d3.select(this).attr("cy")]
d3.select(this).attr("fill", function() {
return d3.polygonContains(polygon, points) ? "green" : "red";
})
});
Here is a demo:
var w = 400,
h = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var rectangle = svg.append("rect")
.datum({
x: 100,
y: 50
})
.attr("x", function(d) {
return d.x
})
.attr("y", function(d) {
return d.y
})
.attr("width", 150)
.attr("height", 100)
.attr("fill", "white")
.attr("stroke", "teal");
var border = rectangle.node().getBBox();
var corners = {
left: border.x,
right: border.x + border.width,
top: border.y,
bottom: border.y + border.height
};
var polygon = [
[corners.left, corners.top],
[corners.right, corners.top],
[corners.right, corners.bottom],
[corners.left, corners.bottom]
];
var circles = svg.selectAll("schrodinger")
.data(d3.range(100))
.enter()
.append("circle")
.attr("cx", function(d) {
return Math.random() * w
})
.attr("cy", function(d) {
return Math.random() * h
})
.attr("r", 3);
circles.each(function() {
var points = [+d3.select(this).attr("cx"), +d3.select(this).attr("cy")]
d3.select(this).attr("fill", function() {
return d3.polygonContains(polygon, points) ? "green" : "red";
})
});
<script src="https://d3js.org/d3.v4.min.js"></script>
Upvotes: 2
Reputation: 539
You can use
containing_rect.attr("x")
containing_rect.attr("y")
to retrive x and y coordinate of the top-left point of the rect and
containing_rect.attr("width")
containing_rect.attr("height")
to retrieve width and height. Then you'll do the maths ;)
Upvotes: -1