Skip to content

Instantly share code, notes, and snippets.

@xunhanliu
Last active June 13, 2019 18:02
transform_d3

展示见: https://bl.ocks.org/xunhanliu/cf0ef8ecb8d6d7472db047597b6d4117 编写了一个rotate、translate、scale的组件。注:一定是对坐标进行变换。提供了两种更新方式。 操作方式: 按住t键,拖动鼠标--》translate 按住r键,拖动鼠标--》rotate 按住s键,拖动鼠标--》scale

<!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>
/**
* 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