【问题标题】:D3 SVG Move zoom to rect inside <g> having different angleD3 SVG将缩放移动到具有不同角度的<g>内的矩形
【发布时间】:2021-03-30 00:55:18
【问题描述】:

我有 &lt;svg&gt; 的背景图像和该 svg 上的不同矩形,它们处于适当的位置。
我的矩形按&lt;g&gt; 和平移值分组,每个矩形都有特定的旋转值。现在我希望我的 SVG 将焦点(显示在页面中心)放在特定的矩形上,而不会干扰背景图像和 svg 在那里翻译。主要问题是这些矩形也有不同的角度。 所以翻译不能正常工作....

SVG 如下所示。
任何人都可以为我提供指导吗?

<svg width="4500" height="3610" style="background: url(&quot;/images/001.jpg&quot;) no-repeat;">   
<g id="228" transform="translate(2001,620)rotate(216) translate(0 -216)">
 <g>
<rect x="0" y="0" height="72" width="36" stroke="black" style="fill: transparent;">
</rect><text x="0" y="36" text-anchor="start" style="fill: black;">228</text>
</g>
 <g>
  <rect x="36" y="0" height="72" width="36" stroke="black" style="fill: transparent;">
  </rect><text x="36" y="36" text-anchor="start" style="fill: black;">229</text>
   </g>
 </g>
 </svg>

