Binding on click events to context menu list elements - d3.js

I am generating dynamic context menus that appear when the user right clicks on a shape. I have managed to create the context menu, but I am having trouble catching the click event when the user selects an entry from the menu.
The event keeps binding to the right click action to create the context menu instead of a left click on the list items within the menu itself.
I have dug around SO a bunch and been unable to unearth something that will get me the rest of the way.
What I want is for the console.log function in this example to trigger when the user clicks a list element and pass the name of the item clicked. In a perfect world it would not trigger on the initial right click, but I'll take what I can get.
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<style>
.context-menu {
position: absolute;
display: none;
background-color: #f2f2f2;
border-radius: 4px;
font-family: Arial, sans-serif;
font-size: 14px;
min-width: 150px;
border: 1px solid #d4d4d4;
z-index:1200;
}
.context-menu ul {
list-style-type: none;
margin: 4px 0px;
padding: 0px;
cursor: default;
}
.context-menu ul li {
padding: 4px 16px;
}
.context-menu ul li:hover {
background-color: #4677f8;
color: #fefefe;
}
</style>
<script>
var fruits = ["Apple", "Orange", "Banana", "Grape"];
var svgContainer = d3.select("body")
.append("svg")
.attr("width", 200)
.attr("height", 200);
var circle = svgContainer
.append("circle")
.attr("cx", 30)
.attr("cy", 30)
.attr("r", 20)
.on('contextmenu', function(d,i) {
// create the div element that will hold the context menu
d3.selectAll('.context-menu').data([1])
.enter()
.append('div')
.attr('class', 'context-menu');
// close menu
d3.select('body').on('click.context-menu', function() {
d3.select('.context-menu').style('display', 'none');
});
// this gets executed when a contextmenu event occurs
d3.selectAll('.context-menu')
.html('')
.append('ul')
.selectAll('li')
.data(fruits).enter()
.append('li')
// THIS IS WHAT I CAN NOT GET TO WORK THE WAY I WANT IT TO WORK
.on('click' , console.log( function(d) { return d; } + " clicked!"))
.text(function(d) { return d; });
d3.select('.context-menu').style('display', 'none');
// show the context menu
d3.select('.context-menu')
.style('left', (d3.event.pageX - 2) + 'px')
.style('top', (d3.event.pageY - 2) + 'px')
.style('display', 'block');
d3.event.preventDefault();
});
</script>
</body>
</html>
Here is a plunkr demonstrating the code (I can't figure out why I couldn't get it to run with jsfiddle): http://run.plnkr.co/plunks/paPKKlUFtQCGpOmjQztS/

Please see this fiddle
I basically updated your click listener to this:
.on('click' , function(d) { console.log(d); return d; })
It seems to be working fine.

Related

Adding div Element on d3 Bar Chart

I have a requirement to add div on top of each bar in bar chart using d3. Is it possible to add div instead of adding label. Can anyone help on this?
You can add a div to your chart and show/hide it, based on condition (for example when a user hovers over the element.
Here is a good example by d3noob.
The useful parts to look for in the code:
Define div styles in style tag
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
Append div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
Then show your tooltip on mouseover and hide on mouseout:
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html(formatTime(d.date) + "<br/>" + d.close)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});

Cannot get D3 .on('mouseover', ...) to work

I'm learning D3 and I'm trying to display data infomation on a scatterplot by hovering on the SVG circles. I take the data from a csv file (data is on the Solar System, with planet names, masses and radiuses) and all the circles show up correctly but when I try to console.log the data information (for instance the mass) on mouseover it does not log anything. The mouseover action is functioning OK but the console tells me that the value requested is "undefined".
Where did I mess up? This is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<style type="text/css">
body{
background-color: black;
}
#chart{
background-color: black;
width: 800px;
height: 500px;
border: solid 1px white;
}
svg{
background-color: white;
}
.dot{
stroke: red;
stroke-width: 1px;
}
</style>
<script type="text/javascript" src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript">
var width = 800;
var height = 500;
var circles;
var svg = d3.select('#chart')
.append('svg')
.attr('width', width + 'px')
.attr('height', height + 'px');
d3.csv('astro.csv').then(function(data){
xScale = d3.scaleLinear()
.domain([0,500])
.range([0, 800]);
circles = svg.selectAll('.dot')
.data(data)
.enter()
.append('circle')
.attr('class', '.dot')
.attr('cx', function(d){
return xScale(d.mass);
})
.attr('cy', 100)
.attr('r', 20)
.on('mouseover', function(d){
console.log(d.mass);
})
});
</script>
</body>
</html>
Since D3 V6, all mouse event handlers has an event passed as the first argument and data as the second. In the V5 or earlier, your code will work.
V6 or higher:
.on('mouseover', (event, d) => console.log(d.mass));
V5 or earlier:
.on('mouseover', d => console.log(d.mass));

Why is there a multi-second delay in this d3 code running?

