I'm trying to implement both dragging and zooming event handlers for a circle item using d3js. I've added behaviors for both events as given below
var circle = svg.append("circle")
.attr("fill", "green")
.attr("opacity", 0.6)
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 13)
.call(d3.behavior.drag().on("drag", drag))
.call(d3.behavior.zoom().on("zoom", zoom));
without zooming the object, dragging works fine. after zooming in/out of the object, dragging does not work, yet all events containing a mousedown is caught as "zoom" event.
For full source please see http://jsfiddle.net/xTaDC/
Seems that i did not understand the "d3.behavior". https://github.com/mbostock/d3/blob/master/examples/mercator/mercator-zoom-constrained.html provides only zoom handler and handles both dragging and zooming.
What am i doing wrong here?
As far as I know, d3's zoom behavior already handles dragging, so the drag thing is redundant. Try making use of the zoom's d3.event.translate (which is a 2 element array, so if you want to get the x value only, you can go d3.event.translate[0]) to replicate the functionality in your Drag into your Zoom.
Additional tip: To make things easier on yourself, make sure that you apply your call(zoom) on the parent of whatever it is that you're trying to drag. This will prevent jittery and shaky behavior.
Source is of course, the d3 wiki.
"This behavior automatically creates event listeners to handle zooming and panning gestures on a container element. Both mouse and touch events are supported."
https://github.com/mbostock/d3/wiki/Zoom-Behavior
I faced similar problem and solved it by overriding other sub events of zoom.
Add below code after zoom and drag event listeners
.on("mousedown.zoom", null)
.on("touchstart.zoom", null)
.on("touchmove.zoom", null)
.on("touchend.zoom", null);
So the full code will look like
var circle = svg.append("circle")
.attr("fill", "green")
.attr("opacity", 0.6)
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 13)
.call(d3.behavior.drag().on("drag", drag))
.call(d3.behavior.zoom().on("zoom", zoom))
.on("mousedown.zoom", null)
.on("touchstart.zoom", null)
.on("touchmove.zoom", null)
.on("touchend.zoom", null);
Append your graph with sensitive event area (must be the last append) :
var rect = svg.append("svg:rect")
.attr("class", "pane")
.attr("width", w)
.attr("height", h);
AFTER (not include) add event management on this area
rect.call(d3.behavior.zoom().x(x).scaleExtent([0.5, 4]).on("zoom", draw));
and the draw function is
function draw() {
svg.select("g.x.axis").call(xAxis);
svg.select("g.y.axis").call(yAxis);
svg.select("path.area").attr("d", area);
svg.select("path.line").attr("d", line);
}
see this exemple : https://groups.google.com/forum/?fromgroups=#!topic/d3-js/6p7Lbnz-jRQ%5B1-25-false%5D
In 2020, use d3.zoom to enable zooming and panning: https://observablehq.com/#d3/zoom
If you want to enable panning for the background, while allowing to drag the circle, see the official example where d3.drag and d3.zoom are used on different elements: https://observablehq.com/#d3/drag-zoom
Related
I have simple d3 code binding data with circles. An event is fired when mouse hover over the circles. The mouseOver function is also very simple. It just makes tooltip div visible.
It's a client work so I cannot share the full code. But it has the simple structure like below:
function mouseOver() {
tooltip.style("visibility", "visible")
.style("top", d3.event.pageY + "px")
.style("left", d3.event.pageX +"px")
.html('<p>'+'</p>')
}
var circles = g
.selectAll('.circles')
.data(data)
.join('circle')
.attr('class', 'circles')
.attr('cx', d => x(d))
.attr('cy', d => y(d))
.attr('r', r)
.attr('fill', d => colour(d.type))
.attr('fill-opacity', 0.6)
.on('mouseover', mouseOver)
.on('mouseleave', mouseLeave)
circles.exit().remove()
It works well on desktop. But for some reasons, the tooltip doesn't show up on mobile.
I changed the 'mouseover' into 'touchstart' in case it's the reason. To capture if the window ontouchstart, I made like this.
var hover = ('ontouchstart' in window) ?
'touchstart click' : 'mouseover';
Then, in the selection, I changed as below:
.on(hover, mouseOver)
But it still doesn't trigger the event on mobile.
On a side note, I also have bars(rect) with the same setting as circles above but the mouseover event is trigerred on mobile as well as desktop.
I really have no idea why one event works well on mobile although it's fired by 'mouseover' event handler while the other doesn't work at all despite the same setting as the other.
Does anyone have ideas??
I'm trying to open a new window on button click and draw a simple circle on it.
I can open a window but circle won't appear on it with this code. Any ideas on how to solve this.0
Here is my code:
function drawBarChart(newWindowRoot){
var sampleSVG = d3.select(newWindowRoot)
.append("svg")
.attr("width", 100)
.attr("height", 100);
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 50)
.attr("cy", 50)
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", "white");});
}
function createNewPage(){
var button = content.append("button")
.attr("class", "button")
.attr("id", "myButton")
.text("Visualize");
document.getElementById("myButton").onclick = function () {
var newWindow = window.open('');
newWindowRoot = d3.select(newWindow.document.body)
.attr("width","1060px")
.attr("margin","50px auto");
drawBarChart(newWindowRoot);
}
}
When you call drawBarChart(newWindowRoot), you're passing a d3 selection — the result of d3.select(newWindow.document.body) — into that function. That makes sense.
However, from within drawBarChart, you call var sampleSVG = d3.select(newWindowRoot), essentially making a d3 selection of a d3 selection. That doesn't work (unlike what you might expect from jQuery). You should skip that latter d3.select and that'll make your code begin to work. More specifically, that'll make the button appear in the new window, but not the circle.
To make the circle appear, you need to fix another issue: Your fiddle attempts to add the circle to the button, instead of to an SVG (this is different than your example above which isn't trying to create a button).
Here's a working fiddle
By the way, the reason the jsFiddle you linked to didn't work is because the link you embedded was using secure protocol (https) and the jsfiddle functionality that was trying to load d3 is insecure (http), which resulted in a failure to load d3 and in turn an error executing your code.
I've generated a D3 visualization (a force directed graph) that requires zooming and panning. I've got 2 problems however when it comes to zooming, and I can't find any decent examples on how I might overcome these problems:
The first problem is I've followed all the examples I can find about zooming, which involves adding groupings and adding a rectangle to ensure that the entire area is zoomeable. If I style the rectangle a slightly opaque blue then I get SVG that looks like this when I zoom out:
The problem with this is that I can zoom in/out absolutely fine while I've got my mouse over the blue rectangle area. The problem is I want this to be fully opaque, which means that when I zoom right out, it's very easy to place the cursor outside of this box and then you're unable to zoom in. Is there a way I can make the SVG itself zoomeable or pick up on these events?
This is how I go about generating the various layers and the zoomed function:
function zoomed() {
group2.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
svg = d3.select(target)
.attr("pointer-events", "all")
.append("svg");
group = svg.append('svg:g')
.call(d3.behavior.zoom().on('zoom', zoomed))
.on("dblclick.zoom", null);
group2 = group.append("g");
rect = group2.append('svg:rect')
.style("opacity", 0.3)
.attr('width', width)
.attr('height', height);
The second problem I have is that I'm trying to automatically size my text based on this http://bl.ocks.org/mbostock/1846692 example. When I've tried this however I seem to be getting text that renders really poorly. It seems to suffer from:
Being difficult to read
Not appearing contained within the circle
Being so small the entire thing compresses (image 2)
var texts = planets.append("text")
.text(function(d) { return d.name; })
.style("font-size", "24px") // initial guess
.style("font-size", function(d) {
return Math.min( 2 * d.size, (2 * d.size - 8) / this.getComputedTextLength() * 24) + "px";
})
.attr("dx", function(d) { return -d.size; })
.attr("dy", ".35em")
.style("fill", "white");
I thought that SVG would just handle this, I understand that some of the font-sizes can come out small, but if you zoom in should that not all sort itself out?
I've got a JSFiddle http://jsfiddle.net/IPWright83/vo7Lpefs/22/ to demonstrate.
I've not yet managed to work out a resolution to my first issue (regarding the zooming box) however I did manage to track down the text rendering issue.
This was actually because the each circle/node had a stroke property to provide the white border. This was also applying to the text element, and when the font was very small the stroke was much larger than the overall fill of the text. Removing the stroke from the text elements ensured that they rendered even when very small.
d3js SVG:title tooltip doesn't show up
I have a graph which contains a lot of circles, now I would like to insert to each circle a tooltip but it doesn't work neither as title in circle nor as tooltip box. I cannot find my mistake:
var circleSmall = canvas.append("circle")
.attr("cx", 869)
.attr("cy", 693)
.attr("r", 10)
.attr("fill", "green")
.append("svg:title").text("Your tooltip info")
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
http://jsfiddle.net/WLYUY/57/
The most important obstacle here was your rects. They were appended AFTER the circles and were preventing mouse events from reaching the circles. So, first thing to do is:
/* do this or append circles AFTER appending recs */
rect {
pointer-events: none;
}
I have added the mouseover and mouseout events necessary to show/hide the tooltip.
Complete FIDDLE here.
NOTE: For demonstration, I have painted the one circle receiving the events in large orange with an orange-red border (you can't miss it). This is just an example...normally you would apply the event listeners to all circles. And this brings me to a sanity check: you are currently appending shapes "manually" but I assume you will eventually do it based on data binding, one of the main points of D3.
I'm attempting to integrate AngularJS with d3 for dragging and resizing. I've managed to create a rect object that is draggable in an SVG element, and resizable using resize handles. The resize handles work as they should, but resizing is choppy when I try to resize in the north or east direction. I created the following Plunk as a demo of the issue: http://plnkr.co/tG19vpyyw0OHMetLOu2U. (I've simplified it to show the issue I've run into, so there's only one resize handle.)
Dragging works as it should, and resizing in the west and south directions works as well (not shown in the demo).
Figured I'd ask the community and see if anyone had run into this before. Thank you all.
The problem is that you're modifying the rect element itself and the enclosing g element. There's a very short delay between setting the size of the rect and the position of the g simply because this has to be done with two separate commands. During this delay, the cursor position relative the the drag rectangle changes, firing a new drag event with values that correspond to the inconsistent intermediate state. This is fixed immediately afterwards (as soon as the attributes of both elements have been adjusted) and a new drag event is fired that fixes the inconsistency, but it is noticeable as a flicker.
The easiest way to fix this is to change both size and position for the rect and nothing for the g element. This means adjusting the position of the drag rectangle as well and makes the code less nice, but avoids the timing/inconsistency problem.
So myrect becomes
var myRect = d3.select(document.createElementNS("http://www.w3.org/2000/svg", "rect"))
.attr("data-ng-width", "{{square.w}}")
.attr("data-ng-height", "{{square.h}}")
.attr("stroke", "yellow")
.attr("stroke-width", 3)
.attr("fill-opacity", 0)
.attr("data-ng-x", "{{square.x}}")
.attr("data-ng-y", "{{square.y}}");
and resizer
var resizer = myGroup.append("rect")
.attr("width", 5)
.attr("height", 5)
.attr("stroke", "blue")
.attr("stroke-width", 1)
.attr("fill-opacity", 0)
.attr("cursor", "nw-resize")
.attr("x", "{{square.x-2.5}}")
.attr("y", "{{square.y-2.5}}")
.call(nresize);
I've updated your code with this solution here.