d3 How to trigger event on the object behind text - javascript

I draw A rect and text on svg.
In order to show text, I render rect first.
I add mouse click event to rect
when I click the text, it seems the rect is not selected, because rect is behind text, so text is selected frist.
I need to select trigger the event when mouse click inside the rect.
How should I do?
Thanks!
Fiddle
You can see, when you mouse click on the text, the rect is not clicked.
var data = ["TEXT SAMPLE TEXT SAMPLE TEXT SAMPLE"];
var svg = d3.select("svg");
var bar = svg.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(100,100)"; });
bar.append("rect")
.attr("width", 200)
.attr("height", 50)
.style("fill", "#f00")
.on("click", function (d) {
alert("text");
});
bar.append("text")
.attr("x", 10)
.attr("y", 25)
.attr("dy", "-.35em")
.text(function(d) { return d; });

You can attach a style to the text to ignore mouse events. Here is the example:
Style:
.bar-text {
pointer-events: none;
}
Modified code:
bar.append("text")
.attr("x", 10)
.attr("y", 25)
.attr("class", "bar-text") // add class
.attr("dy", "-.35em")
.text(function(d) { return d; });

Attach the handler to the g element. Complete demo here.

Related

d3-zoom breaks when cursor is over an inner svg element

I have implemented d3-zoom by following this brief tutorial.
I'm using https://d3js.org/d3.v5.min.js. This is my first project with d3.
My goal is to have a kind of floor plan showing booth tables at a venue. Similar to the tutorial, I've drawn shape elements from an array. In my case I've entered an array of booth information into a grid of elements.
The zoom functionality works just fine, except when my cursor is over the border or fill of one of my rectangles, or on the text of a element. If the point of my cursor is touching any of these elements, the zooming behavior stops working.
Try to zoom with the mousewheel with your cursor in blank space versus touching a shape or text.
I've tried to fit a console.log in somewhere to see what's not getting passed in the event, but have had trouble even finding where I can get the event argument.
Any help greatly appreciated! Here is my code:
var svg = d3.select("#venue-svg"); // this is my svg element
// the zoom rectangle. from the tutorial: 'The zoom behavior is applied
// to an invisible rect overlaying the SVG element; this ensures that it
// receives input, and that the pointer coordinates are not affected by
// the zoom behavior’s transform.'
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all")
.call(
d3
.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed)
);
function zoomed() {
g.attr("transform", d3.event.transform);
}
// a parent <g> that holds everything else and is targeted
// for the transform (from the tutorial).
var g = svg.append("g");
// the groups that hold each booth table, associated org name, etc.
var tables = g
.selectAll("g")
.data(venueBooths)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d.x + " " + d.y + ")";
});
var tableRects = tables
.append("rect")
.attr("stroke", "steelblue")
.attr("stroke-width", "2px")
.attr("width", function(d) {
return d.w;
})
.attr("height", function(d) {
return d.h;
})
.attr("fill", "none")
.attr("fill", function(d) {
return $.isEmptyObject(d.reservation) ? "none" : "#FF5733";
})
.attr("id", function(d) {
return "table-" + d.id;
});
tables
.append("text")
.text(function(d) {
return "Booth " + d.id;
})
.attr("dx", 5)
.attr("dy", 60)
.attr("font-size", "8px");
tables
.append("text")
.text(function(d) {
return d.reservation.orgName ? d.reservation.orgName : "Available";
})
.attr("dy", 15)
.attr("dx", 5)
.attr("font-size", "9px")
.attr("font-weight", "bold");
Try creating the rect in the end such that the DOM looks like this:
<svg>
<g></g>
<rect></rect>
</svg>
Since the zoom function is attached to the large rectangle, creating the smaller boxes above it prevents a zoom event from propagating to the large rectangle below them. It works for the boxes with a fill: none; since it behaves like a hollow box.
Try modifying the code to something like:
var svg = d3.select("#venue-svg"); // this is my svg element
// the zoom rectangle. from the tutorial: 'The zoom behavior is applied
// to an invisible rect overlaying the SVG element; this ensures that it
// receives input, and that the pointer coordinates are not affected by
// the zoom behavior’s transform.'
function zoomed() {
g.attr("transform", d3.event.transform);
}
// a parent <g> that holds everything else and is targeted
// for the transform (from the tutorial).
var g = svg.append("g");
// the groups that hold each booth table, associated org name, etc.
var tables = g
.selectAll("g")
.data(venueBooths)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d.x + " " + d.y + ")";
});
var tableRects = tables
.append("rect")
.attr("stroke", "steelblue")
.attr("stroke-width", "2px")
.attr("width", function(d) {
return d.w;
})
.attr("height", function(d) {
return d.h;
})
.attr("fill", "none")
.attr("fill", function(d) {
return $.isEmptyObject(d.reservation) ? "none" : "#FF5733";
})
.attr("id", function(d) {
return "table-" + d.id;
});
tables
.append("text")
.text(function(d) {
return "Booth " + d.id;
})
.attr("dx", 5)
.attr("dy", 60)
.attr("font-size", "8px");
tables
.append("text")
.text(function(d) {
return d.reservation.orgName ? d.reservation.orgName : "Available";
})
.attr("dy", 15)
.attr("dx", 5)
.attr("font-size", "9px")
.attr("font-weight", "bold");
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all")
.call(
d3
.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed)
);