I add a group of <span> elements to a <div> and want a popup when I mouseover them.
My code works but there is a 5 (or more) second delay between the DOM being complete and the mouse events being handled. (d3 v3 on Chrome):
var popup=d3.select("body")
.append("div")
.attr("class","popup")
.style("opacity",0)
.style("overflow","auto");
var bucket=d3.select("body")
.append("div").text("DIV")
.attr("id","bucket");
bucket.append("div").attr("class","xxx").text("List: ");
document.addEventListener("DOMContentLoaded",
function(e) {
var ptypes =["Pigs","Geese","Ducks","Elephants"];
var content=[];
for(i=0; i<ptypes.length; i++) {
content.push({"key":ptypes[i],"data":"xxx"});
}
keys=d3.select("body").selectAll(".xxx");
d3.select("body").select(".xxx")
.selectAll("p")
.data(content)
.enter()
.append("span")
.html(function(d) {return " "+d.key+" "; })
.on("mouseover",function(d) {
popup.text(d.data)
.style("opacity",0.9)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY+20) + "px");
})
.on("mouseout",function(d) {
popup.style("opacity",0)
});
});
div.popup {
position: absolute;
text-align: left;
width: 200px; height: 200px; padding: 3px;
background: lightsteelblue;
border: 0px; border-radius: 4px;
}
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
<style>
div.popup {
position: absolute;
text-align: left;
width: 200px; height: 200px; padding: 3px;
background: lightsteelblue;
border: 0px; border-radius: 4px;
}
</style>
</head>
<body>
<script>
var popup=d3.select("body")
.append("div")
.attr("class","popup")
.style("opacity",0)
.style("overflow","auto");
var bucket=d3.select("body")
.append("div").text("DIV")
.attr("id","bucket");
bucket.append("div").attr("class","xxx").text("List: ");
document.addEventListener("DOMContentLoaded",
function(e) {
var ptypes =["Pigs","Geese","Ducks","Elephants"];
var content=[];
for(i=0; i<ptypes.length; i++) {
content.push({"key":ptypes[i],"data":"xxx"});
}
keys=d3.select("body").selectAll(".xxx");
d3.select("body").select(".xxx")
.selectAll("p")
.data(content)
.enter()
.append("span")
.html(function(d) {return " "+d.key+" "; })
.on("mouseover",function(d) {
popup.text(d.data)
.style("opacity",0.9)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY+20) + "px");
})
.on("mouseout",function(d) {
popup.style("opacity",0)
});
});
</script>
</body>
</html>
The code works, but there is a big delay (5 or more seconds) before it gets going and recognises the mouse events.
There's no timing issue, the pointer events appear to be working right away. The issue is that your div covers the elements you want to interact with:
I've set the opacity to 0.4 rather than 0, and we can see the div smothering your interactive elements. If you move the mouse over the "ts" of elephants, I get interaction right away. After the mouse over on the "ts", the div moves out of the way enabling interaction with the rest of the elements:
var popup=d3.select("body")
.append("div")
.attr("class","popup")
.style("opacity",0.4)
.style("overflow","auto");
var bucket=d3.select("body")
.append("div").text("DIV")
.attr("id","bucket");
bucket.append("div").attr("class","xxx").text("List: ");
document.addEventListener("DOMContentLoaded",
function(e) {
var ptypes =["Pigs","Geese","Ducks","Elephants"];
var content=[];
for(i=0; i<ptypes.length; i++) {
content.push({"key":ptypes[i],"data":"xxx"});
}
keys=d3.select("body").selectAll(".xxx");
d3.select("body").select(".xxx")
.selectAll("p")
.data(content)
.enter()
.append("span")
.html(function(d) {return " "+d.key+" "; })
.on("mouseover",function(d) {
popup.text(d.data)
.style("opacity",0.9)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY+20) + "px");
})
.on("mouseout",function(d) {
popup.style("opacity",0.4)
});
});
div.popup {
position: absolute;
text-align: left;
width: 200px; height: 200px; padding: 3px;
background: lightsteelblue;
border: 0px; border-radius: 4px;
}
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
The solution would then be to set the pointer-events style of the div to none:
var popup=d3.select("body")
.append("div")
.attr("class","popup")
.style("opacity",0.4)
.style("overflow","auto");
var bucket=d3.select("body")
.append("div").text("DIV")
.attr("id","bucket");
bucket.append("div").attr("class","xxx").text("List: ");
document.addEventListener("DOMContentLoaded",
function(e) {
var ptypes =["Pigs","Geese","Ducks","Elephants"];
var content=[];
for(i=0; i<ptypes.length; i++) {
content.push({"key":ptypes[i],"data":"xxx"});
}
keys=d3.select("body").selectAll(".xxx");
d3.select("body").select(".xxx")
.selectAll("p")
.data(content)
.enter()
.append("span")
.html(function(d) {return " "+d.key+" "; })
.on("mouseover",function(d) {
popup.text(d.data)
.style("opacity",0.9)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY+20) + "px");
})
.on("mouseout",function(d) {
popup.style("opacity",0.4)
});
});
div.popup {
position: absolute;
text-align: left;
width: 200px; height: 200px; padding: 3px;
background: lightsteelblue;
border: 0px; border-radius: 4px;
pointer-events:none;
}
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>

