In my index.html I have an svg viewbox:
<svg viewBox = "0 0 2000 2000" version = "1.1">
</svg>
I wish to animate an svg ellipse I have created such that when the ellipse is clicked it moves vertically to a certain y point we'll call TOP, and if clicked again moves back to its original position called BOTTOM. Currently I am using the following code which works to an extent.
var testFlag = 0;
d3.select("#ellipseTest").on("click", function(){
if (testFlag == 0){
d3.select(this)
.attr("transform", "translate(0,0)")
.transition()
.duration(450)
.ease("in-out")
.attr("transform", "translate(0,-650)")
testFlag = 1;
}else{
d3.select(this)
.attr("transform", "translate(0,-650)")
.transition()
.duration(450)
.ease("in-out")
.attr("transform", "translate(0,0)")
testFlag = 0;
}
});
The issue however, is that I have also made the ellipse drag-able up to the point TOP and down to the point BOTTOM. So if I drag the ellipse halfway in between TOP and BOTTOM, then click the ellipse, it animates vertically above TOP, instead of stopping when it reaches TOP (and like wise for BOTTOM when animating down). This seems to be a result of how the transform translate method works.
I believe I can resolve this if I create a function that dynamically returns the amount the ellipse should translate relative to where the mouse clicks (or better yet, to where the ellipse is currently position) . The problem is that I can't figure out how to get the current y position of the element relative to the viewbox, rather I can only get the position with respect to the entire page.
Here is the faulty code I am using to get the position of my click:
var svg2 = document.getElementsByTagName('svg')[0];
var pt = svg2.createSVGPoint();
document.documentElement.addEventListener('click',function(evt){
pt.x = evt.clientX;
pt.y = evt.clientY;
console.log('Position is.... ' + pt.y);
},false);
Here is my working code to make the ellipse draggable:
//These points are all relative to the viewbox
var lx1 = -500;
var ly1 = 1450;
var lx2 = -500;
var ly2 = 800;
function restrict(mouse){ //Y position of the mouse
var x;
if ( (mouse < ly1) && (mouse > ly2) ) {
x = mouse;
}else if (mouse > ly1){
x = ly1;
}else if (mouse < ly2) {
x = ly2;
}
return x;
}
var drag = d3.behavior.drag()
.on("drag", function(d) {
var mx = d3.mouse(this)[0];
var my = d3.mouse(this)[1];
d3.select("#ellipseTest")
.attr({
cx: lx2,
cy: restrict(d3.mouse(this)[1])
});
})
Animate cy instead of the transformation...
var testFlag = 0;
d3.select("#ellipseTest").on("click", function(){
if (testFlag == 0){
d3.select(this)
.transition()
.duration(450)
.ease("in-out")
.attr("cy", 0)
testFlag = 1;
}else{
d3.select(this)
.transition()
.duration(450)
.ease("in-out")
.attr("cy", 650)
testFlag = 0;
}
});
Related
I'm currently working on a circular progress bar. I've been able to create the progress bar but I need to at border-radius to the end and start of the progress and also change de font-size of the % number. I've searched around and in theory, the border-radius is added with stroke-linecap="round", but that doesn't seem to work for me. I haven't been able to find anything regarding the font-size.Adding a shadow to the bar would also be great, but that's not truly necessary.
I have already looked at this answer but I can't seem to get it right.
function drawProgress(percentage, element, svg) {
if (svg) {
svg.selectAll("*").remove();
}
var wrapper = element;
var start = 0;
var colours = {
fill: '#3F88FB',
track: '#DDDDDD',
text: '#444444',
}
var radius = 34;
var border = 8;
var strokeSpacing = 4;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = percentage;
var progress = start;
var step = percentage < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.svg.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('fill', colours.track)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('fill', colours.fill)
.attr('stroke-width', strokeSpacing + 'px')
.attr('stroke-linecap', 'round');
//Add text value
var numberText = track.append('text')
.attr('fill', colours.text)
.attr('text-anchor', 'middle')
.attr('dy', '0.5rem');
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * percentage));
//update text value
numberText.text(formatText(percentage));}
var svgVisitas;
drawProgress(50 / 100, document.getElementById('radialprogressVisitas'), svgVisitas);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="radialprogressVisitas"></div>
Thank you. If you need anything else, please specify it in the comments and I'll edit the question. The project is run on Visual Basic.
I'm using D3 to zoom onto an image on click and on Mousewheel. Everything is working fine but the first zoom glitches a lot.
Here is the demo of the app.
This is how I'm zooming towards the objects:
const star = "https://gmg-world-media.github.io/skymap-v1dev/static/media/star.19b34dbf.svg";
const galaxy = "https://gmg-world-media.github.io/skymap-v1dev/static/media/galaxy.c5e7b011.svg";
const nebula = "https://gmg-world-media.github.io/skymap-v1dev/static/media/nebula.d65f45e5.svg";
const exotic = "https://gmg-world-media.github.io/skymap-v1dev/static/media/exotic.21ad5d39.svg";
const sWidth = window.innerWidth;
const sHeight = window.innerHeight;
const x = d3.scaleLinear().range([0, sWidth]).domain([-180, 180]);
const y = d3.scaleLinear().range([0, sHeight]).domain([-90, 90]);
const svg = d3.select("#render_map").append("svg").attr("width", sWidth).attr("height", sHeight);
const node = svg.append("g").attr('class', 'scale-holder');
const zoom = d3
.zoom()
.scaleExtent([1, 30])
.translateExtent([
[0, 0],
[sWidth, sHeight]
])
svg.call(zoom);
const imgG = node.append("g");
imgG
.insert("svg:image")
.attr("preserveAspectRatio", "none")
.attr("x", 0)
.attr("y", 0)
.attr("width", sWidth)
.attr("height", sHeight)
.attr("xlink:href", "https://gmg-world-media.github.io/skymap-v1dev/img-set/image-1.jpg");
imgG
.insert("svg:image")
.attr("preserveAspectRatio", "none")
.attr("x", 0)
.attr("y", 0)
.attr("width", sWidth)
.attr("height", sHeight)
.attr("xlink:href", "https://gmg-world-media.github.io/skymap-v1dev/img-set/image.jpg");
// Draw objects on map with icon size 8
drawObjects(8)
function drawObjects(size) {
const dataArray = [];
const to = -180;
const from = 180;
const fixed = 3;
const objectType = ["ST", "G", "N", "E"];
// Following loop is just for demo.
// Actual data comes from a JSON file.
for (let i = 0; i < 350; i++) {
const latitude = (Math.random() * (to - from) + from).toFixed(fixed) * 1;
const longitude = (Math.random() * (to - from) + from).toFixed(fixed) * 1;
const random = Math.floor(Math.random() * objectType.length);
dataArray.push({
"Longitude": longitude,
"Latitude": latitude,
"Category": objectType[random]
})
}
for (let index = 0; index < dataArray.length; index++) {
// Loop over the data
const item = dataArray[index]
const mY = y(Number(item.Latitude))
const mX = x(Number(item.Longitude))
if (node.select(".coords[index='" + index + "']").size() === 0) {
let shape = star;
// Plot various icons based on Category
switch (item.Category) {
case "ST":
shape = star;
break;
case "G":
shape = galaxy;
break;
case "N":
shape = nebula;
break;
case "E":
shape = exotic;
break;
}
const rect = node
.insert("svg:image")
.attr("class", "coords")
.attr("preserveAspectRatio", "none")
.attr("x", mX)
.attr("y", mY)
.attr("width", size)
.attr("height", size)
.attr("cursor", "pointer")
.attr("index", index)
.attr("xlink:href", shape)
.attr("opacity", "0")
.on("click", function() {
handleObjectClick(index, mX, mY)
})
// Add the objects on the map
rect.transition().duration(Math.random() * (2000 - 500) + 500).attr("opacity", "1")
}
}
}
function boxZoom(x, y) {
// Zoom towards the selected object
// This is the part responsible for zooming
svg
.transition()
.duration(1000)
.call(
zoom.transform,
d3.zoomIdentity
.translate(sWidth / 2, sHeight / 2)
.scale(6)
.translate(-x, -y)
);
}
function handleObjectClick(currentSelect, x, y) {
// Appending some thumbnails to the clicked object here...
//Call the zoom function
boxZoom(x, y)
}
#render_map {
width: 100vw;
height: 100vh;
margin: 0 auto;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="render_map">
</div>
This zoom doesn't seem to be working here. But it does definitely work in the app. I've not modified the piece of code responsible for zooming. (See this demo instead.)
The problem is that zoom jumps when you do it for the first time after a page load, and then it fixes itself.
I don't understand what I'm doing wrong here. Any hints would be lovely.
TIA!
The issue seems caused by a very expensive CSS repaint cycle. I tested this in Firefox by going to Performance in the DEV tools and starting a recording, then zooming for the first time.
I saw the fps drop enormously, and that the repaint took as much as 250ms. Normally, that is 10-50ms.
I have some pointers:
Why do you have two images behind each other? Big images are definitely the reason why repainting takes this long, and your image is 8000x4000 pixels! Start by removing the image that we're not even seeing;
Try adding an initial value of transform="translate(0, 0) scale(1)" to .scale-holder. I have a feeling that adding this the first time is what forces the entire screen to be repainted. Maybe changing an existing scale value is an easier mathematical operation than applying a scale value to something that was not scaled before;
If that doesn't help, compress the image to at most 1600 or even 1080 pixels wide. Us mortals should not even be able to see the difference.
I have a svg element ; the nodes, links, labels etc. are appended to it. I got the zoom-to-particular-node-by-name functionality running but the issue is after zooming automatically to the respective node , whenever I try to pan svg (by clicking and dragging it around), it resets the zoom and the coordinates to how it was before I zoomed to a particular node. I think it has to do with the way d3.event.transform works but I am not able to fix it. I want to be able to continue panning and zooming from the node I zoomed to without resetting any values.
(Also, from a bit of debugging , I observed that the cx and cy coordinates for the nodes did not change by zooming and panning from the code, but If I were to zoom and pan to a node manually , then it would. I guess that is the problem)
var svg1 = d3.select("svg");
var width = +screen.width;
var height = +screen.height - 500;
svg1.attr("width", width).attr("height", height);
var zoom = d3.zoom();
var svg = svg1
.call(
zoom.on("zoom", function() {
svg.attr("transform", d3.event.transform);
})
)
.on("dblclick.zoom", null)
.append("g");
function highlightNode() {
var userInput = document.getElementById("targetNode");
theNode = d3.select("#" + userInput.value);
const isEmpty = theNode.empty();
if (isEmpty) {
document.getElementById("output").innerHTML = "Given node doesn't exist";
} else {
document.getElementById("output").innerHTML = "";
}
svg
.transition()
.duration(750)
.attr(
"transform",
"translate(" +
-(theNode.attr("cx") - screen.width / 2) +
"," +
-(theNode.attr("cy") - screen.height / 4) +
")"
// This works correctly
);
}
I'm having trouble translating a D3 example with a zoom behavior from v3 to v5. My code is based on this example: https://bl.ocks.org/mbostock/2206340 by Mike Bostock. I use react and I get these errors "d3.zoom(...).translate is not a function" and "d3.zoom(...).scale is not a function". I looked in the documentation, but could not find scale or translate just scaleBy and translateTo and translateBy. I can't figure out how to do it either way.
componentDidMount() {
this.drawChart();
}
drawChart = () => {
var width = window.innerWidth * 0.66,
height = window.innerHeight * 0.7,
centered,
world_id;
window.addEventListener("resize", function() {
width = window.innerWidth * 0.66;
height = window.innerHeight * 0.7;
});
var tooltip = d3
.select("#container")
.append("div")
.attr("class", "tooltip hidden");
var projection = d3
.geoMercator()
.scale(100)
.translate([width / 2, height / 1.5]);
var path = d3.geoPath().projection(projection);
var zoom = d3
.zoom()
.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([height * 0.197, 3 * height])
.on("zoom", zoomed);
var svg = d3
.select("#container")
.append("svg")
.attr("width", width)
.attr("class", "map card shadow")
.attr("height", height);
var g = svg.append("g").call(zoom);
g.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
var world_id = data2;
var world = data;
console.log(world);
var rawCountries = topojson.feature(world, world.objects.countries)
.features,
neighbors = topojson.neighbors(world.objects.countries.geometries);
console.log(rawCountries);
console.log(neighbors);
var countries = [];
// Splice(remove) random pieces
rawCountries.splice(145, 1);
rawCountries.splice(38, 1);
rawCountries.map(country => {
//console.log(parseInt(country.id) !== 010)
// Filter out Antartica and Kosovo
if (parseInt(country.id) !== parseInt("010")) {
countries.push(country);
} else {
console.log(country.id);
}
});
console.log(countries);
g.append("g")
.attr("id", "countries")
.selectAll(".country")
.data(countries)
.enter()
.insert("path", ".graticule")
.attr("class", "country")
.attr("d", path)
.attr("data-name", function(d) {
return d.id;
})
.on("click", clicked)
.on("mousemove", function(d, i) {
var mouse = d3.mouse(svg.node()).map(function(d) {
return parseInt(d);
});
tooltip
.classed("hidden", false)
.attr(
"style",
"left:" + mouse[0] + "px;top:" + (mouse[1] - 50) + "px"
)
.html(getCountryName(d.id));
})
.on("mouseout", function(d, i) {
tooltip.classed("hidden", true);
});
function getCountryName(id) {
var country = world_id.filter(
country => parseInt(country.iso_n3) == parseInt(id)
);
console.log(country[0].name);
console.log(id);
return country[0].name;
}
function updateCountry(d) {
console.log(world_id);
var country = world_id.filter(
country => parseInt(country.iso_n3) == parseInt(d.id)
);
console.log(country[0].name);
var iso_a2;
if (country[0].name === "Kosovo") {
iso_a2 = "xk";
} else {
iso_a2 = country[0].iso_a2.toLowerCase();
}
// Remove any current data
$("#countryName").empty();
$("#countryFlag").empty();
$("#countryName").text(country[0].name);
var src = "svg/" + iso_a2 + ".svg";
var img = "<img id='flag' class='flag' src=" + src + " />";
$("#countryFlag").append(img);
}
// Remove country when deselected
function removeCountry() {
$("#countryName").empty();
$("#countryFlag").empty();
}
// When clicked on a country
function clicked(d) {
if (d && centered !== d) {
centered = d;
updateCountry(d);
} else {
centered = null;
removeCountry();
}
g.selectAll("path").classed(
"active",
centered &&
function(d) {
return d === centered;
}
);
console.log("Clicked");
console.log(d);
console.log(d);
var centroid = path.centroid(d),
translate = projection.translate();
console.log(translate);
console.log(centroid);
projection.translate([
translate[0] - centroid[0] + width / 2,
translate[1] - centroid[1] + height / 2
]);
zoom.translate(projection.translate());
g.selectAll("path")
.transition()
.duration(700)
.attr("d", path);
}
// D3 zoomed
function zoomed() {
console.log("zoomed");
projection.translate(d3.event.translate).scale(d3.event.scale);
g.selectAll("path").attr("d", path);
}
};
render() {
return (
<div className="container-fluid bg">
<div class="row">
<div className="col-12">
<h2 className="header text-center p-3 mb-5">
Project 2 - World value survey
</h2>
</div>
</div>
<div className="row mx-auto">
<div className="col-md-8">
<div id="container" class="mx-auto" />
</div>
<div className="col-md-4">
<div id="countryInfo" className="card">
<h2 id="countryName" className="p-3 text-center" />
<div id="countryFlag" className="mx-auto" />
</div>
</div>
</div>
</div>
);
}
I won't go into the differences between v3 and v5 partly because it has been long enough that I have forgotten much of the specifics and details as to how v3 was different. Instead I'll just look at how to implement that example with v5. This answer would require adaptation for non-geographic cases - the geographic projection is doing the visual zooming in this case.
In your example, the zoom keeps track of the zoom state in order to set the projection properly. The zoom does not set a transform to any SVG element, instead the projection reprojects the features each zoom (or click).
So, to get started, with d3v5, after we call the zoom on our selection, we can set the zoom on a selected element with:
selection.call(zoom.transform, transformObject);
Where the base transform object is:
d3.zoomIdentity
d3.zoomIdentity has scale (k) of 1, translate x (x) and y (y) values of 0. There are some methods built into the identity prototype, so a plain object won't do, but we can use the identity to set new values for k, x, and y:
var transform = d3.zoomIdentity;
transform.x = projection.translate()[0]
transform.y = projection.translate()[1]
transform.k = projection.scale()
This is very similar to the example, but rather than providing the values to the zoom behavior itself, we are building an object that describes the zoom state. Now we can use selection.call(zoom.transform, transform) to apply the transform. This will:
set the zoom's transform to the provided values
trigger a zoom event
In our zoom function we want to take the updated zoom transform, apply it to the projection and then redraw our paths:
function zoomed() {
// Get the new zoom transform
transform = d3.event.transform;
// Apply the new transform to the projection
projection.translate([transform.x,transform.y]).scale(transform.k);
// Redraw the features based on the updaed projection:
g.selectAll("path").attr("d", path);
}
Note - d3.event.translate and d3.event.scale won't return anything in d3v5 - these are now the x,y,k properties of d3.event.transform
Without a click function, we might have this, which is directly adapted from the example in the question. The click function is not included, but you can still pan.
If we want to include a click to center function like the original, we can update our transform object with the new translate and call the zoom:
function clicked(d) {
var centroid = path.centroid(d),
translate = projection.translate();
// Update the translate as before:
projection.translate([
translate[0] - centroid[0] + width / 2,
translate[1] - centroid[1] + height / 2
]);
// Update the transform object:
transform.x = projection.translate()[0];
transform.y = projection.translate()[1];
// Apply the transform object:
g.call(zoom.transform, transform);
}
Similar to the v3 version - but by applying the zoom transform (just as we did initially) we trigger a zoom event, so we don't need to update the path as part of the click function.
All together that might look like this.
There is on detail I didn't include, the transition on click. As we triggering the zoomed function on both click and zoom, if we included a transition, panning would also transition - and panning triggers too many zoom events for transitions to perform as desired. One option we have is to trigger a transition only if the source event was a click. This modification might look like:
function zoomed() {
// Was the event a click?
var event = d3.event.sourceEvent ? d3.event.sourceEvent.type : null;
// Get the new zoom transform
transform = d3.event.transform;
// Apply the new transform to the projection
projection.translate([transform.x,transform.y]).scale(transform.k);
// Redraw the features based on the updaed projection:
(event == "click") ? g.selectAll("path").transition().attr("d",path) : g.selectAll("path").attr("d", path);
}
I'm using d3.js to draw some green circles on an SVG container based on data in my list myList.
Here is an example of that circle:
Now I want to implement the following behavior:
When the user's mouse passes over the circle, a rectangle should appear.
The rectangle's top-left corner should be the center of the circle.
The rectangle should disappear if and only if the mouse is outside the borders of the circle and the rectangle.
Below is the code I have written to solve this problem (with #Cyril's help, Thank you!). But it doesn't work right. While the mouse pointer hovers over the circle, the rectangle is visible. However, when the mouse pointer moves South-East into the rectangle (even the part of the rectangle that overlaps a quadrant of the circle), the circle's mouseout event fires and the rectangle disappears -- even before the rectangle's mouseover event has yet to fire. Technically, I consider this to still be in the circle. But clearly d3.js does not.
So how can I implement this feature given the complexity of these mouse events and minute differences (and race conditions) that accompany them?
var myList = [
{"centerX": 200, "centerY": 300, "mouseIn": {"circle":false, "rectangle":false}},
{"centerX": 400, "centerY": 500, "mouseIn": {"circle":false, "rectangle":false}},
];
var myCircle = self.svgContainer.selectAll(".dots")
.data(myList).enter().append("circle")
.attr("class", "dots")
.attr("cx", function(d, i) {return d.centerX})
.attr("cy", function(d, i) {return d.centerY})
.attr("r", 15)
.attr("stroke-width", 0)
.attr("fill", function(d, i) {return "Green"})
.style("display", "block");
myCircle.on({
"mouseover": function(d) {
console.log('\n\nCircle MouseOver ******************************************');
var wasCursorIn = d.mouseIn.circle || d.mouseIn.rectangle;
console.log('wasCursorIn = ', JSON.stringify(wasCursorIn));
d.mouseIn.circle = true;
console.log('d.mouseIn = ', JSON.stringify(d.mouseIn));
var isCursorIn = d.mouseIn.circle || d.mouseIn.rectangle;
console.log('isCursorIn = ', isCursorIn);
if ((!wasCursorIn) && isCursorIn) {
if (typeof d.rectangle === 'undefined' || d.rectangle === null)
d.rectangle = self.svgContainer.append("rect")
.attr("x", d.centerX)
.attr("y", d.centerY)
.attr("width", 100)
.attr("height", 50)
.attr("stroke-width", 2)
.attr("fill", "DimGray")
.attr("stroke", "DarkKhaki")
.on("mouseover", function(e) {
console.log('\n\nRectangle MouseOver ***************************************');
console.log("d = ", JSON.stringify(d));
d.mouseIn.rectangle = true;
console.log("d = ", JSON.stringify(d));
}
)
.on("mouseout", function(e) {
console.log('\n\nRectangle MouseOut ****************************************');
console.log("d = ", JSON.stringify(d));
var wasCursorOut2 = (!d.mouseIn.circle) && (!d.mouseIn.rectangle);
console.log('wasCursorOut2 = ', wasCursorOut2);
d.mouseIn.rectangle = false;
console.log('d.mouseIn = ', JSON.stringify(d.mouseIn));
var isCursorOut2 = (!d.mouseIn.circle) && (!d.mouseIn.rectangle);
console.log('isCursorOut2 = ', isCursorOut2);
if ((!wasCursorOut2) && isCursorOut2) {
d3.select(this).style("cursor", "default");
d.rectangle.remove();
d.rectangle = null;
}
}
)
.style("display", "block");
else
d.rectangle.style("display", "block");
}
},
"mouseout": function(d) {
console.log('\n\nCircle MouseOut *******************************************');
var wasCursorOut = (!d.mouseIn.circle) && (!d.mouseIn.rectangle);
console.log('wasCursorOut = ', wasCursorOut);
d.mouseIn.circle = false;
console.log('d.mouseIn = ', JSON.stringify(d.mouseIn));
var isCursorOut = (!d.mouseIn.circle) && (!d.mouseIn.rectangle);
console.log('isCursorOut = ', isCursorOut);
if ((!wasCursorOut) && isCursorOut) {
if (!(typeof d.rectangle === 'undefined' || d.rectangle === null))
d.rectangle.style("display", "none");
}
}
}
);
When SVG elements overlap, the mouse events fire for the top most element. When the mouse moves from one element to another element, the order of events is mouseout event (for element that mouse is leaving) followed by mouseover event (for element that mouse is entering). Since you only want to remove the rect element when the mouse has left both the circle and rect elements, you will need to listen to the mouseout events on both the circle and rect elements and only remove the rect element when the mouse position is outside both elements.
The following is one possible solution for determining whether or not a mouse position is inside an element. Use the svg's getScreenCTM().inverse() matrix to convert the mouse event's client coordinates to svg coordinates. Use the point to construct a 1x1 matrix. Use the svg's checkIntersection() to determine if the rectangle intersects element.
The following snippet demostrates this solution in plain javascript (i.e. without D3.js).
var svgNS = "http://www.w3.org/2000/svg";
var svg = document.getElementById("mySvg");
var circle = document.getElementById("myCircle");
var rect = null;
circle.addEventListener("mouseover", circle_mouseover);
circle.addEventListener("mouseout", circle_mouseout);
function circle_mouseover(e) {
if (!rect) {
rect = document.createElementNS(svgNS, "rect");
rect.setAttribute("x", circle.getAttribute("cx"));
rect.setAttribute("y", circle.getAttribute("cy"));
rect.setAttribute("width", 100);
rect.setAttribute("height", 50);
rect.setAttribute("style", "fill: gray;");
rect.addEventListener("mouseout", rect_mouseout);
svg.appendChild(rect);
}
}
function circle_mouseout(e) {
console.log("circle_mouseout");
if (rect) {
var p = svg.createSVGPoint();
p.x = e.clientX;
p.y = e.clientY;
p = p.matrixTransform(svg.getScreenCTM().inverse());
var r = svg.createSVGRect();
r.x = p.x;
r.y = p.y;
r.width = 1;
r.height = 1;
if(!svg.checkIntersection(rect, r)) {
rect.removeEventListener("mouseout", rect_mouseout);
svg.removeChild(rect);
rect = null;
}
}
}
function rect_mouseout(e) {
var p = svg.createSVGPoint();
p.x = e.clientX;
p.y = e.clientY;
p = p.matrixTransform(svg.getScreenCTM().inverse());
var r = svg.createSVGRect();
r.x = p.x;
r.y = p.y;
r.width = 1;
r.height = 1;
if(!svg.checkIntersection(circle, r)) {
rect.removeEventListener("mouseout", rect_mouseout);
svg.removeChild(rect);
rect = null;
}
}
<svg id="mySvg" width="150" height="150">
<circle id="myCircle" cx="50" cy="50" r="25" style="fill: green;"/>
</svg>
Note: I think FireFox has not yet implemented the checkIntersection() function. If you need to support FireFox then you will need a different means for checking intersection of point and element. If you are only dealing with circles and rectangles then it is easy to write your own functions for checking intersection.