【问题讨论】:

    标签: javascript html svg d3.js


    【解决方案1】:

    这在 SVG 中应该更容易,而且可能是这样,但这是我的解决方案:

    首先,我们需要在其坐标空间中获取要放大到的矩形的边界框:

    // Bounds in local coordinates space:
    var bounds = rectElement.getBBox();
    

    其次,我们从这个边界框中得到四个角:

     // Corners of rect in local coordinate space:
     var corners = [
         {x: bounds.x,y: bounds.y},
         {x: bounds.x + bounds.width, y: bounds.y},
         {x: bounds.x + bounds.width, y: bounds.y + bounds.height },
         {x: bounds.x, y: bounds.y + bounds.height }
     ];
    

    第三,我们得到矩形和SVG之间的变换矩阵:

     // relevant transform:
     var t = rectElement.getCTM();
    

    现在,我们可以将角转换为全局 SVG 单位:

      // Convert the points to global SVG coordainte space:
      for(var i = 0; i < corners.length; i++) {
          var point = svg.node().createSVGPoint();
          point.x = corners[i].x;
          point.y = corners[i].y;
          corners[i] = point.matrixTransform(t);
       }
    

    好的,此时我们在可用坐标中得到了旋转矩形的角。我们要找出这些坐标的宽高:

     // get extents for x,y in global SVG space:
     var x = d3.extent(corners, function(d) { return d.x; });
     var y = d3.extent(corners, function(d) { return d.y; });
     var w = x[1] - x[0];
     var h = y[1] - y[0];    
    

    这为我们提供了旋转后的宽度和高度,我们现在可以使用它们来缩放 SVG:。我在这里的缩放函数中使用了一个小边距(每边 10% 边距):

    // For getting scale factor:
    var scale = function(elementWidth,elementHeight,SVGWidth,SVGHeight) {
      return Math.min(SVGWidth/elementWidth*0.8,SVGHeight/elementHeight*0.8);
    }
    

    我是这样使用的:

    var k = scale(w,h,SVGwidth,SVGheight);
    

    此时我可以缩放到带有-x[0]*k-y[0]*kk 比例的矩形。但是,这将导致 SVG 左上角的形状,我想考虑边距,矩形宽度/高度相对于 SVG 宽度/高度更小。为此,我计算了每个 x, y 的偏移量:

    // Offset to center feature:
    var ox = (SVGwidth - w*k)/2;
    var oy = (SVGheight- h*k)/2;
    

    现在我可以创建我的变换以在 SVG 中居中给定矩形:

    var newTransform = "translate("+[-x[0]*k+ox,-y[0]*k+oy]+")scale("+k+")";
    

    这些 sn-ps 使用父 g 来保存 SVG 中的所有内容,这样可以更轻松地管理转换。此外,这个g 在缩放到矩形时会擦除它的变换,因为我们不想要从当前缩放状态/变换的相对变换,而是到原始缩放状态/变换的相对变换,否则我们会看到奇怪的行为。

    这是一个允许您手动缩放/平移的缩放行为示例,但可以选择单击以缩放到矩形:

    var svg = d3.select("svg");
    var contents = svg.html();
    svg.selectAll("*").remove();
    var g = svg.append("g")
       .attr("transform", "translate(0,0)scale(1)")
       .html(contents);
    
    var SVGwidth = 400;
    var SVGheight = 180;
    
    // For getting scale factor:
    var scale = function(elementWidth,elementHeight,SVGWidth,SVGHeight) {
      return Math.min(SVGWidth/elementWidth*0.8,SVGHeight/elementHeight*0.8);
    }
    
    var zoom = d3.zoom()
      .on("zoom", function(d) {
        g.attr("transform",d3.event.transform);
      })
    
    svg.call(zoom);
    
    function zoomToRect(rect) {
    
            // Bounds in local coordinates space:
            var bounds = rect.getBBox();
            
            // Corners of rect in local coordinate space:
            var corners = [
              {x: bounds.x,y: bounds.y},
              {x: bounds.x + bounds.width, y: bounds.y},
              {x: bounds.x + bounds.width, y: bounds.y + bounds.height },
              {x: bounds.x, y: bounds.y + bounds.height }
            ];
    
            // Reset transform:
            g.attr("transform","");
         
            // relevant transform:
            var t = rect.getCTM();
            
            // Convert the points to global SVG coordainte space:
            for(var i = 0; i < corners.length; i++) {
              var point = svg.node().createSVGPoint();
              point.x = corners[i].x;
              point.y = corners[i].y;
              corners[i] = point.matrixTransform(t);
            }
            
            // get extents for x,y in global SVG space:
            var x = d3.extent(corners, function(d) { return d.x; });
            var y = d3.extent(corners, function(d) { return d.y; });
            var w = x[1] - x[0];
            var h = y[1] - y[0]; 
            var k = scale(w,h,SVGwidth,SVGheight);
            // Offset to center feature:
            var ox = (SVGwidth - w*k)/2;
            var oy = (SVGheight- h*k)/2;
             
            var newTransform = d3.zoomIdentity
              .translate(-x[0]*k+ox,-y[0]*k+oy)
              .scale(k);
              
            svg.call(zoom.transform,newTransform);              
            
      }
      
      
    d3.select("button").on("click", function() { zoomToRect(d3.select("rect").node()) });
    svg {
      border: 1px dotted black;
    }
    div {
      position: absolute;
      top: 0;
      left: 0;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <svg width="400" height="180" style="background: url(&quot;/images/001.jpg&quot;) no-repeat;">   
    <g id="228" transform="translate(2001,620)rotate(216) translate(0 -216)">
      <g>
        <rect x="0" y="0" height="72" width="36" stroke="black" style="fill: transparent;"></rect>
        <text x="0" y="36" text-anchor="start" style="fill: black;">228</text>
      </g>
      <g>
         <rect x="36" y="0" height="72" width="36" stroke="black" style="fill: transparent;">
         </rect><text x="36" y="36" text-anchor="start" style="fill: black;">229</text>
      </g>
    </g>
    <g id="228" transform="translate(1801,550)rotate(65) translate(0 0)">
       <g>
        <rect x="0" y="0" height="100" width="20" stroke="black" style="fill: transparent;"></rect>
        <text x="0" y="36" text-anchor="start" style="fill: black;">454</text>
      </g>
      <g>
         <rect x="36" y="0" height="20" width="100" stroke="black" style="fill: transparent;">
         </rect><text x="36" y="36" text-anchor="start" style="fill: black;">455</text>
      </g> 
    </g>
    
     </svg>
     <div><button>Zoom to 228</button></div>

    这是一个 sn-p,它只是在没有缩放行为的情况下进行缩放,使用 SVG 在四个矩形和两个额外矩形之间循环:

    var svg = d3.select("svg");
    var contents = svg.html();
    svg.selectAll("*").remove();
    var g = svg.append("g")
       .attr("transform", "translate(0,0)scale(1)")
       .html(contents);
    
    var SVGwidth = 400;
    var SVGheight = 180;
    
    // For getting scale factor:
    var scale = function(elementWidth,elementHeight,SVGWidth,SVGHeight) {
      return Math.min(SVGWidth/elementWidth*0.8,SVGHeight/elementHeight*0.8);
    }
    
    var current = 0;
    
    function transition() {
         g.selectAll("rect").each(function(_,i,n) {
            if(i != current) return;
           
            
            // Bounds in local coordinates space:
            var bounds = n[i].getBBox();
            
            // Corners of rect in local coordinate space:
            var corners = [
              {x: bounds.x,y: bounds.y},
              {x: bounds.x + bounds.width, y: bounds.y},
              {x: bounds.x + bounds.width, y: bounds.y + bounds.height },
              {x: bounds.x, y: bounds.y + bounds.height }
            ];
    
            // Hold original transform:
            var oldTransform = g.attr("transform");
            // Reset transform:
            g.attr("transform","");
         
            // relevant transform:
            var t = n[i].getCTM();
            
            // Convert the points to global SVG coordainte space:
            for(var i = 0; i < corners.length; i++) {
              var point = svg.node().createSVGPoint();
              point.x = corners[i].x;
              point.y = corners[i].y;
              corners[i] = point.matrixTransform(t);
            }
            
            // get extents for x,y in global SVG space:
            var x = d3.extent(corners, function(d) { return d.x; });
            var y = d3.extent(corners, function(d) { return d.y; });
            var w = x[1] - x[0];
            var h = y[1] - y[0]; 
            var k = scale(w,h,SVGwidth,SVGheight);
            // Offset to center feature:
            var ox = (SVGwidth - w*k)/2;
            var oy = (SVGheight- h*k)/2;
             
            var newTransform = "translate("+[-x[0]*k+ox,-y[0]*k+oy]+")scale("+k+")"
                    
            g.attr("transform",oldTransform)
              .transition()
              .on("start", function() {
               d3.select(n[current]).style("fill","orange");
              })
              .duration(2000)
              .attr("transform",newTransform)
              .on("end", function() {
                 d3.select(n[current]).style("fill","none");
                 current++;
                 current = current%g.selectAll("rect").size();
                 transition();         
              })
    
         })
    
      }
    
    transition();
    svg {
      border: 1px dotted black;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <svg width="400" height="180" style="background: url(&quot;/images/001.jpg&quot;) no-repeat;">   
    <g id="228" transform="translate(2001,620)rotate(216) translate(0 -216)">
      <g>
        <rect x="0" y="0" height="72" width="36" stroke="black" style="fill: transparent;"></rect>
        <text x="0" y="36" text-anchor="start" style="fill: black;">228</text>
      </g>
      <g>
         <rect x="36" y="0" height="72" width="36" stroke="black" style="fill: transparent;">
         </rect><text x="36" y="36" text-anchor="start" style="fill: black;">229</text>
      </g>
    </g>
    <g id="228" transform="translate(1801,550)rotate(65) translate(0 0)">
       <g>
        <rect x="0" y="0" height="100" width="20" stroke="black" style="fill: transparent;"></rect>
        <text x="0" y="36" text-anchor="start" style="fill: black;">454</text>
      </g>
      <g>
         <rect x="36" y="0" height="20" width="100" stroke="black" style="fill: transparent;">
         </rect><text x="36" y="36" text-anchor="start" style="fill: black;">455</text>
      </g> 
    </g>
    
     </svg>

    【讨论】:

    • 您在 上应用了变换,但我们可以在 上应用变换吗,因为在应用变换时,背景图像和矩形不在完全相同的位置,我必须...背景图像和像一样需要同步工具
    • 非常感谢@andrew Reid。你节省了我很多时间......
    猜你喜欢
    • 1970-01-01
    • 2014-02-24
    • 2018-05-16
    • 1970-01-01
    • 1970-01-01
    • 2012-08-20
    • 2013-04-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多