Search code examples
javascriptd3.jspolygonbounding-box

D3 JS - Making a Polygon Draggable using hard coded Bounding Box Attributes Does Not Work


I draw a Polygon using D3 mouse events as shown in this fiddle.

Below is the method that get's the polygon's bounding box and sets the polygon's bounding box properties.

function completePolygon() {
  d3.select('g.outline').remove();

  gPoly = svgCanvas.append('g')
    .classed("polygon", true);

  polyPoints.splice(polyPoints.length - 1);

  polyEl = gPoly.append("polygon")
    .attr("points", polyPoints);

  for (var i = 0; i < polyPoints.length; i++) {
    gPoly.append('circle')
      .attr("cx", polyPoints[i][0])
      .attr("cy", polyPoints[i][1])
      .attr("r", 4)
      .call(dragBehavior);
  }

  isDrawing = false;
  isDragging = true;

  bbox = polyEl._groups[0][0].getBBox();
  var bbox2 = gPoly._groups[0][0].getBBox();

  //Altering the bounding box's attribute of polygon
  bbox.x = 0;
  bbox.y = 0;
  bbox.width = 50;
  bbox.height = 50;

  gPoly.attr("transform", "translate(" + 0 + "," + 0 + ")");
  // polyEL.attr("transform", "translate(" + 0 + "," + 0 + ")");
  //
  // gPoly.call(d3.drag().on("drag", movePolygon(bbox)));
}

I want to make the entire polygon draggable. I tried getting the Bounding Box of the drawn Polygon and setting the X and Y coordinates to 0 then translating it on drag like I did for the circle and rectangle elements in this fiddle but changing any of the polygon's bounding box properties don't seem to have an affect on the polygon element. However translating for the polygon works.

Is there any other way other than looping through the polygon's 2 dimensional array of coordinates and updating all the coordinate points on to implement a draggable polygon?


