bootstrap form-inline not rendering properly with d3 - javascript

I'm creating an interactive visualization using d3.js; the top of the visualization has a GUI which I'm styling with bootstrap. I'm trying to style the GUI as a .form-inline, however the labels for the checkboxes are not showing and the spacing between the .form-group isn't working.
Rendered html:
Rendered code:
JS code
For the checkboxes I'm using (where selector is the div ID that everything is being rendered in):
var gui = d3.select(selector).append("div")
.attr("id", "gui")
.attr("class","form-inline")
gui.append("div")
.attr("class","checkbox")
.append("label")
.append("input")
.attr("type","checkbox")
.attr("id","toggle_distance")
.attr("checked","")
.attr("onclick","guiUpdate(this)")
.text(" Toggle distance labels")
For the dropdown I'm using:
var leafSelect = gui.append("div")
.attr("class","form-group")
.append("select")
.attr('onchange','guiUpdate(this)')
.attr('id','leafColor')
.attr("class","form-control")
leafSelect.selectAll("option")
.data(mapParse.keys()).enter()
.append("option")
.text(function(d) { return d; })
Looking at the bootstrap form examples, it looks like everything should be rendering properly. What am I missing?

For the checkbox problem: move the text outside the inputand inside the label:
gui.append("div")
.attr("class","checkbox")
.append("label")
.text(" Toggle distance labels")
.append("input")
.attr("type","checkbox")
.attr("id","toggle_distance")
.attr("checked","")
.attr("onclick","guiUpdate(this)")
Now, for the spacing problem, this is a guess: append form instead of div:
var gui = d3.select(selector).append("form")
.attr("id", "gui")
.attr("class","form-inline")
And the inner div like you did:
var leafSelect = gui.append("div")
.attr("class","form-group")
.append("select")
.attr('onchange','guiUpdate(this)')
.attr('id','leafColor')
.attr("class","form-control")

I'm solving the checkbox label issue with the following:
var gui = d3.select(selector).append("div")
.attr("id", "gui")
.attr("class","form-inline")
var check1 = gui.append("div")
.attr("class","checkbox")
.append("label")
check1.append("input")
.attr("type","checkbox")
.attr("id","toggle_distance")
.attr("checked","")
.attr("onclick","guiUpdate(this)")
check1.append('text')
.text("Toggle distance labels")
The alignment issue I'm patching into place with some CSS unfortunately:
.form-inline text, .form-inline label {
margin: 0px 3px 0px 3px !important;
}

Related

Tooltip in worldmap created via d3.js

