Add mouse hover containing specific data to d3.js tree map - javascript

I put together the following treemap using d3.js. It's the top 20 states in terms of voter turnout. Link
I'm unsure how to add the 'values' into the hover. Ideally, it should show the unique value for each state. I'm able to do it for one (California in the example), but I'm not sure how to make it flexible so that it generates other values for different states.
let margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
},
width = 1100 - margin.left - margin.right,
height = 900 - margin.top - margin.bottom;
// append the svg object to the body of the page
let svg = d3
.select('#treemap')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Read data
d3.csv('https://raw.githubusercontent.com/oihamza/Interactive-Data-Vis-Fall2020/master/Project%201/stateVoterTurnout.csv', function(data) {
// stratify the data: reformatting for d3.js
var root = d3
.stratify()
.id(function(d) {
return d.name;
}) // Name of the entity (column name is name in csv)
.parentId(function(d) {
return d.parent;
})(
// Name of the parent (column name is parent in csv)
data
);
// data is an object
console.log(data);
let values = data[1].value;
let tooltip = d3
.select('body')
.append('div')
.style('position', 'absolute')
.style('z-index', '10')
.style('visibility', 'hidden')
.style('background-color', 'white')
.style('border', 'solid')
.style('border-width', '2px')
.style('border-radius', '5px')
.style('padding', '5px')
.text(`${values} voters`);
root.sum(function(d) {
return +d.value;
}); // Compute the numeric value for each entity
// Then d3.treemap computes the position of each element of the hierarchy
// The coordinates are added to the root object above
d3.treemap().size([width, height]).padding(3)(root);
console.log(root.leaves());
// use this information to add rectangles:
svg
.selectAll('rect')
.data(root.leaves())
.enter()
.append('rect')
.attr('x', function(d) {
return d.x0;
})
.attr('y', function(d) {
return d.y0;
})
.attr('width', function(d) {
return d.x1 - d.x0;
})
.attr('height', function(d) {
return d.y1 - d.y0;
})
.style('stroke', 'black')
.style('fill', '#945f04')
.on('mouseover', function() {
return 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');
});
// and to add the text labels
svg
.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.attr('x', function(d) {
return d.x0 + 10;
}) // +10 to adjust position (more right)
.attr('y', function(d) {
return d.y0 + 20;
}) // +20 to adjust position (lower)
.text(function(d) {
return d.data.name;
})
.attr('font-size', '15px')
.attr('fill', 'white');
});
body,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Montserrat", sans-serif
}
.styling {
transform-origin: center center;
align-content: center;
position: relative;
}
.tooltip {
position: relative;
display: inline-block;
/* border-bottom: 1px dotted black; */
}
.tooltip .tooltiptext {
visibility: hidden;
width: 180px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
/* Position the tooltip */
position: absolute;
z-index: 1;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat">
<span class="styling"><div id="treemap"></div></span>
<!-- Loading this version of d3 -->
<script src="https://d3js.org/d3.v4.js"></script>

When you mouseover or mousemove the rectangle, you can find it's assigned datum with the first argument (often called d) - just like you do when you set attributes with .attr() or styles with .style().
You can set the .text() of the tooltip dynamically, using this d:
let margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
},
width = 1100 - margin.left - margin.right,
height = 900 - margin.top - margin.bottom;
// append the svg object to the body of the page
let svg = d3
.select('#treemap')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Read data
d3.csv('https://raw.githubusercontent.com/oihamza/Interactive-Data-Vis-Fall2020/master/Project%201/stateVoterTurnout.csv', function(data) {
// stratify the data: reformatting for d3.js
var root = d3
.stratify()
.id(function(d) {
return d.name;
}) // Name of the entity (column name is name in csv)
.parentId(function(d) {
return d.parent;
})(
// Name of the parent (column name is parent in csv)
data
);
let tooltip = d3
.select('body')
.append('div')
.style('position', 'absolute')
.style('z-index', '10')
.style('visibility', 'hidden')
.style('background-color', 'white')
.style('border', 'solid')
.style('border-width', '2px')
.style('border-radius', '5px')
.style('padding', '5px');
root.sum(function(d) {
return +d.value;
}); // Compute the numeric value for each entity
// Then d3.treemap computes the position of each element of the hierarchy
// The coordinates are added to the root object above
d3.treemap().size([width, height]).padding(3)(root);
// use this information to add rectangles:
svg
.selectAll('rect')
.data(root.leaves())
.enter()
.append('rect')
.attr('x', function(d) {
return d.x0;
})
.attr('y', function(d) {
return d.y0;
})
.attr('width', function(d) {
return d.x1 - d.x0;
})
.attr('height', function(d) {
return d.y1 - d.y0;
})
.style('stroke', 'black')
.style('fill', '#945f04')
.on('mouseover', function() {
tooltip.style('visibility', 'visible');
})
.on('mousemove', function(d) {
tooltip
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px')
.text(`${d.data.value} voters`);
})
.on('mouseout', function() {
tooltip.style('visibility', 'hidden');
});
// and to add the text labels
svg
.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.attr('x', function(d) {
return d.x0 + 10;
}) // +10 to adjust position (more right)
.attr('y', function(d) {
return d.y0 + 20;
}) // +20 to adjust position (lower)
.text(function(d) {
return d.data.name;
})
.attr('font-size', '15px')
.attr('fill', 'white');
});
body,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Montserrat", sans-serif
}
.styling {
transform-origin: center center;
align-content: center;
position: relative;
}
.tooltip {
position: relative;
display: inline-block;
/* border-bottom: 1px dotted black; */
}
.tooltip .tooltiptext {
visibility: hidden;
width: 180px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
/* Position the tooltip */
position: absolute;
z-index: 1;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat">
<span class="styling"><div id="treemap"></div></span>
<!-- Loading this version of d3 -->
<script src="https://d3js.org/d3.v4.js"></script>
It's not necessary to return anything inside these .on() functions.

Related

How do I properly sort the data for my d3 bubble map so that smaller bubbles show up on top of larger bubbles?

I'm making a bubble map similar to this one: https://observablehq.com/#d3/bubble-map
Everything is working except that my smaller bubbles are not always showing on top of the larger ones. I can't see why, as I've sorted the data before drawing the circles. Can anyone see what I'm doing wrong?
Here is a plunker:
https://plnkr.co/edit/JKWeQKkhN2TQwvNZ?open=lib%2Fscript.js
Code is below. The other files are too large for stack overflow but can be accessed via the Plunker.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.states {
fill: #d3d3d3;
stroke: #ffffff;
stroke-linejoin: round;
}
div.tooltip {
position: absolute;
left: 75px;
text-align: center;
height: 12px;
padding: 8px;
font-size: 13px;
font-family: 'Proxima-Nova', sans-serif;
background: #FFFFFF;
border: 1px solid #989898;
pointer-events: none;
}
.block {
width: 18%;
height: 15px;
display: inline-block;
}
</style>
<body>
<div class="g-chart"></div>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/geo-albers-usa-territories#0.1.0/dist/geo-albers-usa-territories.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script>
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var margin = { top: 10, left: 10, bottom: 10, right: 10 },
width = window.outerWidth,
width = width - margin.left - margin.right,
mapRatio = .5,
height = width * mapRatio;
const epsilon = 1e-6;
var projection = geoAlbersUsaTerritories.geoAlbersUsaTerritories()
.scale(width)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var map = d3.select(".g-chart").append("svg")
.style('height', height + 'px')
.style('width', width + 'px')
.call(d3.zoom().on("zoom", function () {
map.attr("transform", d3.event.transform)
d3.selectAll()
}))
.append("g");
queue()
.defer(d3.json, "us.json")
.defer(d3.csv, "test.csv")
.await(ready);
d3.selection.prototype.moveToFront = function () {
return this.each(function () {
this.parentNode.appendChild(this);
});
};
d3.selection.prototype.moveToBack = function () {
return this.each(function () {
var firstChild = this.parentNode.firstChild;
if (firstChild) {
this.parentNode.insertBefore(this, firstChild);
}
});
};
function ready(error, us, data) {
if (error) throw error;
data.forEach(function (d) {
d.amount = +d.amount;
})
map.append("g")
.attr("class", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
// sort by descending size so that the smaller circles are drawn on top - not working
map.append('g')
.attr('class', 'facility')
.selectAll("circle")
.data(data.sort((a, b) => +b.amount - +a.amount))
.enter()
.append("circle")
.attr("cx", function (d) {
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function (d) {
return projection([d.longitude, d.latitude])[1];
})
.attr('r', function (d) {
if (d.amount <= 25) { return 3 }
else if (d.amount > 25 && d.amount <= 50) { return 5 }
else if (d.amount > 50 && d.amount <= 75) { return 7 }
else { return 9 }
})
.style("fill", "#EF4136")
.style("stroke", "#BE2C2D")
.style("stroke-width", 1)
.style("opacity", 0.5)
.on("mouseover", function (d) {
var sel = d3.select(this);
sel.moveToFront();
d3.select(this).transition().duration(0)
.style("opacity", 0.8)
.style("stroke", "#FFFFFF")
div.transition().duration(0)
.style("opacity", .85)
div.text(d.amount)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function () {
var sel = d3.select(this);
d3.select(this)
.transition().duration(0)
.style("opacity", 0.5)
.style("stroke", "#BE2C2D")
div.transition().duration(0)
.style("opacity", 0);
});
//RESPONSIVENESS
d3.select(window).on('resize', resize);
function resize() {
var w = d3.select(".g-chart").node().clientWidth;
width = w - margin.left - margin.right;
height = width * mapRatio;
var newProjection = d3.geoAlbersUsa()
.scale(width)
.translate([width / 2, height / 2]);
path = d3.geoPath()
.projection(newProjection);
map.selectAll("circle")
.attr("cx", function (d) {
return newProjection([d.longitude, d.latitude])[0];
})
.attr("cy", function (d) {
return newProjection([d.longitude, d.latitude])[1];
})
.attr("r", 5)
map
.style('width', width + 'px')
.style('height', height + 'px');
map.selectAll("path").attr('d', path);
}
}
</script>
I would suggest you to split your data to a couple separate datasets grouped by size and create distinct group (g element) for each one. This will also fix issues with circles highlighting.
I slightly updated your plunker to make it work as described (check the lines 91-167) https://plnkr.co/edit/rayo5IZQrBqfqBWR?open=lib%2Fscript.js&preview
Also check the raise and lower methods. They might be a good replacement for your moveToFront and moveToBack methods.
https://riptutorial.com/d3-js/example/18029/svg--the-drawing-order

Extend D3 Chart to add tooltip from HTML page

I have a D3 graph showing tooltip on mouseover. The graph is currently created from a JS file.
D3 graph code:
var tooltip = d3.select('body')
.append('div')
.style('position', 'absolute')
.style('padding', '0 10px')
.style('background', 'white')
.style('opacity', 0);
var svgE = svg
.append("g")
.selectAll()
.data(graph.data)
.join("rect")
.attr("x", xFunc)
.attr("y", yFunc)
.on('mouseover', function (d) {
tooltip.transition().duration(200)
.style('opacity', .9);
var toolTipText = d.x + " - " + d.y;
tooltip.html(toolTipText)
.style('left', (d3.event.pageX - 35) + 'px')
.style('top', (d3.event.pageY - 30) + 'px');
})
.on('mouseout', function (d) {
tooltip.transition().duration(200)
.style('opacity', 0);
tooltip.html("");
});
Is it possible to remove the tooltip code from the JS file and move it to an HTML page so I can add tooltip functionality whenever needed?
D3 code in JS file will be:
var svgE = svg
.append("g")
.selectAll()
.data(graph.data)
.join("rect")
.attr("x", xFunc)
.attr("y", yFunc);
What do I add in the HTML block to add tooltip functionality?
The example you provided creates a tooltip entirely in JavaScript, but if you for whatever reason prefer creating a tooltip in HTML you can do that as well.
const mainSvg = d3.select('svg#mainSvg');
const tooltip = d3.select('div.tooltip');
const aRectangle = mainSvg.append('rect')
.attr('x', 50)
.attr('y', 50)
.attr('height', 100)
.attr('width', 100)
.attr('fill', 'green');
aRectangle.on('mouseover', (d) => {
tooltip.style('visibility', 'visible');
tooltip.html('You are mousing over a rectangle.');
tooltip.style('left', `${d3.event.pageX}px`)
.style('top', `${d3.event.pageY}px`)
});
aRectangle.on('mouseout', (d) => {
tooltip.style('visibility', 'hidden');
});
svg#mainSvg {
height: 200px;
width: 200px;
background-color: gray;
}
div.tooltip {
z-index: 1;
background-color: white;
position: absolute;
padding: 0 10px;
border: 1px solid black;
pointer-events: none;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div>
<svg id='mainSvg'></svg>
</div>
<div class='tooltip'>
This is a tooltip
</div>

Tooltip Stays Hidden In D3 Treemap

I'm building a D3 treemap diagram that requires a tooltip. The tooltip is a div element that I'm toggling on and off with the 'mouseover' and 'mouseleave' events attached to each leaf of the treemap. The developer console confirms that the element is being added to the DOM, that it has the correct text to display within the div, that 'display' is set to 'block' when hovering, 'z-index' is 10, and that the 'position' is 'absolute', however, I can't get the tooltip to actually appear as expected.
Here's a link to the Codepen.
Here's my actual code:
HTML:
<script src="https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js"></script>
<div>
<h1 id='title'>JSON Data Treemaps</h1>
<h2 id='description'>Top 100 Most Pledged Kickstarter Campaigns</h2>
<svg id="canvas"></svg>
<svg id="legend"></svg>
</div>
CSS:
#import url('https://fonts.googleapis.com/css?family=Noto+Sans');
body {
background-color: ghostwhite;
display: flex;
flex-direction: column;
font-size: 16px;
font-family: 'Noto Sans', sans-serif;
div#tooltip {
position: absolute;
}
div {
display: flex;
flex-direction: column;
h1 {
margin: 4.6rem auto 0;
font-size: 3.2rem;
}
h2 {
margin: 1rem auto 2rem;
font-size: 1.4rem;
}
svg {
margin: auto;
}
svg#legend {
margin: 2rem auto;
}
}
}
JS:
const margin = {
top: 30,
bottom: 100,
left: 30,
right: 30
},
width = 960 - margin.left - margin.right,
height = 570 - margin.top - margin.bottom;
const KICK_STARTER_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/kickstarter-funding-data.json';
const MOVIE_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json';
const VIDEO_GAME_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/video-game-sales-data.json';
// Load all data and assign to variables.
d3.queue()
.defer(d3.json, KICK_STARTER_DATA_URL)
.defer(d3.json, MOVIE_DATA_URL)
.defer(d3.json, VIDEO_GAME_DATA_URL)
.await(storeDataCategories);
let KICK_STARTER_DATA;
let MOVIE_DATA;
let VIDEO_GAME_DATA;
let currentDataSet;
function storeDataCategories(error, kickStarter, movies, videoGames) {
// console.log(...arguments);
if(error) throw error;
KICK_STARTER_DATA = kickStarter;
MOVIE_DATA = movies;
VIDEO_GAME_DATA = videoGames;
currentDataSet = kickStarter;
buildTreemap();
buildLegend();
}
var svg = d3.select('svg#canvas')
.attr('width', width)
.attr('height', height)
.style('background-color', 'ghostwhite')
.style('box-shadow', '0 6px 26px darkgray');
var fader = (color) => {
return d3.interpolateRgb(color, 'ghostwhite')(0.2);
},
test = d3.schemeCategory20.map(fader),
colorScale = d3.scaleOrdinal(d3.schemeCategory20.map(fader)),
format = d3.format(',d');
var treemap = d3.treemap()
.tile(d3.treemapResquarify)
.size([width, height]);
// Adding tooltip for info on hover
const tooltip = d3.select('svg#canvas')
.append('div')
.attr('id', 'tooltip')
.attr('width', 60 + 'px')
.attr('height', 40 + 'px')
.style('z-index', 10)
.style('display', 'none')
.style('position', 'absolute');
function buildTreemap() {
// console.log(KICK_STARTER_DATA);
// console.log(MOVIE_DATA);
// console.log(VIDEO_GAME_DATA);
// console.log(currentDataSet);
var root = d3.hierarchy(currentDataSet)
.sum(sumValue)
.sort((a, b) => b.height - a.height || b.value - a.value);
treemap(root);
var cell = svg.selectAll('g')
.data(root.leaves())
.enter().append('g')
.attr('transform', d => 'translate(' + [d.x0, d.y0] + ')')
.on('mouseover', d => {
// console.log('mouseover - d:\n', d);
const tooltipText = formatTooltip(d);
tooltip.transition().duration(200)
.style('position', 'absolute')
.style('display', 'block');
tooltip.html(tooltipText)
.attr('data-value', d.value)
.style('top', (d3.event.pageY - 50) + 'px')
.style('left', (d3.event.pageX + 20) + 'px');
})
.on('mouseout', d => {
// console.log('mouseout!');
tooltip.transition().duration(500)
.style('display', 'none');
});
cell.append('rect')
.attr('id', d => d.data.id)
.attr('class', 'tile')
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', d => colorScale(d.parent.data.name))
.attr('data-name', d => d.data.name)
.attr('data-category', d => d.parent.data.name)
.attr('data-value', d => d.data.value);
cell.append('clipPath')
.attr('id', d => `clip-${d.data.name}`)
.append('use')
.attr('xlink:href', d => `#${d.data.name}`);
cell.append('text')
.attr('clip-path', d => `url(#clip-${d.data.name})`)
.selectAll('tspan')
.data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g))
.enter().append('tspan')
.attr('x', 4)
.attr('y', (d, i) => 13 + i * 10)
.text(d => d)
.style('font-size', '10');
}
function buildLegend() {
console.log(`inside 'buildLegend'`);
// console.log(currentDataSet);
const rectWidth = width / 8,
rectHeight = 40;
const legend = d3.select('svg#legend')
.attr('width', width)
.attr('height', rectHeight);
legend.append('g')
.selectAll('g')
.data(colorScale.domain())
.enter()
.append('g')
.attr('class', 'legend')
.append('rect')
.attr('class', 'legend-item')
.attr('width', '40px')
.attr('height', '40px')
.attr('transform', (d, i) => {
return 'translate(' + i * 40 + ',' + 0 + ')';
})
.style('fill', d => colorScale(d))
.style('stroke', d => colorScale(d));
}
function sumValue(d) {
return d.value;
}
function formatTooltip(d) {
const name = d.data.name,
category = d.data.category,
value = d.data.value,
tooltipText = `
<span>Name:</span> ${name} <br>
<span>Category: </span> ${category} <br>
<span>Value:</span> ${value} <br>`;;
console.log('formatted tooltip:\n', tooltipText);
return tooltipText;
}
Minor mistake: You're appending a <div> to a SVG which wouldn't work. Changing the code and appending it to the body, here's a fork:
https://codepen.io/anon/pen/KbVvwR
Code changes:
const tooltip = d3.select('body')
and a couple of minor CSS additions:
div#tooltip {
background: #FFF;
pointer-events: none; // important
padding: 4px;
border: 1px solid #CCC;
border-radius: 3px;
}
Hope this helps.

Enable scroll on the axis of D3 chart

I have a simple stacked bar chart :
The code is here.
I would like to have scroll-bar on the axis but as you can see in the link the scroll appears for the div container with the help of CSS.
But i need something like this chart with scroll!
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<script src="https://ajax.goquery.min.js"></script>
<style>
.axis path,
.axis line {
fill: none;
stroke: #BDBDBD;
}
.axis text {
font-family: 'Open Sans regular', 'Open Sans';
font-size: 13px;
}
.y.axis{
direction: ltr;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
.rect {
stroke: lightgrey;
fill-opacity: 0.6;
}
.wrapperDiv {
Width: 984px;
height: 35px;
border: thin solid black;
#margin-top: 36px;
#margin-bottom: 48px;
#margin-right: 20px;
#margin-left: 0px;
}
.divChart {
float:left;
font-size:13px;
color : #424242;
font-family: 'Open Sans regular', 'Open Sans';
#border: thin solid white;
margin-top: -0px;
#margin-bottom: 48px;
#margin-right: 150px;
margin-left: 50px;
#background-color: lightgrey;
width: 984px;
height: 500px;
#padding: 25px;
border: thin solid navy;
#margin: 25px;
#max-height:500px;
overflow-y:scroll;
direction: rtl;
}
</style>
</head>
<body>
<div class="divChart" id="wrapper-chart">
<div id ="chartID" ></div>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script><script>
<script>
var dataset = [{"key":"Completion","values":[{"name":"Module 1","value":0},{"name":"Module 2","value":0},{"name":"Module 3","value":0},{"name":"Module 4","value":0},{"name":"Module 5","value":0},{"name":"Module 6","value":0},{"name":"Module 7","value":0},{"name":"Module 8","value":0.56},{"name":"Module 9","value":13.24},{"name":"Module 10","value":12.66}]},{"key":"NonCompletion","values":[{"name":"Module 1","value":100},{"name":"Module 2","value":100},{"name":"Module 3","value":100},{"name":"Module 4","value":100},{"name":"Module 5","value":100},{"name":"Module 6","value":100},{"name":"Module 7","value":100},{"name":"Module 8","value":99.44},{"name":"Module 9","value":86.76},{"name":"Module 10","value":87.34}]}];
function intChart(chartID, dataset) {
var margins = {top: 20, right: 20, bottom: 30, left: 120};
var width = 880 - margins.left -margins.right;
var height = 5250- margins.top - margins.bottom;
var old_width = width,old_height= height;
var module_fixed = 80;
height = Math.floor((dataset[0].values.length * height)/module_fixed)
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1,.1)
var y = d3.scale.linear().rangeRound([height, 0], .1);
var series = dataset.map(function(d) {
return d.key;
});
dataset = dataset.map(function(d) {
return d.values.map(function(o, i) {
// Structure it so that your numeric
// axis (the stacked amount) is y
return {
y: o.value,
x: o.name
};
});
});
var stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function(
group) {
return group.map(function(d) {
// Invert the x and y values, and y0 becomes x0
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
});
var xMax = d3.max(dataset, function(
group) {
return d3.max(group, function(d) {
return d.x + d.x0;
});
});
var xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]);
var moduleName = dataset[0]
.map(function(d) {
return d.y;
});
var yScale = d3.scale.ordinal()
.domain(moduleName)
.rangeRoundBands([height,0]);
var svg = d3.select('#chartID')
.append('svg')
.attr("width", width + margins.left +
margins.right)
.attr("height", height + margins.top +
margins.bottom)
.append('g')
.attr('transform', 'translate(60,' + margins.top +
')');
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(2)
.tickSize(0)
.tickPadding(20)
.tickFormat(function(d) {
return d + "%";
});
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(0);
var colours = d3.scale.ordinal().range(
["#8bc34a", "#ff8a65"]);
var groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g').attr('class', 'stacked')
.style('fill', function(d, i) {
return colours(i);
});
var rects = groups.selectAll(
'stackedBar')
.data(function(d, i) {
return d;
})
.enter()
.append('rect')
.attr('class', 'stackedBar')
.attr('x', function(d) {
return xScale(d.x0);
})
.attr('y', function(d, i) {
return yScale(d.y);
})
.attr('height', 48)
.attr('width', 0)
rects.transition()
.delay(function(d, i) {
return i * 50;
})
.attr("x", function(d) {
return xScale(d.x0);
})
.attr("width", function(d) {
return xScale(d.x);
})
.duration(3000);
//Added
x.domain(dataset.map(function(d) {
return d.value;
}));
y.domain([0, d3.max(dataset, function(
d) {
return d.name;
})]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' +height + ')')
.call(xAxis)
.append("text")
.attr("transform", "rotate(360)")
.attr("y",10)
.attr("x", 140)
.attr("dy", ".30em")
.text("Percentage of Students");
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.5em")
.attr("dy", ".15em")
.attr("y", "-")
.attr("opacity", 1)
.attr("transform", function(d) {
return "rotate(-40)"
})
// Draw Y-axis grid lines
svg.selectAll("line.y")
.data(y.ticks(2))
.enter().append("line")
.attr("class", "y")
.attr("x1", 0)
.attr("x2", 450)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#ccc");
}
$(document).ready(function(){
intChart("chartID", dataset);
});
</script>
Would appreciate any help.
Thanks in advance.
I don't think this can be done using D3 only. If you want to use CSS, you need to fix the position of the x-axis. You can add a separate DIV and SVG container for the X axis (these are not scrollable), and the rest of the chart in another.
I modified you code to do this see here. Please note that you code needs a lot of cleaning, as there are several non-functional parts that makes it really confusing.
The modifications are as follows:
HTML
Added a new DIV (xaxis)
<div id="wrapper-chart">
<div class="divChart" id="chartID"></div>
<div id="xaxis"></div>
</div>
CSS
Added styling for the new div (same as divChart but without the scrolling)
#xaxis {
float: left;
font-size: 13px;
color: #424242;
font-family: 'Open Sans regular', 'Open Sans';
width: 984px;
direction: rtl;
}
JS
A new SVG container for the x-axis. Notice the height attribute.
var xaxis_svg = d3.select('#xaxis')
.append('svg')
.attr("width", width + margins.left + margins.right)
.attr("height", margins.bottom)
.append('g')
.attr('transform', 'translate(60,0)');
Append the x-axis to the container.
xaxis_svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + 0 + ')')
.call(xAxis)
.append("text")
.attr("y", 10)
.attr("x", 140)
.attr("dy", ".30em")
.text("Percentage of Students");
Hope this helps.