D3 - Legend in seperate div element

I have a plunker here - https://plnkr.co/edit/inW1NfaUwechJt8C1hC9?p=preview
I'm trying to create a legend for this graph
I can do it by adding the legend to the svg but Id like more control over the styling and position
Is it possible to create the legend in a separate div element and use something like a ul list.
var legend = d3.select(".legend")
.data(colors)
.enter()
.append("g")
.attr("class", "legend")
.attr("transform", (d, i) => {
return "translate(20," + i * 35 + ")";
});
Yes, Based on your comment it appears as though a html list approach is what you want, I'll answer for that approach.
Rather than appending a g for each legend item, append li to a list. You can also append the div and ul with d3, just as any svg component. The only major change is appending the right type of element and setting its relevant properties - as this will be different with html than svg.
As with svg components, you can style with css or .attr/.style methods (I use both below).
Here's an example:
var data = [{"name":"Category 1", "value":1},{"name":"Category 2", "value":2},{"name":"Category 3", "value":3},{"name":"Category 4", "value":4},{"name":"Category 5", "value":5}];
var scale = d3.scaleLinear()
.range(["red","orange"])
.domain([1,5]);
var divLegend = d3.select("#divLegend");
divLegend.append("p")
.html("Legend Title")
.style("text-align","center")
.style("font-size","20px")
var list = divLegend.append("ul");
var entries = list.selectAll("li")
.data(data)
.enter()
.append("li");
// append rectangle and text:
entries.append("span")
.attr("class","rect")
.style("background-color", function(d) { return scale(d.value); })
entries.append("span")
.attr("class","label")
.html(function(d) { return d.name; })
ul {
list-style-type: none;
}
.rect {
width: 10px;
height: 10px;
display: inline-block;
}
.label {
margin-left: 10px;
}
#divLegend {
width: 150px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<div id="divLegend"></div>

CSS class not working in tooltip on mouseover on text in D3.js

I am using CSS to show font size and background color on mouseover on text in D3.js
d3.select(this).append("text")
.classed("hover", true)
.attr('transform', function(d){
return 'translate(5, 50)';
})
.text(d.name);
"hover" class is not appling, its just displaying simple text
here is my CSS class
text.hover {
position: absolute;
text-align: left;
background-color: #FFFFEF;
width: 400px;
height: 135px;
padding: 10px;
border: 1px solid #D5D5D5;
font-family: arial,helvetica,sans-serif;
position: absolute;
font-size: 1.1em;
color: #333;
padding: 10px;
border-radius: 3px;
background-color: rgba(255,255,255,0.9);
color: #000;
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
-moz-box-shadow: 0 1px 5px rgba(0,0,0,0.4);
border:1px solid rgba(200,200,200,0.85);
}
What is the best way to apply CSS on text
The way you are handling the text is like you want it to be a div. You are using the wrong attributes, how can text have a background fill ?
I have edited the fiddle provided and shown that the class does work : https://jsfiddle.net/u1gpny6o/1/
All I have put in the hover class is a fill like so :
.hover {
fill:red;
}
What is it you're trying to do ? Is it create a div with text ? If so create a div, give it the class you have in the question, and append text to that div, does that make sense ?
EDIT:
From your comments I have come up with this : https://jsfiddle.net/u1gpny6o/3/
From this question (not the selected answer but the second one) : Show data on mouseover of circle
I have made a tooltip div like so :
var tooltip = d3.select("body")
.append("div")
.classed('hover', true)
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
So you can edit attributes in the css :
.hover {
background: #FFFFEF;
width: 400px;
height: 135px;
padding: 10px;
stroke: black;
stroke-width: 2px;
}
And it will appear on mouseover, move on mousemove(to mouse coordinates, this can be edited) and disappear on mouseout :
.on("mouseover", function(d) {
tooltip.text(d.name)
tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return tooltip.style("top",
(d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 10) + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
What you were doing previously made no sense. You had the class hover and you were duplicating attributes in the CSS, for example, setting color twice and so on. Also giving the elements attributes you can't give. For example, SVG elements can't have a border but can have a stroke and so on.
As you mentioned, you want to load the visualization in a pop up window. I would do it like so :
function update(id, data){
var container = d3.select('#'+id) // then use this to append your vis to
. //the rest of the code the create the vis
.
.
.
}
And then when you hover over the node, just pass the id of the pop up to the update function along with the data (if need be) like so :
update(popupid, data);
It's not working likely because of the way you set up the hover class (doing text.hover) and how you are attaching it. Why not attach a mouseover listener to text and then assign the class:
Define your hover CSS class:
`.hover { ...styles ...}`
Use it on mouseover:
`select your text
.on("mouseover", function() {
select text
.classed("hover",true);
});`
Then you can reverse it similarly with mouseout.

Resources