Adding div Element on d3 Bar Chart - d3.js

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);
});

Related

Adding tooltip to donut slices

I'm trying to add a tooltip to my donut's slices on moues over as follow:
var tooltip = select("g.arc--first")
//.append("div") // if I add this div here it stops working
.append("text")
firstSlice.append('path')
.attr('d', coolArc(firstRadius, thickness))
.attr('class', d => `fill_${weightScale(d.data.weight)}`)
.on('mouseover', (d) => {
tooltip.style("visibility", "visible")
.text(this.nodeByName.get(d.data.name)["Short Description"])
.style("fill", "red")
.style("position", "absolute")
.style("text-align", "center")
.style("width", "60px")
.style("height", "28px")
.style("padding", "2px")
.style("background", "lightsteelblue")
.style("border", "10px")
})
My goal is to have a tooltip similar to http://bl.ocks.org/d3noob/a22c42db65eb00d4e369 , but right now it only shows a red text at the middle of the page. I think I need to have a new div but when I try to add append("div") it stops working and doesn't show the text anymore. How should I fix it?
The tooltip from the example that you mentioned works pretty simply. It is appended as a child element for body (you try to append it as a child for g element, but you cannot append html elements into svg). So you should change you code:
var tooltip = select('body')
.append('div')
.attr('class', 'tooltip');
I also recommend you style this tooltip in your css (add appropriate class name and rules in your css file) you can avoid chaining many .style methods in this case. You should set position: absolute; rule - you can update top and left styles and position the tooltip where you need. Also set visibility: hidden rule - your tooltip should be hidden by default.
In mouseover event handler you need to change:
visibility style to show the tooltip
left and top styles to position the tooltip
text to update text on the tooltip
.on('mouseover', (d) => {
tooltip
.style('visibility', 'visible')
.style('left', d3.event.pageX + 'px')
.style('top', d3.event.pageY + 'px')
.text(d);
})
In mouseout event handler you should just hide the tooltip:
.on('mouseout', (d) => {
tooltip
.style('visibility', 'hidden')
});
See simplified demo in the hidden snippet below:
var tooltip = d3.select("body")
.append("div")
.attr('class', 'tooltip');
d3.selectAll('circle')
.data(['one', 'two', 'three'])
.on('mouseover', (d) => {
tooltip
.style('visibility', 'visible')
.style('left', d3.event.pageX + 'px')
.style('top', d3.event.pageY + 'px')
.text(d);
})
.on('mouseout', (d) => {
tooltip
.style('visibility', 'hidden')
})
.tooltip {
background-color: lightblue;
font-weight: bold;
padding: 5px;
border-radius: 9px;
position: absolute;
display: inline-block;
margin-top: -50px;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>
<svg width="720" height="120">
<circle cx="20" cy="60" r="10"></circle>
<circle cx="180" cy="60" r="10"></circle>
<circle cx="340" cy="60" r="10"></circle>
</svg>

D3: Mouseover to display data in top right corner

I'm working in D3 with a tooltip in a mouseover event. What I have is the data displayed slightly to the right and above the dot. However, it's a line graph and this is sometimes unreadable. What I would like is the data displayed in some fixed position (I've decided the upper right hand corner is free of clutter). Can anyone give me some advice? Code below
.on("mouseover", function(d) {
d3.select(this).attr("r", 5.5).style("fill", "red");
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html("(" + xTemp(d)
+ ", " + yTemp(d)+ ")")
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
I assume your tooltip is a <div> with position: absolute.
In that case, you can wrap your SVG in a container div (since SVGs don't support offsetTop and offsetLeft) and set the position of your tooltip using offsetTop and offsetLeft.
Here is a simple demo. I put two paragraphs ("Foo" and "Bar"), so you can see that the position of the <div> is correct, even if the SVG is not the first element on the page:
var svg = d3.select("svg");
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
svg.selectAll(null)
.data(d3.range(50, 300, 50))
.enter()
.append("circle")
.attr("cx", Number)
.attr("cy", 100)
.attr("r", 20)
.style("fill", "teal")
.on("mouseover", function(d) {
d3.select(this).style("fill", "red");
tooltip.html(d)
.style("top", svg.node().parentNode.offsetTop + 10 + "px")
.style("left", svg.node().parentNode.offsetLeft + 270 + "px")
.style("opacity", .9);
})
.on("mouseout", function() {
d3.select(this).style("fill", "teal")
tooltip.style("opacity", 0)
})
svg {
border: 1px solid gray;
}
.tooltip {
position: absolute;
broder: 1px solid black;
background-color: tan;
padding: 2px;
border-radius: 2px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p>Foo</p>
<p>Bar</p>
<div>
<svg id="svg"></svg>
</div>

sortable bar chart in d3.js

I'm a newbie in d3.js and I'm trying to create a sortable bar chart. I'm following the example but I'm unable to get the same result. The bars move but the labels do not. I'm not sure what I'm doing wrong. Any guidance is hugely appreciated. Thanks
<label><input id="sort" type="checkbox"> Sort values</label>
<div id="check"></div>
<script src="~/Scripts/d3-tip.js"></script>
<script>
debugger;
var data=#Html.Raw(#JsonConvert.SerializeObject(Model));
var margin = {
top: 20,
right: 20,
bottom: 50,
left: 40
},
width = 250 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x=d3.scaleBand()
.rangeRound([0, width])
.padding(0.1);
var y=d3.scaleLinear().range([height,0]);
var chart = d3.select("#check")
.append("svg:svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tip=d3.tip()
.attr("class","d3-tip")
.offset([-10,0])
.html(function(d){
return "<strong>Rec Items:</strong> <span style='color:red'>" + d.TotalRecItemsSum + "</span>"
})
chart.call(tip)
x.domain(data.map(function(d){
return d.AppClientName;
})) //x domain is a map of all client names
y.domain([0,d3.max(data,function(d){return d.TotalRecItemsSum;})])
//y is range from maximum(count) value in array until 0
chart.selectAll(".bar").data(data).enter().append("svg:rect")
.attr("x",function(d){
return x(d.AppClientName);
})
.attr("class", "bar")
.attr("width",x.bandwidth())
.attr("y",function (d){
return y(d.TotalRecItemsSum);})
.attr("height",function(d){
return height-y(d.TotalRecItemsSum);})
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
var xAxis=d3.axisBottom().scale(x);
var yAxis=d3.axisLeft().scale(y);
chart.append("g")
.attr("class","y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("TotalRecItemSum");
chart.append("g").attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("transform","rotate(90)")//In some cases the labels will overlap
//so I want to rotate the label's so they are vertical
.attr("x", 0) //After that some finetuning to align everything the right way:
.attr("y", -6)
.attr("dx", ".6em")
.attr("class","x axis")
.attr("class","text")
.style("text-anchor", "start")
d3.select("#sort").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("#sort").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.TotalRecItemsSum - a.TotalRecItemsSum; }
: function(a, b) { return d3.ascending(a.AppClientName, b.AppClientName); })
.map(function(d) { return d.AppClientName; }))
.copy();
chart.selectAll(".bar")
.sort(function(a, b) { return x0(a.AppClientName) - x0(b.AppClientName); });
var transition = chart.transition().duration(750),
delay = function(d, i) { return i * 100; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.AppClientName); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.chart rect {
stroke: white;
fill: orange;
}
.bar:hover {
fill: orangered;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
.x.axis path {
display: none;
}
I'm using d3 version 4. And in the above code, my x-axis has the client names and y-axis is a numerical value. So the bars are arranged in ascending order on checking the checkbox, only the labels do not move. Also, another issue is the checkbox gets checked automatically after a couple of seconds, that's happening because of var timeout, so should I be putting timeout inside function change?
Pls ask me any questions if my explanation is unclear, I always have trouble articulating. Thanks.

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.

Binding on click events to context menu list elements

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.

Resources