Adding tooltip in d3.js map

I'm trying to add a tooltip showing the name of districts when you hover over it in the map in d3.js. The input is a topojson file and I've been able to successfully generate the map with district boundaries and highlight the currently selected district.
For the tooltip I tried doing something similar to this, but nothing happens at all. The code I've used is given below. The tooltip code is towards the end.
var width = 960,
height = 600;
var projection = d3.geo.albers()
.center([87, 28])
.rotate([-85, 0])
.parallels([27, 32]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 1e-6);
d3.json("data/nepal3.json", function(error, npl) {
var districts = topojson.feature(npl, npl.objects.nepal_districts);
projection
.scale(1)
.translate([0, 0]);
var b = path.bounds(districts),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
g.selectAll(".nepal_districts")
.data(districts.features)
.enter().append("path")
.attr("class", function(d) { return "nepal_districts " + d.id; })
.attr("d", path)
.on("mouseover", function(d,i) {
d3.select(this.parentNode.appendChild(this)).transition().duration(300)
.style({'stroke-width':2,'stroke':'#333333','stroke-linejoin':'round','cursor':'pointer','fill':'#b9270b'});
})
.on("mouseout", function(d,i) {
d3.select(this.parentNode.appendChild(this)).transition().duration(100)
.style({'stroke-width':2,'stroke':'#FFFFFF','stroke-linejoin':'round','fill':'#3d71b6'});
});
g.append("path")
.datum(topojson.mesh(npl, npl.objects.nepal_districts, function(a, b) { return a !== b;}))
.attr("d", path)
.attr("class", "district-boundary");
/* Tooltip */
g.selectAll(".nepal_districts")
.data(districts.features)
.enter().append("text")
.append("svg:rect")
.attr("width", 140)
.attr("height", 140)
.text(function(d) { return d.properties.name; })
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
function mouseover() {
div.transition()
.duration(300)
.style("opacity", 1);
}
function mousemove() {
div
.text(d3.event.pageX + ", " + d3.event.pageY)
.style("left", (d3.event.pageX - 34) + "px")
.style("top", (d3.event.pageY - 12) + "px");
}
function mouseout() {
div.transition()
.duration(100)
.style("opacity", 1e-6);
}
});
The CSS is
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: #4c4c4c;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
The code I added for "Tooltip" does nothing at all. What am I doing wrong here?
The topojson file has this format. I wanted to get the "name" property to show up in the Tooltip.
{
"type": "Topology",
"objects": {
"nepal_districts": {
"type": "GeometryCollection",
"geometries": [
{
"type": "Polygon",
"id": 0,
"properties": {
"name": "HUMLA"
},
"arcs": [
[
0,
1,
2,
3
]
]
},
Had a similar problem where I ended up adding absolute positioned tooltip to body element, and modyfying its placement according to mouse position.
Add to directive:
function addTooltip(accessor) {
return function(selection) {
var tooltipDiv;
var bodyNode = d3.select('body').node();
selection.on("mouseover", function(topoData, countryIndex) {
if (!accessor(topoData, countryIndex)) {
return;
}
// Clean up lost tooltips
d3.select('body').selectAll('div.tooltipmap').remove();
formatValue(topoData, countryIndex);
tooltipDiv = d3.select('body').append('div').attr('class', 'tooltipmap');
var absoluteMousePos = d3.mouse(bodyNode);
tooltipDiv.style('left', (absoluteMousePos[0] + 10) + 'px')
.style('top', (absoluteMousePos[1] - 15) + 'px')
.style('opacity', 1)
.style('z-index', 1070);
accessor(topoData, countryIndex) || '';
})
.on('mousemove', function(topoData, countryIndex) {
if (!accessor(topoData, countryIndex)) {
return;
}
var absoluteMousePos = d3.mouse(bodyNode);
tooltipDiv.style('left', (absoluteMousePos[0] + 10) + 'px')
.style('top', (absoluteMousePos[1] - 15) + 'px');
var tooltipText = accessor(topoData, countryIndex) || '';
tooltipDiv.html(tooltipText);
})
.on("mouseout", function(topoData, countryIndex) {
if (!accessor(topoData, countryIndex)) {
return;
}
tooltipDiv.remove();
});
};
.tooltipmap{
background-color: #000000;
margin: 10px;
height: 50px;
width: 150px;
padding-left: 10px;
padding-top: 10px;
border-radius: 5px;
overflow: hidden;
display: block;
color: #FFFFFF;
font-size: 12px;
position: absolute;
opacity: 1;
h6{
margin: 0;
padding: 0;
}
p{
color: #FFFFFF;
}
}
Hope it helps!

Categories