【d3js】d3.zoom 实现画布放大、缩小、居中-灵析社区

JOHO

前言

d3.zoom还是一个很强大得zoom工具包(放大、缩小、平移), 不只适合用在svg, 可以应用再dom、canvas等需要zoom得任何场景

基础图形绘制代码

绘制基础图形用来做zoom来用。 效果图如下:
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #container {
      width: 500px;
      margin: 50px auto 0;
    }
  </style>
</head>

<body>
  <div id="container">
  </div>
</body>

</html>
<script src="https://d3js.org/d3.v5.min.js"></script>

<script>
  const svg = d3.select('#container')
    .append('svg')
    .attr('width', 500)
    .attr('height', 500);

  const container = svg.append('g'); // group

  const data = [
    { id: 1, fill: 'black', x: 10, y: 10 },
    { id: 2, fill: 'black', x: 50, y: 50 },
    { id: 3, fill: 'black', x: 100, y: 70 },
    { id: 4, fill: 'black', x: 20, y: 100 }
  ];

  draw(); // 绘制
  function draw() {
    const update = container.selectAll('rect')
      .data(data, d => d.id);

    //修改层
    update.attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)

    //渲染层
    const enter = update.enter();

    //删除层
    const exit = update.exit();

    enter.append('rect')
      .attr('width', 20)
      .attr('height', 20)
      .attr('id', d => d.id)
      .attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)
      .attr('stroke', 'blue')
      .attr('strokeWidth', 1)

    exit.remove()
  }
</script>

绑定基础zoom

给画布绑定上zoom(平移、放大、缩小)

code

  let transform = {
    x: 0,
    y: 0,
    k: 1,
  };

  let zoom = null;
  const zoomMin = 0.3;
  const zoomMax = 5;

  const container = svg.append('g')
    .attr(
      'transform',
      `translate(${transform.x},${transform.y} ) scale(${transform.k})`,
    ); // 绑定transform

function bindZoom() { // 绑定zoom
    zoom = d3.zoom()
      .scaleExtent([zoomMin, zoomMax]) // 设置最大、最小zoom范围
      .on('zoom', function () {
        transform = d3.zoomTransform(this); // 使用改方法获取到zoom当前得新值{x,y,k}
        container.attr('transform', transform); // 重新绑定新得transform
        console.log(transform, 'transform...')
      });

    svg.call(zoom).on('dblclick.zoom', null); // 把方法指定再svg
  }

效果图:

如何解除zoom绑定

解除zoom绑定 code
function stopZoom() { //解除zoom绑定
  svg.on('mousedown.zoom', null);
  svg.on('mousemove.zoom', null);
  svg.on('dblclick.zoom', null);
  svg.on('touchstart.zoom', null);
}

放大缩小按钮功能键

实现放大、缩小按钮功能键

code

function zoomIn() { // 缩小
  svg.transition().call(zoom.scaleBy, 0.7);
};

function zoomOut() { //放大
  svg.transition().call(zoom.scaleBy, 1.3);
};

效果图:

实现容器再画布居中显示

实现容器再画布得中间显示(再画布得正中间位置)
  • 算法可以想像一下一个大盒子和内部一个小盒子,怎么才能让小盒子居中显示呢

code


  function zoomCenter() {
    const containerX = data.map((item) => item.x);
    const containerY = data.map((item) => item.y);

    const minX = Math.min.apply(null, containerX);
    const maxX = Math.max.apply(null, containerX);
    const minY = Math.min.apply(null, containerY);
    const maxY = Math.max.apply(null, containerY);

    const containerGroupBBox = { // 用数据获取到当前group得w、h、x、y
      width: maxX - minX,
      height: maxY - minY,
      x: minX,
      y: minY,
    };

    const scaleX = width / containerGroupBBox.width;
    const scaleY = height / containerGroupBBox.height;

    let k = Math.min(scaleX, scaleY) * 0.7;
    k = Math.max(k, zoomMin);
    k = Math.min(k, zoomMax); // 用画布大小 / 当前svg得大小 获取到比例值

    // 算出居中得x、y坐标(往俩盒子 一个大盒子(画布)和另外一个盒子(group)怎么让group居中呢!!!
    // 用画布自身得一半 减去 gropu得一半 * 缩放 K, 再减去gropu得translate 
    const translateByX = width / 2 - (containerGroupBBox.width / 2) * k - (containerGroupBBox.x * k);
    const translateByY = height / 2 - (containerGroupBBox.height / 2) * k - (containerGroupBBox.y * k);


    const transform = d3.zoomIdentity
      .translate(translateByX, translateByY)
      .scale(k); // 获取到目标 transform

    svg.transition() // 过渡效果
      .duration(100) // 100ms
      .call(zoom.transform, transform);
  }

效果图如下:

源码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #container {
      width: 500px;
      margin: 50px auto 0;
    }
  </style>
</head>

<body>
  <div id="container">
    <button onclick="zoomOut()">放大</button>
    <button onclick="zoomIn()">缩小</button>
    <button onclick="zoomCenter()">居中</button>
  </div>
</body>

</html>
<script src="https://d3js.org/d3.v5.min.js"></script>

<script>

  let transform = {
    x: 0,
    y: 0,
    k: 1,
  };

  let zoom = null;
  const zoomMin = 0.3;
  const zoomMax = 5;
  const width = 500;
  const height = 500;

  const svg = d3.select('#container')
    .append('svg')
    .attr('width', width)
    .attr('height', width);

  const container = svg.append('g')
    .attr(
      'transform',
      `translate(${transform.x},${transform.y} ) scale(${transform.k})`,
    );

  const data = [
    { id: 1, fill: 'black', x: 10, y: 10 },
    { id: 2, fill: 'black', x: 50, y: 50 },
    { id: 3, fill: 'black', x: 100, y: 70 },
    { id: 4, fill: 'black', x: 20, y: 100 }
  ];

  draw(); // 绘制
  bindZoom(); //绑定zoom
  function draw() {
    const update = container.selectAll('rect')
      .data(data, d => d.id);

    //修改层
    update.attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)

    //渲染层
    const enter = update.enter();

    //删除层
    const exit = update.exit();

    enter.append('rect')
      .attr('width', 20)
      .attr('height', 20)
      .attr('id', d => d.id)
      .attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)
      .attr('stroke', 'blue')
      .attr('strokeWidth', 1)

    exit.remove()
  }

  function bindZoom() {
    zoom = d3.zoom()
      .scaleExtent([zoomMin, zoomMax])
      .on('zoom', function () {
        transform = d3.zoomTransform(this);
        container.attr('transform', transform);
        console.log(transform, 'transform...')
      })

    svg.call(zoom).on('dblclick.zoom', null);
  };

  function stopZoom() { //解除zoom绑定
    svg.on('mousedown.zoom', null);
    svg.on('mousemove.zoom', null);
    svg.on('dblclick.zoom', null);
    svg.on('touchstart.zoom', null);
  };


  function zoomIn() {
    svg.transition().call(zoom.scaleBy, 0.7);
  };

  function zoomOut() {
    svg.transition().call(zoom.scaleBy, 1.3);
  };

  function zoomCenter() {
    const containerX = data.map((item) => item.x);
    const containerY = data.map((item) => item.y);

    const minX = Math.min.apply(null, containerX);
    const maxX = Math.max.apply(null, containerX);
    const minY = Math.min.apply(null, containerY);
    const maxY = Math.max.apply(null, containerY);

    const containerGroupBBox = { // 用数据获取到当前group得w、h、x、y
      width: maxX - minX,
      height: maxY - minY,
      x: minX,
      y: minY,
    };

    const scaleX = width / containerGroupBBox.width;
    const scaleY = height / containerGroupBBox.height;

    let k = Math.min(scaleX, scaleY) * 0.7;
    k = Math.max(k, zoomMin);
    k = Math.min(k, zoomMax); // 用画布大小 / 当前svg得大小 获取到比例值

    // 算出居中得x、y坐标(往俩盒子 一个大盒子(画布)和另外一个盒子(图形撑起来得))怎么让图形撑起来得居中呢!!!
    // 用画布自身得一半 减去 gropu得一半 * 缩放 K, 再减去gropu得translate 
    const translateByX = width / 2 - (containerGroupBBox.width / 2) * k - (containerGroupBBox.x * k);
    const translateByY = height / 2 - (containerGroupBBox.height / 2) * k - (containerGroupBBox.y * k);


    const transform = d3.zoomIdentity
      .translate(translateByX, translateByY)
      .scale(k); // 获取到目标 transform

    svg.transition() // 过渡效果
      .duration(100) // 100ms
      .call(zoom.transform, transform);
  }
</script>

总结

本篇文章主要讲了下使用zoom,和常用得一些zoom场景得实现,相信尼对zoom又有了一个新得认识。他不只可以用在svg里面,我再canvas、doms上也用到了该zoom方法,就是哪里需要就可以用在哪里就好了。


阅读量:1009

点赞量:0

收藏量:0