I was just wondering how to add a box with text in it to my D3.js chart. It will be acting as my legend, and will describe each variable on my x-axis. It should look something like this like this .
I would like to match each of the "wuc" labels to the "nomenclature" labels in this json array:
data = [{
"Wuc": "23A",
"Nomenclature": "Engine, Basic (F117-PW)",
"Hours": 155899.90
},
{
"Wuc": "23V",
"Nomenclature": "Fan Thrust Reverser",
"Hours": 56576
}
]
The bottom code is for my D3.js chart. Thanks for the help!
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<style>
.bars:hover {
fill: blue;
}
.legend:hover {
fill: blue;
}
/* tooltip for bar chart */
div.tooltip {
position: absolute;
text-align: center;
width: 50px;
height: 60px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<div id="bar_chart">
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// d3.json("data.json", function(error, data) {
// if (error) throw error;
// var parseTime = d3.timeParse("%M:%S");
// var timeformat = d3.timeFormat("%M:%S")
data = [{
"Wuc": "23A",
"Nomenclature": "Engine, Basic (F117-PW)",
"Hours": 155899.90
},
{
"Wuc": "23V",
"Nomenclature": "Fan Thrust Reverser",
"Hours": 56576
}
]
data.forEach(function(d) {
// d.atime = parseTime(d.atime);
d.Hours = +d.Hours;
});
var margin = {
top: 70,
right: 50,
bottom: 100,
left: 80
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
//Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.Wuc
}))
.range([0, width])
.padding([0.8]); //sets decimal of the space between bar centres
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.Hours
})])
.range([height, 0]);
var svg = d3.select("#bar_chart")
.data(data)
.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 + ")");
// Add the X Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// text label for the x axis
svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top + height)
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text("x-Axis Title");
// Add the Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(5));
// text label for the y axis
svg.append("text")
.attr("class", "blah")
.attr("transform", "rotate(-90)")
.attr("x", 0 - height / 2)
.attr("y", -50)
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text("y-Axis Title");
// graph main title
svg.append("text")
.attr("x", width / 2)
.attr("y", -20)
.style("text-anchor", "middle")
.style("font-weight", "bold")
.style("font-size", "20px")
.text("Main Title");
//********* Bar Chart ****************
var rects = svg.selectAll('rect')
.data(data)
.enter();
rects.append('rect')
.on("mouseover", function(d, i, node) { //this is repeated should be in a function
div.transition()
.duration(200)
.style("opacity", .85);
div.html("<strong> Name:</strong> " + d.Wuc + "</br><strong> Value:</strong> " + d.Hours)
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
d3.select(this)
.style("fill", "blue");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
d3.select(this)
.style("fill", "lightblue");
})
.attr("class", "bars") //should fill blue on mouseover, not working???
.attr('x', function(d, i) {
return x(d.Wuc);
})
.attr('y', function(d, i) {
return y(d.Hours);
})
.attr('height', function(d, i) {
return height - y(d.Hours)
})
.attr('width', x.bandwidth())
.attr("transform", "translate(0,0)")
.style('fill', 'lightblue')
.style('stroke', 'lightgray');
// }); //closes function d3.json("data.json", function(error, data) {.....
</script>
You can simply append a 'rect' to the svg and also append text with data.
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<style>
.bars:hover {
fill: blue;
}
.legend:hover {
fill: lightblue;
}
/* tooltip for bar chart */
div.tooltip {
position: absolute;
text-align: center;
width: 50px;
height: 60px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<div id="bar_chart">
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// d3.json("data.json", function(error, data) {
// if (error) throw error;
// var parseTime = d3.timeParse("%M:%S");
// var timeformat = d3.timeFormat("%M:%S")
data = [{
"Wuc": "23A",
"Nomenclature": "Engine, Basic (F117-PW)",
"Hours": 155899.90
},
{
"Wuc": "23V",
"Nomenclature": "Fan Thrust Reverser",
"Hours": 56576
}
]
data.forEach(function(d) {
// d.atime = parseTime(d.atime);
d.Hours = +d.Hours;
});
var margin = {
top: 70,
right: 50,
bottom: 100,
left: 80
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
//Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.Wuc
}))
.range([0, width])
.padding([0.8]); //sets decimal of the space between bar centres
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.Hours
})])
.range([height, 0]);
var svg = d3.select("#bar_chart")
.data(data)
.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 + ")");
// Add the X Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// text label for the x axis
svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top + height)
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text("x-Axis Title");
// Add the Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(5));
// text label for the y axis
svg.append("text")
.attr("class", "blah")
.attr("transform", "rotate(-90)")
.attr("x", 0 - height / 2)
.attr("y", -50)
.style("text-anchor", "middle")
.style("font-weight", "bold")
.text("y-Axis Title");
// graph main title
svg.append("text")
.attr("x", width / 2)
.attr("y", -20)
.style("text-anchor", "middle")
.style("font-weight", "bold")
.style("font-size", "20px")
.text("Main Title");
//************* Legend ***************
// add legend rect gray
svg.append("rect")
.attr("class", "legend")
.attr("x", 280)
.attr("y", 30)
.attr("rx", "5px")
.attr("width", 230)
.attr("height", 80)
.attr("stroke", "darkgray")
.attr("fill", "white");
var legend_text = svg.selectAll("legend_text")
.data(data)
.enter();
legend_text.append("text")
.attr("class", "legend_text")
.attr("x", 290)
.attr("y", function(d, i) {
return (i * 20) + 40;
})
.attr("dy", "0.32em")
.style("font-weight", "bold")
.text(function(d) {
return d.Wuc + " " + d.Nomenclature;
});
//********* Bar Chart ****************
var rects = svg.selectAll(".bars")
.data(data)
.enter();
rects.append('rect')
.attr("class", "bars")
.on("mouseover", function(d, i, node) { //this is repeated should be in a function
div.transition()
.duration(200)
.style("opacity", .85);
div.html("<strong> Name:</strong> " + d.Wuc + "</br><strong> Value:</strong> " + d.Hours)
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
d3.select(this)
.style("fill", "blue");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
d3.select(this)
.style("fill", "lightblue");
})
.attr("class", "bars") //should fill blue on mouseover, not working???
.attr('x', function(d, i) {
return x(d.Wuc);
})
.attr('y', function(d, i) {
return y(d.Hours);
})
.attr('height', function(d, i) {
return height - y(d.Hours)
})
.attr('width', x.bandwidth())
.attr("transform", "translate(0,0)")
.style('fill', 'lightblue')
.style('stroke', 'lightgray');
// }); //closes function d3.json("data.json", function(error, data) {.....
</script>
You may also want to make the height of the rect dynamic to fit different amounts of of data.
I am adapting this gradient example in AngularJS. Here is a bit of the dataset I'm using:
var stays=[
{
day:2,
hour:1,
time_spent:127
},
{
day:4,
hour:1,
time_spent:141
},
{
day:1,
hour:1,
time_spent:134
},
{
day:5,
hour:1,
time_spent:174
},
{
day:3,
hour:1,
time_spent:131
},
{
day:6,
hour:1,
time_spent:333
}];
The problem is that I want to construct a tooltip for each of the squares that are created in the heatmap. The tooltip is here:
var heatMap = svg.selectAll(".hour")
.data(stays)
.enter().append("rect")
.attr("x", function(d) { return (d.hour - 1) * gridSize; })
.attr("y", function(d) { return (d.day - 1) * gridSize; })
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("stroke", "white")
.style("stroke-opacity", 0.6)
.style("stroke-width", 0.8)
.style("fill", function(d) { return colorScale(d.time_spent); })
.on("mouseover", function(d, i) {
// Construct tooltip
var tooltip_html = '';
tooltip_html += '<div class="header"><strong>' + 'Stays' + ' </strong></div><br>';
// Add info to the tooltip
angular.forEach(stays, function (d) {
tooltip_html += '<div><span><strong>' + makeid() + '</strong></span>';
tooltip_html += '<span>' + ' ' + d.time_spent + '</span></div>';
console.log(d.time_spent);
}, days);
// Set tooltip width
tooltip.html(tooltip_html)
.style("width", 300 + "px")
.style("left", (d3.event.layerX+10) + "px")
.style("top", (d3.event.layerY+10) + "px");
// Tooltip transition and more styling
tooltip.style('display', 'block')
.transition()
.ease('ease-in')
.duration(100)
.style("opacity", .9);
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(100)
.ease('ease-in')
.style('opacity', 0);
});
The idea here is, for each square that I visit, the tooltip will show the labels associated (I'm still not using labels but they are going to be part of the dataset I've shown, I'm using the makeid() function to create random names) to that square, together with a breakdown of the associated time_spent data. With what I'm using now it is writing the full list of numbers and not the ones associated to each square. Ideas? Thank you.
Rather than building tootip HTML for each cell, consider attaching the data to each cell rect. Create a single data div and fill it when the cursor moves over the cell.
I added this to the heatmap gradient example. Give it a try.
var accidents=[{day:2,hour:1,count:127},{day:4,hour:1,count:141},{day:1,hour:1,count:134},{day:5,hour:1,count:174},{day:3,hour:1,count:131},{day:6,hour:1,count:333},{day:7,hour:1,count:311},{day:2,hour:2,count:79},{day:4,hour:2,count:99},{day:1,hour:2,count:117},{day:5,hour:2,count:123},{day:3,hour:2,count:92},{day:6,hour:2,count:257},{day:7,hour:2,count:293},{day:2,hour:3,count:55},{day:4,hour:3,count:73},{day:1,hour:3,count:107},{day:5,hour:3,count:89},{day:3,hour:3,count:66},{day:6,hour:3,count:185},{day:7,hour:3,count:262},{day:2,hour:4,count:39},{day:4,hour:4,count:67},{day:1,hour:4,count:59},{day:5,hour:4,count:83},{day:3,hour:4,count:45},{day:6,hour:4,count:180},{day:7,hour:4,count:220},{day:2,hour:5,count:48},{day:4,hour:5,count:57},{day:1,hour:5,count:73},{day:5,hour:5,count:76},{day:3,hour:5,count:72},{day:6,hour:5,count:168},{day:7,hour:5,count:199},{day:2,hour:6,count:129},{day:4,hour:6,count:102},{day:1,hour:6,count:129},{day:5,hour:6,count:140},{day:3,hour:6,count:117},{day:6,hour:6,count:148},{day:7,hour:6,count:193},{day:2,hour:7,count:314},{day:4,hour:7,count:284},{day:1,hour:7,count:367},{day:5,hour:7,count:270},{day:3,hour:7,count:310},{day:6,hour:7,count:179},{day:7,hour:7,count:192},{day:2,hour:8,count:806},{day:4,hour:8,count:811},{day:1,hour:8,count:850},{day:5,hour:8,count:609},{day:3,hour:8,count:846},{day:6,hour:8,count:208},{day:7,hour:8,count:144},{day:2,hour:9,count:1209},{day:4,hour:9,count:1214},{day:1,hour:9,count:1205},{day:5,hour:9,count:960},{day:3,hour:9,count:1073},{day:6,hour:9,count:286},{day:7,hour:9,count:152},{day:2,hour:10,count:750},{day:4,hour:10,count:808},{day:1,hour:10,count:610},{day:5,hour:10,count:655},{day:3,hour:10,count:684},{day:6,hour:10,count:482},{day:7,hour:10,count:253},{day:2,hour:11,count:591},{day:4,hour:11,count:593},{day:1,hour:11,count:573},{day:5,hour:11,count:695},{day:3,hour:11,count:622},{day:6,hour:11,count:676},{day:7,hour:11,count:326},{day:2,hour:12,count:653},{day:4,hour:12,count:679},{day:1,hour:12,count:639},{day:5,hour:12,count:736},{day:3,hour:12,count:687},{day:6,hour:12,count:858},{day:7,hour:12,count:402},{day:2,hour:13,count:738},{day:4,hour:13,count:749},{day:1,hour:13,count:631},{day:5,hour:13,count:908},{day:3,hour:13,count:888},{day:6,hour:13,count:880},{day:7,hour:13,count:507},{day:2,hour:14,count:792},{day:4,hour:14,count:847},{day:1,hour:14,count:752},{day:5,hour:14,count:1033},{day:3,hour:14,count:942},{day:6,hour:14,count:983},{day:7,hour:14,count:636},{day:2,hour:15,count:906},{day:4,hour:15,count:1031},{day:1,hour:15,count:954},{day:5,hour:15,count:1199},{day:3,hour:15,count:1014},{day:6,hour:15,count:1125},{day:7,hour:15,count:712},{day:2,hour:16,count:1101},{day:4,hour:16,count:1158},{day:1,hour:16,count:1029},{day:5,hour:16,count:1364},{day:3,hour:16,count:1068},{day:6,hour:16,count:1062},{day:7,hour:16,count:736},{day:2,hour:17,count:1303},{day:4,hour:17,count:1426},{day:1,hour:17,count:1270},{day:5,hour:17,count:1455},{day:3,hour:17,count:1407},{day:6,hour:17,count:883},{day:7,hour:17,count:666},{day:2,hour:18,count:1549},{day:4,hour:18,count:1653},{day:1,hour:18,count:1350},{day:5,hour:18,count:1502},{day:3,hour:18,count:1507},{day:6,hour:18,count:830},{day:7,hour:18,count:652},{day:2,hour:19,count:998},{day:4,hour:19,count:1070},{day:1,hour:19,count:787},{day:5,hour:19,count:1027},{day:3,hour:19,count:1019},{day:6,hour:19,count:575},{day:7,hour:19,count:519},{day:2,hour:20,count:661},{day:4,hour:20,count:756},{day:1,hour:20,count:596},{day:5,hour:20,count:730},{day:3,hour:20,count:648},{day:6,hour:20,count:494},{day:7,hour:20,count:486},{day:2,hour:21,count:431},{day:4,hour:21,count:539},{day:1,hour:21,count:430},{day:5,hour:21,count:509},{day:3,hour:21,count:457},{day:6,hour:21,count:443},{day:7,hour:21,count:421},{day:2,hour:22,count:352},{day:4,hour:22,count:428},{day:1,hour:22,count:362},{day:5,hour:22,count:462},{day:3,hour:22,count:390},{day:6,hour:22,count:379},{day:7,hour:22,count:324},{day:2,hour:23,count:329},{day:4,hour:23,count:381},{day:1,hour:23,count:293},{day:5,hour:23,count:393},{day:3,hour:23,count:313},{day:6,hour:23,count:374},{day:7,hour:23,count:288},{day:2,hour:24,count:211},{day:4,hour:24,count:249},{day:1,hour:24,count:204},{day:5,hour:24,count:417},{day:3,hour:24,count:211},{day:6,hour:24,count:379},{day:7,hour:24,count:203}];
///////////////////////////////////////////////////////////////////////////
//////////////////// Set up and initiate svg containers ///////////////////
///////////////////////////////////////////////////////////////////////////
var days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
times = d3.range(24);
var margin = {
top: 170,
right: 50,
bottom: 70,
left: 50
};
var width = Math.max(Math.min(window.innerWidth, 1000), 500) - margin.left - margin.right - 20,
gridSize = Math.floor(width / times.length),
height = gridSize * (days.length+2);
//SVG container
var svg = d3.select('#trafficAccidents')
.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 + ")");
//Reset the overall font size
var newFontSize = width * 62.5 / 900;
d3.select("html").style("font-size", newFontSize + "%");
///////////////////////////////////////////////////////////////////////////
//////////////////////////// Draw Heatmap /////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Based on the heatmap example of: http://blockbuilder.org/milroc/7014412
var colorScale = d3.scale.linear()
.domain([0, d3.max(accidents, function(d) {return d.count; })/2, d3.max(accidents, function(d) {return d.count; })])
.range(["#FFFFDD", "#3E9583", "#1F2D86"])
//.interpolate(d3.interpolateHcl);
var dayLabels = svg.selectAll(".dayLabel")
.data(days)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) { return i * gridSize; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize / 1.5 + ")")
.attr("class", function (d, i) { return ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"); });
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("x", function(d, i) { return i * gridSize; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + gridSize / 2 + ", -6)")
.attr("class", function(d, i) { return ((i >= 8 && i <= 17) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });
var heatMap = svg.selectAll(".hour")
.data(accidents)
.enter().append("rect")
//----attach data to rect---
.attr("data","This is the data for this cell")
.attr("onmouseover","showData(evt)")
.attr("onmouseout","hideData(evt)")
.attr("x", function(d) { return (d.hour - 1) * gridSize; })
.attr("y", function(d) { return (d.day - 1) * gridSize; })
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("stroke", "white")
.style("stroke-opacity", 0.6)
.style("fill", function(d) { return colorScale(d.count); });
//Append title to the top
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", -90)
.style("text-anchor", "middle")
.text("Number of Traffic accidents per Day & Hour combination");
svg.append("text")
.attr("class", "subtitle")
.attr("x", width/2)
.attr("y", -60)
.style("text-anchor", "middle")
.text("The Netherlands | 2014");
//Append credit at bottom
svg.append("text")
.attr("class", "credit")
.attr("x", width/2)
.attr("y", gridSize * (days.length+1) + 80)
.style("text-anchor", "middle")
.text("Based on Miles McCrocklin's Heatmap block");
///////////////////////////////////////////////////////////////////////////
//////////////// Create the gradient for the legend ///////////////////////
///////////////////////////////////////////////////////////////////////////
//Extra scale since the color scale is interpolated
var countScale = d3.scale.linear()
.domain([0, d3.max(accidents, function(d) {return d.count; })])
.range([0, width])
//Calculate the variables for the temp gradient
var numStops = 10;
countRange = countScale.domain();
countRange[2] = countRange[1] - countRange[0];
countPoint = [];
for(var i = 0; i < numStops; i++) {
countPoint.push(i * countRange[2]/(numStops-1) + countRange[0]);
}//for i
//Create the gradient
svg.append("defs")
.append("linearGradient")
.attr("id", "legend-traffic")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.selectAll("stop")
.data(d3.range(numStops))
.enter().append("stop")
.attr("offset", function(d,i) {
return countScale( countPoint[i] )/width;
})
.attr("stop-color", function(d,i) {
return colorScale( countPoint[i] );
});
///////////////////////////////////////////////////////////////////////////
////////////////////////// Draw the legend ////////////////////////////////
///////////////////////////////////////////////////////////////////////////
var legendWidth = Math.min(width*0.8, 400);
//Color Legend container
var legendsvg = svg.append("g")
.attr("class", "legendWrapper")
.attr("transform", "translate(" + (width/2) + "," + (gridSize * days.length + 40) + ")");
//Draw the Rectangle
legendsvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", 0)
//.attr("rx", hexRadius*1.25/2)
.attr("width", legendWidth)
.attr("height", 10)
.style("fill", "url(#legend-traffic)");
//Append title
legendsvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -10)
.style("text-anchor", "middle")
.text("Number of Accidents");
//Set scale for x-axis
var xScale = d3.scale.linear()
.range([-legendWidth/2, legendWidth/2])
.domain([ 0, d3.max(accidents, function(d) { return d.count; })] );
//Define x-axis
var xAxis = d3.svg.axis()
.orient("bottom")
.ticks(5)
//.tickFormat(formatPercent)
.scale(xScale);
//Set up X axis
legendsvg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (10) + ")")
.call(xAxis);
//--show/hide data---
function showData(evt)
{
var target=evt.target
target.setAttribute("opacity",".8")
//---locate dataDiv near cursor--
var x = evt.clientX;
var y = evt.clientY;
//---scrolling page---
var offsetX=window.pageXOffset
var offsetY=window.pageYOffset
dataDiv.style.left=10+x+offsetX+"px"
dataDiv.style.top=20+y+offsetY+"px"
//---data--
var data=target.getAttribute("data")
//---format as desired---
var html=data
dataDiv.innerHTML=html
dataDiv.style.visibility="visible"
}
function hideData(evt)
{
dataDiv.style.visibility="hidden"
var target=evt.target
target.removeAttribute("opacity")
}
<head>
<meta charset="utf-8">
<!-- D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<!-- Google Font -->
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400' rel='stylesheet' type='text/css'>
<style>
html { font-size: 62.5%; }
body {
font-size: 1rem;
font-family: 'Open Sans', sans-serif;
font-weight: 400;
fill: #8C8C8C;
text-align: center;
}
.timeLabel, .dayLabel {
font-size: 1.6rem;
fill: #AAAAAA;
font-weight: 300;
}
text.axis-workweek, text.axis-worktime {
fill: #404040;
font-weight: 400;
}
.title {
font-size: 2.8rem;
fill: #4F4F4F;
font-weight: 300;
}
.subtitle {
font-size: 1.4rem;
fill: #AAAAAA;
font-weight: 300;
}
.credit {
font-size: 1.2rem;
fill: #AAAAAA;
font-weight: 400;
}
.axis path, .axis tick, .axis line {
fill: none;
stroke: none;
}
text {
font-size: 1.2rem;
fill: #AAAAAA;
font-weight: 400;
}
.legendTitle {
font-size: 1.6rem;
fill: #4F4F4F;
font-weight: 300;
}
</style>
</head>
<body>
<div id="trafficAccidents"></div>
<div id=dataDiv style='box-shadow: 4px 4px 4px #888888;-webkit-box-shadow:2px 3px 4px #888888;padding:2px;position:absolute;top:0px;left:0px;visibility:hidden;background-color:linen;border: solid 1px black;border-radius:5px;'></div>
</body>
I think you may be making this too complex.
Try using foxToolTip.js
https://github.com/MichaelRFox/foxToolTip.jS
After you create your unique I'd just use the .each method to add a unique tooltip.
I'm trying to make a d3 scatterplot recurring to json data. I know that d3 has d3.json to load json data but my code isn't working. I'm not that good dealing with js (it's my first time with this language), that's why I need help in this.
Basically, I need to plot this data (date in xAxis and IADP_mGycm2 in yAxis):
[
{
"imageID": 1,
"value": 288.3,
"date": "2015-10-22"
},
{
"imageID": 2,
"value": 188.1,
"date": "2015-10-22"
}
]
JS:
var margin = { top: 50, right: 300, bottom: 50, left: 50 },
outerWidth = 1050,
outerHeight = 500,
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]).nice();
var y = d3.scale.linear()
.range([height, 0]).nice();
var xCat = "date",
yCat = "value";
d3.json("CR.json", function(error, rawData) {
if (error) {
console.error(error);
return;
}
var data = rawData.map(function (d) {
return {
date: d.date,
value: d.value
}
});
var xMax = d3.max(data, function(d) { return d["date"]; }),
xMin = d3.min(data, function(d) { return d["date"]; }),
xMin = xMin > 0 ? 0 : xMin,
yMax = d3.max(data, function(d) { return d["value"]; }) ,
yMin = d3.min(data, function(d) { return d["value"]; }),
yMin = yMin > 0 ? 0 : yMin;
x.domain([xMin, xMax]);
y.domain([yMin, yMax]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(-width);
var color = d3.scale.category10();
var tip = d3.tip()
.attr("class", "d3-tip")
.offset([-10, 0])
.html(function(d) {
return xCat + ": " + d["date"] + "<br>" + yCat + ": " + d["value"];
});
var zoomBeh = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([0, 500])
.on("zoom", zoom);
var svg = d3.select("#scatter")
.append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoomBeh);
svg.call(tip);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.classed("x axis", true)
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.classed("label", true)
.attr("x", width)
.attr("y", margin.bottom - 10)
.style("text-anchor", "end")
.text(xCat);
svg.append("g")
.classed("y axis", true)
.call(yAxis)
.append("text")
.classed("label", true)
.attr("transform", "rotate(-90)")
.attr("y", -margin.left)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yCat);
var objects = svg.append("svg")
.classed("objects", true)
.attr("width", width)
.attr("height", height);
objects.append("svg:line")
.classed("axisLine hAxisLine", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", width)
.attr("y2", 0)
.attr("transform", "translate(0," + height + ")");
objects.append("svg:line")
.classed("axisLine vAxisLine", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", height);
objects.selectAll(".dot")
.data(data)
.enter().append("circle")
.classed("dot", true)
.attr("cy", function (d) { return d.value; })
.attr("cx", function (d) { return d.date; })
.attr("transform", transform)
.style("fill", "red")
.on("mouseover", tip.show)
.on("mouseout", tip.hide);
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.classed("legend", true)
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("circle")
.attr("r", 3.5)
.attr("cx", width + 20)
.attr("fill", color);
legend.append("text")
.attr("x", width + 26)
.attr("dy", ".35em")
.text(function(d) { return d; });
d3.select("input").on("click", change);
function change() {
xCat = "date";
xMax = d3.max(data, function(d) { return d["date"]; });
xMin = d3.min(data, function(d) { return d["date"]; });
zoomBeh.x(x.domain([xMin, xMax])).y(y.domain([yMin, yMax]));
var svg = d3.select("#scatter").transition();
svg.select(".x.axis").duration(750).call(xAxis).select(".label").text("date");
objects.selectAll(".dot").transition().duration(1000).attr("transform", transform);
}
function zoom() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll(".dot")
.attr("transform", transform);
}
function transform(d) {
return "translate(" + x(d["date"]) + "," + y(d["value"]) + ")";
}
});
HTML:
<html>
<head>
<meta charset="utf-8">
<title>Visualization</title>
<link rel="stylesheet" href="scatter.css" charset="utf-8">
</head>
<body>
<div id="scatter"></div>
<input type="button" name="xAxis" value="xAxis">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script src="scatter.js" charset="utf-8"></script>
</body>
</html>
CSS:
rect {
fill: transparent;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: rgba(0, 0, 0, 0.1);
shape-rendering: crispEdges;
}
.axisLine {
fill: none;
shape-rendering: crispEdges;
stroke: rgba(0, 0, 0, 0.5);
stroke-width: 2px;
}
.dot {
fill-opacity: .5;
}
.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;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
Thanks in advance!
One issue I see is that you're missing a }) at the end of this code chunk:
var data = rawData.map(function (d) {
return {
date: d.date,
IADP_mGycm2: d.IADP_mGycm2
};
Try changing it to this:
var data = rawData.map(function (d) {
return {
date: d.date,
IADP_mGycm2: d.IADP_mGycm2
}
});
It also helps in debugging if you include the specific error message you're getting.
Well, you define xCat as:
var xCat = "Date"
but your mapping function uses:
date: d.date
so, when you reference d[xCat] what you are getting is d.Date (which is undefined and would cause NaN) instead of d.date. That's all I can see with a quick look through.
You can fix this by using d['date'] or d.date instead of d[xCat].
Ok, after looking a little further at this, I've identified a couple of problems. The main issue is that the chart you are trying to emulate has numeric values on both the x and y axes. However, you are trying to use dates. In order to do that, you have to use d3.time.scale() for the x-axis instead of a linear scale. You also have to transform the date strings from your data to date objects and use your time scale to scale your x-axis values. Here is scatter.js with the changes:
var margin = { top: 50, right: 300, bottom: 50, left: 50 },
outerWidth = 1050,
outerHeight = 500,
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom;
// Use a time scale for the x-axis
var x = d3.time.scale()
.range([0, width]).nice();
var y = d3.scale.linear()
.range([height, 0]).nice();
var xCat = "date",
yCat = "Dose";
d3.json("CR.json", function(error, rawData) {
if (error) {
console.error(error);
return;
}
var data = rawData.map(function(d) {
return {
// Create date objects, not strings
date: new Date(d.date),
IADP_mGycm2: d.IADP_mGycm2
}
});
var xMax = d3.max(data, function(d) { return d["date"]; }),
xMin = d3.min(data, function(d) { return d["date"]; }),
//xMin = xMin > 0 ? 0 : xMin,
yMax = d3.max(data, function(d) { return d["IADP_mGycm2"]; }),
yMin = d3.min(data, function(d) { return d["IADP_mGycm2"]; });
//yMin = yMin > 0 ? 0 : yMin;
x.domain([xMin, xMax]);
y.domain([yMin, yMax]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(-width);
var color = d3.scale.category10();
var tip = d3.tip()
.attr("class", "d3-tip")
.offset([-10, 0])
.html(function(d) {
return xCat + ": " + d["date"] + "<br>" + yCat + ": " + d["IADP_mGycm2"];
});
var zoomBeh = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([0, 500])
.on("zoom", zoom);
var svg = d3.select("#scatter")
.append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoomBeh);
svg.call(tip);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.classed("x axis", true)
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.classed("label", true)
.attr("x", width)
.attr("y", margin.bottom - 10)
.style("text-anchor", "end")
.text(xCat);
svg.append("g")
.classed("y axis", true)
.call(yAxis)
.append("text")
.classed("label", true)
.attr("transform", "rotate(-90)")
.attr("y", -margin.left)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yCat);
var objects = svg.append("svg")
.classed("objects", true)
.attr("width", width)
.attr("height", height);
objects.append("svg:line")
.classed("axisLine hAxisLine", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", width)
.attr("y2", 0)
.attr("transform", "translate(0," + height + ")");
objects.append("svg:line")
.classed("axisLine vAxisLine", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", height);
objects.selectAll(".dot")
.data(data)
.enter().append("circle")
.classed("dot", true)
.attr("cy", function(d) { return d.IADP_mGycm2; })
// Use the time scale to scale the values for the x-axis
.attr("cx", function(d) { return x(d.date); })
.attr("transform", transform)
.style("fill", "red")
.on("mouseover", tip.show)
.on("mouseout", tip.hide);
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.classed("legend", true)
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("circle")
.attr("r", 3.5)
.attr("cx", width + 20)
.attr("fill", color);
legend.append("text")
.attr("x", width + 26)
.attr("dy", ".35em")
.text(function(d) { return d; });
d3.select("input").on("click", change);
function change() {
xCat = "date";
xMax = d3.max(data, function(d) { return d["date"]; });
xMin = d3.min(data, function(d) { return d["date"]; });
zoomBeh.x(x.domain([xMin, xMax])).y(y.domain([yMin, yMax]));
var svg = d3.select("#scatter").transition();
svg.select(".x.axis").duration(750).call(xAxis).select(".label").text("date");
objects.selectAll(".dot").transition().duration(1000).attr("transform", transform);
}
function zoom() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll(".dot")
.attr("transform", transform);
}
function transform(d) {
return "translate(" + x(d["date"]) + "," + y(d["IADP_mGycm2"]) + ")";
}
});
This gets rid of the errors you were seeing, but it still doesn't plot the circles correctly. Sorry, I don't have the time to work all that out. However, this should move you forward and get you closer to figuring it out yourself. To learn more about time scales, see https://github.com/mbostock/d3/wiki/Time-Scales. Also, if you really want to learn D3, I highly recommend the book D3.js in Action by Elijah Meeks. https://www.manning.com/books/d3-js-in-action. One of the better programming books I have read.