javascript - d3 v5 轴比例更改平移方式太多

标签 javascript reactjs d3.js zooming

我有一个以时间为 X 轴的简单图表。预期的行为是,在图形中拖动时,X 轴只会平移以显示数据的其他部分。

为方便起见,由于我的 X 轴位于 react 组件中,因此创建图表的函数将 X 比例、x 轴和它附加到的元素设置为 this.xScale , this.xAxis , 和 this.gX , 分别。

如果我将此设置为缩放方法的内容,则一切正常:

        this.gX.call(this.xAxis.scale(d3.event.transform.rescaleX(this.xScale)))

X 轴通过触摸输入平滑移动。但是,这对我不起作用,因为稍后当我更新图表(移动数据点以响应轴的变化)时,我需要 this.xAxis进行更改,以便点将映射到不同的位置。

因此,我将缩放方法的内容设置为:
        this.xScale = d3.event.transform.rescaleX(this.xScale);
        this.xAxis = this.xAxis.scale(this.xScale);
        this.gX.call(this.xAxis);

据我所知,这应该完全相同。但是,当我使用此代码时,即使没有运行我的 updateChart()函数(更新数据点),X 轴在平移时不规则地缩放,远超正常。我的 X 轴是基于时间的,所以突然间从 2014 年到 2018 年的时间域包括了 1920 年代初期。

我究竟做错了什么?

最佳答案

问题

当您使用 scale.rescaleX 时,您正在根据当前缩放变换(基于平移和缩放)修改比例域。

但是,从 d3.event.transfrom 返回的转换不是上一次缩放变换的变化,它代表的是累积变换。我们希望在原始尺度上应用此变换,因为变换表示从原始状态的变化。但是,您正在将这个累积变换应用到由以前的缩放变换修改的比例上:

     this.xScale = d3.event.transform.rescaleX(this.xScale);

让我们来看看它在平移事件(例如平移)期间的作用:
  • 平移右10个单位
  • 将标尺的域移动 10 个单位。

  • 这行得通,但如果我们再次平移:
  • 再向右平移 10 个单位
  • 将标尺的域再移动 20 个单位。

  • 为什么?因为缩放变换会跟踪相对于初始状态的缩放状态,但您希望仅使用状态变化来更新比例,而不是缩放变换的累积变化。因此,此时域已经移动了 30 个单位,但用户只平移了 20 个。

    规模也会发生同样的事情:
  • 将图形中心放大 2 倍(缩放变换比例 = 2)
  • 重新缩放比例,使其具有一半的域(细节的两倍)
  • 在图形中心再次放大 2 倍(缩放变换比例 = 4)
  • 重新缩放比例,使其具有当前域的四分之一(已经是原始域的一半,所以我们现在放大 8x:2x4)。

  • 在第四步,d3.event.transform.k == 4 ,并且 rescaleX 现在将比例缩放了四倍,它不“知道”比例已经缩放了两倍。

    如果我们继续应用缩放,情况会变得更糟,例如,如果我们从 k=4 缩小到 k=2,d3.event.transform.k == 2 ,尽管尝试缩小,我们仍然放大 2 倍,现在我们处于 16 倍:2x4x2。如果我们放大,我们得到 64x (2x4x8)

    这种效果在平移时特别糟糕 - 缩放甚至在整个平移事件中不断触发,因此在已经累积应用缩放变换的比例上累积重新应用比例。一个平移可以轻松触发数十个缩放事件。在下面的比较片段中,尽管起始域为 2014-2018 年,但稍微平移一下就可以轻松地将您拉入 1920 年代。

    解决方案

    纠正此问题的最简单方法(以及规范方法)与您在代码中用于平移(但不更新)的方法非常相似:
     this.gX.call(this.xAxis.scale(d3.event.transform.rescaleX(this.xScale)))
    

    我们在这里做什么?我们正在创建一个新比例,同时保持原比例 - d3.event.transform.rescaleX(this.xScale) .我们为轴提供新的刻度。但是,正如您所注意到的,在更新图表时会遇到问题,xScale不是轴使用的比例,因为我们现在有两个不同的比例。

    然后解决方案是使用我所说的引用比例和工作比例。引用比例将用于根据当前缩放变换更新工作比例。每当创建/更新轴或点时,都会使用工作比例。一开始,两个比例可能是相同的,所以我们可以这样创建比例:
    var xScale = d3.scaleLinear().domain(...).range(...) // working
    var xScaleReference = xScale.copy();                 // reference
    

    我们可以像往常一样使用 xScale 更新或放置元素。

    在缩放时,我们可以更新 xScale(和轴):
    xScale = d3.event.transform.rescaleX(xScaleReference)
    xAxis.scale(xScale);
    selection.call(xAxis);
    

    这是一个比较,它具有与您注意到的相同的域,但很快就可以到达 1920 年代的较高比例(使用一个比例)。底部比预期的要多得多(并使用工作和引用比例):

    var svg = d3.select("body")
      .append("svg")
      .attr("width", 400)
      .attr("height", 200);
    
    var parseTime = d3.timeParse("%Y")
     
     
    var start = parseTime("2014");
    var end = parseTime("2018");
     
    ///////////////////
    // Single scale updated by zoom transform:
    var a = d3.scaleTime()
      .domain([start,end])
      .range([20,380])
      
    var aAxis = d3.axisBottom(a).ticks(5);
    
    var aAxisG = svg.append("g")
      .attr("transform","translate(0,30)")
      .call(aAxis);
    
    /////////////////
    // Reference and working scale:
    var b = d3.scaleTime()
      .domain([start,end])
      .range([20,380])
      
    var bReference = b.copy();
    
    var bAxis = d3.axisBottom(b).ticks(5);
      
    var bAxisG = svg.append("g")
      .attr("transform","translate(0,80)")
      .call(bAxis);
    
    /////////////////
    // Zoom:
    var zoom = d3.zoom()
      .on("zoom", function() {
        a = d3.event.transform.rescaleX(a);
        b = d3.event.transform.rescaleX(bReference);
        
        aAxisG.call(aAxis.scale(a));
        bAxisG.call(bAxis.scale(b));
    
    
    })
    
    svg.call(zoom);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>


    我们可以看到 Mike Bostock 的示例采用了相同的方法,例如 brush and zoom ,其中 x2 和 y2 表示引用比例,x 和 y 表示工作比例。

    关于javascript - d3 v5 轴比例更改平移方式太多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56553384/

    相关文章:

    reactjs - typescript :为什么 Material-UI "withStyles()"不能使用显式构造函数?

    ajax - D3 : How to dynamically refresh a graph by changing the data file source?

    javascript - 更改 d3 SVG Canvas 的缩放级别?

    javascript - 如何在 jQuery Submit() 之后使用 PHP 执行表单

    javascript - 如何提出正确的递归解决方案

    javascript - 调度自定义事件

    javascript - 根据过滤器下拉列表在 d3 中的力图中添加和删除节点和链接

    javascript - 如何在 Facebook 个人资料中分享和播放音频 MP3 文件/

    javascript - ReactJS:Redux 状态没有改变

    javascript - react : How to show Spinner & Component (at the same time) when State Changes using `useEffect`