Плавное перетаскивание в холсте D3js
Я пытаюсь нарисовать дорогу с помощью d3js и сделать ее перетаскиваемой. Обратите внимание, что я успешно преобразовал его из svg в canvas. Функция плавного перетаскивания упоминается как How to Pan the Canvas.
Обратите внимание, что когда я попытался реализовать на обычном холсте, он работает, однако в этом модуле он не работает. Я полагаю, логика практически одинакова.
Мой фрагмент кода:
$(document).ready(function(){
let clientWidthz = $(window).width();
let clientHeightz = $(window).height();
var width = clientWidthz;
var height = clientHeightz;
console.log("this is height" + clientHeightz);
console.log("this is width" + clientWidthz);
var color_1 = d3.scaleOrdinal().range(["#605A4C", "#ff9900", "#ff1a1a"]);
console.log(clientWidthz);
console.log(clientHeightz);
//this array will has a total of 99 object
var sampleData = [{radar: 1, timestamp: Date.now(), CongestionLevel: 1400, kilometer: 2.33, bound: "north" },
{radar: 2, timestamp: Date.now(), CongestionLevel: 1600, kilometer: 4.66, bound: "north"},
{radar: 3, timestamp: Date.now(), CongestionLevel: 1200, kilometer: 6.99, bound: "north"},
{radar: 4, timestamp: Date.now(), CongestionLevel: 1700, kilometer: 9.32, bound: "north" },
{radar: 5, timestamp: Date.now(), CongestionLevel: 300, kilometer:11.65, bound: "north"},
{radar: 6, timestamp: Date.now(), CongestionLevel: 1100, kilometer: 13.98, bound: "north" },
{radar: 7, timestamp: Date.now(), CongestionLevel: 300, kilometer: 16.31, bound: "north" },
{radar: 8, timestamp: Date.now(), CongestionLevel: 1700, kilometer: 18.64, bound: "north" }];
var mainRoadValueData = [{section:0, rectWidth:1000, rectHeight: 390},
{section:1, rectWidth:1000, rectHeight: 10},
{section:2, rectWidth:1000, rectHeight: 1},
{section:3, rectWidth:1000, dashlineHeight:270},
{section:4, rectWidth:1000, rectHeight: 4},
{section:5, rectWidth:1000, dashlineHeight:430},
{section:6, rectWidth:1000, rectHeight: 1},
{section:7, rectWidth:1000, rectHeight: 10}];
//mouse attributes
var mouse = {
x: 0,
y: 0,
w: 0,
alt: false,
shift: false,
ctrl: false,
buttonLastRaw: 0, // user modified value
buttonRaw: 0,
over: false,
buttons: [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
};
var displayTransform = {
x: 0,
y: 0,
ox: 0,
oy: 0,
scale: clientHeightz*0.003,
rotate: 0,
cx: 0, // chase values Hold the actual display
cy: 0,
cox: 0,
coy: 0,
cscale: 1,
crotate: 0,
dx: 0, // deltat values
dy: 0,
dox: 0,
doy: 0,
dscale: 1,
drotate: 0,
drag: 0.2, // drag for movements
accel: 1, // acceleration
matrix: [0, 0, 0, 0, 0, 0], // main matrix
invMatrix: [0, 0, 0, 0, 0, 0], // invers matrix;
mouseX: 0,
mouseY: 0,
ctx: context,
sweetSpot: function(){
sweetSpot();
},
setTransform: function() {
var m = this.matrix;
var i = 0;
this.ctx.setTransform(m[i++], m[i++], m[i++], m[i++], m[i++], m[i++]);
},
setHome: function() {
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
},
update: function() {
// smooth all movement out. drag and accel control how this moves
// acceleration
this.dx += (this.x - this.cx) * this.accel;
this.dy += (this.y - this.cy) * this.accel;
this.dox += (this.ox - this.cox) * this.accel;
this.doy += (this.oy - this.coy) * this.accel;
this.dscale += (this.scale - this.cscale) * this.accel;
// drag
this.dx *= this.drag;
this.dy *= this.drag;
this.dox *= this.drag;
this.doy *= this.drag;
this.dscale *= this.drag;
// set the chase values. Chase chases the requiered values
this.cx += this.dx;
this.cy += this.dy;
this.cox += this.dox;
this.coy += this.doy;
this.cscale += this.dscale;
// create the display matrix
this.matrix[0] = Math.cos(this.crotate) * this.cscale;
this.matrix[1] = Math.sin(this.crotate) * this.cscale;
this.matrix[2] = -this.matrix[1];
this.matrix[3] = this.matrix[0];
// set the coords relative to the origin
this.matrix[4] = -(this.cx * this.matrix[0] + this.cy * this.matrix[2]) + this.cox;
this.matrix[5] = -(this.cx * this.matrix[1] + this.cy * this.matrix[3]) + this.coy;
// create invers matrix
var det = (this.matrix[0] * this.matrix[3] - this.matrix[1] * this.matrix[2]);
this.invMatrix[0] = this.matrix[3] / det;
this.invMatrix[1] = -this.matrix[1] / det;
this.invMatrix[2] = -this.matrix[2] / det;
this.invMatrix[3] = this.matrix[0] / det;
// check for mouse. Do controls and get real position of mouse.
if (mouse !== undefined) { // if there is a mouse get the real canvas coordinates of the mouse
if (mouse.oldX !== undefined && (mouse.buttonRaw & 1) === 1) { // check if panning (middle button)
var mdx = mouse.x - mouse.oldX; // get the mouse movement
var mdy = mouse.y - mouse.oldY;
// get the movement in real space
var mrx = (mdx * this.invMatrix[0] + mdy * this.invMatrix[2]);
var mry = (mdx * this.invMatrix[1] + mdy * this.invMatrix[3]);
this.x -= mrx;
this.y -= mry;
}
// do the zoom with mouse wheel
if (mouse.w !== undefined && mouse.w !== 0) {
this.ox = mouse.x;
this.oy = mouse.y;
this.x = this.mouseX;
this.y = this.mouseY;
// zooming around the mouse
this.cox = mouse.x;
this.coy = mouse.y;
this.cx = this.mouseX;
this.cy = this.mouseY;
if (mouse.w > 0) { // zoom in
this.scale *= 1.1;
mouse.w -= 20;
if (mouse.w < 0) {
mouse.w = 0;
}
}
if (mouse.w < 0) { // zoom out
this.scale *= 1/1.1;
mouse.w += 20;
if(this.scale <=0.5){
this.scale = 0.5;
}
if (mouse.w > 0) {
mouse.w = 0;
}
}
}
// get the real mouse position
var screenX = (mouse.x - this.cox);
var screenY = (mouse.y - this.coy);
this.mouseX = this.cx + (screenX * this.invMatrix[0] + screenY * this.invMatrix[2]);
this.mouseY = this.cy + (screenX * this.invMatrix[1] + screenY * this.invMatrix[3]);
mouse.rx = this.mouseX; // add the coordinates to the mouse. r is for real
mouse.ry = this.mouseY;
// save old mouse position
mouse.oldX = mouse.x;
mouse.oldY = mouse.y;
}
}
}
function mouseHandler(event){
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if (mouse.x === undefined) {
mouse.x = event.clientX;
mouse.y = event.clientY;
}
console.log("these are the mouse.x and mouse.y:" + mouse.x + " " + mouse.y);
mouse.alt = event.altKey;
mouse.shift = event.shiftKey;
mouse.ctrl = event.ctrlKey;
if (event.type === "mousedown") {
event.preventDefault();
console.log("this is event.which (mousedown): " + event.which);
mouse.buttonRaw |= mouse.buttons[event.which - 1]; //bitwise AND operator (convert to binary and do the operation)
//console.log("this is value value: " + value);
} else if (event.type === "mouseup") {
//console.log("this is mouse.buttonRaw before (mouseup): " + mouse.buttonRaw);
//console.log("this is event.which (mouseup): " + event.which);
mouse.buttonRaw &= mouse.buttons[event.which + 2]; //bitwise OR operator (convert to binary and do the operation)
} else if (event.type === "mouseout") {
mouse.buttonRaw = 0;
mouse.over = false;
} else if (event.type === "mouseover") {
mouse.over = true;
} else if (event.type === "mousewheel") {
event.preventDefault();
mouse.w = event.wheelDelta;
} else if (event.type === "DOMMouseScroll") { // FF you pedantic doffus
mouse.w = -event.detail;
}
}
function setupMouse(e) {
//syntax addEventListener (event, function, useCapture)
e.addEventListener('mousemove', mouseHandler);
e.addEventListener('mousedown', mouseHandler);
e.addEventListener('mouseup', mouseHandler);
e.addEventListener('mouseout', mouseHandler);
e.addEventListener('mouseover', mouseHandler);
e.addEventListener('mousewheel', mouseHandler);
e.addEventListener('DOMMouseScroll', mouseHandler); // fire fox
e.addEventListener("contextmenu", function(e) {
e.preventDefault();
}, false);
}
//2160 x 3840 resolution
//create a base for you to start
var canvas = d3.select('.roadModelCanvasDiv').append("canvas")
.attr("id", "canvas")
.style("position", "absolute")
.style("top", "0")
.style("left", "0")
.attr("width", width)//optimization
.attr("height", height);//optimization
//initialize the tools to start something on the context
var context = canvas.node().getContext("2d");
//over start binding the elements
var detachedContainer = document.createElement("custom");
var dataContainer = d3.select(detachedContainer);
//Draw the Road
var mainRoad = dataContainer.selectAll("custom.rect")
.data(mainRoadValueData)
.enter().append("custom")
.attr("x", function(d,i){
switch(d.section){
case 0:
return 100;
break;
case 1:
return 100;
break;
case 2:
return 100;
break;
case 3:
break;
case 4:
return 100;
break;
case 5:
break;
case 6:
return 100;
break;
case 7:
return 100;
break;
}
})
.attr("y", function(d,i){
switch(d.section){
case 0:
return 150;
break;
case 1:
return 140;
break;
case 2:
return 200;
break;
case 3:
break;
case 4:
return 350;
break;
case 5:
break;
case 6:
return 500;
break;
case 7:
return 540;
break;
default:
break;
}
})
.attr("class", "rect")
.attr("width", function(d){
return +d.rectWidth;
})
.attr("height", function(d){
return +d.rectHeight;
})
.attr("dottedOriginX", function(d, i){
if(d.section == 3 || d.section == 5){
return 100;
}
})
.attr("dottedOriginY", function(d, i){
if(d.section == 3 || d.section == 5){
return +d.dashlineHeight;
}
})
.attr("dottedEndX", function(d,i){
if(d.section == 3 || d.section == 5){
return 1100;
}
})
.attr("dottedEndY", function(d,i){
if(d.section == 3 || d.section == 5){
return +d.dashlineHeight;
}
})
.attr("strokeStyle", "grey")
.attr("fillStyle", function(d,i){
switch(d.section){
case 0:
return "#1C1C1C";
break;
case 1:
return "grey";
break;
case 2:
return "grey";
break;
case 3:
return "grey";
break;
case 4:
return "grey";
break;
case 5:
return "grey";
break;
case 6:
return "grey";
break;
case 7:
return "grey";
break;
default:
break;
}
});
drawRoadModel();
var canvasWithMouseListener = document.getElementById("canvas");
setupMouse(canvasWithMouseListener);
function drawRoadModel(){
console.log("drawRoadModel");
var elements = dataContainer.selectAll("custom.rect");
context.save();
elements.each(function(d, i){
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();
context.beginPath();
context.strokeStyle = node.attr("strokeStyle");
context.setLineDash([5,10]);
context.moveTo(node.attr("dottedOriginX"), node.attr("dottedOriginY"));
context.lineTo(node.attr("dottedEndX"), node.attr("dottedEndY"));
context.stroke();
});
context.restore();
}
});
html{
overflow: hidden;
}
.overflowXY{
position:absolute;
margin-left:-14px;
z-index:2;
}
.overflowXY > canvas {
}
.widget {
position: absolute;
z-index:3;
}
.chart-container {
padding: 5px;
}
.shadow {
-webkit-filter: drop-shadow( 0px 3px 3px rgba(0, 0, 0, .5));
filter: drop-shadow( 0px 3px 3px rgba(0, 0, 0, .5));
}
body {
margin: 0;
padding: 0;
height: 100vh;
background-color: #a0a0a0;
background-image: linear-gradient(to left top, #434343, #606060, #808080, #a0a0a0, #c2c2c2);
z-index:-1;
}
.canvasRoadModel{
background-color: transparent;
background-image: linear-gradient(0deg, transparent 24%, rgba(255, 255, 255, .05) 25%, rgba(255, 255, 255, .05) 26%, transparent 27%, transparent 74%, rgba(255, 255, 255, .05) 75%, rgba(255, 255, 255, .05) 76%, transparent 77%, transparent), linear-gradient(90deg, transparent 24%, rgba(255, 255, 255, .05) 25%, rgba(255, 255, 255, .05) 26%, transparent 27%, transparent 74%, rgba(255, 255, 255, .05) 75%, rgba(255, 255, 255, .05) 76%, transparent 77%, transparent);
height:100%;
background-size:50px 50px;
z-index:1;
}
<html>
<script
src="https://code.jquery.com/jquery-1.10.2.min.js"
integrity="sha256-C6CB9UYIS9UJeqinPHWTHVqh/E1uhG5Twh+Y5qFQmYg="
crossorigin="anonymous"></script></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
<body id="bodyBackground">
<div class="container-fluid">
<div class="row">
<div class="col-lg-12 canvasRoadModel" style="height:100vh;background-color:#303030">
<div id="chart" class="Chart"></div>
<div id="roadmodelcanvas" class="roadModelCanvasDiv"></div>
</div>
</div>
</div>
</body>
</html>