I have been struggling with this quite sometimes, really need help with this. I wanna to create a heatmap with it content and axis are all scalable and zoom-able accordingly.Notice I have converted the svg content to canvas. I suppose I have already halfway there, unfortunately I'am unable to make the canvas zoom and drag accordingly, meanwhile the axis itself is okay.
Below is my source, your help is really needed. Thanks and sorry for my bad grammar.
$(document).ready(function() {
let w = $('#bodyBackground')[0];
let clientWidthz = w.clientWidth;
let clientHeightz = w.clientHeight;
var radars = [];
var newSample = [];
var timeStampLegends = [];
for (var i = 0; i < 1440; i++) {
var legend_xTime = new Date(Date.now() - (i * 60 * 1000));
timeStampLegends.push(legend_xTime);
}
for (var unit_i = 0; unit_i <= 101;) {
if (unit_i == 0) {
radars.push(1);
unit_i = unit_i + 5;
} else {
radars.push(unit_i);
unit_i = unit_i + 4;
}
}
//by using below method we can observe the delay is not due to the data during insertion
for (var unit = 1; unit <= 5; unit++) {
timeStampLegends.forEach(function(dbData) {
var i = Math.random() * 1400;
newSample.push({ radars: unit, timestamp: dbData, level: i });
});
}
var margin = { top: 20, right: 20, bottom: 30, left: 60 };
var width = 1200 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var x = d3.scaleTime()
.domain([new Date(timeStampLegends[0]), new Date(timeStampLegends[timeStampLegends.length - 1])])
.range([0, width]);
var min = d3.min(newSample, function(d) {
return d.radars;
});
var max = d3.max(newSample, function(d) {
return d.radars;
});
var y = d3.scaleLinear()
.domain([min, max])
.range([0, height]);
var xAxis = d3.axisTop(x).tickSize(height); //the size of the inner grid line (vertical - x-axis)
var yAxis = d3.axisLeft(y).ticks(5).tickSize(-width); //the size of the inner grid line (horizontal - y - axis)
var zoom = d3.zoom()
.translateExtent([
[0, 0],
[width + 2000, height]
])
.scaleExtent([1, 10])
.on("zoom", zoomed);
var svg = d3.select(".TrafficCongestions").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 canvas = d3.select('.TrafficCongestions').append("canvas")
.attr("id", "canvas")
//absolute: a feature that allows any sort of element
//to be positioned disregards to other elements
//i.e. it will calculate its own position again from (0,0)
.style("position", "absolute")
.style("top", "20px")
.style("left", "60px")
.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);
var colorScale = d3.scaleLinear().domain([0, 600, 1200]).range(["#009933", "#FFCC00", "#990000"]);
//x-axis (solely based on data of times)
var timeLabels = svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + (height) + ")")
.style("font-size", "20px")
.call(xAxis);
//y-axis (solely based on data of radars)
var radarLabels = svg.append("g")
.attr("class", "y-axis")
.call(yAxis);
//console.log(JSON.stringify(newSample));
var heatMap = dataContainer.selectAll(".custom.rect")
.data(newSample)
.enter().append("custom")
.attr("x", function(d){
return x(d.timestamp);
})
.attr("y", function(d){
return y(d.radars);
})
.classed("rect", true)
.attr("class", "rect bordered")
.attr("width", function(d){
var offSetX = x(d3.timeMinute.offset(d.timestamp, 1))- x(d.timestamp);
return offSetX;
})
//.attr("width", "1120px")
.attr("height", function(d){
var offSetY = y(d.radars - 1) - y(d.radars);
return offSetY;
})
//.attr("strokeStyle", "rgba(255,255,255,0.2)")
.attr("fillStyle", function(d, i){
return colorScale(d.level);
});
canvas.call(zoom);
drawCanvas();
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();
});
}
function zoomed() {
var currentTransform = d3.event.transform;
// update: rescale x axis
timeLabels.call(xAxis.scale(d3.event.transform.rescaleX(x)));
update();
}
function update() {
// update: cache rescaleX value
var rescaleX = d3.event.transform.rescaleX(x);
heatMap.selectAll(".custom.rect")
// update: apply rescaleX value
.attr("x", function(d) {
return rescaleX(d.timestamp);
})
.attr("y", function(d) {
return y(d.radars);
})
// update: apply rescaleX value
.attr("width", function(d) {
return rescaleX(d3.timeMinute.offset(d.timestamp, 1)) - rescaleX(d.timestamp);
//return rescaleX(x);
})
.attr("height", function(d) {
return y(d.radars - 1) - y(d.radars);
})
//.attr("strokeStyle", "rgba(255,255,255,0.2)")
.attr("fillStyle", function(d, i){
return colorScale(d.level);
});
drawCanvas();
}
});
body {
position: relative;
width: 960px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script
src="https://code.jquery.com/jquery-1.10.0.min.js"
integrity="sha256-2+LznWeWgL7AJ1ciaIG5rFP7GKemzzl+K75tRyTByOE="
crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html>
<head>
<title></title>
</head>
<body id="bodyBackground">
<div class="TrafficCongestions"></div>
</body>
</html>