Solution

  • I'm really not following all this getBBox() stuff. Why don't you drag the element using the traditional way?

    gPoly.call(d3.drag().on("drag", function(d) {
        d3.select(this).attr("transform", "translate(" +
            (d.x = d3.event.x) + "," + (d.y = d3.event.y) + ")")
    }));
    

    Here is your code with that change:

      d3.select('#poly').on('click', function() {
        new Polygon();
      });
    
      var w = 600,
        h = 500;
      var svgCanvas = d3.select('body').append('svg').attr("width", w).attr("height", h);
    
      function Polygon() {
    
        var polyPoints = [];
        var gContainer = svgCanvas.append('g').classed("outline", true);
        var isDrawing = false;
        var isDragging = false;
        var linePoint1, linePoint2;
        var startPoint;
        var bbox;
        var boundingRect;
        var shape;
        var gPoly;
    
        var polyDraw = svgCanvas.on("mousedown", setPoints)
          .on("mousemove", drawline)
          .on("mouseup", decidePoly);
    
        var dragBehavior = d3.drag().on("drag", alterPolygon);
        //        var dragPolygon = d3.drag().on("drag", movePolygon(bbox));
    
        //On mousedown - setting points for the polygon
        function setPoints() {
    
          if (isDragging) return;
    
    
          isDrawing = true;
    
          var plod = d3.mouse(this);
          linePoint1 = {
            x: plod[0],
            y: plod[1]
          };
    
          polyPoints.push(plod);
    
          var circlePoint = gContainer.append("circle")
            .attr("cx", linePoint1.x)
            .attr("cy", linePoint1.y)
            .attr("r", 4)
            .attr("start-point", true)
            .classed("handle", true)
            .style("cursor", "pointer");
    
    
          // on setting points if mousedown on a handle
          if (d3.event.target.hasAttribute("handle")) {
            completePolygon()
          }
    
        }
    
        //on mousemove - appending SVG line elements to the points
        function drawline() {
    
          if (isDrawing) {
            linePoint2 = d3.mouse(this);
            gContainer.select('line').remove();
            gContainer.append('line')
              .attr("x1", linePoint1.x)
              .attr("y1", linePoint1.y)
              .attr("x2", linePoint2[0] - 2) //arbitary value must be substracted due to circle cursor hover not working
              .attr("y2", linePoint2[1] - 2); // arbitary values must be tested
    
          }
        }
    
        //On mouseup - Removing the placeholder SVG lines and adding polyline
        function decidePoly() {
    
          gContainer.select('line').remove();
          gContainer.select('polyline').remove();
    
          var polyline = gContainer.append('polyline').attr('points', polyPoints);
    
          gContainer.selectAll('circle').remove();
    
          for (var i = 0; i < polyPoints.length; i++) {
            var circlePoint = gContainer.append('circle')
              .attr('cx', polyPoints[i][0])
              .attr('cy', polyPoints[i][1])
              .attr('r', 4)
              .attr("handle", true)
              .classed("handle", true);
    
          }
    
        }
    
        //Called on mousedown if mousedown point if a polygon handle
        function completePolygon() {
          d3.select('g.outline').remove();
    
          gPoly = svgCanvas.append('g')
            .classed("polygon", true);
    
          polyPoints.splice(polyPoints.length - 1);
          //console.log(polyPoints);
    
          polyEl = gPoly.append("polygon")
            .attr("points", polyPoints);
    
          for (var i = 0; i < polyPoints.length; i++) {
            gPoly.append('circle')
              .attr("cx", polyPoints[i][0])
              .attr("cy", polyPoints[i][1])
              .attr("r", 4)
              .call(dragBehavior);
          }
    
          isDrawing = false;
          isDragging = true;
    
          bbox = polyEl._groups[0][0].getBBox();
          var bbox2 = gPoly._groups[0][0].getBBox();
    
    
          bbox.x = 0;
          bbox.y = 0;
          bbox.width = 50;
          bbox.height = 50;
    
    
          //            debugger;
    
          gPoly.datum({
            x: 0,
            y: 0
          })
    
          //console.log(bbox);
          gPoly.attr("transform", function(d) {
            return "translate(" + d.x + "," + d.y + ")"
          });
          //          polyEL.attr("transform", "translate(" + 0 + "," + 0 + ")");
          //
          gPoly.call(d3.drag().on("drag", function(d) {
            d3.select(this).attr("transform", "translate(" + (d.x = d3.event.x) + "," + (d.y = d3.event.y) + ")")
          }));
    
        }
    
        //Altering polygon coordinates based on handle drag
        function alterPolygon() {
    
          if (isDrawing === true) return;
    
          var alteredPoints = [];
          var selectedP = d3.select(this);
          var parentNode = d3.select(this.parentNode);
    
          //select only the elements belonging to the parent <g> of the selected circle
          var circles = d3.select(this.parentNode).selectAll('circle');
          var polygon = d3.select(this.parentNode).select('polygon');
    
    
          var pointCX = d3.event.x;
          var pointCY = d3.event.y;
    
          //rendering selected circle on drag
          selectedP.attr("cx", pointCX).attr("cy", pointCY);
    
          //loop through the group of circle handles attatched to the polygon and push to new array
          for (var i = 0; i < polyPoints.length; i++) {
    
            var circleCoord = d3.select(circles._groups[0][i]);
            var pointCoord = [circleCoord.attr("cx"), circleCoord.attr("cy")];
            alteredPoints[i] = pointCoord;
    
          }
    
          //re-rendering polygon attributes to fit the handles
          polygon.attr("points", alteredPoints);
    
          bbox = parentNode._groups[0][0].getBBox();
          console.log(bbox);
        }
    
        function movePolygon() {
    
        }
    
        function prepareTransform(bboxVal) {
    
          var originalPosition = {
            x: bboxVal.x,
            y: bboxVal.y
          };
    
          console.log(bboxVal);
          console.log(bbox);
    
          bbox.x = 0;
          bbox.y = 0;
    
    
    
    
          //            //render a bounding box
          //            shape.rectEl.attr("x", bbox.x).attr("y", bbox.y).attr("height", bbox.height).attr("width", bbox.width);
          //
          //            //drag points
          //            shape.pointEl1.attr("cx", bbox.x).attr("cy", bbox.y).attr("r", 4);
          //            shape.pointEl2.attr("cx", (bbox.x + bbox.width)).attr("cy", (bbox.y + bbox.height)).attr("r", 4);
          //            shape.pointEl3.attr("cx", bbox.x + bbox.width).attr("cy", bbox.y).attr("r", 4);
          //            shape.pointEl4.attr("cx", bbox.x).attr("cy", bbox.y + bbox.height).attr("r", 4);
    
          return originalPosition;
        }
      }
    h1 {
      text-align: center;
    }
    
    .svg-container {
      display: inline-block;
      position: relative;
      width: 100%;
      padding-bottom: 60%;
      /* depends on svg ratio, for my zebra height/width = 1.2 so padding-bottom = 50% * 1.2 = 60% */
      vertical-align: middle;
      /* top | middle | bottom ... do what you want */
    }
    
    .my-svg {
      /* svg into : object, img or inline */
      display: block;
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      /* only required for <img /> */
      z-index: 0;
    }
    
    svg {
      border: solid 1px rgba(221, 61, 16, 0.71);
    }
    
    .rectangle {
      fill: lightblue;
      stroke: blue;
      stroke-width: 2px;
      fill-opacity: 0.5;
    }
    
    .rectangle-bind {
      fill: none;
      stroke: #081c4e;
      stroke-width: 1px;
      stroke-dasharray: 5;
    }
    
    circle {
      fill: lightgreen;
      stroke: green;
      stroke-width: 2px;
      fill-opacity: 0.5;
    }
    
    .rect-active {
      fill: #1578db;
    }
    
    .pointC-active {
      fill: #FF8F00;
    }
    
    .circle-active {
      fill: #4CAF50;
    }
    
    path {
      fill: #ffb7b3;
      stroke: #ff4736;
      stroke-width: 5px;
    }
    
    polygon {
      fill: #b6eeff;
      fill-opacity: 0.5;
      stroke: #0067ff;
      stroke-width: 2px;
    }
    
    line {
      fill: none;
      stroke: #cd08ff;
      stroke-width: 2px;
    }
    
    polyline {
      fill: none;
      stroke: #563aff;
      stroke-width: 2px;
    }
    
    circle.handle {
      fill: yellow;
      stroke: #cb9c0f;
      stroke-width: 2px;
      cursor: pointer;
    }
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <button id='poly'>Poly</button>

    PS: Don't use _groups[0][0]. Use node() instead.