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>
Related
Is there any way to completely remove/unbind the d3.zoom from canvas?
I wanted to enable only the zoom functionality when the zoom is enabled (via separate button setting)
and reclaim the mouse events (mouse down, up etc) when it is removed.
here is how I add the d3 zoom to canvas
///zoom-start
var d3Zoom = d3.zoom().scaleExtent([1, 10]).on("zoom", zoom),
d3Canvas = d3.select("canvas").call(d3Zoom).on("dblclick.zoom", null),
d3Ctx = d3Canvas.node().getContext("2d"),
d3width = d3Canvas.property("width"),
d3height = d3Canvas.property("height");
function zoom() {
}
Any help would be appreciated.
Thanks in advance
According to the API:
Internally, the zoom behavior uses selection.on to bind the necessary event listeners for zooming. The listeners use the name .zoom, so you can subsequently unbind the zoom behavior as follows:
selection.on(".zoom", null);
And, to enable it again, you just need:
selection.call(zoom);
Here is a demo (the buttons are self explanatory):
var svg = d3.select("svg");
var g = svg.append("g");
var zoom = d3.zoom().on("zoom", function() {
g.attr("transform", d3.event.transform)
});
svg.call(zoom);
g.append("circle")
.attr("cx", 150)
.attr("cy", 75)
.attr("r", 50)
.style("fill", "teal");
d3.select("#zoom").on("click", function() {
svg.call(zoom);
console.log("zoom enabled")
});
d3.select("#nozoom").on("click", function() {
svg.on(".zoom", null)
console.log("zoom disabled")
});
svg {
border: 1px solid gray;
}
.as-console-wrapper { max-height: 9% !important;}
<script src="https://d3js.org/d3.v4.min.js"></script>
<button id="zoom">Enable zoom</button>
<button id="nozoom">Disable zoom</button>
<br>
<svg></svg>
PS: I'm using SVG, not canvas, but the principle is the same.
I have an area chart in nvd3:
var chart = nv.models.stackedAreaChart()
.x(function (d) { return d[0] })
.y(function (d) { return Math.round(d[1]) })
.clipEdge(true)
.showControls(true)
.useInteractiveGuideline(true);
As you can see, I have enabled showControls, which displays three small buttons (Stacked, Stream and Expanded) in the top left corner of the chart.
Since it was desired to select subsections of the chart by dragging the mouse over, I implemented the following solution by hooking up mouseup, mousedown and mousemove events on the SVG element that contains the chart.
var mouseDown = false;
var mouseDownCoords;
var rect = svg.append("rect")
.attr("x", 0).attr("y", 0)
.attr("width", 0).attr("height", 0)
.attr("fill", "rgba(43,48,87,0.3)");
svg.on('mousedown', function () {
var height = svg[0][0].height;
mouseDownCoords = d3.mouse(this);
mouseDown = true;
rect.attr("x", mouseDownCoords[0]);
rect.attr("height", height.animVal.value);
// Register mousemove when the mouse button is down
svg.on('mousemove', function () {
var coords = d3.mouse(this);
rect.attr("width", Math.max(coords[0] - mouseDownCoords[0], 0));
});
});
svg.on('mouseup', function () {
if (mouseDown) {
var coords = d3.mouse(this);
var width = Math.max(coords[0] - mouseDownCoords[0], 0);
mouseDown = false;
rect.attr("width", 0);
if (width > 0) {
var totalWidth = svg[0][0].width.animVal.value;
var totalPeriod = dateTo.getTime() - dateFrom.getTime();
var newDateFrom = new Date(Math.floor(dateFrom.getTime() + totalPeriod * mouseDownCoords[0] / totalWidth));
var newDateTo = new Date(Math.floor(newDateFrom.getTime() + totalPeriod * width / totalWidth));
window.setSearchTimeframe(newDateFrom, newDateTo);
}
}
// Unregister mousemove
svg.on('mousemove', null);
});
However, registering these event callbacks stops the control buttons from working. When I click on them, nothing happens, even if the pointer correctly changes when I hover them.
You're right, registering events on elements outside NVD3's built-in event system really seems to destroy things internally (which shouldn't be the case, in my opinion). You could work around this by positioning an invisible element over the part of the chart that needs custom behaviour.
Demo
The red rectangle is the part of the chart with custom behaviour (click it).
var chartElement = d3.select("#chart svg");
var chart;
nv.addGraph(function() {
chart = nv.models.pieChart()
.x(function(d) {
return d.label
})
.y(function(d) {
return d.value
})
.showLabels(true);
var chartData = [{
label: "Foo",
value: 67
}, {
label: "Bar",
value: 33
}];
chartElement
.datum(chartData)
.call(chart);
$("#customUI").on("mousedown", function() {
alert("Some custom behaviour...");
});
return chart;
});
#wrapper {
position: relative;
}
#chart {
position: absolute;
height: 500px;
}
#customUI {
position: absolute;
background: red;
opacity: 0.2;
width: 100px;
height: 100px;
left: 100px;
top: 200px;
}
#customUI:hover {
opacity: 0.5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.css" rel="stylesheet" />
<div id="wrapper">
<div id="chart">
<svg>
</svg>
</div>
<div id="customUI">
</div>
</div>
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.
I'm drawing a scatterplot with d3.js. With the help of this question :
Get the size of the screen, current web page and browser window
I'm using this answer :
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
x = w.innerWidth || e.clientWidth || g.clientWidth,
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
So I'm able to fit my plot to the user's window like this :
var svg = d3.select("body").append("svg")
.attr("width", x)
.attr("height", y)
.append("g");
Now I'd like that something takes care of resizing the plot when the user resize the window.
PS : I'm not using jQuery in my code.
Look for 'responsive SVG' it is pretty simple to make a SVG responsive and you don't have to worry about sizes any more.
Here is how I did it:
d3.select("div#chartId")
.append("div")
// Container class to make it responsive.
.classed("svg-container", true)
.append("svg")
// Responsive SVG needs these 2 attributes and no width and height attr.
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 600 400")
// Class to make it responsive.
.classed("svg-content-responsive", true)
// Fill with a rectangle for visualization.
.append("rect")
.classed("rect", true)
.attr("width", 600)
.attr("height", 400);
.svg-container {
display: inline-block;
position: relative;
width: 100%;
padding-bottom: 100%; /* aspect ratio */
vertical-align: top;
overflow: hidden;
}
.svg-content-responsive {
display: inline-block;
position: absolute;
top: 10px;
left: 0;
}
svg .rect {
fill: gold;
stroke: steelblue;
stroke-width: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chartId"></div>
Note: Everything in the SVG image will scale with the window width. This includes stroke width and font sizes (even those set with CSS). If this is not desired, there are more involved alternate solutions below.
More info / tutorials:
http://thenewcode.com/744/Make-SVG-Responsive
http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php
Use window.onresize:
function updateWindow(){
x = w.innerWidth || e.clientWidth || g.clientWidth;
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
svg.attr("width", x).attr("height", y);
}
d3.select(window).on('resize.updatesvg', updateWindow);
http://jsfiddle.net/Zb85u/1/
UPDATE just use the new way from #cminatti
old answer for historic purposes
IMO it's better to use select() and on() since that way you can have multiple resize event handlers... just don't get too crazy
d3.select(window).on('resize', resize);
function resize() {
// update width
width = parseInt(d3.select('#chart').style('width'), 10);
width = width - margin.left - margin.right;
// resize the chart
x.range([0, width]);
d3.select(chart.node().parentNode)
.style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
.style('width', (width + margin.left + margin.right) + 'px');
chart.selectAll('rect.background')
.attr('width', width);
chart.selectAll('rect.percent')
.attr('width', function(d) { return x(d.percent); });
// update median ticks
var median = d3.median(chart.selectAll('.bar').data(),
function(d) { return d.percent; });
chart.selectAll('line.median')
.attr('x1', x(median))
.attr('x2', x(median));
// update axes
chart.select('.x.axis.top').call(xAxis.orient('top'));
chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));
}
http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/
It's kind of ugly if the resizing code is almost as long as the code for building the graph in first place. So instead of resizing every element of the existing chart, why not simply reloading it? Here is how it worked for me:
function data_display(data){
e = document.getElementById('data-div');
var w = e.clientWidth;
// remove old svg if any -- otherwise resizing adds a second one
d3.select('svg').remove();
// create canvas
var svg = d3.select('#data-div').append('svg')
.attr('height', 100)
.attr('width', w);
// now add lots of beautiful elements to your graph
// ...
}
data_display(my_data); // call on page load
window.addEventListener('resize', function(event){
data_display(my_data); // just call it again...
}
The crucial line is d3.select('svg').remove();. Otherwise each resizing will add another SVG element below the previous one.
In force layouts simply setting the 'height' and 'width' attributes will not work to re-center/move the plot into the svg container. However, there's a very simple answer that works for Force Layouts found here. In summary:
Use same (any) eventing you like.
window.on('resize', resize);
Then assuming you have svg & force variables:
var svg = /* D3 Code */;
var force = /* D3 Code */;
function resize(e){
// get width/height with container selector (body also works)
// or use other method of calculating desired values
var width = $('#myselector').width();
var height = $('#myselector').height();
// set attrs and 'resume' force
svg.attr('width', width);
svg.attr('height', height);
force.size([width, height]).resume();
}
In this way, you don't re-render the graph entirely, we set the attributes and d3 re-calculates things as necessary. This at least works when you use a point of gravity. I'm not sure if that's a prerequisite for this solution. Can anyone confirm or deny ?
Cheers, g
If you want to bind custom logic to resize event, nowadays you may start using ResizeObserver browser API for the bounding box of an SVGElement.
This will also handle the case when container is resized because of the nearby elements size change.
There is a polyfill for broader browser support.
This is how it may work in UI component:
function redrawGraph(container, { width, height }) {
d3
.select(container)
.select('svg')
.attr('height', height)
.attr('width', width)
.select('rect')
.attr('height', height)
.attr('width', width);
}
// Setup observer in constructor
const resizeObserver = new ResizeObserver((entries, observer) => {
for (const entry of entries) {
// on resize logic specific to this component
redrawGraph(entry.target, entry.contentRect);
}
})
// Observe the container
const container = document.querySelector('.graph-container');
resizeObserver.observe(container)
.graph-container {
height: 75vh;
width: 75vw;
}
.graph-container svg rect {
fill: gold;
stroke: steelblue;
stroke-width: 3px;
}
<script src="https://unpkg.com/resize-observer-polyfill#1.5.1/dist/ResizeObserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<figure class="graph-container">
<svg width="100" height="100">
<rect x="0" y="0" width="100" height="100" />
</svg>
</figure>
// unobserve in component destroy method
this.resizeObserver.disconnect()
For those using force directed graphs in D3 v4/v5, the size method doesn't exist any more. Something like the following worked for me (based on this github issue):
simulation
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX(width / 2))
.force("y", d3.forceY(height / 2))
.alpha(0.1).restart();
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;
}
});