I am learning d3 and I came across a previous topic discussing stopPropagation(). jasondavies posted a reply to the question and an example here https://gist.github.com/jasondavies/3186840.
In this example he uses:
.on("mousedown", function() { d3.event.stopPropagation(); })
.on("mousedown.log", log("mousedown circle"))
I don't understand the event "mousedown.log" and how it triggered in this example.
Here is the full code from jasondavies's example:
<!DOCTYPE html>
<style>
circle { fill: lightgreen; stroke: #000; }
</style>
<body>
<script src="http://d3js.org/d3.v2.min.js"></script>
<script>
var svg = d3.select("body").append("svg")
.style("float", "left")
.attr("width", 480)
.attr("height", 480)
.on("mousedown", log("mousedown svg"))
.on("mouseup", log("mouseup svg"))
.on("click", log("click svg"));
svg.append("circle")
.attr("cx", 240)
.attr("cy", 240)
.attr("r", 200)
.on("mousedown", function() { d3.event.stopPropagation(); })
.on("mousedown.log", log("mousedown circle"))
.on("mouseup", log("mouseup circle"))
.on("click", log("click circle"))
var div = d3.select("body").append("div")
.style("float", "left")
function log(message) {
return function() {
div.append("p")
.text(message)
.style("background", "#ff0")
.transition()
.duration(2500)
.style("opacity", 1e-6)
.remove();
};
}
</script>
It's namespacing for events. See the documentation:
If an event listener was already registered for the same type on the selected element, the existing listener is removed before the new listener is added. To register multiple listeners for the same event type, the type may be followed by an optional namespace, such as "click.foo" and "click.bar". The first part of the type ("click" for example) is used to register the event listener (using element.addEventListener()) and methods are added on the selected elements as __onclick.foo and __onclick.bar. To remove a listener, pass null as the listener. To remove all listeners for a particular event type, pass null as the listener, and .type as the type, e.g. selection.on(".foo", null).
In this particular example, it means that both handlers are called when a mousedown event occurs. Without the namespacing, the second handler would overwrite the first.
Related
In this d3 force layout viz project I'm trying to remove the user's ability to click on the bigger yellow nodes. I have an on-click, fire the clicknodeControl function,
nodes.append('circle')
.attr("r", 28)
.attr("id", "hoverdots")
.style("opacity", 0)
.style("fill", "#00bedd")
.on("click", function(d) { return clicknodeControl(d.group); })
.style("z-index", "10")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
and then ideally this variable would pass through the clicknode function to one group of nodes but not the other:
var clicknodeControl = d3.scaleOrdinal([`clicknode`, ``]);
This does not seem to be working in practice, the clicknode function is not being passed through.
Thanks if anyone has any ideas on this!!
In D3 V7 mouse event handlers have event as the first argument and datum as the second.
Replace
.on("click", function(d) { return clicknodeControl(d.group); })
with:
.on("click", (_, d) => clicknodeControl(d.group))
I face a problem with context-event and its parameter.
Following to versions are used in my site, to attach events:
This approach is used for simple graphic Elements that get their events after loading from database:
const graph = editor.append("g")
.attr("id", "a" + graphic.Id.toString())
.html(graphic.SvgString)
.on("mouseover", graphicMouseOver)
.on("mouseout", graphicMouseOut)
.on("mousedown", graphicMouseDown)
.on("contextmenu", onGraphicContext)
.call(d3.drag()
.on("start", graphicDragStart)
.on("drag", graphicDragging)
.on("end", graphicDragEnd));
Then I have a different approach for some kind of symbols, Ioad to my editor:
const g = editor.append("g")
.attr("transform", "translate(" + symbol.SymbolPosition + ")")
.attr("id", subFunctionId.toString())
.attr("class", "draggable preview")
.attr("pointer-events", "fill")
.call(d3.drag()
.on("start", symbolDragStart)
.on("drag", symbolDragging)
.on("end", symbolDragEnd)
);
Depending on some circumstance, I attach some events to these "symbols" later:
function addSymbolEvents(svgSymbols) {
log.debug("addSymbolEvents");
for (var i = 0; i < svgSymbols.length; i++) {
svgSymbols[i].addEventListener('mouseenter', symbolMouseEnter);
svgSymbols[i].addEventListener('mouseover', symbolMouseOver);
svgSymbols[i].addEventListener('mouseout', symbolMouseOut);
svgSymbols[i].addEventListener('pointerdown', symbolMouseDown);
svgSymbols[i].addEventListener('dblclick', symbolDblClick);
svgSymbols[i].addEventListener('contextmenu', symbolRightClick);
}
return svgSymbols;
}
If I log the event-parameter the following way, I get undefined on onGraphicContext and a complete MouseEvent-object on symbolRightClick
function onGraphicContext(evt) {
console.log("onGraphicContext", evt);
}
function symbolRightClick(evt) {
console.log("symbolRightClick", evt);
}
What is the difference here?
Isn't the d3.js-.on-attribute the same as addEventListener?
Thanks Carsten
selection.on() is a D3 method, not native JavaScript. Internally, selection.on() uses addEventListener, as you can see in the source code.
However, selection.on() is not just a wrapper for addEventListener. As the API says, when you attach a listener using selection.on(), that listener...
...will be evaluated for the element, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i])
Because of that, selection.on() is way more handy for D3 programmers than just using the vanilla addEventListener.
Ok, I found the solution.
As #Teemu wrote it's obviously not the same.
So if you use .on("contextmenu", onGraphicContext), you need to work with the d3.event-object in the event function:
function onGraphicContext() {
console.log("onGraphicContext", d3.event);
}
I want to implement a list on right-click of a data node. In order to do so I came across d3-context-menu plugin of d3.js. The problem I am facing is that the div element is getting appened outside the body tag.
I have never seen such an issue before.
I am following the plugin example given here:
http://plnkr.co/edit/hAx36JQhb0RsvVn7TomS?p=preview
This is the link to the library documentation:
https://github.com/patorjk/d3-context-menu
I have no clue why it is behaving in such manner. My code structure looks like this :
eventGroup = focusClip.selectAll(".event").data(data);
// Enter phase ---
eventGroupEnter = eventGroup.enter().append("svg");
eventGroupEnter.append("rect");
eventGroupEnter.append("circle");
eventGroupEnter.append("text");
// Event Group
eventGroup
.attr("class", "event")
.attr("x", function(d) {
return parseInt(x(d.time)) - 10;
}) // offset for the bg and center of dot
.attr("y", function(d) {
return parseInt(y(d.plotY));
})
.attr("width", function(d) {
return parseInt((d.label.length / 2)) + 60 + "em";
})
.attr("height", "20");
// Background
eventGroup.select("rect")
.attr("x", 0) // removes the "<rect> attribute x: Expected length, 'NaN'" Error
.attr("y", 4)
.attr("width", "100%")
.attr("height", "12")
.attr("fill", "url(#event-bg)");
menu = [{
title: "Item #1"
}];
// Dot
eventGroup.select("circle")
.attr("class", "dot")
.attr("r", 4)
.attr("cx", 10)
.attr("cy", 10)
.attr("fill", function(d) {
return d.evtColor ? d.evtColor : "#229ae5";
})
.attr("stroke", function(d) {
return d.evtColor ? d.evtColor : "#229ae5";
})
.attr("stroke-width", 2)
.on("contextmenu", d3.contextMenu(menu, function() {
console.log("Quick! Before the menu appears!");
}))
.on("mouseenter", tooltip.mouseover)
.on("mouseleave", tooltip.mouseout)
.on("click", annotateBox.click);
In order to explain it well I am adding the image of the chart:
The right click event is being called on the "dot" part of the event. Why would div element get appended outside the body?
This seems to be by design. If you look at the source code of that plugin, you'll see:
d3.selectAll('.d3-context-menu').data([1])
.enter()
.append('div')
.attr('class', 'd3-context-menu');
Since selectAll is called on the root, the div will be appended to the <html>, not to the <body>.
So, the author either did this intentionally or she/he forgot that d3.selectAll is different from selection.selectAll.
Here is a basic demo, click "Run code snippet", open your browser's dev tools and inspect the snippet window.
d3.selectAll("foo")
.data([1])
.enter()
.append("div")
.attr("class", "test")
<script src="https://d3js.org/d3.v3.min.js"></script>
You're gonna see this:
<html>
<head>...</head>
<body>...</body>
<div class="test"></div>
</html>
Here is an svg with a circle drawn on it:
svg = d3.select("body").append("svg")
.on("mouseover", function() { console.log("callback");} );
svg.append("circle")
.attr("cx", 50)
.attr("cy",50)
.attr("r",20)
.attr("fill","red");
Why does mouseover fire when I mouse over the circle? I assume because its a child element of its parent svg?
But I'd like to inhibit this action. How can I do this?
The arguments on the mouseover function don't seem to pass in the event, so I came up with this solution instead.
svg = d3.select("body").append("svg")
.on("mouseover", function() {
var event = window.event;
if (event.target.nodeName === "svg") {
console.log("callback");
}
});
svg.append("circle")
.attr("cx", 50)
.attr("cy",50)
.attr("r",20)
.attr("fill","red");
If you want to inhibit pointer interactions set pointer-events="none" on that element.
.attr("pointer-events","none")
Container elements don't trigger mouse events in SVG. The event will only get triggered when you mouse over any of the child graphical elements it contains.
Ok so I have the following code example where I have circles in an svg element. Each circle has a click event and I'm trying to animate the circle that was clicked. Currently all circles animate because I'm referring to the bubble object. What I want is to refer to the clicked object its self and not the other ones:
var data_items=[100,200,300];
var svg = d3.select("#chart")
.append("svg").attr("width", 800).attr("height", 600);
var g = svg.selectAll(".bubbleContainer")
.data(data_items)
.enter().append("g")
.attr("class","bubbleContainer");
var bubble = g.append("circle")
.attr("class","bubble")
.attr("cx", function(d) {
return d;
})
.attr("cy", function(d) {
return d
})
.attr("r", function(d) {
return d/2
})
.on("click",function(d){
bubble
.transition()
.duration(1000)
.attr("r",1000)
})
Any help is much appreciated
Thanks!
What Lars Kotthoff wrote would work. Alternatively – and I'm not sure which is more idiomatic:
Inside the click handler, the this context refers to the clicked DOM element.
So the following would do it too:
.on("click",function(d){
d3.select(this)
.transition()
.duration(1000)
.attr("r",1000)
});
You can use d3.event.target to access the element that is being clicked in the event handler. See for example this jsfiddle.