Related
I am trying to add a hover effect without success. How to show data when hovering over datapoint?
https://plnkr.co/edit/Va1Dw3hg2D5jPoNGKVWp?p=preview&preview
<!DOCTYPE html>
svg {
font: 12px sans-serif;
text-anchor: middle;
}
rect {
stroke: lightgray;
stoke-width: 1px;
fill: none;
}
.y.axis path {
fill: none;
stroke: none;
}
</style>
d3.csv('data.csv', function (error, rows) {
var data = [];
rows.forEach(function (d) {
var x = d[''];
delete d[''];
for (prop in d) {
var y = prop,
value = d[prop];
data.push({
x: x,
y: y,
value: +value,
});
}
});
var margin = {
top: 25,
right: 80,
bottom: 25,
left: 25,
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
domain = d3
.set(
data.map(function (d) {
return d.x;
})
)
.values(),
num = Math.sqrt(data.length),
color = d3.scale
.linear()
.domain([-1, 0, 1])
.range(['#B22222', '#fff', '#000080']);
var x = d3.scale.ordinal().rangePoints([0, width]).domain(domain),
y = d3.scale.ordinal().rangePoints([0, height]).domain(domain),
xSpace = x.range()[1] - x.range()[0],
ySpace = y.range()[1] - y.range()[0];
var svg = d3
.select('body')
.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 + ')'
);
var cor = svg
.selectAll('.cor')
.data(data)
.enter()
.append('g')
.attr('class', 'cor')
.attr('transform', function (d) {
return 'translate(' + x(d.x) + ',' + y(d.y) + ')';
});
cor
.append('rect')
.attr('width', xSpace)
.attr('height', ySpace)
.attr('x', -xSpace / 2)
.attr('y', -ySpace / 2);
cor
.filter(function (d) {
var ypos = domain.indexOf(d.y);
var xpos = domain.indexOf(d.x);
for (var i = ypos + 1; i < num; i++) {
if (i === xpos) return false;
}
return true;
})
.append('text')
.attr('y', 5)
.text(function (d) {
if (d.x === d.y) {
return d.x;
} else {
return d.value.toFixed(2);
}
})
.style('fill', function (d) {
if (d.value === 1) {
return '#000';
} else {
return color(d.value);
}
});
cor
.filter(function (d) {
var ypos = domain.indexOf(d.y);
var xpos = domain.indexOf(d.x);
for (var i = ypos + 1; i < num; i++) {
if (i === xpos) return true;
}
return false;
})
.append('circle')
.attr('r', function (d) {
return (width / (num * 2)) * (Math.abs(d.value) + 0.1);
})
.style('fill', function (d) {
if (d.value === 1) {
return '#000';
} else {
return color(d.value);
}
});
var aS = d3.scale
.linear()
.range([-margin.top + 5, height + margin.bottom - 5])
.domain([1, -1]);
var yA = d3.svg.axis().orient('right').scale(aS).tickPadding(7);
var aG = svg
.append('g')
.attr('class', 'y axis')
.call(yA)
.attr(
'transform',
'translate(' + (width + margin.right / 2) + ' ,0)'
);
var iR = d3.range(-1, 1.01, 0.01);
var h = height / iR.length + 3;
iR.forEach(function (d) {
aG.append('rect')
.style('fill', color(d))
.style('stroke-width', 0)
.style('stoke', 'none')
.attr('height', h)
.attr('width', 10)
.attr('x', 0)
.attr('y', aS(d));
});
});
</script>
Create a new div and give it some styles. also set the opacity to 0.
var popupDiv = d3
.select("#root")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
Next, you need to add a mouseenter and mouseout to your desired elements
d3.append("div") // Change this to your element(s)
.on("mouseenter", function (d, i) {
popupDiv.transition().duration(200).style("opacity", 0.9);
popupDiv
.html(i.Description) // Example of display a data item description
.style("left", d.pageX + "px") // X offset can be added here
.style("top", d.pageY + "px"); // Y offset can be added here
})
.on("mouseout", function (d, i) {
popupDiv.transition().duration(200).style("opacity", 0);
})
Bear in mind the first parameter d to the mouseenter and mouseout functions is the mouse event itself which is being used to find out the x and y position of the mouse. The second parameter i is the data attached to your element if you wanted to use any of the data for specific descriptions or colours etc.
These are the base styles I used when creating a popup
div.tooltip {
position: absolute;
text-align: center;
width: auto;
height: auto;
max-width: 200px;
padding: 8px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
z-index: 10;
}
Consider the following:
var dataAsCsv = `date,col_1,col_2
11/1/2012,1977652,1802851
12/1/2012,1128739,948687
1/1/2013,1201944,1514667
2/1/2013,1863148,1834006
3/1/2013,1314851,1906060
4/1/2013,1283943,1978702
5/1/2013,1127964,1195606
6/1/2013,1773254,977214
7/1/2013,1929574,1127450
8/1/2013,1980411,1808161
9/1/2013,1405691,1182788
10/1/2013,1336790,937890
11/1/2013,1851053,1358400
12/1/2013,1472623,1214610
1/1/2014,1155116,1757052
2/1/2014,1571611,1935038
3/1/2014,1898348,1320348
4/1/2014,1444838,1934789
5/1/2014,1235087,950194
6/1/2014,1272040,1580656
7/1/2014,980781,1680164
8/1/2014,1391291,1115999
9/1/2014,1211125,1542148
10/1/2014,1020824,1782795
11/1/2014,1685081,926612
12/1/2014,1469254,1767071
1/1/2015,1168523,935897
2/1/2015,1602610,1450541
3/1/2015,1830278,1354876
4/1/2015,1275158,1412555
5/1/2015,1560961,1839718
6/1/2015,949948,1587130
7/1/2015,1413765,1494446
8/1/2015,1166141,1305105
9/1/2015,958975,1202219
10/1/2015,902696,1023987
11/1/2015,961441,1865628
12/1/2015,1363145,1954046
1/1/2016,1862878,1470741
2/1/2016,1723891,1042760
3/1/2016,1906747,1169012
4/1/2016,1963364,1927063
5/1/2016,1899735,1936915
6/1/2016,1300369,1430697
7/1/2016,1777108,1401210
8/1/2016,1597045,1566763
9/1/2016,1558287,1140057
10/1/2016,1965665,1953595
11/1/2016,1800438,937551
12/1/2016,1689152,1221895
1/1/2017,1607824,1963282
2/1/2017,1878431,1415658
3/1/2017,1730296,1947106
4/1/2017,1956756,1696780
5/1/2017,1746673,1662892
6/1/2017,989702,1537646
7/1/2017,1098812,1592064
8/1/2017,1861973,1892987
9/1/2017,1129596,1406514
10/1/2017,1528632,1725020
11/1/2017,925850,1795575`;
var margin = {
top: 50,
right: 20,
bottom: 50,
left: 80
},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// parse the date / time
// look at the .csv in Notepad! DO NOT LOOK AT EXCEL!
var parseDate = d3.timeParse("%m/%d/%Y");
var sessions_desktop_col = "#CE1126",
sessions_mobile_col = "#00B6D0";
var x = d3.scaleTime()
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
.range([sessions_desktop_col, sessions_mobile_col]); // red and blue
var xMonthAxis = d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")); // label every month
var xYearAxis = d3.axisBottom(x)
.ticks(d3.timeYear.every(1))
.tickFormat(d3.timeFormat("%Y")); // label every year
var yAxis = d3.axisLeft(y).tickFormat(d3.format('.2s')).tickSize(-width + margin.left + margin.right);
var formatNum = d3.format(",");
var barPad = 8;
var data = d3.csvParse(dataAsCsv, function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
});
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var keys = data.columns.slice(1);
data.sort(function(a, b) {
return b.date - a.date;
});
x.domain(d3.extent(data, function(d) {
return d.date
}));
var max = x.domain()[1];
var min = x.domain()[0];
var datePlusOneMonth = d3.timeDay.offset(d3.timeMonth.offset(max, 1), -1); // last day of current month: move up one month, back one day
x.domain([min, datePlusOneMonth]);
y.domain([0, d3.max(data, function(d) {
return d.total;
})]).nice();
z.domain(keys);
// x-axis
var monthAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xMonthAxis);
var defaultSliderValue = 25;
// calculate offset date based on initial slider value
var offsetDate = defaultSliderValue ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth() - defaultSliderValue)), 1), -1) : min
// set x domain and re-render xAxis
x.domain([offsetDate, datePlusOneMonth]);
g.select('.x.axis').call(xMonthAxis);
var filteredData = data.filter(function(d) {
return d.date >= offsetDate;
});
barWidth = (width - margin.right - margin.left) / (filteredData.length + barPad);
// the bars
g.append("g").classed('bars', true)
.selectAll("g")
.data(d3.stack().keys(keys)(filteredData))
.enter().append("g")
.attr('class', function(d) {
return d.key;
})
.attr("fill", function(d) {
return z(d.key);
})
.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.data.date);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", barWidth)
.on("mousemove", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
var yPosition = parseFloat(d3.mouse(this)[1]) + 50;
var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values
var totalCount = d.data["total"];
var valuePercent = value / totalCount;
var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
d3.select("#tooltip")
.select("#count")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.select("#totalCount")
.text(formatNum(totalCount)); // return the value
d3.select("#tooltip")
.select("#percent")
.text((valuePercent * 100).toFixed(1) + "%"); // return the value
d3.select("#tooltip")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
d3.select("#tooltip")
.select("#sessionLabel")
.text(valueClass); // return the class
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
g.selectAll("bars")
.attr("transform", "translate(0,300)");
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11) { // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
var yearAxis = g.append("g")
.attr("class", "yearaxis axis")
.attr("transform", "translate(0," + (height + 25) + ")")
.call(xYearAxis);
var valueAxis = g.append("g")
.attr("class", "y axis grid")
.call(yAxis);
monthAxis.selectAll("g").select("text")
.attr("transform", "translate(" + barWidth / 2 + ",0)");
var options = d3.keys(data[0]).filter(function(key) {
return key !== "date";
}).reverse();
var legend = svg.selectAll(".legend")
.data(options.slice().filter(function(type) {
return type != "total"
}))
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.attr('class', function(d) {
return d;
})
.style("fill", z)
.on("mouseover", function(d) {
if (d == 'col_2') {
d3.selectAll(".col_2").style("fill", '#008394');
}
if (d == 'col_1') {
d3.selectAll(".col_1").style("fill", '#80061b');
};
})
.on("mouseout", function() {
d3.selectAll(".col_2").style("fill", col_2_col);
d3.selectAll(".col_1").style("fill", col_1_col);
});
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return capitalizeFirstLetter(d.replace("col_1", "Column 1").replace("col_2", "Column 2"));
})
.on("mouseover", function(d) {
if (d == 'col_2') {
d3.selectAll(".col_2").style("fill", '#008394');
}
if (d == 'col_1') {
d3.selectAll(".col_1").style("fill", '#80061b');
};
})
.on("mouseout", function() {
d3.selectAll(".col_2").style("fill", col_2_col);
d3.selectAll(".col_1").style("fill", col_1_col);
});;
// Initialize jQuery slider
$('div#month-slider').slider({
min: 1,
max: 25,
value: defaultSliderValue,
create: function() {
// add value to the handle on slider creation
$(this).find('.ui-slider-handle').html($(this).slider("value"));
},
slide: function(e, ui) {
// change values on slider handle and label based on slider value
$(e.target).find('.ui-slider-handle').html(ui.value);
// calculate offset date based on slider value
var offsetDate = ui.value ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth() - ui.value)), 1), -1) : min
// set x domain and re-render xAxis
x.domain([offsetDate, datePlusOneMonth]);
g.select('.x.axis').call(xMonthAxis);
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11) { // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length == 2 && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
g.select('.yearaxis.axis').call(xYearAxis);
// calculate filtered data based on new offset date, set y axis domain and re-render y axis
var filteredData = data.filter(function(d) {
return d.date >= offsetDate;
});
y.domain([0, d3.max(filteredData, function(d) {
return d.total;
})]).nice();
g.select('.y.axis').transition().duration(200).call(yAxis);
// re-render the bars based on new filtered data
// the bars
var bars = g.select("g.bars")
.selectAll("g")
.data(d3.stack().keys(keys)(filteredData));
var barRects = bars.enter().append("g").merge(bars)
.attr('class', function(d) {
return d.key;
})
.attr("fill", function(d) {
return z(d.key);
})
.selectAll("rect")
.data(function(d) {
return d;
});
barRects.exit().remove();
barRects.enter()
.append("rect");
barWidth = (width - margin.right - margin.left) / (filteredData.length + barPad);
monthAxis.selectAll("g").select("text")
.attr("transform", "translate(" + barWidth / 2 + ",0)");
g.select("g.bars").selectAll('g rect')
.attr("x", function(d) {
return x(d.data.date);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", barWidth)
.on("mousemove", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
var yPosition = parseFloat(d3.mouse(this)[1]) + 50;
var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values
var totalCount = d.data["total"];
var valuePercent = value / totalCount;
var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
d3.select("#tooltip")
.select("#count")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.select("#totalCount")
.text(formatNum(totalCount)); // return the value
d3.select("#tooltip")
.select("#percent")
.text((valuePercent * 100).toFixed(1) + "%"); // return the value
d3.select("#tooltip")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
d3.select("#tooltip")
.select("#sessionLabel")
.text(valueClass); // return the class
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
}
});
g.append("text")
.attr("class", "title")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text("Title");
#tooltip {
position: absolute;
width: 290px;
z-index: 2;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
g[class="col_1"] rect:hover {
fill: #80061b;
}
g[class="col_2"] rect:hover {
fill: #008394;
}
div.slider-container {
margin: 20px 20px 20px 80px;
}
div#month-slider {
width: 50%;
margin: 0px 0px 0px 330px;
background: #e3eff4;
}
div#month-slider .ui-slider .ui-slider-handle {
width: 25%;
}
div#month-slider .ui-slider-handle {
text-align: center;
}
.title {
font-size: 30px;
font-weight: bold;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
pointer-events: none;
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js" integrity="sha256-eGE6blurk5sHj+rmkfsGYeKyZx3M4bG+ZlFyA7Kns7E=" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
<body>
<div id="tooltip" class="hidden">
<p><strong>Month: </strong><span id="month"></span>
<p>
<p><strong><span id="sessionLabel"></span>: </strong><span id="count"></span> (<span id="percent"></span>)</p>
<p><strong>Total: </strong><span id="totalCount"></span></p>
</div>
<div class="slider-container" style='inline-block;'>
<span style='margin-left:6em;display:inline-block;position:relative;top:15px;'><strong>Number of Months Displayed:</strong></span>
<div id="month-slider" style='inline-block;'></div>
</div>
I would like the title to be above the slider in this. My understanding for why this isn't working is because the slider is in a div completely separate from the svg element. The thing that makes the most sense here, then, is to insert the div with class slider-container into the svg element. How can this be done?
Here's your options:
Use a forigenObject svg node to insert your html slider but this is poorly supported under IE.
Remove the title from your svg make it html.
Create an svg slider like here.
I just managed to convert the respective heatmap from SVG to canvas. (due to large dataset vs performance issue) However the position of the generated heatmap has goes to a new region. I not sure how am I going to do about this. By changing the transform does not change anything as well.
My code:
var units = [];
for(var unit_i = 0; unit_i<=101;){
if(unit_i==0){
units.push(1);
unit_i = unit_i + 5;
}
else{
units.push(unit_i);
unit_i = unit_i + 4;
}
}
var times = [];
for(var times_i = 0; times_i<=1440;){
if(times_i==0){
times.push(1);
times_i = times_i + 10;
}
else{
times.push(times_i);
times_i = times_i + 9;
}
}
var newSample = [{unit:null, timestamp: null, level: null}];
//by using below method we can observe the delay is not due to the data during insertion
for(var unit=1; unit<=99; unit++){
for(var timestamp = 1; timestamp<=100; timestamp++){
var i = Math.random() * 1400;
newSample.push({unit:unit, timestamp: timestamp, level:i});
}
}
var hours = 0;
var hoursIndicator = 0;
var margin = {
top: 170,
right: 100,
bottom: 70,
left: 100
};
var width = 2500,//optimized width
//gridSize = Math.floor(width / times.length),//optimized gridsize
gridSize = 10;//if 20 each interval will have 5
height = 50 * (units.length); //optimized, the greater the length, the greater the height
console.log("this is gridSize:" + gridSize +", this is height: " + height + ", and this is width: " + width);
//SVG container
var svg = d3.select('.trafficCongestions')
.append("svg")
//.style("position", "absolute")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom)//optimization
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var canvas = d3.select('.trafficCongestions').append("canvas")
.attr("id", "canvas")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom);//optimization
var context = canvas.node().getContext("2d");
context.clearRect(0, 0, width, height);
var detachedContainer = document.createElement("custom");
var dataContainer = d3.select(detachedContainer);
//Reset the overall font size
var newFontSize = width * 62.5 / 900;
//heatmap drawing starts from here
var colorScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d, i) {return d.level; })/2, d3.max(newSample, function(d, i) {return d.level; })])
.range(["#009933", "#FFCC00", "#990000"])//obtain the max data value of count
//y-axis (solely based on data of days)
var dayLabels = svg.selectAll(".dayLabel")
.data(units)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) {
return (i) * (gridSize * 4)/*adjusts the interval distance with (n - 1) concept*/; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize + ")");
//x-axis (solely based on data of times)
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d, i) {
var hrs = Math.floor(d/60);
var mins = d%60;
if(hrs<10){
if(mins<10){
return "0"+hrs + ":0" + mins;
}
return "0"+ hrs + ":" + mins;
}
return hrs +":"+ mins;
})
.attr("x", function(d, i) { return i * (gridSize * 9)/*adjusts the interval distance with (n - 1) concept*/; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + 1 + ", -6)")
var heatMap = dataContainer.selectAll("custom.rect")
.data(newSample)
.enter().append("custom")
.attr("x", function(d) {
return (d.timestamp - 1) * (gridSize);
})
.attr("y", function(d) {
console.log(d.unit);
return (d.unit - 1) * (gridSize);
})
.classed("rect", true)
.attr("class", " rect bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.attr("strokeStyle", "rgba(255,255,255, 0.6)")//to have the middle line or not
.attr("fillStyle", function(d,i){
return colorScale(d.level);
});
drawCanvas();
//Append title to the top
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", -90)
.style("text-anchor", "middle")
.text("Sample Result");
svg.append("text")
.attr("class", "subtitle")
.attr("x", width/2)
.attr("y", -60)
.style("text-anchor", "middle")
.text("HEATMAP");
//Append credit at bottom
svg.append("text")
.attr("class", "credit")
.attr("x", width/2)
.attr("y", gridSize * (units.length+1) + 80)
.style("text-anchor", "middle");
//Extra scale since the color scale is interpolated
var countScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d) {return d.level; })])
.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", "legendLevel")
.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] );
});
var legendWidth = Math.min(width, 400);//the width of the legend
console.log(width);
//Color Legend container
var legendsvg = svg.append("g")
.attr("class", "legendWrapper")
.attr("transform", "translate(" + (width/2) + "," + (gridSize * 100 + 40) + ")");
//Draw the Rectangle
legendsvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", 0)
.attr("width", legendWidth)
.attr("height", 10)
.style("fill", "url(#legendLevel)");
//Append title
legendsvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -10)
.style("text-anchor", "middle")
.text("Level");
//Set scale for x-axis
var xScale = d3.scaleLinear()
.range([-legendWidth/2, legendWidth/2])
.domain([ 0, d3.max(newSample, function(d) { return d.level; })] );
//Define x-axis
var xAxis = d3.axisBottom()
.ticks(5)
.scale(xScale);
//Set up X axis
legendsvg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (10) + ")")
.call(xAxis);
function drawCanvas(){
var elements = dataContainer.selectAll("custom.rect");
elements.each(function(d){
var node = d3.select(this);
context.beginPath();
context.fillStyle = node.attr("fillStyle");
context.rect(node.attr("x"), node.attr("y"), node.attr("width"), node.attr("height"));
context.fill();
context.closePath();
});
}
html { font-size: 100%; }
.timeLabel, .dayLabel {
font-size: 1rem;
fill: #AAAAAA;
font-weight: 300;
}
.title {
font-size: 1.8rem;
fill: #4F4F4F;
font-weight: 300;
}
.subtitle {
font-size: 1.0rem;
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;
}
.legendTitle {
font-size: 1.3rem;
fill: #4F4F4F;
font-weight: 300;
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="trafficCongestions" class="trafficCongestions"></div>
As you're using svg to just show up the text and legend, I'd say you can absolute position the canvas on top of the SVG - by CSS.
Here are the CSS changes:
div#trafficCongestions {
position: relative;
}
canvas {
position: absolute;
top: 170px;
left: 100px;
}
You can do the above using d3 style as well `cause the margins are defined in the script. I just wanted to show that this is an option to use.
var units = [];
for(var unit_i = 0; unit_i<=101;){
if(unit_i==0){
units.push(1);
unit_i = unit_i + 5;
}
else{
units.push(unit_i);
unit_i = unit_i + 4;
}
}
var times = [];
for(var times_i = 0; times_i<=1440;){
if(times_i==0){
times.push(1);
times_i = times_i + 10;
}
else{
times.push(times_i);
times_i = times_i + 9;
}
}
var newSample = [{unit:null, timestamp: null, level: null}];
//by using below method we can observe the delay is not due to the data during insertion
for(var unit=1; unit<=99; unit++){
for(var timestamp = 1; timestamp<=100; timestamp++){
var i = Math.random() * 1400;
newSample.push({unit:unit, timestamp: timestamp, level:i});
}
}
var hours = 0;
var hoursIndicator = 0;
var margin = {
top: 170,
right: 100,
bottom: 70,
left: 100
};
var width = 2500,//optimized width
//gridSize = Math.floor(width / times.length),//optimized gridsize
gridSize = 10;//if 20 each interval will have 5
height = 50 * (units.length); //optimized, the greater the length, the greater the height
//console.log("this is gridSize:" + gridSize +", this is height: " + height + ", and this is width: " + width);
//SVG container
var svg = d3.select('.trafficCongestions')
.append("svg")
//.style("position", "absolute")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom)//optimization
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var canvas = d3.select('.trafficCongestions').append("canvas")
.attr("id", "canvas")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom);//optimization
var context = canvas.node().getContext("2d");
context.clearRect(0, 0, width, height);
var detachedContainer = document.createElement("custom");
var dataContainer = d3.select(detachedContainer);
//Reset the overall font size
var newFontSize = width * 62.5 / 900;
//heatmap drawing starts from here
var colorScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d, i) {return d.level; })/2, d3.max(newSample, function(d, i) {return d.level; })])
.range(["#009933", "#FFCC00", "#990000"])//obtain the max data value of count
//y-axis (solely based on data of days)
var dayLabels = svg.selectAll(".dayLabel")
.data(units)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) {
return (i) * (gridSize * 4)/*adjusts the interval distance with (n - 1) concept*/; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize + ")");
//x-axis (solely based on data of times)
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d, i) {
var hrs = Math.floor(d/60);
var mins = d%60;
if(hrs<10){
if(mins<10){
return "0"+hrs + ":0" + mins;
}
return "0"+ hrs + ":" + mins;
}
return hrs +":"+ mins;
})
.attr("x", function(d, i) { return i * (gridSize * 9)/*adjusts the interval distance with (n - 1) concept*/; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + 1 + ", -6)")
var heatMap = dataContainer.selectAll("custom.rect")
.data(newSample)
.enter().append("custom")
.attr("x", function(d) {
return (d.timestamp - 1) * (gridSize);
})
.attr("y", function(d) {
//console.log(d.unit);
return (d.unit - 1) * (gridSize);
})
.classed("rect", true)
.attr("class", " rect bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.attr("strokeStyle", "rgba(255,255,255, 0.6)")//to have the middle line or not
.attr("fillStyle", function(d,i){
return colorScale(d.level);
});
drawCanvas();
//Append title to the top
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", -90)
.style("text-anchor", "middle")
.text("Sample Result");
svg.append("text")
.attr("class", "subtitle")
.attr("x", width/2)
.attr("y", -60)
.style("text-anchor", "middle")
.text("HEATMAP");
//Append credit at bottom
svg.append("text")
.attr("class", "credit")
.attr("x", width/2)
.attr("y", gridSize * (units.length+1) + 80)
.style("text-anchor", "middle");
//Extra scale since the color scale is interpolated
var countScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d) {return d.level; })])
.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", "legendLevel")
.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] );
});
var legendWidth = Math.min(width, 400);//the width of the legend
//console.log(width);
//Color Legend container
var legendsvg = svg.append("g")
.attr("class", "legendWrapper")
.attr("transform", "translate(" + (width/2) + "," + (gridSize * 100 + 40) + ")");
//Draw the Rectangle
legendsvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", 0)
.attr("width", legendWidth)
.attr("height", 10)
.style("fill", "url(#legendLevel)");
//Append title
legendsvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -10)
.style("text-anchor", "middle")
.text("Level");
//Set scale for x-axis
var xScale = d3.scaleLinear()
.range([-legendWidth/2, legendWidth/2])
.domain([ 0, d3.max(newSample, function(d) { return d.level; })] );
//Define x-axis
var xAxis = d3.axisBottom()
.ticks(5)
.scale(xScale);
//Set up X axis
legendsvg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (10) + ")")
.call(xAxis);
function drawCanvas(){
var elements = dataContainer.selectAll("custom.rect");
elements.each(function(d){
var node = d3.select(this);
context.beginPath();
context.fillStyle = node.attr("fillStyle");
context.rect(node.attr("x"), node.attr("y"), node.attr("width"), node.attr("height"));
context.fill();
context.closePath();
});
}
html { font-size: 100%; }
div#trafficCongestions {
position: relative;
}
canvas {
position: absolute;
top: 170px;
left: 100px;
}
.timeLabel, .dayLabel {
font-size: 1rem;
fill: #AAAAAA;
font-weight: 300;
}
.title {
font-size: 1.8rem;
fill: #4F4F4F;
font-weight: 300;
}
.subtitle {
font-size: 1.0rem;
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;
}
.legendTitle {
font-size: 1.3rem;
fill: #4F4F4F;
font-weight: 300;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="trafficCongestions" class="trafficCongestions"></div>
Let me know if this makes sense. If not, let's look for other approaches.
Edit as per comments: (visual studio didn't support the CSS styling added by above approach)
Added the style using d3:
canvas.style('position', 'absolute').style('top', margin.top+'px').style('left', margin.left+'px')
And this worked.
Currently I am working on one of FCC's project and essentially I am done with the overall project.
However, I am hard time making a simple legend, especially when I am using scaleQuantile as my colorScale
Here's my codepen link : http://codepen.io/neotriz/pen/PGOLBb
Essentially I want to create a horizontal legend that outputs all of the colors (inside the array color) and append its text, based on the data.
I could hard-code the text of the legend, but I rather do it in abstract (text is based on the data itself)
If anyone can point me the right direction, I will appreciate it.
let toolTip = d3.select("#canvas")
.append("div")
.classed("toolTip",true)
.style("opacity",0)
const colors = ["#5e4fa2", "#3288bd", "#66c2a5", "#abdda4",
"#e6f598", "#ffffbf", "#fee08b", "#fdae61",
"#f46d43", "#d53e4f", "#9e0142"];
const width = w - (margin.left + margin.right);
const height = h - (margin.top + margin.bottom);
const yOffset = 40;
//lets create new object to add degree key and its value
data = rawData.map( oneData => {
let degree = base + oneData.variance
return Object.assign({}, oneData, {degree: degree})
})
const svg = d3.select("#canvas")
.append("svg")
.attr("id","chart")
.attr("width", w)
.attr("height", h)
const chart = svg.append("g")
.classed("display", true)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const yearParser = d3.timeParse("%Y")
const monthParser = d3.timeParse("%m")
const x = d3.scaleTime()
.domain(d3.extent(data,function(d){
let year = yearParser(d.year)
return year
}))
.range([0,width]);
const y = d3.scaleTime()
.domain([monthParser(data[0].month),monthParser(data[11].month)])
.range([0,height-yOffset])
const xAxis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y")).tickSize(9)
const yAxis = d3.axisLeft(y)
.tickFormat(d3.timeFormat("%B")).tickSize(0).tickPadding(6);
const colorScale = d3.scaleQuantile()
.domain(d3.extent(data,function(d){
return d.degree
}))
.range(colors)
function toolTipText(d){
const rawMonth = monthParser(d.month);
const monthFormat = d3.timeFormat("%B");
const month = monthFormat(rawMonth)
let text = '<strong>' + month + " " + d.year + '</strong>'+ '<br>';
text += d.degree.toFixed(3) +' °C'+ '<br>';
text += 'Variance: '+d.variance +' °C'+ '<br>';
return text
}
function drawAxis(params){
//draw xAxis
this.append("g")
.call(params.axis.x)
.classed("x axis", true)
.attr("transform", "translate(0,"+ height +")")
.selectAll("text")
.style("font-size",16)
//draw yAxis
this.append("g")
.call(params.axis.y)
.classed("y axis",true)
.attr("transform","translate(0,0)")
.selectAll("text")
.attr("dy",25)
.style("font-size",14)
//label x axis
this.select(".x.axis")
.append("text")
.classed("x axis-label",true)
.attr("transform","translate(-60,"+ -height/2 +") rotate(-90)")
.style("fill","black")
.text("Months")
this.select(".y.axis")
.append("text")
.classed("y axis-label",true)
.attr("transform","translate("+ width/2 +","+ (height+60) +")")
.style("fill","black")
.text("Years")
}
function plot(params){
if (params.initialize){
drawAxis.call(this,params)
}
//enter()
this.selectAll(".degree")
.data(params.data)
.enter()
.append("rect")
.classed("degree", true)
//update
this.selectAll(".degree")
.transition()
.delay(100)
.attr("x",function(d,i){
let year = yearParser(d.year)
return x(year)
})
this.selectAll(".degree")
.attr("y",function(d,i){
let month = monthParser(d.month)
return y(month)
})
this.selectAll(".degree")
.attr("width", 4)
this.selectAll(".degree")
.attr("height", yOffset)
this.selectAll(".degree")
.style("fill", function(d,i){
return colorScale(d.degree)
})
.on("mouseover",function(d,i){
let text = toolTipText(d)
toolTip.transition()
.style("opacity",.9)
toolTip.html(text)
.style("left", (d3.event.pageX + 15) + "px")
.style("top", (d3.event.pageY - 28) + "px")
d3.select(this)
.style("stroke","gray")
.style("stroke-width", 3)
})
.on("mouseout",function(d,i){
toolTip.transition()
.style("opacity",0)
d3.select(this)
.style("stroke","none")
})
//exit()
this.selectAll(".degree")
.data(params.data)
.exit()
.remove()
}
plot.call(chart,{
base: base,
data: data,
axis: {
x: xAxis,
y: yAxis
},
initialize: true
})
Here's a quick implementation:
function drawLegend(){
var legend = this.select(".x.axis").append("g"),
legW = 40;
legend.selectAll('rect')
.data(colorScale.range())
.enter()
.append('rect')
.attr('width', legW)
.attr('x', function(d,i){
return i * legW;
})
.attr('y', 50)
.attr('height', 20)
.style('fill', function(d){
return d;
});
legend.selectAll('text')
.data(colorScale.quantiles())
.enter()
.append('text')
.attr('x', function(d,i){
return (i + 1) * legW;
})
.attr('y', 80)
.text(function(d,i){
var rv = Math.round(d*10)/10;
if (i === 0) rv = '<' + rv;
else if (i === (colorScale.quantiles().length - 1)) rv = '>' + rv;
return rv;
})
.style('fill', 'black')
.style('stroke', 'none');
}
Full running code:
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="UTF-8" />
<title>FCC Heat-Map</title>
<style>
body,
html {
text-align: center;
background: #e2e2e2;
}
#canvas {
margin: 65px auto;
width: 1300px;
height: 600;
background: white;
-webkit-box-shadow: 0px 0px 31px -5px rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0px 0px 31px -5px rgba(0, 0, 0, 0.5);
box-shadow: 0px 0px 31px -5px rgba(0, 0, 0, 0.5);
}
#chart {}
.title {
padding-top: 11px;
font-size: 2em;
font-weight: bold;
}
.year {
font-size: 1.7em;
}
.description {
font-size: 0.9em;
margin-bottom: -35px;
}
.axis-label {
font-size: 25px;
}
.toolTip {
color: white;
position: absolute;
text-align: center;
max-width: 250px;
max-height: 98px;
padding: 7px;
font: 15px sans-serif;
background: black;
border: 25px;
border-radius: 12px;
line-height: 18px;
pointer-events: none;
box-shadow: 0px 0px 12px -10px;
}
</style>
</head>
<body>
<div id="canvas"></div>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script>
var colorScale = null;
$(document).ready(function() {
const w = 1300;
const h = 600;
const margin = {
top: 50,
bottom: 80,
left: 100,
right: 20
}
function title() {
const mainTitle = document.createElement("div");
mainTitle.innerHTML = "Monthly Global Land-Surface Temperature"
mainTitle.className = "title";
const year = document.createElement("div");
year.innerHTML = "1753 - 2015";
year.className = "year";
const description = document.createElement("div")
description.innerHTML = 'Temperatures are in Celsius and reported as anomalies relative to the Jan 1951-Dec 1980 average.<br>' +
'Estimated Jan 1951-Dec 1980 absolute temperature ℃: 8.66 +/- 0.07';
description.className = "description"
let chart = document.getElementById("canvas")
chart.appendChild(mainTitle)
chart.appendChild(year)
chart.appendChild(description)
}
function render(base, rawData) {
let toolTip = d3.select("#canvas")
.append("div")
.classed("toolTip", true)
.style("opacity", 0)
const colors = ["#5e4fa2", "#3288bd", "#66c2a5", "#abdda4",
"#e6f598", "#ffffbf", "#fee08b", "#fdae61",
"#f46d43", "#d53e4f", "#9e0142"
];
const width = w - (margin.left + margin.right);
const height = h - (margin.top + margin.bottom);
const yOffset = 40;
//lets create new object to add degree key and its value
data = rawData.map(oneData => {
let degree = base + oneData.variance
return Object.assign({}, oneData, {
degree: degree
})
})
const svg = d3.select("#canvas")
.append("svg")
.attr("id", "chart")
.attr("width", w)
.attr("height", h)
const chart = svg.append("g")
.classed("display", true)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const yearParser = d3.timeParse("%Y")
const monthParser = d3.timeParse("%m")
const x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
let year = yearParser(d.year)
return year
}))
.range([0, width]);
const y = d3.scaleTime()
.domain([monthParser(data[0].month), monthParser(data[11].month)])
.range([0, height - yOffset])
const xAxis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y")).tickSize(9)
const yAxis = d3.axisLeft(y)
.tickFormat(d3.timeFormat("%B")).tickSize(0).tickPadding(6);
colorScale = d3.scaleQuantile()
.domain(d3.extent(data, function(d) {
return d.degree
}))
.range(colors)
function toolTipText(d) {
const rawMonth = monthParser(d.month);
const monthFormat = d3.timeFormat("%B");
const month = monthFormat(rawMonth)
let text = '<strong>' + month + " " + d.year + '</strong>' + '<br>';
text += d.degree.toFixed(3) + ' °C' + '<br>';
text += 'Variance: ' + d.variance + ' °C' + '<br>';
return text
}
function drawAxis(params) {
//draw xAxis
this.append("g")
.call(params.axis.x)
.classed("x axis", true)
.attr("transform", "translate(0," + height + ")")
.selectAll("text")
.style("font-size", 16)
//draw yAxis
this.append("g")
.call(params.axis.y)
.classed("y axis", true)
.attr("transform", "translate(0,0)")
.selectAll("text")
.attr("dy", 25)
.style("font-size", 14)
//label x axis
this.select(".x.axis")
.append("text")
.classed("x axis-label", true)
.attr("transform", "translate(-60," + -height / 2 + ") rotate(-90)")
.style("fill", "black")
.text("Months")
this.select(".y.axis")
.append("text")
.classed("y axis-label", true)
.attr("transform", "translate(" + width / 2 + "," + (height + 60) + ")")
.style("fill", "black")
.text("Years")
}
function drawLegend(){
var legend = this.select(".x.axis").append("g"),
legW = 40;
legend.selectAll('rect')
.data(colorScale.range())
.enter()
.append('rect')
.attr('width', legW)
.attr('x', function(d,i){
return i * legW;
})
.attr('y', 40)
.attr('height', 20)
.style('fill', function(d){
return d;
});
legend.selectAll('text')
.data(colorScale.quantiles())
.enter()
.append('text')
.attr('x', function(d,i){
return (i + 1) * legW;
})
.attr('y', 70)
.text(function(d,i){
var rv = Math.round(d*10)/10;
if (i === 0) rv = '<' + rv;
else if (i === (colorScale.quantiles().length - 1)) rv = '>' + rv;
return rv;
})
.style('fill', 'black')
.style('stroke', 'none');
}
function plot(params) {
if (params.initialize) {
drawAxis.call(this, params)
}
//enter()
this.selectAll(".degree")
.data(params.data)
.enter()
.append("rect")
.classed("degree", true)
//update
this.selectAll(".degree")
.transition()
.delay(100)
.attr("x", function(d, i) {
let year = yearParser(d.year)
return x(year)
})
this.selectAll(".degree")
.attr("y", function(d, i) {
let month = monthParser(d.month)
return y(month)
})
this.selectAll(".degree")
.attr("width", 4)
this.selectAll(".degree")
.attr("height", yOffset)
this.selectAll(".degree")
.style("fill", function(d, i) {
return colorScale(d.degree)
})
.on("mouseover", function(d, i) {
let text = toolTipText(d)
toolTip.transition()
.style("opacity", .9)
toolTip.html(text)
.style("left", (d3.event.pageX + 15) + "px")
.style("top", (d3.event.pageY - 28) + "px")
d3.select(this)
.style("stroke", "gray")
.style("stroke-width", 3)
})
.on("mouseout", function(d, i) {
toolTip.transition()
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
})
//exit()
this.selectAll(".degree")
.data(params.data)
.exit()
.remove()
drawLegend.call(this);
}
plot.call(chart, {
base: base,
data: data,
axis: {
x: xAxis,
y: yAxis
},
initialize: true
})
}
const url = 'https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/global-temperature.json';
$.ajax({
type: "GET",
dataType: "json",
url: url,
beforeSend: () => {},
complete: () => {},
success: data => {
title()
const baseTemperature = data.baseTemperature;
const dataAPI = data.monthlyVariance;
render(baseTemperature, dataAPI);
},
fail: () => {
console.log('failure!')
},
error: () => {
let chart = document.getElementById('card');
chart.style.display = "table"
let errorMessage = document.createElement("h1");
errorMessage.innerHTML = "ERROR 404: File Not Found!"
errorMessage.className = "errorMessage";
chart.appendChild(errorMessage)
}
});
});
</script>
</body>
</html>
I am trying to combine two D3 Visualizations. I found a question before, but it did not really have a solution.
When I combine the two files the visualizations overlap and produce this:
the streamgraph component:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.chart {
background: #fff;
}
p {
font: 12px helvetica;
}
.axis path, .axis line {
fill: none;
stroke: #000;
stroke-width: 2px;
shape-rendering: crispEdges;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<div class="chart">
</div>
<script>
chart("Data.csv", "blue");
var datearray = [];
var colorrange = [];
function chart(csvpath, color) {
if (color == "blue") {
colorrange = ["#045A8D", "#2B8CBE", "#74A9CF", "#A6BDDB", "#D0D1E6", "#F1EEF6"];
}
else if (color == "pink") {
colorrange = ["#980043", "#DD1C77", "#DF65B0", "#C994C7", "#D4B9DA", "#F1EEF6"];
}
else if (color == "orange") {
colorrange = ["#B30000", "#E34A33", "#FC8D59", "#FDBB84", "#FDD49E", "#FEF0D9"];
}
strokecolor = colorrange[0];
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 40, bottom: 30, left: 50};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var tooltip = d3.select("body")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "20")
.style("visibility", "hidden")
.style("top", "30px")
.style("left", "75px");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var z = d3.scale.ordinal()
.range(colorrange);
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(x)
.ticks(d3.time.years, 10); //tick on every 10 years
/*.scale(x)
.orient("bottom")
.text(date)
//;*/
//. tickFormat(x)
//. tickValues(date)
//was already there but out of view -> changed the left margin
var yAxis = d3.svg.axis()
.scale(y);
var stack = d3.layout.stack()
.offset("silhouette")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select(".chart").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 + ")");
/* correct this function
var graph = d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});*/
var graph = d3.csv(csvpath, function(raw) {
var data = [];
raw.forEach(function (d) {
data.push({
key: d.Country,
date : new Date(1980,0,1), //I had a bug in creating the right dates
value : parseInt(d['1980-1989'].replace(',','')) //get rid of the thousand separator
});
data.push({
key: d.Country,
date : new Date(1990,0,1),
value : parseInt(d['1990-1999'].replace(',',''))
});
data.push({
key: d.Country,
date : new Date(2000,0,1),
value : parseInt(d['2000-2009'].replace(',','') )
});
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
//adding .text causes axis to dissapear
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.text(date)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
//.text(value)
.call(yAxis.orient("right"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis.orient("left"));
var pro;
svg.selectAll(".layer")
.attr("opacity", 1)
.on("mouseover", function(d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function(d, j) {
return j != i ? 0.6 : 1;
})})
.on("mousemove", function(d, i) {
var mousex = d3.mouse(this);
mousex = mousex[0];
var invertedx = x.invert(mousex);
//find the largest smaller element
var dd = d.values.filter(function(d) { return d.date <= invertedx; });
dd = dd[dd.length -1]; //use the last element
pro = dd.value;
d3.select(this)
.classed("hover", true)
.attr("stroke", strokecolor)
.attr("stroke-width", "0.5px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "visible");
})
.on("mouseout", function(d, i) {
svg.selectAll(".layer")
.transition()
.duration(250)
.attr("opacity", "1");
d3.select(this)
.classed("hover", false)
.attr("stroke-width", "0px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "hidden");
})
var vertical = d3.select(".chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "19")
.style("width", "1px")
.style("height", "380px")
.style("top", "10px")
.style("bottom", "30px")
.style("left", "0px")
.style("background", "#fff");
d3.select(".chart")
.on("mousemove", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px" )})
.on("mouseover", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")});
});
}
</script>
Map component:
<!DOCTYPE html>
<meta charset="utf-8">
<title>U.S Immigration Data Visualization</title>
<style>
.country:hover{
stroke: #fff;
stroke-width: 1.5px;
}
.text{
font-size:10px;
text-transform:capitalize;
}
#container {
margin: 10px 10%;
border:2px solid #000;
border-radius: 5px;
height:100%;
overflow:hidden;
background: #e1eafe;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #fff;
padding: .5em;
text-shadow: #f5f5f5 0 1px 0;
border-radius: 2px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
opacity: 0.9;
position: absolute;
}
.graticule {
fill: none;
stroke: #bbb;
stroke-width: .5px;
stroke-opacity: .5;
}
.equator {
stroke: #ccc;
stroke-width: 1px;
}
</style>
</head>
<br>
<h1><center>U.S Immigration Data Visualization</center></h1>
<h2><b>Work in Progress</b></h2>
<h3><b>Ex-USSR countries included in Russia</b></h3>
<h3><b>Ex-Yugoslavia included in Macedonia</b></h3>
<div id="container"></div>
<script src="js/d3.min.js"></script>
<script src="js/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.geo.tile.v0.min.js"></script>
<script>
d3.select(window).on("resize", throttle);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 9])
.on("zoom", move);
var width = document.getElementById('container').offsetWidth;
var height = width / 2;
var topo,projection,path,svg,g;
var graticule = d3.geo.graticule();
var tooltip = d3.select("#container").append("div").attr("class", "tooltip hidden");
setup(width,height);
function setup(width,height){
projection = d3.geo.mercator()
.translate([(width/2), (height/2)])
.scale( width / 2 / Math.PI);
path = d3.geo.path().projection(projection);
svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.on("click", click)
.append("g");
g = svg.append("g");
}
d3.json("data/world-topo-min.json", function(error, world) {
var countries = topojson.feature(world, world.objects.countries).features;
topo = countries;
draw(topo);
});
function draw(topo) {
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
g.append("path")
.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
.attr("class", "equator")
.attr("d", path);
var country = g.selectAll(".country").data(topo);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.attr("title", function(d,i) { return d.properties.name; })
.style("fill", function(d, i) { return d.properties.color; });
//offsets for tooltips
var offsetL = document.getElementById('container').offsetLeft+20;
var offsetT = document.getElementById('container').offsetTop+10;
//tooltips
country
.on("mousemove", function(d,i) {
var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
.html(d.properties.name);
})
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
});
//EXAMPLE: adding some capitals from external CSV file
d3.csv("Data.csv", function(err, capitals) {
capitals.forEach(function(i){
addpoint(i.CapitalLongitude, i.CapitalLatitude );
});
});
}
function redraw() {
width = document.getElementById('container').offsetWidth;
height = width / 2;
d3.select('svg').remove();
setup(width,height);
draw(topo);
}
function move() {
var t = d3.event.translate;
var s = d3.event.scale;
zscale = s;
var h = height/4;
t[0] = Math.min(
(width/height) * (s - 1),
Math.max( width * (1 - s), t[0] )
);
t[1] = Math.min(
h * (s - 1) + h * s,
Math.max(height * (1 - s) - h * s, t[1])
);
zoom.translate(t);
g.attr("transform", "translate(" + t + ")scale(" + s + ")");
//adjust the country hover stroke width based on zoom level
d3.selectAll(".country").style("stroke-width", 1.5 / s);
}
var throttleTimer;
function throttle() {
window.clearTimeout(throttleTimer);
throttleTimer = window.setTimeout(function() {
redraw();
}, 200);
}
//geo translation on mouse click in map
function click() {
var latlon = projection.invert(d3.mouse(this));
console.log(latlon);
}
//function to add points and text to the map (used in plotting capitals)
function addpoint(lat,lon,text) {
var gpoint = g.append("g").attr("class", "gpoint");
var x = projection([lat,lon])[0];
var y = projection([lat,lon])[1];
gpoint.append("svg:circle")
.attr("cx", x)
.attr("cy", y)
.attr("class","point")
.attr("r", 1);
//conditional in case a point has no associated text
//if(text.length>0){
// gpoint.append("text")
// .attr("x", x+2)
// .attr("y", y+2)
// .attr("class","text")
// .text(text);
//}
}
</script>
</body>
</html>
This is hard to answer without the code you actually use when you 'combine' the two SVG elements, and without the data or a working example.
What I've done is take the 2 basic components, the streamgraph (and svg node inside <div class="chart">) and the map (a separate svg node in <div id="container"></div>), and create working code that combines the 2:
http://plnkr.co/edit/WjlObRIasLYXOuEL4HDE?p=preview
This is the basic code:
<body>
<div class="chart">
</div>
<div id="container">
</div>
<script type="text/javascript">
var width = 300;
var height = width / 2;
// Equivalent of streamgraph code...
var svg_stream = d3.select(".chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("rect")
... // rect attributes
// Equivalent of map code...
var svg_map = d3.select("#container")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("circle")
... // circle attributes
</script>
</body>
What's inside each svg shouldn't make a difference to the positioning, so I've just used a rect to represent the streamgraph and a circle for the map. I've taken as much of the CSS and code from your snippets as it makes sense to. If you combine the two components as in the example above, you should not see any overlap. I'm not really able to correct your version as I don't know how you did it.
Note - You should also avoid defining duplicate variable names (like var svg for both SVGs) when combining the components