【问题标题】:Constrain zoom and pan in d3在 d3 中约束缩放和平移
【发布时间】:2018-01-04 08:24:54
【问题描述】:

我正在尝试使用 d3 的缩放行为正确地限制我的 d3 图表上的缩放和缩放。我已将问题简化为以下最小可运行示例。我希望用户不能以一种允许他看到 y 轴上 0 线以下的方式进行缩放。

示例在非缩放状态下工作,通过将translateExtent 设置为 svg 的全高,但是一旦用户放大一点,这当然会中断。事实上,你放大得越远,你就越能看到负片区域。

我需要将translateExtent 设置为什么?

我在每个缩放事件上重绘线和轴的原因是通常我使用 react 来渲染我的 svg 并使用 d3 仅用于计算 - 但是我已经删除了对 react 的依赖以提供更简洁的示例.

const data = [ 0, 15, 30, 32, 44, 57, 60, 60, 85];

// set up dimensions and margins
const full = { w: 200, h: 200 };
const pct = { w: 0.7, h: 0.7 };
const dims = { w: pct.w * full.w, h: pct.h * full.h };
const margin = { w: (full.w - dims.w)/2, h: (full.h - dims.h)/2 };

// scales
const x = d3.scaleLinear()
  .rangeRound([0, dims.w])
  .domain([0, data.length]);
  
const y = d3.scaleLinear()
  .rangeRound([dims.h, 0])
  .domain(d3.extent(data));

// axes
const axes = {
  x: d3.axisBottom().scale(x).tickSize(-dims.w),
  y: d3.axisLeft().scale(y).tickSize(-dims.h)
}


const g = d3.select('.center');

// actual "charting area"
g
  .attr('transform', `translate(${margin.w}, ${margin.h})`)
  .attr('width', dims.w)
  .attr('height', dims.h)
  
// x-axis
g.append('g')
.attr('transform', `translate(0, ${dims.h})`)
.attr('class', 'axis x')
.call(axes.x)

// y-axis
g.append('g')
.attr('class', 'axis y')
.call(axes.y)


// generator for the line
const line = d3.line()
  .x( (_, i) => x(i) )
  .y( d => y(d) );

// the actual data path
const path = g
  .append('path')
  .attr('class', 'path')
  .attr('d', line(data))
  .attr('stroke', 'black')
  .attr('fill', 'none')

const zoomBehaviour = d3.zoom()
  .scaleExtent([1, 10])
  .translateExtent([[0,0], [Infinity, full.h]])
  .on('zoom', zoom);
  
d3.select('svg.chart').call(zoomBehaviour);
  
function zoom() {
  const t = d3.event.transform;
  
  const scaledX = t.rescaleX(x);
  const scaledY = t.rescaleY(y);
  axes.x.scale(scaledX);
  axes.y.scale(scaledY);
  d3.select('.axis.x').call(axes.x);
  d3.select('.axis.y').call(axes.y);
  
  line
    .x( (_, i) => scaledX(i) )
    .y( d => scaledY(d) );
  
  const scaledPath = path.attr('d', line(data));
}
body {
  width: 200px;
  height: 200px;
}

svg.chart {
  width: 200px;
  height: 200px;
  border: 1px solid black;
}

.axis line, .axis path {
  stroke: grey;
}
<script src="https://d3js.org/d3.v4.js"></script>
<body>
<svg class="chart">
  <g class="center"></g>
</svg>
</body>

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    我能够根据 Robert 的建议使其工作。我不得不稍微调整一下条件以适应我的用例:

    const dims = /* object holding svg width and height */;
    const [xMin, xMax] = this.scales.x.domain().map(d => this.scales.x(d));
    const [yMin, yMax] = this.scales.y.domain().map(d => this.scales.y(d));
    
    if (t.invertX(xMin) < 0) {
      t.x = -xMin * t.k;
    }
    if (t.invertX(xMax) > dims.width) {
      t.x = xMax - dims.width * t.k;
    }
    if (t.invertY(yMax) < 0) {
      t.y = -yMax * t.k;
    }
    if (t.invertY(yMin) > dims.height) {
      t.y = yMin - dims.height * t.k;
    }
    

    【讨论】:

      【解决方案2】:

      查看this example by Mike Bostock 中的zoomed 函数,我相信它可以满足您的要求,关键部分是t.x 和/或t.y 的突变(如果违反约束):

      function zoomed() {
          var t = d3.event.transform;
          if (t.invertX(0) > x0) t.x = -x0 * t.k;
          else if (t.invertX(width) < x1) t.x = width - x1 * t.k;
          if (t.invertY(0) > y0) t.y = -y0 * t.k;
          else if (t.invertY(height) < y1) t.y = height - y1 * t.k;
          g.attr("transform", t);
      }
      

      【讨论】:

      • 可悲的是,这产生的行为与我需要的不同。在这个例子中,图像的任何部分都不会是不可见的,因此放大实际上是不可能的。如果您在提供的链接中尝试它,您会注意到您永远无法“放大”示例图像。我希望我的用户能够做到这一点,他们只是不能在放大时平移“过去”图像,即图像的一部分必须始终可见。
      • 我并不是说上面是您用例的确切解决方案,更像是显示您的应用程序中不存在的额外步骤,即t.xt.y 的条件突变.也许您可以调整条件,因为 Mike 的示例将整个内容限制为始终可见,而您希望将内容(我想是行内容的边界框)限制为“包围”您的视口。只是一个不同的条件(如果我理解您的需求,内容边界框和视口框与 Mike 的示例相反)。
      猜你喜欢
      • 1970-01-01
      • 2019-07-25
      • 1970-01-01
      • 2013-05-16
      • 2021-10-01
      • 2022-09-29
      • 2019-01-15
      • 2017-08-22
      • 1970-01-01
      相关资源
      最近更新 更多