I have created a worldmap using the d3 and now able to create the specific countries to have hover effect , however I have also created the tooltip what I want to do now is to get the country map in the tooltip (the country which is hovered) i have used d3 v4 to do all this.
I have made changes suggested by CodeSmit but it seems I'm missing a lot of things.
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<style>
#country_name{
border:none;
}
.bigmap {
width: 100%;
height: 100%;
position: center;
background-color: #080e1e;
float:right;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #19202d;
border-radius: 3px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
padding: .2em;
text-shadow: #f5f5f5 0 1px 0;
opacity: 0.9;
position: absolute;
}
div {
color: #fff;
font-family: Tahoma, Verdana, Segoe, sans-serif;
padding: 5px;
}
.container {
display:flex;
}
.fixed {
text-align:center;
border-width: 1px;
vertical-align:middle;
border-style: solid;
border-color:#55a4bf ;
width: 80px;
margin-right:10px;
}
.flex-item {
border-width: 1px;
text-align:center;
vertical-align:middle;
border-style: solid;
border-color:#55a4bf ;
//background-color:#7887AB;
width: 120px;
}
</style>
</head>
<svg class= "bigmap"width="760" height="340"></svg>
<div class="tooltip"></div>
<body>
<script>
var margin = {top: 0, right: 10, bottom: 10, left: 10};
var width = 760 ;
var height = 400 ;
var projection = d3.geoNaturalEarth1()
.center([0, 15])
.rotate([-11,0])
.scale([150])
.translate([750,350]);
var path = d3.geoPath()
.projection(projection);;
var svg = d3.select(".bigmap")
.append("g")
.attr("width", width)
.attr("height", height);
var tooltip = d3.select("div.tooltip");
d3.queue()
.defer(d3.json, "https://cdn.rawgit.com/mbostock/4090846/raw/d534aba169207548a8a3d670c9c2cc719ff05c47/world-110m.json")
.defer(d3.tsv, "https://cdn.rawgit.com/mbostock/4090846/raw/d534aba169207548a8a3d670c9c2cc719ff05c47/world-country-names.tsv")
.await(ready);
function ready(error, world, names) {
if (error) throw error;
var countries1 = topojson.feature(world, world.objects.countries).features;
countries = countries1.filter(function(d) {
return names.some(function(n) {
if (d.id == n.id) return d.name = n.name;
})});
console.log("countries",countries);
var arr = ["India","Sri Lanka","Afghanistan","Russian Federation"];
svg.selectAll("path")
.data(countries.filter(d => d.name !== "Antarctica"))
.enter()
.append("path")
.attr("stroke","#080e1e")
.attr("stroke-width",1)
.attr("fill", "#0d2331")
.attr("d", path )
.on("mouseover",function(d,i){
if (arr.includes(d.name)){
var tipSVG = tooltip.append("svg")
.attr("width", 220)
.attr("height", 55);
var bbox =tipSVG.append("path")
.attr("stroke","#080e1e")
.attr("stroke-width",1)
.attr("fill", "#0d2331")
.attr("d", path(d) )
.node().getBBox()
tipSVG.attr("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`);
tooltip.classed("hidden", false)
.attr("fill","#19202d")
.style("top", 150+ "px")
.style("left", 20 + "px")
.html('<div class="container"><div class="fixed" id="country_name">'+d.name+'</div><div class="flex-item">Fixed 2</div></div><div class="container"><div class="fixed">Fixed 3</div><div class="flex-item">Fixed 4</div></div><div class="container"><div class="fixed">Fixed 5</div><div class="flex-item">Fixed 6</div></div><div class="container"><div class="fixed">Fixed 7</div><div class="flex-item">Fixed 8</div></div>');
d3.select(this).attr("fill","#0d2331").attr("stroke-width",2).style("stroke","#b72c2f")
return tooltip.style("hidden", false).html(d.name);
}
})
.on("mousemove",function(d){
})
.on("mouseout",function(d,i){
if (arr.includes(d.name)){
d3.select(this).attr("fill","#0d2331").attr("stroke-width",1).style("stroke","#080e1e")
tooltip.classed("hidden", true);
}
});
var g = svg.append('g');
g.selectAll('circle')
.data(countries)
.enter().append('circle')
.attr("fill","white")
.attr('transform', function(d) { return 'translate(' + path.centroid(d) + ')'; })
.attr('r',function(d){
if (arr.includes(d.name)){
return "3"
}
return "0";
}
);
};
</script>
</body>
Any guidance or help is greatly appreciated and thanks in advance
TL;DR:
The .html method on D3 selections first deletes anything that's already inside those elements before setting the new contents. Thus, to initiate an element's base HTML content with .html, be sure to call it first before adding anything else to the element, and also do not call .html later on, or risk it overwriting anything that was added to it.
You're close. You've got a number of issues though.
1. d3-tip Not Used
You're including the d3-tip library, but you're not making real use of it at all. Because of this, it's adding to the confusion. You have your own <div class="tooltip"></div> which is what actually appears. If you don't need the tooltip to float where the cursor is (which is what d3-tip is for), then I'd highly recommend starting by stripping out all your code making use of this library.
2. <svg> Doesn't Make It Into Tooltip
Your "mouseover" event fails to add the country SVG element for two reasons:
First, because you're selecting the #tipDiv element which never appears since it's part of the d3-tip code that doesn't get used. To fix this, I think you want to select the div.tooltip element instead. Since you already have the 'tooltip' variable set to this, you don't need d3.select; you can simply do:
var tipSVG = tooltip.append("svg")
.attr("width", 220)
.attr("height", 55);
At this point, it will add the <svg>, but the issue is that, following this, it is immediately written over. This happens in the "mousemove" event:
tooltip.classed("hidden", false)
...
.html('<div class="container">...</div>');
As soon as the mouse moves over the element, the .html call overwrites the newly added svg, deleting it. I was able to fix this by moving this block of code out of the "mousemove" event and to the start of the "mouseover" event.
After this, the SVG successfully appears in the tooltip.
3. Tooltip SVG Contains Entire Map (But Looks Empty)
Before you're done, though, at this point the newly appearing SVG looks blank. It's actually not blank, but all the contents are appearing outside the SVG's rendering region. Before fixing this, though, first note you're loading the entire map into the SVG in your "mouseover" event:
tipSVG.selectAll("path")
.data(countries.filter(d => d.name !== "Antarctica"))
.enter()
.append("path")
.attr("stroke","#080e1e")
.attr("stroke-width",1)
.attr("fill", "#0d2331")
.attr("d", path )
I'm guessing you want to load just the country being hovered over? Since you already have that data with 'd' in the function, you can do that with something like:
tipSVG.append("path")
.attr("stroke","#080e1e")
.attr("stroke-width",1)
.attr("fill", "#0d2331")
.attr("d", path(d) )
Note now I'm calling 'path' right on the data.
4. Country Path Way Out Of SVG View
So now the SVG only contains the country being hovered over, which makes fixing the last issue much easier: it's way outside the bounding box. Because it's a single path now, a simple fix is to set the SVG's 'viewBox' to the path's bounding box, which you can get by calling getBBox right on the path element (not the d3 selection of it though).
You can do this like so:
var bbox = tipSVG.append("path")
...
.attr("d", path(d) )
.node().getBBox(); // get the bbox from the path we just added
tipSVG.attr("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`);
This effectively copies the bbox into the viewBox, and fortunately for us, due to the default value of the preserveAspectRatio <svg> attribute, this nicely centers the country in the SVG box:
EDIT:
So, now you're very close! You've just got a couple things wrong:
1. .html() Called After SVG Insert
So, the code you moved out of the "mousemove" should be placed at the beginning of the "mouseover" event. Here's what's happening:
// code appends the new SVG element
var tipSVG = tooltip.append("svg")
.attr("width", 220)
.attr("height", 55);
// creates the path in the SVG and gets the bounding box
var bbox = tipSVG.append("path")
// ...
.node().getBBox()
// makes the SVG view the path
tipSVG.attr("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`);
// DELETES the SVG once .html is called
tooltip.classed("hidden", false)
// ...
.html('<div class="container">...</div>');
Again, .html calls replace everything in the element. Generally you want to use the method sparingly. In this case, it's replacing the content and the newly added SVG with content that doesn't have the SVG. You can still keep the .html call, but it must be moved above the SVG append call so it doesn't delete the SVG:
// replaces all contents with tooltip html
tooltip.classed("hidden", false)
// ...
.html('<div class="container">...</div>');
// code appends the new SVG element
var tipSVG = tooltip.append("svg")
.attr("width", 220)
.attr("height", 55);
// creates the path in the SVG and gets the bounding box
var bbox = tipSVG.append("path")
// ...
.node().getBBox()
// makes the SVG view the path
tipSVG.attr("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`);
// now SVG exists
2. .html Called After SVG Insert
So, the second issue is, yes, the same issue:
return tooltip.style("hidden", false).html(d.name);
This line, at the end, re-calls .html on your tooltip, which has the same effect. In this case, though, it replaces everything with just the country name.
To be honest, I'm not exactly sure of the intent of this line.
Unless I'm mistaken, returning a value in an event listener in D3 has no specific effect.
.style is used for applying CSS styles, but "hidden" is not a CSS style. Perhaps you meant .classed?
The .html call clears the well-setup tooltip with just the name. If you were looking to replace just the country name, remember it's under the subelement #country_name. So, you could do so with, e.g., tooltip.select("#country_name").text(d.name). Note, I used .text which safely escapes HTML code.
Also note you're already embedding the country name in the original .html call, making it unnecessary. The safest option, though, would be to actually remove the d.name from the other .html call, just leaving it <div class="fixed" id="country_name"></div>, and then right after the .html call, include the .text code I have above. You could even link it in D3 style like so,
tooltip.classed("hidden", false)
.attr("fill","#19202d")
.style("top", 150+ "px")
.style("left", 20 + "px")
// .html line does not contain d.name embed:
.html('<div class="container">...</div>')
.select("#country_name")
.text(d.name);
Using your updated code, I was able to get this to work for me.
Keep up the good work!

Why is the hover state behaviour removed?

I have a D3 generated map which needs to be able to dynamically change the fill element of drawn paths. The original paths are generated and assigned a class of 'boundaries'. The hover behaviour is set to turn the country yellow when the user hovers the cursor over the country. However, if I then go and dynamically change the fill color of the country, for example by using d3.selectAll- (I have simplified the below example so that this behaviour is simulated by uncommenting the last section), the hover behaviour stops working. The class has not changed, so why is the hover behaviour now not occurring.. and is there a workaround for this?
CSS
.countryMap{
width: 500px;
height: 500px;
position: relative;
}
.boundaries{
fill:green;
}
.boundaries:hover{
fill:yellow;
}
Javascript
const countryMap = {};
const FILE = `aus.geojson`; // the file we will use
var svg = d3
.select('div.country__map')
.append('svg')
.attr('width',200)
.attr('height',200)
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('viewBox','770 270 200 150')
d3.json(FILE).then(function (outline) {
countryMap.features = outline.features;
// choose a projection
const projection = d3.geoMercator();
// create a path generator function initialized with the projection
const geoPath = d3.geoPath().projection(projection);
drawOutline();
function drawOutline() {
svg
.selectAll('path.boundaries') // CSS styles defined above
.data(countryMap.features)
.enter()
.append('path')
.attr('class', 'boundaries')
.attr('d', geoPath)
// .style('fill', d => {
// return 'green';
// })
}
})
As #Michael mentioned it will be better to manually add or remove class using js.
D3 provides us mouseover and mouseout events which can be used to add and remove class.
Here on hover, we are applying the 'active' class on the element.
svg
.selectAll('path.boundaries')
.data(countryMap.features)
.enter()
.append('path')
.attr('class', 'boundaries')
.attr('d', geoPath)
.on('mouseover', function () {
d3.select(this).classed("active", true)
})
.on('mouseout', function () {
d3.select(this).classed("active", false)
})
We also need to update the CSS according to these changes.
You can update the CSS to:
.boundaries{
fill:green;
}
.boundaries.active{
fill:yellow;
}

Not able to append label on edge of graph in d3.js

I am new to js & d3. I am trying to plot a graph with d3.js forced layout.
My purpose is to append labels ("XXX" & "OOO") on either sides of edges but it's not working.
Here the code snippet:
link.append('svg:text')
.attr('class', 'aEnd')
.text(function(d) { return "XXX"; });
link.append('svg:text')
.attr('class', 'zEnd')
.text(function(d) { return "OOO"; });
Here is the complete code I am working on.
JSFIDDLE : d3_graph_labelled_edge.js
Nesting a text element inside a line element is not allowed, as summed up here.
What you can do is wrapping the line elements inside g containers and append the link labels to them:
var glink = vis.selectAll("line.link")
.data(data.links)
.enter().append("svg:g");
var link = glink.append("svg:line")
[...]
glink.append('svg:text')
.attr('class', 'aEnd')
.text(function(d) { return "XXX"; });
Updated fiddle.

addeventlistener to <div> appended in d3

I am trying to add events (addeventlistner) to multiple elements that are appended with d3.However, when I click on , it won't trigger the attached "alert".Interestingly, this works on <div> that I manually add inside .Could anyone shed light on this?
d3.csv("output1.csv",function(data){
var width = 700,
height = 600;
d3.select('body').selectAll("div")
.data(data)
.enter()
.append("div")
.style("width",30)
.style('height',30)
.style("background-color",function(d){
return d.color;
})
var divs = document.getElementsByTagName("div") // this returns array of div elements
function show(){
alert("ya")
}
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click',function(){
alert("yaho")
})
}
})
Since your are using d3, use d3 event listeners as shown below.
var divs = d3.select('body').selectAll("div")
.data(data)
.enter()
.append("div")
.style("width",30)
.style('height',30)
.style("background-color",function(d){
return d.color;
});
divs.on("click",function(){
alert("yaho")
});
Or
function show(){
alert("Hi");
}
divs.on("click",show);
Listeners bonded with addEventListeners will also work. Here is the working fiddle using similar code. JSFiddle

Displaying labels on horizontal chart with d3.js

I'm creating a simple chart with d3.js using the following code. Now suppose the var data is properly formatted (ex. "[20,15,15,40,10]"), the horizontal chart displays properly but I'd like to add some labels on the left and I'm a bit overwhelmed by d3.js . I was trying to insert an extra div containing the label before every other div representing and showing the data, but I can't understand how or if that's the proper way.
The result should look something like:
Label 1 |=============================40%==|
Label 2 |=================30%==|
Label 3 |======15%==|
and so on. The bars display just fine, but how do I add the labels on the left ? This is the piece of script that displays the bars (given a div with id 'chart' and the proper css).
chart = d3.select('#chart')
.append('div').attr('class', 'chart')
.selectAll('div')
.data(data).enter()
.append('div')
.transition().ease('elastic')
.style('width', function(d) { return d + '%'; })
.text(function(d) { return d + '%'; });
You'll find a working example here . So, how do I add labels on the left of every bar so that the user knows what every bar represents ?
I think your idea of appending a <div> containing a label before each bar is reasonable. To do so, you first need to append a <div> for each bar, then append a <div> containing the label, and finally append a <div> containing the bars. I've updated your example with a JSFiddle here, with the Javascript code below (some changes were also required to the CSS):
// Add the div containing the whole chart
var chart = d3.select('#chart').append('div').attr('class', 'chart');
// Add one div per bar which will group together both labels and bars
var g = chart.selectAll('div')
.data([52,15,9,3,3,2,2,2,1,1]).enter()
.append('div')
// Add the labels
g.append("div")
.style("display", "inline")
.text(function(d, i){ return "Label" + i; });
// Add the bars
var bars = g.append("div")
.attr("class", "rect")
.text(function(d) { return d + '%'; });
// Execute the transition to show the bars
bars.transition()
.ease('elastic')
.style('width', function(d) { return d + '%'; })
Screenshot of JSFiddle output

Categories

Resources