adding input text box on clicking the data labels d3.js

I am trying to implement annotations on my scatterplot using d3.js v3. I want to insert a text box when I click on the text labels on my chart. For which I wrote this code:
circleGroup.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function (d) {
if (d.label) {
return d.label;
}
return "";
})
.attr("x", function (d) {
return x(d.time) + 6;
})
.attr("y", function (d) {
return y(d.plotY) + 4;
})
.attr("font-size", "10px")
.attr("fill", "#2d3d45")
.on("click", function(d) {
d3.select(this).append("input").attr("type", "text").attr("name", "textInput").attr("value", "Text goes here");
});
The selection of the text element is working correctly. Its just not popping text box when i click on the text labels. Where am i going wrong? Is my approach not in right direction ?
You cannot append html element into svg. The only way to do it - use foreignObject element.
The foreignObject SVG element allows for inclusion of a foreign XML
namespace which has its graphical content drawn by a different user
agent. The included foreign graphical content is subject to SVG
transformations and compositing.
Look at the very simplified example:
var texts = ['foo', 'bar'];
var container = d3.select('g');
container
.selectAll('text')
.data(texts)
.enter()
.append('text')
.text(function(d) { return d; })
.attr('y', function(d,i) { return 50 * i })
.on('click', function(d,i) {
container
.append("foreignObject")
.attr("x", 0)
.attr("y", function() { return 50 * i })
.attr("width", 140)
.attr("height", 20)
.html(function(d) {
return '<input type="text" value="Text goes here" />'
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div style="font-size: 12px;">Click on the texts below:</div>
<svg width="300" heigth="400">
<g transform="translate(20, 20)">
</g>
</svg>

Getting the value of SVG attributes in D3.js

I am new to D3.js and am trying to build rectangles that represent all nodes from an XML file. So far so good but I want interactivity with each of the rectangles I draw and to be able to capture the nodes that have been touched for further processing. So let's say I click on a rectangle, I can make it react by doing an onclick event (like increasing the font size) but I can't seem to retrieve some of the info. I'd like to create an array with the text of each item that was clicked on.
Here's the code for one instance of the rectangle.
d3.select("#chart")
.append("svg")
.attr("width", 600)
.attr("height", 2000)
.style("background", "#93A1A1")
d3.select("svg")
.append("rect").attr("x", 50)
.attr("y", 25)
.attr("height", 20)
.attr("width", 200)
.attr("title", "resourceDef")
.style("fill", "#CB4B19")
d3.select("svg")
.append("text")
.attr("x", 55)
.attr("y", 37)
.attr("font-size", 11)
.attr("font-family", "sans-serif")
.text("resourceDef")
.on("mouseover", function(d) {
tempText = this.text;
alert(tempText);
d3.select(this)
.attr("font-size", 15)})
.on("mouseout", function(d) {
d3.select(this)
.attr("font-size", 11)})
I can grab style info by using but not the title and I can't find that info anywhere. Thanks for your help, I know it's a long question with probably a simple answer.
You can attach a mouse over event on the rectangle DOM by doing something like this:
d3.select("svg")
.append("rect").attr("x", 50)
.attr("y", 25)
.attr("height", 20)
.attr("width", 200)
.attr("title", "resourceDef")
.style("fill", "#CB4B19")
.on("click", function (d) {
var t = d3.select(this).attr("title");
//pushing the title into the array.
clickedTitles.push(t);
console.log(t);
});
You can get the attribute of a DOM(in your case tite) by doing something like this:
.on("click", function (d) {
var t = d3.select(this).attr("title");
clickedTitles.push(t);
console.log(t)
})
You can store the clicked rectangles title in an array like this:
//create an array
var clickedTitles = [];
//in your click function push the title into the array
clickedTitles.push(t);
//use the clickedTitles where ever you need in the code
Full code is here.

d3 rect not showing up

I want to display the legend (caption?) of some data.
For each part of the legend, I append a rect and a p to a parent division.
The problem is that rect are not showing up.
Here is my code:
var groups = {{ groups|safe }};
groups.forEach(function(group, i) {
var div = d3.select("#collapse-legend");
div.append("rect")
.attr("width", 17)
.attr("height", 17)
.style("fill-opacity", 1)
.style("fill", function (d) { return c(i); });
div.append("p").text(group)
});
Now, when I check the content of my web page, I get both rect and p, but rect:
is not showing up
seems to have a width of 0 (showing its area with firefox)
Is there some mistake in my code? Are there better ways to achieve this? I am very new to javascript and d3.js so please be indulgent ^^
Update
So this is what I ended with.
HTML:
<div ...>
<svg id="legend-svg"></svg>
</div>
JavaScript:
// set height of svg
d3.select("#legend-svg").attr("height", 18*(groups.length+1));
// for each group, append rect then text
groups.forEach(function(group, i) {
d3.select("#legend-svg").append("rect")
.attr("y", i*20)
.attr("width", 18)
.attr("height", 18)
.style("fill-opacity", 1)
.style("fill", function (d) { return c(i); });
d3.select("#legend-svg").append("text")
.attr("x", 25)
.attr("y", i*20+15)
.text(group);
});
SVG elements like <rect> cannot be direct children of html <div> elements. You must put them inside an <svg> container element.
SET X + Y values for rect :
.attr("x", 50)
.attr("y", 50)

Interactive Legend onclick or mouseover - D3js

I've been looking for a way to have my legend control my chart animation (similar to NVD3 examples). I've run into a problem though - nested selections.
var legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(70,10)")
;
var legendRect = legend.selectAll('rect').data(colors);
legendRect.enter()
.append("rect")
.attr("x", w - 65)
.attr("width", 10)
.attr("height", 10)
.attr("y", function(d, i) {
return i * 20;
})
.style("stroke", function(d) {
return d[1];
})
.style("fill", function(d) {
return d[1];
});
I'm using a bit of a hack to do my animation. Basically setting style to display: none.
I want to be able to click on the rectangles and call the function. But putting a mouseover or onclick within legendRect doesn't work. The bars to animate are not children of the legend. How can I call the function, or chain my function to my legend?
function updateBars(opts) {
var gbars = svg.selectAll("rect.global");
var lbars = svg.selectAll("rect.local");
if (opts === "global") {
gbars.style("display", "block") ;
lbars.style("display", "none");
gbars.transition()
.duration(500)
.attr("width", xScale.rangeBand());
};
if (opts === "local") {
lbars.style("display", "block")
;
gbars.style("display", "none");
lbars.transition()
.duration(500)
.attr("x", 1 / -xScale.rangeBand())
.attr("width", xScale.rangeBand());
};
}
My other obstacle is changing the fill color on click. I want it to almost imitate a checkbox, so clicking (to deselect) would turn the fill white. I tried something similar as .on("click",(".style" ("fill", "white"))). But that is incorrect.
Here is my fiddle. For some reason, the function isn't updating things on Fiddle. It works on my localhost though. Not sure the problem with that.
I'm not completely sure I understand you correctly, but if your first question is how to change element X when clicking on element Y, you need something along the lines of:
legendRect.on("click", function() {
gbars.transition()
.duration(500)
.style("display", "block")
// etc...
}
As for changing the fill on click, try:
gbars.on("click", function() {
d3.select(this)
.attr("fill", "white");
}

Categories

Resources