d3.js 实现鹰眼图的记录

0 阅读2分钟

许多地方有现成的鹰眼图,但是我需要在d3.js中实现,想不到一个简单的需求居然也花了一两天的时间。

总结:

  1. 用一个 textarea来显示x,y,k,还是很省事的,比用console.log()强太多
  2. event.transform的 x,y,k x,y 是图层平移的坐标
  • 初始时:

    Translate X: 0, Translate Y: 0, Scale: 1

  • 向右平移 100 像素,向下平移 50 像素:

    Translate X: 100, Translate Y: 50, Scale: 1

  • 缩放比例变为 2:

    Translate X: 100, Translate Y: 50, Scale: 2

  1. 这只是普通的一个点图,如果要用在我想用的复杂topo图上,这个缩略图应该如何绘制?
  2. 这图缩放到最大时还有问题,恢复不回去
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>D3.js Minimap Example with Consistent Viewport</title>
  <script src="https://d3js.org/d3.v7.min.js"></script>
  <style>
    .main-view {
      border: 1px solid #ccc;
      width: 800px;
      height: 400px;
      position: relative;
      overflow: hidden;
    }
    .minimap {
      border: 1px solid #ccc;
      width: 200px;
      height: 100px;
      position: relative;
      overflow: hidden;
      margin-top: 10px;
    }
    .main-view circle, .minimap circle {
      fill: steelblue;
    }
    .viewport {
      fill: none;
      stroke: red;
      stroke-width: 1;
    }
  </style>
</head>
<body>
<div class="main-view"></div>
<div class="minimap"></div>
<div class="txt"></div>
  <div id="textarea-container"></div>

<script>
    const content = "这是一个示例文本内容。";

    // 选择容器并添加一个文本区域
    const container = d3.select("#textarea-container");
    const textarea = container.append("textarea")
      .attr("rows", 4)
      .attr("cols", 50)
      .text(content); // 设置文本内容

    // 如果需要,可以为文本区域添加一些样式
    textarea.style("margin", "20px")
      .style("padding", "10px")
      .style("font-size", "16px");
  const mainView = d3.select(".main-view").append("svg")
    .attr("width", 800)
    .attr("height", 400);

  const minimap = d3.select(".minimap").append("svg")
    .attr("width", 200)
    .attr("height", 100);

  const xMin=0;
  const xMax=800;
  const yMin=0;
  const yMax=400;
  // 生成一些随机数据
  const data = d3.range(50).map(() => [Math.random() * 800, Math.random() * 400]);

  // 主视图的节点
  const mainNodes = mainView.selectAll("circle")
    .data(data)
    .enter().append("circle")
    .attr("cx", d => d[0])
    .attr("cy", d => d[1])
    .attr("r", 5);

  // Minimap 的节点
  const minimapNodes = minimap.selectAll("circle")
    .data(data)
    .enter().append("circle")
    .attr("cx", d => d[0] * (200 / 800))
    .attr("cy", d => d[1] * (100 / 400))
    .attr("r", 2);

  // 主视图的缩放和移动
  const zoom = d3.zoom()
    .scaleExtent([1, 5]) // 设置最小缩放比例为 1,最大缩放比例为 10
    .on("zoom", (event) => {
      let { x, y, k } = event.transform;

            // 限制平移范围
        console.log(x,y,k)

      //x = Math.min(x - 800 / k, Math.max(xMin, x));
      //y = Math.min(y - 400 / k, Math.max(yMin, y));
      //console.log(x,y,k);
        // 不被允许的平移包括:
        // x最小是0,最大是 -width*(k-1)  注意这方向,反的,反常识
        if (x<-xMax*(k-1)){
            x=-xMax*(k-1)
        }
        if (x>0){
            x=0
        }

        // y最小为0,最大是-height*(k-1)
        if (y<-yMax*(k-1)){
            y=-yMax*(k-1)
        }
        if (y>0){
            y=0
        }


        textarea.text(x+"\n"+y+"\n"+k);
      mainView.selectAll("circle")
        .attr("transform", `translate(${x}, ${y}) scale(${k})`);
              const viewport = minimap.select(".viewport");

        if (k === 1) {
        // 如果是全图,禁用平移
            console.log("k = 1 , 禁用移动");
        mainView.selectAll("circle")
          .attr("transform", `translate(0, 0) scale(1)`);

        minimap.select(".viewport").attr("transform", `translate(0, 0) scale(1)`);
      } else {
        mainView.selectAll("circle")
          .attr("transform", `translate(${x}, ${y}) scale(${k})`);
         viewport.attr("x", -x / k * (200 / 800))
        .attr("y", -y / k * (100 / 400))
        .attr("width", 200 / k)
        .attr("height", 100 / k);
      }


      // 更新 minimap 的视图

    });

  mainView.call(zoom);

  // 在 minimap 上添加一个矩形,表示主视图的可视区域
  const viewport = minimap.append("rect")
    .attr("class", "viewport")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", 200)
    .attr("height", 100);
</script>
</body>
</html>