展示见: https://bl.ocks.org/xunhanliu/cf0ef8ecb8d6d7472db047597b6d4117 编写了一个rotate、translate、scale的组件。注:一定是对坐标进行变换。提供了两种更新方式。 操作方式: 按住t键,拖动鼠标--》translate 按住r键,拖动鼠标--》rotate 按住s键,拖动鼠标--》scale
Last active
June 13, 2019 18:02
transform_d3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<meta charset="utf-8"> | |
<style> | |
svg{ | |
border: 1px #555 dashed; | |
margin: 1px; | |
display: inline-block; | |
} | |
</style> | |
<body> | |
<script src="https://d3js.org/d3.v4.js"></script> | |
<script src="./RTS.js"></script> | |
<script> | |
var margin = {top: 20, right: 20, bottom: 30, left: 40}, | |
width = 300, | |
height = 300; | |
const points=d3.range(10).map(function (d,i) { | |
return { | |
x:Math.random()*width, | |
y:Math.random()*height, | |
r:5, | |
} | |
}); | |
const svg=d3.select('body').append('svg').attr('width',width+margin.left+margin.right) | |
.attr('height',height+margin.top+margin.bottom).attr('tabindex',0).attr('id','svg'); | |
const draw=svg.append('g').attr('transform','translate('+margin.left+','+margin.top+')'); | |
draw.selectAll('circle').data(points).enter().append('circle') | |
.attr('r',d=>d.r).attr('cx',d=>d.x).attr('cy',d=>d.y); | |
var rts=new RTS(svg.node(),draw.node(),[width/2,height/2]); | |
function callback(scale) { | |
//scale是RTS中,用户操作的变换函数 | |
d3.select('#svg').selectAll('circle').each(function (d) { | |
var datum=d3.select(this).datum(); | |
var point=scale([datum.x,datum.y]); | |
d3.select(this).attr('cx',function (d) { | |
d.x=point[0]; | |
d.y=point[1]; | |
return d.x; | |
}) | |
.attr('cy',d=>d.y); | |
}) | |
} | |
rts.setCallback(callback,false); //非懒更新模式 | |
//第二个图 | |
const points1=d3.range(10).map(function (d,i) { | |
return { | |
x:Math.random()*width, | |
y:Math.random()*height, | |
r:5, | |
} | |
}); | |
const svg1=d3.select('body').append('svg').attr('width',width+margin.left+margin.right) | |
.attr('height',height+margin.top+margin.bottom).attr('tabindex',0).attr('id','svg1'); | |
const draw1=svg1.append('g').attr('transform','translate('+margin.left+','+margin.top+')'); | |
draw1.selectAll('circle').data(points1).enter().append('circle') | |
.attr('r',d=>d.r).attr('cx',d=>d.x).attr('cy',d=>d.y); | |
var rts1=new RTS(svg1.node(),draw1.node(),[width/2,height/2]); | |
function callback1(scale) { | |
//scale是RTS中,用户操作的变换函数 | |
d3.select('#svg1').selectAll('circle').each(function (d) { | |
var datum=d3.select(this).datum(); | |
var point=scale([datum.x,datum.y]); | |
d3.select(this).attr('cx',function (d) { | |
d.x=point[0]; | |
d.y=point[1]; | |
return d.x; | |
}) | |
.attr('cy',d=>d.y); | |
}) | |
} | |
rts1.setCallback(callback1,true); //懒更新模式 | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* author:xunhanliu | |
* time:2019-6-14 | |
*/ | |
var keyR=false; | |
var keyT=false; | |
var keyS=false; | |
//外部写callback示例 | |
// function callback(scale) { | |
// //scale是RTS中,用户操作的变换函数 | |
// const newPoint=scale(point) | |
// } | |
class RTS{ | |
dotProduct(v1,v2) { | |
return v1[0]*v2[0]+v1[1]*v2[1]; | |
} | |
distance(v1) { | |
return Math.sqrt(v1[0]**2+v1[1]**2); | |
} | |
getIncludedAng(v1,v2){ | |
return Math.acos(this.dotProduct(v1,v2)/(this.distance(v1)*this.distance(v2))); | |
} | |
minus(v1,v2){ | |
return [v1[0]-v2[0],v1[1]-v2[1]]; | |
} | |
cross(pointA, pointB) { //大于0是逆时针(旋转的正方向) | |
return pointA[0] * pointB[1] - pointA[1] * pointB[0]; | |
} | |
constructor(parentDom,drawDom,center) { | |
//drawDom 坐标系层,用于定位缩放中心和旋转中心 | |
this.draw=d3.select(drawDom).append('g'); | |
this.parent=d3.select(parentDom); | |
this.center=center; | |
this.parent.on('keydown',this._keydown(this)) | |
.on('keyup',this._keyup(this)); | |
this.drag=d3.drag() | |
.on("start", this._dragstart(this)) | |
.on("drag", this._dragmove(this)) | |
.on("end", this._dragended(this)); | |
this.parent.call(this.drag); | |
this.callback=function () {}; | |
this.isLazy=true; | |
this.initMousePoint=[0,0]; | |
this.lastMousePoint=[0,0]; //用于dragmove记录 | |
} | |
//两种更新方式:1:start-end 懒更新 2:move更新 | |
_dragstart(that){ | |
return function () { | |
that.initMousePoint[0]=d3.mouse(that.draw.node())[0]; | |
that.initMousePoint[1]=d3.mouse(that.draw.node())[1]; | |
that.lastMousePoint=[that.initMousePoint[0],that.initMousePoint[1]]; | |
} | |
} | |
_dragmove(that){ | |
return function () { | |
if(that.isLazy) return; //lazy直接退出 | |
that._drag_move_end_log(that,that.lastMousePoint); | |
that.lastMousePoint=[d3.mouse(that.draw.node())[0], | |
d3.mouse(that.draw.node())[1]]; | |
} | |
} | |
_dragended(that){ | |
return function () { | |
if(!that.isLazy) return; //非lazy直接退出 | |
that._drag_move_end_log(that,that.initMousePoint); | |
} | |
} | |
_drag_move_end_log(that,lastMousePoint){ | |
var pos=[0,0]; | |
pos[0]=d3.mouse(that.draw.node())[0]; | |
pos[1]=d3.mouse(that.draw.node())[1]; | |
const v1= that.minus(lastMousePoint,that.center); | |
const v2=that.minus(pos,that.center); | |
if(keyR){ | |
//获得旋转的角度 | |
var angle=that.getIncludedAng(v1,v2); //正方向是顺时针(屏幕坐标系) | |
if(that.cross(v1,v2)<0){ | |
angle=-angle; | |
} | |
function scale(point) { | |
var pointBuf=[point[0]-that.center[0], point[1]-that.center[1]] | |
return [pointBuf[0] * Math.cos(angle) - pointBuf[1] * Math.sin(angle)+that.center[0], | |
pointBuf[0] * Math.sin(angle) + pointBuf[1] * Math.cos(angle)+that.center[1]] | |
} | |
that.callback(scale); | |
}else if(keyT){ | |
//获得移动的偏移 | |
// const offset=that.minus(v2,v1); | |
const offset=that.minus(pos,lastMousePoint); | |
function scale(point) { | |
return [point[0]+offset[0], | |
point[1]+offset[1]] | |
} | |
that.callback(scale); | |
}else if(keyS){ | |
//根据在y轴的拖动,进行缩放 | |
const height=that.center[1]*2; | |
const myScale=d3.scaleLinear().domain([-height,0,height]).range([0.5,1,2]); | |
const rate=myScale(lastMousePoint[1]-pos[1]); | |
function scale(point) { | |
var pointBuf=[point[0]-that.center[0], point[1]-that.center[1]] | |
return [pointBuf[0] *rate +that.center[0], | |
pointBuf[1]*rate +that.center[1]] | |
} | |
that.callback(scale); | |
} | |
} | |
setCallback(callback,lazy=true){ | |
this.isLazy=lazy; | |
this.callback=callback; | |
} | |
_keydown(that){ | |
return function () { | |
switch (d3.event.key){ | |
case 'r': keyR=true;break; | |
case 't':keyT=true;break; | |
case 's':keyS=true;break; | |
} | |
if(keyR){ | |
//更换鼠标样式 | |
that.parent.style('cursor','wait'); | |
}else if(keyT){ | |
that.parent.style('cursor','move'); | |
}else if(keyS){ | |
that.parent.style('cursor','zoom-in'); | |
} | |
} | |
} | |
_keyup(that){ | |
return function () { | |
//code here | |
switch (d3.event.key){ | |
case 'r': keyR=false; that.parent.style('cursor','');break; | |
case 't':keyT=false; that.parent.style('cursor','');break; | |
case 's':keyS=false; that.parent.style('cursor','');break; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment