【d3js】实现焦点定位效果-灵析社区

JOHO

前言

什么焦点定位效果不知道长什么样? 就是搜索然后选中最后把焦点聚集再画布中心(效果图如下)。 焦点定位就是搜索加zoom到中心点。

效果图:

基础代码准备

基础代码准备如下(如果有不明白的可以去翻翻zoom篇),包含选中和zoom效果。 基础代码效果图:

code

<!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">
    <input type="text" id="ipt" onchange="onInputChange()">
  </div>
</body>

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

<script>
  const selected = [];
  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(); // 绘制
  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 transformZoomBy([x, y] = [], trans) { //转化成zoom后的坐标
    return {
      transformX: (x * trans.k) + trans.x,
      transformY: (y * trans.k) + trans.y,
    }
  };



  bindEvent();
  function bindEvent() { // 绑定点击事件(看不懂的去前面d3event事件绑定去看看)
    svg.selectAll('rect').on('click', (d) => {

      const index = selected.indexOf(d.id);
      if (index !== -1) {
        selected.splice(index, 1);
      } else {
        selected.push(d.id);
      }
      d3.event.preventDefault();
      updateColor();
    })
  };


  function updateColor() { // 更新color
    data.forEach((item) => {
      if (selected.includes(item.id)) {
        item.fill = 'red';
      }
    });
    draw();
  };


  function bindZoom() { // 设置zoom
    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() {
    svg.on('mousedown.zoom', null);
    svg.on('mousemove.zoom', null);
    svg.on('dblclick.zoom', null);
    svg.on('touchstart.zoom', null);
  }


  function onInputChange() {
    const dom = document.querySelector('#ipt');
    console.log(dom.value, 'dom', dom);
  }


</script>

实现焦点定位效果

大家如果看到过zoom篇应该会有印象有一个功能是居中,其实焦点定位是把局部(搜索到的)作为一个块居中,那么怎么把搜索到的作为一个块呢?隐式绘制一份图形(搜索到的数据),再把整个块居中后,把居中的transform和scale获取到移植到咱们的画布zoom 就可以了。

实现效果图:

core code

  function drawCloneGroup(item) { // draw clone group
    const cloneGroup = svg.append('g')
      .attr('class', 'clone-group')
      .attr(
        'transform',
        `translate(${transform.x},${transform.y} ) scale(${transform.k})`,
      );

    cloneGroup // 绘制搜索到的数据
      .append('rect')
      .datum(item)
      .attr('width', 20)
      .attr('height', 20)
      .attr('id', d => d.id)
      .attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y);

    return cloneGroup;
  }

  function zoomCenter(targetGroupBBox = { width: 0, height: 0, x: 0, y: 0 }) { // 跟之前居中一样
    const scaleX = width / targetGroupBBox.width;
    const scaleY = height / targetGroupBBox.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 - (targetGroupBBox.width / 2) * k - (targetGroupBBox.x * k);
    const translateByY = height / 2 - (targetGroupBBox.height / 2) * k - (targetGroupBBox.y * k);

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

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

  function onInputChange() { // 暂时用id进行搜索
    const dom = document.querySelector('#ipt');
    const value = dom?.value; // get input value

    const item = data.find(d => d.id === +value);

    if (item) {
      const cloneGroup = drawCloneGroup(item); // 绘制并返回group 留作删除绘制的图形用。cloneGroup.remove()
      const cloneGroupBBox = document.querySelector('.clone-group').getBBox(); // 使用getBBox 能获取到该的盒子的一些属性
      selected.push(item.id);
      updateColor(); //选中该节点
      zoomCenter(cloneGroupBBox); // 居中
      cloneGroup.remove(); //删除clone group
    }
  }

完整代码

<!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">
    <input type="text" id="ipt" placeholder="请输入id(1,2,3,4)" onchange="onInputChange()">
  </div>
</body>

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

<script>
  const selected = [];
  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(); // 绘制
  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 transformZoomBy([x, y] = [], trans) { //转化成zoom后的坐标
    return {
      transformX: (x * trans.k) + trans.x,
      transformY: (y * trans.k) + trans.y,
    }
  };



  bindEvent();
  function bindEvent() { // 绑定点击事件(看不懂的去前面d3event事件绑定去看看)
    svg.selectAll('rect').on('click', (d) => {

      const index = selected.indexOf(d.id);
      if (index !== -1) {
        selected.splice(index, 1);
      } else {
        selected.push(d.id);
      }
      d3.event.preventDefault();
      updateColor();
    })
  };


  function updateColor() { // 更新color
    data.forEach((item) => {
      if (selected.includes(item.id)) {
        item.fill = 'red';
      }
    });
    draw();
  };


  function bindZoom() { // 设置zoom
    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() {
    svg.on('mousedown.zoom', null);
    svg.on('mousemove.zoom', null);
    svg.on('dblclick.zoom', null);
    svg.on('touchstart.zoom', null);
  }



  function drawCloneGroup(item) { // draw clone group
    const cloneGroup = svg.append('g')
      .attr('class', 'clone-group')
      .attr(
        'transform',
        `translate(${transform.x},${transform.y} ) scale(${transform.k})`,
      );

    cloneGroup // 绘制搜索到的数据
      .append('rect')
      .datum(item)
      .attr('width', 20)
      .attr('height', 20)
      .attr('id', d => d.id)
      .attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y);


    return cloneGroup;
  }


  /**
   * @params { Object } targetGroupBBox 
  */
  function zoomCenter(targetGroupBBox = { width: 0, height: 0, x: 0, y: 0 }) { // 跟之前居中一样
    const scaleX = width / targetGroupBBox.width;
    const scaleY = height / targetGroupBBox.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 - (targetGroupBBox.width / 2) * k - (targetGroupBBox.x * k);
    const translateByY = height / 2 - (targetGroupBBox.height / 2) * k - (targetGroupBBox.y * k);

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

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




  function onInputChange() { // 暂时用id进行搜索
    const dom = document.querySelector('#ipt');
    const value = dom?.value; // get input value

    const item = data.find(d => d.id === +value);

    if (item) {
      const cloneGroup = drawCloneGroup(item); // 绘制并返回group 留作删除绘制的图形用。cloneGroup.remove()
      const cloneGroupBBox = document.querySelector('.clone-group').getBBox(); // 使用getBBox 能获取到该的盒子的一些属性
      selected.push(item.id);
      updateColor(); //选中该节点
      zoomCenter(cloneGroupBBox); // 居中
      cloneGroup.remove(); //删除clone group
    }
  }


</script>

结束语

如果你看过前面zoom篇是不是觉得焦点定位特简单,就是画了一个搜索到的数据重新画了一个盒子,然后用算法居中获取到目标的transform和scale,再给画布做移动就好了。


阅读量:407

点赞量:0

收藏量:0