使用canvas和d3.js分层圆结构计算缩放和平移的方法

Posted

技术标签:

【中文标题】使用canvas和d3.js分层圆结构计算缩放和平移的方法【英文标题】:Way to calculate zoom and pan using canvas and d3.js hierarchical circle structure 【发布时间】:2019-10-28 08:43:48 【问题描述】:

我目前在画布中有一个 d3 分层圆形包图(在 this awesome tutorial! 的帮助下)

除了放大到节点之外,我还想让图形也可以用鼠标平移并用滚轮缩放。 I used this.

现在,当我第一次平移然后使用初始比例缩放到一个节点时(当页面首次加载时),它按预期工作。它从图表的最后一个平移位置缩放到节点。

但是当我尝试从缩放状态平移和缩放时,视图会在完全不同的位置开始,然后在预期的节点处结束。如果从缩放状态我不平移,而是从一个节点跳转/缩放到另一个节点,则转换按预期工作。我不确定为什么会这样。谁能帮我弄清楚我做错了什么?提前致谢!

(抱歉有任何错误——这是我的第一个堆栈溢出问题...)


  @ViewChild('myCanvas') canvas: ElementRef;
  @ViewChild('myCanvas') hiddenCanvas: ElementRef;
  public context: CanvasRenderingContext2D;
  public hiddenContext: CanvasRenderingContext2D;

  public focus;
  public xleftView = 0;
  public ytopView = 0;

  public widthViewOriginal = window.innerWidth;           
  public heightViewOriginal = window.innerHeight;
  public widthView = this.widthViewOriginal;           
  public heightView = this.heightViewOriginal;
  public widthCanvas = window.innerWidth;
  public heightCanvas = window.innerHeight;

  public mouseDown = false;
  public movedCoordinatesX;
  public movedCoordinatesY;
  public lastX = 0;
  public lastY = 0;

  public zoomInfo = 
    centerX: window.innerWidth / 2,
    centerY: window.innerHeight / 2,
    scale: 1
  ;

    public handleMouseDown(event) 
      // get where the mouse first clicked to pan the graph
      this.movedCoordinatesX = event.clientX
      this.movedCoordinatesY = event.clientY
      if (event.which === 1) 
        this.mouseDown = true;
      
    

    public handleMouseUp(event) 
      this.mouseDown = false;
      // Get the distance moved in the x & y direction from the initial click to the end mouse position
      this.movedCoordinatesX = this.movedCoordinatesX - event.clientX
      this.movedCoordinatesY =  this.movedCoordinatesY - event.clientY

      // add distance in the X & Y to the old view's X & Y (to make the transition happen from the current position
      this.vOld[0] = (this.vOld[0] + this.movedCoordinatesX) 
      this.vOld[1] = (this.vOld[1] + this.movedCoordinatesY) 
    

    public handleMouseMove(event) 

      var X = event.clientX - this.context.canvas.offsetLeft -  this.context.canvas.clientLeft + this.context.canvas.scrollLeft;
      var Y = event.clientY -  this.context.canvas.offsetTop -  this.context.canvas.clientTop +  this.context.canvas.scrollTop;
      if (this.mouseDown) 
          var dx = (X - this.lastX) / this.widthCanvas * this.widthView;
          var dy = (Y - this.lastY)/ this.heightCanvas * this.heightView;
          this.xleftView -= dx;
          this.ytopView -= dy;
      
      this.lastX = X;
      this.lastY = Y;
  

  // Listen for clicks on the main canvas
  public zoomInToNode(e) 
    // We actually only need to draw the hidden canvas when there is an interaction. 
    // This sketch can draw it on each loop, but that is only for demonstration.
    this.drawCanvas(this.hiddenContext, true);

    //Figure out where the mouse click occurred.
    var mouseX = e.layerX;
    var mouseY = e.layerY;
    this.lastX = mouseX
    this.lastY = mouseY

    // Get the corresponding pixel color on the hidden canvas and look up the node in our map.
    // This will return that pixel's color
    var col = this.hiddenContext.getImageData(mouseX, mouseY, 1, 1).data;
    //Our map uses these rgb strings as keys to nodes.
    var colString = "rgb(" + col[0] + "," + col[1] + ","+ col[2] + ")";
    var node = this.colToCircle[colString];

    if(node) 
      if (this.focus !== node) 

        this.xleftView = 0
        this.ytopView = 0
        this.hiddenContext.setTransform(1, 0, 0, 1, 0, 0);
        this.zoomToCanvas(node); 
      
      else 

        this.xleftView = 0
        this.ytopView = 0
        this.hiddenContext.setTransform(1, 0, 0, 1, 0, 0);

        this. widthView = this.widthViewOriginal; 
        this. heightView = this.heightViewOriginal;
        this. widthCanvas = window.innerWidth;
        this. heightCanvas = window.innerHeight;
        this.zoomToCanvas(this.rooting);
      
    //if
  ;

  public zoomToCanvas(focusNode) 
    // the node that we want to zoom to
    this.focus = focusNode;

    var v = [this.focus.x, this.focus.y, this.focus.r * 2.05]; //The center and width of the new "viewport"

    this.interpolator = d3.interpolateZoom(this.vOld, v); //Create interpolation between current and new "viewport"

    this.duration = this.interpolator.duration; //Interpolation gives back a suggested duration         
    this.timeElapsed = 0; //Set the time elapsed for the interpolateZoom function to 0  
    this.vOld = v; //Save the "viewport" of the next state as the next "old" state

  //function zoomToCanvas

  // Perform the interpolation and continuously change the zoomInfo while the "transition" occurs
  public interpolateZoom(dt) 
    if (this.interpolator) 
      this.timeElapsed += dt;
      var t = this.ease(this.timeElapsed / this.duration);

      if(isFinite(t))  
      this.zoomInfo.centerX = this.interpolator(t)[0];
      this.zoomInfo.centerY = this.interpolator(t)[1];
      this.zoomInfo.scale = this.diameter / this.interpolator(t)[2];

      if (this.timeElapsed >= this.duration) this.interpolator = null;
      
    //if
  //function zoomToCanvas

  public drawCanvas(chosenContext, hidden) 
    chosenContext.save(); 
    chosenContext.setTransform(1,0,0,1,0,0);
    chosenContext.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height);
    chosenContext.scale(this.widthCanvas/this.widthView, this.heightCanvas/this.heightView);
    chosenContext.translate(-this.xleftView,-this.ytopView);
    chosenContext.restore(); 

  

