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.
Related
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 want to display a moving cross hairs with coordinates when the cursor is moved over a particular DIV containing an SVG.
On mouseenter I can successfully create a rect displaying the coordinates (and remove it on mouseout), however, moving the cursor over the newly created rect or text itself fires a mouseout mouseenter event cycle.
I've tried d3.event.stopPropagation() in several places, but none seem to work.
The picture shows if you carefully move the mouse onto the grey "screen" - the rect & text is created and stays in one place.
But if you move the cursor to touch "bobo" or the green rectangle, it starts moving.
var infoBox = null;
var theSVG = d3.select("#theScreen")
.append("svg")
.attr("width", 250)
.attr("height", 250);
// Register mouse events
theSVG
.on("mouseenter", mouseEnter)
.on("mouseout", mouseExit);
function mouseEnter()
{
if (infoBox !== null)
return;
var coord = d3.mouse(d3.event.currentTarget);
x1 = parseInt(coord[0]);
y1 = parseInt(coord[1]);
console.log("mouseEnter", x1, y1, infoBox);
infoBox = theSVG.append("g")
.attr('class', 'ssInfoBox');
var rectItem = infoBox.append("rect")
.attr('x', x1)
.attr('y', y1)
.attr('width', 30)
.attr('height', 20);
var textItem = infoBox.append("text")
.attr('x', x1)
.attr('y', y1)
.text("bobo");
}
function mouseExit()
{
if (infoBox === null)
return;
console.log("mouseExit", infoBox);
infoBox.remove()
infoBox = null;
}
The code doesn't implement the moving yet. To start, I just want the rect/text created and destroyed on mouseenter and mouseout.
How do I do that?
Link to Fiddle.
Instead of mouseout, use mouseleave.
The MDN has a good explanation about the differences between them: https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseleave_event
And here is your code with that change only:
var infoBox = null;
var theSVG = d3.select("#theScreen")
.append("svg")
.attr("width", 250)
.attr("height", 250);
// Register mouse events
theSVG
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseExit);
function mouseEnter() {
if (infoBox !== null)
return;
var coord = d3.mouse(d3.event.currentTarget);
x1 = parseInt(coord[0]);
y1 = parseInt(coord[1]);
console.log("mouseEnter", x1, y1, infoBox);
infoBox = theSVG.append("g")
.attr('class', 'ssInfoBox');
var rectItem = infoBox.append("rect")
.attr('x', x1)
.attr('y', y1)
.attr('width', 30)
.attr('height', 20);
var textItem = infoBox.append("text")
.attr('x', x1)
.attr('y', y1)
.text("bobo");
}
function mouseExit() {
if (infoBox === null)
return;
console.log("mouseExit", infoBox);
infoBox.remove()
infoBox = null;
}
#container {
width: 400px;
height: 400px;
background-color: #0BB;
}
#theScreen {
position: absolute;
top: 50px;
left: 50px;
width: 250px;
height: 250px;
background-color: #333;
cursor: crosshair;
}
.ssInfoBox rect {
fill: #383;
}
.ssInfoBox text {
fill: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id='container'>
<div id='theScreen'>
</div>
</div>
You may create transparent div or any other tag on top of your svg with same size. Than handle mouse events of this overlay.
This way you will not going be interrupted by internal components events.
Downside - you will have to handle interaction with internals manually.
Like so:
<svg style="z-index:1;position:absolute;left:0;width:200px;top:0;height:200px">...</svg>
<div id="overlay" style="background:rgba(0,0,0,0);z-index:2;position:absolute;left:0;width:200px;top:0;height:200px"></div>
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 to animate a route (path) on a map. When the route reaches a point along the route I'd like to popup some information.
Most of my code is based on the following example. http://bl.ocks.org/mbostock/1705868. I'm really just trying to determine if there is a way to detect when the transitioning circle collides or overlaps any of the stationary circles in this example.
You can detect collision in your tween function. Define a collide function to be called from inside the tween function as follows:
function collide(node){
var trans = d3.transform(d3.select(node).attr("transform")).translate,
x1 = trans[0],
x2 = trans[0] + (+d3.select(node).attr("r")),
y1 = trans[1],
y2 = trans[1] + (+d3.select(node).attr("r"));
var colliding = false;
points.each(function(d,i){
var ntrans = d3.transform(d3.select(this).attr("transform")).translate,
nx1 = ntrans[0],
nx2 = ntrans[0] + (+d3.select(this).attr("r")),
ny1 = ntrans[1],
ny2 = ntrans[1] + (+d3.select(this).attr("r"));
if(!(x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1))
colliding=true;
})
return colliding;
}
Where points are the stationary points, and node is the transitioning element. What collide does is check whether node overlaps with any of the points (as shown in collision detection example here).
Because we need the node to be passed to the tween function, we replace attrTween used in Mike's example, with tween:
circle.transition()
.duration(10000)
.tween("attr", translateAlong(path.node()))
.each("end", transition);
Finally, the tween function calling our collide:
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
d3.select(this).attr("transform","translate(" + p.x + "," + p.y + ")");
if(collide(this))
d3.select(this).style("fill", "red")
else
d3.select(this).style("fill", "steelblue")
};
};
}
See the full demo here
The easiest way is to just check how "close" the transitioning circle is to the other points.
var pop = d3.select("body").append("div")
.style("position","absolute")
.style("top",0)
.style("left",0)
.style("display", "none")
.style("background", "yellow")
.style("border", "1px solid black");
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
var epsilon = 5;
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
points.forEach(function(d,i){
if ((Math.abs(d[0] - p.x) < epsilon) &&
(Math.abs(d[1] - p.y) < epsilon)){
pop.style("left",d[0]+"px")
.style("top",d[1]+20+"px")
.style("display","block")
.html(d);
return false;
}
})
return "translate(" + p.x + "," + p.y + ")";
};
};
}
The faster the circle moves the greater your epsilon will need to be.
Example here.
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;
}
});