javascript - 使用 JavaScript 使 SVG 转换后的元素适合矩形边界

标签 javascript svg matrix transformation matrix-multiplication

我正在努力解决一个问题,以使经过实用转换的 SVG 元素适合给定的矩形边界。

  • 目标矩形是给定的,而不是转换的。
  • 输入 rect 具有任何类型的转换。
  • 输入 rect 可以是任何转换组的子级。
  • 转换应仅应用于输入矩形。
  • 这个问题仅与 JavaScript 元素转换有关。

  • 当元素本身只有转换时,这是一项简单的任务:
    When parent groups are not transformed
    在这种情况下,目标和输入 getBoundingClientRect(屏幕坐标中的边界矩形)之间的比例等于适当的缩放因子。
    但是当父元素也被转换时它不起作用:

       var inputElement = document.getElementById("input");
    var destinationElement = document.getElementById("destination");
    
    
    var inputBB = inputElement.getBoundingClientRect();
    var outputBB = destinationElement.getBoundingClientRect();
    var scaleX = outputBB.width / inputBB.width;
    var scaleY = outputBB.height / inputBB.height;
    // get offsets between figure center and destination rect center:
    var offsetX = outputBB.x + outputBB.width / 2 - (inputBB.x + inputBB.width / 2);
    var offsetY =
      outputBB.y + outputBB.height / 2 - (inputBB.y + inputBB.height / 2);
    
    // get current figure transformation
    let currentMatrix = (
      inputElement.transform.baseVal.consolidate() ||
      inputElement.ownerSVGElement.createSVGTransform()
    ).matrix;
    
    // Get center of figure in element coordinates:
    const inputBBox = inputElement.getBBox();
    const centerTransform = inputElement.ownerSVGElement.createSVGPoint();
    centerTransform.x = inputBBox.x + inputBBox.width / 2;
    centerTransform.y = inputBBox.y + inputBBox.height / 2;
    // create scale matrix:
    const svgTransform = inputElement.ownerSVGElement.createSVGTransform();
    svgTransform.setScale(scaleX, scaleY);
    
    let scalingMatrix = inputElement.ownerSVGElement
      .createSVGMatrix()
      // move the figure to the center of the destination rect.
      .translate(offsetX, offsetY)
      // Apply current matrix, so old transformations are not lost
      .multiply(currentMatrix)
      .translate(centerTransform.x, centerTransform.y)
      // multiply is used instead of the scale method while for some reasons matrix scale is giving proportional scaling...
      // From a transforms proper matrix is generated.
      .multiply(svgTransform.matrix)
      .translate(-centerTransform.x, -centerTransform.y);
    
    // Apply new created matrix to element back:
    const newTransform = inputElement.ownerSVGElement.createSVGTransform();
    newTransform.setMatrix(scalingMatrix);
    inputElement.transform.baseVal.initialize(newTransform);
    
    var bboundsTest= document.getElementById("bboundsTest");
    const resultBBounds = inputElement.getBoundingClientRect();
    bboundsTest.setAttribute('x', resultBBounds .x);
    bboundsTest.setAttribute('y', resultBBounds .y);
    bboundsTest.setAttribute('width', resultBBounds .width);
    bboundsTest.setAttribute('height', resultBBounds .height);
    document.getElementById('test2').innerHTML = 'expected: 100x100 . Results: ' + resultBBounds.width + 'x' + resultBBounds.height
    <svg
      version="1.2"
      viewBox="0 0 480 150"
      width="480"
      height="150"
      xmlns="http://www.w3.org/2000/svg"
    >
    
    <g transform="skewX(10) translate(95,1) rotate(30)">
      <g transform="skewX(30) translate(-3,3) rotate(30)">
        <g transform="skewX(10) translate(-3,4) rotate(10)">
          <rect
            id="input"
            transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
            width="30"
            height="30"
            fill="red"
          />
        </g>
      </g>
    </g>
    
    <rect
      id="destination"
      x="20"
      y="20"
      width="100"
      height="100"
      fill="transparent"
      stroke="blue"
    />
     <rect
      id="bboundsTest"
      x="20"
      y="20"
      width="100"
      height="100"
      fill="transparent"
      stroke="black"
    />
    
    </svg>
    <div id="test2"></div>

    关于如何将父转换计入计数以找到适当的缩放因子的任何想法?
    提前感谢您的想法!
    Dipen Shah 给出的答案侧重于对父元素应用转换,这也是一种选择,但我的目标是将元素转换为目标矩形边界。

    最佳答案

    正如您所发现的,这是一个棘手的问题。它比你想象的还要棘手(见下文)。
    您在两个不同的坐标空间中有矩形。其中之一是变形的。因此,您正试图将一个转换后的矩形映射到另一个可能转换的矩形。由于它们被转换,这些矩形中的一个或两个(可能)不再是矩形。
    由于您的要求是将“输入”转换为“目标”,因此解决问题的方法是将坐标空间切换到“输入”矩形的视点。从“输入”的 Angular 来看,“目的地”是什么样的?为了看到,我们需要用“输入”所具有的变换的逆来变换“目的地”。
    <rect id="input" transform=""/> 的目的地是什么样的

    <svg
      version="1.2"
      viewBox="-50 -50 160 260"
      height="500"
      xmlns="http://www.w3.org/2000/svg"
    >
    
    <rect
      id="input"
      transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
      width="30"
      height="30"
      fill="red"
    />
    
    <g transform="rotate(-10) translate(3,-4) skewX(-10)">
    <g transform="rotate(-30) translate(3,-3) skewX(-30)">
    <g transform="rotate(-30) translate(-95,-1) skewX(-10)">
    <rect
      id="destination"
      x="20"
      y="20"
      width="100"
      height="100"
      fill="transparent"
      stroke="blue"
    />
    </g>
    </g>
    </g>

    <rect id="input"/> 的目的地是什么样的

    <svg
      version="1.2"
      viewBox="-80 -70 120 230"
      height="500"
      xmlns="http://www.w3.org/2000/svg"
    >
    
    <rect
      id="input"
      width="30"
      height="30"
      fill="red"
    />
    
    <g transform="rotate(-45) translate(0,0) translate(50,50) scale(0.67) translate(-50,-50) skewX(-25) translate(-95,-76.5)">
    <g transform="rotate(-10) translate(3,-4) skewX(-10)">
    <g transform="rotate(-30) translate(3,-3) skewX(-30)">
    <g transform="rotate(-30) translate(-95,-1) skewX(-10)">
    <rect
      id="destination"
      x="20"
      y="20"
      width="100"
      height="100"
      fill="transparent"
      stroke="blue"
    />
    </g>
    </g>
    </g>
    </g>

    所以,你可以明白为什么它现在如此棘手。我们要么必须找到将平行四边形映射到另一个平行四边形的变换,要么必须找到将矩形映射到平行四边形的变换。显然我们会选择后者。你会期望它是两个选项中更简单的一个。
    我们也得到了帮助,因为我们可以假设转换是 affine .直线保持直线,平行线保持平行。
    所以我们的任务是扩大我们的矩形,使它整齐地适合我们的目标平行四边形。此外,由于平行四边形具有 180° 旋转对称性,我们知道拟合矩形的中心将与平行四边形的中心重合。
    所以,让我们假设“输入”矩形位于“目标”平行四边形的中心,然后从矩形射出假想的光线,直到它们射到平行四边形的边上。无论哪条光线首先击中目标平行四边形,都会为我们提供应该应用于矩形以使其适合的比例。

    .ray {
      stroke: lightgrey;
      stroke-dasharray: 2 2;
    }
    <svg
      version="1.2"
      viewBox="0 0 120 230"
      height="500"
      xmlns="http://www.w3.org/2000/svg"
    >
    
    <g transform="translate(47.1,101.2)"><!-- positioning conveniently for our figure -->
      <!-- scaling rays -->
      <line class="ray" x1="-100" y1="0" x2="100" y2="0"/>
      <line class="ray" x1="-100" y1="30" x2="100" y2="30"/>
      <line class="ray" x1="0" y1="-100" x2="0" y2="100"/>
      <line class="ray" x1="30" y1="-100" x2="30" y2="100"/>
    
      <rect
        id="input"
        width="30"
        height="30"
        fill="red"
      />
      
    </g>
    
    <g transform="translate(80,70)"><!-- positioning conveniently for our figure -->
    
      <g transform="rotate(-45) translate(0,0) translate(50,50) scale(0.67) translate(-50,-50) skewX(-25) translate(-95,-76.5)">
      <g transform="rotate(-10) translate(3,-4) skewX(-10)">
      <g transform="rotate(-30) translate(3,-3) skewX(-30)">
      <g transform="rotate(-30) translate(-95,-1) skewX(-10)">
      <rect
       id="destination"
       x="20"
       y="20"
       width="100"
       height="100"
       fill="transparent"
       stroke="blue"
      />
      </g>
      </g>
      </g>
      </g>
      
    </g>



    var inputElement = document.getElementById("input");
    var destinationElement = document.getElementById("destination");
    var svg = inputElement.ownerSVGElement;
    
    // Get the four corner points of rect "input"
    var inX = inputElement.x.baseVal.value;
    var inY = inputElement.y.baseVal.value;
    var inW = inputElement.width.baseVal.value;
    var inH = inputElement.height.baseVal.value;
    
    // Get the four corner points of rect "destination"
    var destX = destinationElement.x.baseVal.value;
    var destY = destinationElement.y.baseVal.value;
    var destW = destinationElement.width.baseVal.value;
    var destH = destinationElement.height.baseVal.value;
    var destPoints = [
       createPoint(svg, destX,         destY),
       createPoint(svg, destX + destW, destY),
       createPoint(svg, destX + destW, destY + destH),
       createPoint(svg, destX,         destY + destH)
    ];
    
    // Get total transform applied to input rect
    var el = inputElement;
    var totalMatrix = el.transform.baseVal.consolidate().matrix;
    // Step up ancestor tree till we get to the element before the root SVG element
    while (el.parentElement.ownerSVGElement != null) {
      el = el.parentElement;
      if (el.transform) {
        totalMatrix = el.transform.baseVal.consolidate().matrix.multiply( totalMatrix );
      }
    }
    //console.log("totalMatrix = ",totalMatrix);
    
    // Transform the four "destination" rect corner points by the inverse of the totalMatrix
    // We will then have the corner points in the same coordinate space as the "input" rect
    for (var i=0; i<4; i++) {
      destPoints[i] = destPoints[i].matrixTransform(totalMatrix.inverse());
    }
    //console.log("transformed destPoints=",destPoints);
    
    // Find the equation for the rays that start at the centre of the "input" rect & "destination" parallelogram
    // and pass through the corner points of the "input" rect.
    var destMinX = Math.min(destPoints[0].x, destPoints[1].x, destPoints[2].x, destPoints[3].x);
    var destMaxX = Math.max(destPoints[0].x, destPoints[1].x, destPoints[2].x, destPoints[3].x);
    var destMinY = Math.min(destPoints[0].y, destPoints[1].y, destPoints[2].y, destPoints[3].y);
    var destMaxY = Math.max(destPoints[0].y, destPoints[1].y, destPoints[2].y, destPoints[3].y);
    var destCentreX = (destMinX + destMaxX) / 2;
    var destCentreY = (destMinY + destMaxY) / 2;
    
    // Find the scale in the X direction by shooting rays horizontally from the top and bottom of the "input" rect
    var scale1 = findDistanceToDestination(destCentreX, destCentreY - inH/2, inW/2, 0, // line equation of ray line 1
                                           destPoints);
    var scale2 = findDistanceToDestination(destCentreX, destCentreY + inH/2, inW/2, 0, // line equation of ray line 2
                                           destPoints);
    var scaleX = Math.min(scale1, scale2);
    
    // Find the scale in the Y direction by shooting rays vertically from the left and right of the "input" rect
    scale1 = findDistanceToDestination(destCentreX - inW/2, destCentreY, 0, inH/2, // line equation of ray line 1
                                       destPoints);
    scale2 = findDistanceToDestination(destCentreX + inW/2, destCentreY, 0, inH/2, // line equation of ray line 2
                                       destPoints);
    var scaleY = Math.min(scale1, scale2);
    
    
    // Now we can position and scale the "input" element to fit the "destination" rect
    inputElement.transform.baseVal.appendItem( makeTranslate(svg, destCentreX, destCentreY));
    inputElement.transform.baseVal.appendItem( makeScale(svg, scaleX, scaleY));
    inputElement.transform.baseVal.appendItem( makeTranslate(svg, -(inX + inW)/2, -(inY + inH)/2));
    
    function createPoint(svg, x, y)
    {
      var pt = svg.createSVGPoint();
      pt.x = x;
      pt.y = y;
      return pt;
    }
    
    function makeTranslate(svg, x, y)
    {
      var t = svg.createSVGTransform();
      t.setTranslate(x, y);
      return t;
    }
    
    function makeScale(svg, sx, sy)
    {
      var t = svg.createSVGTransform();
      t.setScale(sx, sy);
      return t;
    }
    
    function findDistanceToDestination(centreX, centreY, rayX, rayY, // line equation of ray
                                       destPoints)                           // parallelogram points
    {
      // Test ray against each side of the dest parallelogram
      for (var i=0; i<4; i++) {
        var from = destPoints[i];
        var to   = destPoints[(i + 1) % 4];
        var dx =  to.x - from.x;
        var dy =  to.y - from.y;
        var k = intersection(centreX, centreY, rayX, rayY,    // line equation of ray
                             from.x, from.y, dx, dy); // line equation of parallogram side
        if (k >= 0 && k <= 1) {
           // Ray intersected with this side
           var interceptX = from.x + k * dx;
           var interceptY = from.y + k * dy;
           var distanceX = interceptX - centreX;
           var distanceY = interceptY - centreY;
           if (rayX != 0)
             return Math.abs(distanceX / rayX);
           else if (rayY != 0)
             return Math.abs(distanceY / rayY);
           else
             return 0;  // How to handle case where "input" rect has zero width or height?
        }
      }
      throw 'Should have intersected one of the sides!'; // Shouldn't happen
    }
    
    // Returns the position along the 'side' line, that the ray hits.
    // If it intersects the line, thre return value will be between 0 and 1.
    function intersection(rayX, rayY, rayDX, rayDY,
                          sideX, sideY, sideDX, sideDY)
    {
      // We want to find where:
      //    rayXY + t * rayDXDY = sideXY + k * sideDXDY
      // Returning k.
      // See: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
      var den = -rayDX * -sideDY - -rayDY * -sideDX;
      return (den != 0) ? - (-rayDX * (rayY-sideY) - -rayDY * (rayX-sideX)) / den
                        : -9999;  // Lines don't intersect. Return a value outside range 0..1.
    }
    <svg
      version="1.2"
      viewBox="0 0 480 150"
      width="480"
      height="150"
      xmlns="http://www.w3.org/2000/svg"
    >
    
    <g transform="skewX(10) translate(95,1) rotate(30)">
      <g transform="skewX(30) translate(-3,3) rotate(30)">
        <g transform="skewX(10) translate(-3,4) rotate(10)">
          <rect
            id="input"
            transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
            width="30"
            height="30"
            fill="red"
          />
        </g>
      </g>
    </g>
    
    <rect
      id="destination"
      x="20"
      y="20"
      width="100"
      height="100"
      fill="transparent"
      stroke="blue"
    />
    
    </svg>
    <div id="test2"></div>

    我们接近了,但我们有点过大了。发生了什么?
    如果我们像以前一样回到“输入”矩形空间中查看它,我们可以更好地看到问题。

    <svg width="500" height="500" viewBox="-40 -40 50 180">
    
      <polygon points="-38.5008,  79.5321,
                       -32.7704, -35.2044,
                         3.5896,  12.3685,
                        -2.1406, 127.1050"
               fill="none"
               stroke="blue"
               stroke-width="0.5"/>
    
      <!-- input -->
      <rect x="-32.4555" y="30.9503" width="30" height="30"
            fill="red"/>
    
      <!-- centre of dest -->
      <circle cx="-17.4555" cy="45.9503" r="1"/>
    
      <!-- intercepts X -->
      <circle cx="-36.0744" cy="30.9503" r="1" fill="green"/>
      <circle cx="-37.5727" cy="60.9503" r="1" fill="green"/>
    
      <!-- intercepts Y -->
      <circle cx="-32.4555" cy="-34.7923" r="1" fill="green"/>
      <circle cx="-2.4555" cy="4.4590" r="1" fill="green"/>
    
      <!-- scaled input -->
      <rect x="-32.4555" y="30.9503" width="30" height="30"
            fill="red" fill-opacity="0.2"
            transform="translate(-17.4556 45.9503) scale(1.24126 2.76608) translate(17.4556 -45.9503)"/>
    
    </svg>

    绿点代表我们从“输入”矩形水平和垂直拍摄光线得到的交点。褪色的红色矩形代表按比例放大以接触我们的截点的“输入”矩形。它溢出了我们的“目的地”形状。这就是为什么我们之前代码片段中的形状也会溢出。
    这就是我的意思,在最上面,当我说它比你想象的要棘手时。要使“输入”与“目标”匹配,您必须调整两个相互依赖的 X 和 Y 比例。如果您调整 X 比例以适应,它将不再适合 Y 方向。反之亦然。

    This is as far as I want to go. I've spent a couple of hours on this answer already. Perhaps their's a mathematical solution for finding a rectangle that fits inside a parallelogram and touches all four sides. But I don't really want to spend the time to work it out. Sorry. :)


    Perhaps you or someone else can take this further. You could also try an iterative solution that nudges the X and Y scales iteratively until it gets close enough.


    最后,如果您准备接受 的条件不要水平和垂直拉伸(stretch)输入,如果您可以按比例放大(或缩小)输入以适应(即保持纵横比相同),那么解决这个问题就更简单了。

    var inputElement = document.getElementById("input");
    var destinationElement = document.getElementById("destination");
    var svg = inputElement.ownerSVGElement;
    
    // Get the four corner points of rect "input"
    var inX = inputElement.x.baseVal.value;
    var inY = inputElement.y.baseVal.value;
    var inW = inputElement.width.baseVal.value;
    var inH = inputElement.height.baseVal.value;
    
    // Get the four corner points of rect "destination"
    var destX = destinationElement.x.baseVal.value;
    var destY = destinationElement.y.baseVal.value;
    var destW = destinationElement.width.baseVal.value;
    var destH = destinationElement.height.baseVal.value;
    var destPoints = [
       createPoint(svg, destX,         destY),
       createPoint(svg, destX + destW, destY),
       createPoint(svg, destX + destW, destY + destH),
       createPoint(svg, destX,         destY + destH)
    ];
    
    // Get total transform applied to input rect
    var el = inputElement;
    var totalMatrix = el.transform.baseVal.consolidate().matrix;
    // Step up ancestor tree till we get to the element before the root SVG element
    while (el.parentElement.ownerSVGElement != null) {
      el = el.parentElement;
      if (el.transform) {
        totalMatrix = el.transform.baseVal.consolidate().matrix.multiply( totalMatrix );
      }
    }
    //console.log("totalMatrix = ",totalMatrix);
    
    // Transform the four "destination" rect corner points by the inverse of the totalMatrix
    // We will then have the corner points in the same coordinate space as the "input" rect
    for (var i=0; i<4; i++) {
      destPoints[i] = destPoints[i].matrixTransform(totalMatrix.inverse());
    }
    //console.log("transformed destPoints=",destPoints);
    
    // Find the equation for the rays that start at the centre of the "input" rect & "destination" parallelogram
    // and pass through the corner points of the "input" rect.
    var destMinX = Math.min(destPoints[0].x, destPoints[1].x, destPoints[2].x, destPoints[3].x);
    var destMaxX = Math.max(destPoints[0].x, destPoints[1].x, destPoints[2].x, destPoints[3].x);
    var destMinY = Math.min(destPoints[0].y, destPoints[1].y, destPoints[2].y, destPoints[3].y);
    var destMaxY = Math.max(destPoints[0].y, destPoints[1].y, destPoints[2].y, destPoints[3].y);
    var destCentreX = (destMinX + destMaxX) / 2;
    var destCentreY = (destMinY + destMaxY) / 2;
    
    // Shoot diagonal rays from the centre through two adjacent corners of the "input" rect.
    // Whichever one hits the destination shape first, provides the scaling factor we need.
    var scale1 = findDistanceToDestination(destCentreX, destCentreY, inW/2, inH/2, // line equation of ray line 1
                                           destPoints);
    var scale2 = findDistanceToDestination(destCentreX, destCentreY, -inW/2, inW/2, // line equation of ray line 2
                                           destPoints);
    var scale = Math.min(scale1, scale2);
    
    // Now we can position and scale the "input" element to fit the "destination" rect
    inputElement.transform.baseVal.appendItem( makeTranslate(svg, destCentreX, destCentreY));
    inputElement.transform.baseVal.appendItem( makeScale(svg, scale, scale));
    inputElement.transform.baseVal.appendItem( makeTranslate(svg, -(inX + inW)/2, -(inY + inH)/2));
    
    function createPoint(svg, x, y)
    {
      var pt = svg.createSVGPoint();
      pt.x = x;
      pt.y = y;
      return pt;
    }
    
    function makeTranslate(svg, x, y)
    {
      var t = svg.createSVGTransform();
      t.setTranslate(x, y);
      return t;
    }
    
    function makeScale(svg, sx, sy)
    {
      var t = svg.createSVGTransform();
      t.setScale(sx, sy);
      return t;
    }
    
    function findDistanceToDestination(centreX, centreY, rayX, rayY, // line equation of ray
                                       destPoints)                           // parallelogram points
    {
      // Test ray against each side of the dest parallelogram
      for (var i=0; i<4; i++) {
        var from = destPoints[i];
        var to   = destPoints[(i + 1) % 4];
        var dx =  to.x - from.x;
        var dy =  to.y - from.y;
        var k = intersection(centreX, centreY, rayX, rayY,    // line equation of ray
                             from.x, from.y, dx, dy); // line equation of parallogram side
        if (k >= 0 && k <= 1) {
           // Ray intersected with this side
           var interceptX = from.x + k * dx;
           var interceptY = from.y + k * dy;
           var distanceX = interceptX - centreX;
           var distanceY = interceptY - centreY;
           if (rayX != 0)
             return Math.abs(distanceX / rayX);
           else if (rayY != 0)
             return Math.abs(distanceY / rayY);
           else
             return 0;  // How to handle case where "input" rect has zero width or height?
        }
      }
      throw 'Should have intersected one of the sides!'; // Shouldn't happen
    }
    
    // Returns the position along the 'side' line, that the ray hits.
    // If it intersects the line, thre return value will be between 0 and 1.
    function intersection(rayX, rayY, rayDX, rayDY,
                          sideX, sideY, sideDX, sideDY)
    {
      // We want to find where:
      //    rayXY + t * rayDXDY = sideXY + k * sideDXDY
      // Returning k.
      // See: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
      var den = -rayDX * -sideDY - -rayDY * -sideDX;
      return (den != 0) ? - (-rayDX * (rayY-sideY) - -rayDY * (rayX-sideX)) / den
                        : -9999;  // Lines don't intersect. Return a value outside range 0..1.
    }
    <svg
      version="1.2"
      viewBox="0 0 480 150"
      width="480"
      height="150"
      xmlns="http://www.w3.org/2000/svg"
    >
    
    <g transform="skewX(10) translate(95,1) rotate(30)">
      <g transform="skewX(30) translate(-3,3) rotate(30)">
        <g transform="skewX(10) translate(-3,4) rotate(10)">
          <rect
            id="input"
            transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
            width="30"
            height="30"
            fill="red"
          />
        </g>
      </g>
    </g>
    
    <rect
      id="destination"
      x="20"
      y="20"
      width="100"
      height="100"
      fill="transparent"
      stroke="blue"
    />
    
    </svg>
    <div id="test2"></div>

    关于javascript - 使用 JavaScript 使 SVG 转换后的元素适合矩形边界,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63881255/

    相关文章:

    javascript - 通过 HTML 表单查询和更新融合表?

    html - 网页设计中 SVG 的 100% 宽度

    r - 如何将日期向量拆分为 k 列?

    lisp - 矩阵添加 Lisp

    javascript - 使用 javascript 生成并下载带有表情符号的 utf8 文件

    javascript - 面向对象 JS : Is there point using prototype property in the base object?

    svg - 如何在 D3 折线图中比较不同年份?

    r - R中矩阵的就地修改

    javascript - .click() 的行为不一致?

    javascript - 在 JavaScript 中将 DOM 节点或文档转换为 XML