【问题讨论】:

【参考方案1】:

所以我想通了...我应该除以整体规模。

真的要感谢this answer给了我灵感!

    public handleMouseUp(event) 
      this.mouseDown = false;
        this.movedCoordinatesX = this.movedCoordinatesX - event.clientX
        this.movedCoordinatesY =  this.movedCoordinatesY - event.clientY

        if(this.zoomInfo.scale == 1) 
          this.vOld[0] = (this.vOld[0] + this.movedCoordinatesX) 
          this.vOld[1] = (this.vOld[1] + this.movedCoordinatesY)
         else 
          this.vOld[0] = (this.vOld[0] + (this.movedCoordinatesX/this.zoomInfo.scale)) 
          this.vOld[1] = (this.vOld[1] + (this.movedCoordinatesY/this.zoomInfo.scale))
        
    


【讨论】:

以上是关于使用canvas和d3.js分层圆结构计算缩放和平移的方法的主要内容,如果未能解决你的问题,请参考以下文章

canvas画布实现一个动态效果小demo(滚动圆)

canvas画布实现一个动态效果小demo(滚动圆)

d3.js画圆弧和圆的坐标、弧长计算方法

Javascript / D3.js - 绘制大型数据集 - 提高 d3.js 绘制的 svg 图表中的缩放和平移速度

从 MySQL 结果和 PHP 为 D3.js 树创建分层 JSON?

在 D3.js 中缩放或平移时限制域