1

I have some d3 code with drag and drop support. I have an imported SVG with a limited polygon area inside. I need to add RECS to this area and limit drag and drop support inside this area boundaries. Does anyone know how to do this? The main problem is I cant do a function to calculate this area because it is variable.

Thank a lot for your help!

Note: I created a jsfiddle link.

http://jsfiddle.net/k3LS3/

SVG File XML (image data has been eliminated)

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="3386"
   height="1498"
   id="svg2"
   version="1.1"
   inkscape:version="0.48.4 r9939"
   sodipodi:docname="SAL_default_at.svg">
  <defs
     id="defs4" />
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.35"
     inkscape:cx="1220.1429"
     inkscape:cy="749"
     inkscape:document-units="px"
     inkscape:current-layer="layer1"
     showgrid="false"
     inkscape:window-width="1600"
     inkscape:window-height="1138"
     inkscape:window-x="-8"
     inkscape:window-y="-8"
     inkscape:window-maximized="1"
     fit-margin-top="0"
     fit-margin-left="0"
     fit-margin-right="0"
     fit-margin-bottom="0"
     borderlayer="false"
     showborder="false"
     inkscape:showpageshadow="false" />
  <metadata
     id="metadata7">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title />
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Capa 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(1343,216.63782)">
    <image
       y="-216.63782"
       x="-1343"
       id="image2993"
       xlink:href="data has been elminated"
       height="1498"
       width="3386" />
  </g>
  <g
     inkscape:groupmode="layer"
     id="layer2"
     inkscape:label="capaAreaTrabajo"
     transform="translate(1343,216.63782)">
    <path
       style="opacity:0.05;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       d="m -1285.7143,58.076468 677.14287,2.857143 -5.71428,551.428569 539.999996,-2.85714 -2.857143,368.57143 -1128.571443,-2.85714 z"
       id="d3AreaTrabajo"
       inkscape:connector-curvature="0"
       inkscape:label="d3AreaTrabajo" />
  </g>
</svg>

D3 call function

function d3plantExplorerGenerarMapa(movilabDataset, w, h, anchoSalaCm, altoSalaCm, dwh, urlImagenFondoSvg, reescalado) {


    var urlFondo2 = urlImagenFondoSvg.replace(".png", "_at.svg");

    d3.xml(urlFondo2, "image/svg+xml", function(xml) {

        var importedNode = document.importNode(xml.documentElement, true);
        d3.select("#d3PlantExplorer").node().appendChild(importedNode);
        var svgTmp = d3.select("#svg2");   


        d3plantExplorerGenerarMapaProcess(movilabDataset, w, h, anchoSalaCm, altoSalaCm, dwh, urlImagenFondoSvg, reescalado, svgTmp);
    });

}



function d3plantExplorerGenerarMapaProcess(movilabDataset, w, h, anchoSalaCm, altoSalaCm, dwh, urlImagenFondoSvg, reescalado, rootSvgImported) {

    console.log("d3plantExplorerGenerarMapa --> init");

    //
    // Imagen de fondo para usar. Representa la planta.
    //
    var urlFondo = urlImagenFondoSvg; 

    //
    // Datos para pintar en formato JSON
    // 
    // NOTA: Si los datos NO viajan no sobreescribimos.
    if(movilabDataset != null) {
        svgJsonData = JSON.parse(movilabDataset);   
        jsonMovilabDataset = svgJsonData;
    }

    //
    // Definimos la escala
    //
    // - Dominio -> Datos REALES que viajarán en el dataset.
    // en nuestro caso posicion X,Y en CM en una sala. Por tanto nuestro dominio es {0, ancho/alto sala}
    //
    // - Rango -> Representa nuestro máximo valor en la VISUAL, es decir, en el explorador.
    // En nuestro caso representa el alto y el ancho DE LA IMAGEN EN NAVEGADOR, es decir {0, ancho/alto imagens svg}
    //
    var widthCm = anchoSalaCm;
    var higthtCm = altoSalaCm; 
    widthScaleCm2Px = d3.scale.linear() //this.
                        .domain([0, widthCm]) 
                        .range([0, w]);
    heightScaleCm2Px = d3.scale.linear() //this.
                        .domain([0, higthtCm]) 
                        .range([0, h]);

    /*
    this.widthScalePx2Cm = d3.scale.linear()
                        .domain([0, w]) 
                        .range([0, widthCm]);
    this.heightScalePx2Cm = d3.scale.linear()
                        .domain([0, h]) 
                        .range([0, higthtCm]);
    */

    //
    // Ejes
    //
    /*
    xAxis = d3.svg.axis()
    .scale(widthScaleCm2Px)
    .orient("bottom")
    .tickSize(-h);

    yAxis = d3.svg.axis()
    .scale(heightScaleCm2Px)
    .orient("left")
    .ticks(5)
    .tickSize(-w);
    */

    //
    // Variable para controlar el zoom.
    //
    var zoomListener = d3.behavior.zoom()
    .on("zoom", d3zoomHandler);

    var zoomListener2 = d3.behavior.zoom()
    .on("zoom", d3zoomHandler2);

    //
    // Variable para controlar los eventos Drag&Drop usado el motor D3
    // Nota: El evento dragend se dispara con el evento onclick.
    //
    var drag = d3.behavior.drag()
    .origin(function(d) { return d; })
    .on("dragstart", d3dragstarted)
    .on("drag", d3dragged) 
    .on("dragend", d3dragended);


    //
    // Comprobamos que no exista la imagen SVG ya creada en el cliente
    // Si NO existe la creamos y si existe borramos todos los elementos
    // creados y repintamos.
    //
    // NOTA: La consistencia de los datos a pintar la mantiene el codigo java del backing bean

    var svgBackgrounImageId = "d3PlantExplorerBackgroundImage";
    var svgBackgrounImageQueryId = "#" + svgBackgrounImageId;
    var svgName = "d3PlantExplorerSvg";
    var svgQueryName = "#" + svgName;

    console.log("d3plantExplorerGenerarMapa [01] --> OK");

    if(rootSvgImported != null) {
        rootSvg = rootSvgImported;
    }

    if(d3.select(svgQueryName).empty()) {
        /*
        var tooltip = d3.select("body")
        .append("div")
        .style("position", "absolute")
        .style("z-index", "10")
        .style("visibility", "hidden")
        .attr("id", "d3PlantExplorerSvgTooltip")
        .text("Sin Datos");

        svg = d3.select("#d3PlantExplorer")
        .append("svg")
        .attr("id", "d3PlantExplorerSvg")
        .attr("width", w) 
        .attr("height", h) 
        .style("border", "1px solid black");
        */ 

        if(rootSvgImported == null) {
            rootSvg = d3.select("#d3PlantExplorer")
            .append("svg")
            .attr("id", svgName)
            .attr("width", w) 
            .attr("height", h) 
            .style("border", "0px solid black")
            .append("g");   
        } 


        if(rootSvgImported == null) {
            imgs = rootSvg.selectAll("image").data([0]); 
                imgs.enter() 
                .append("svg:image")
                .attr("id", svgBackgrounImageId)
                .attr("xlink:href", urlFondo) 
                .attr("x", "0") 
                .attr("y", "0")
                .attr("width", w) 
                .attr("height", h);
        }

        console.log("d3plantExplorerGenerarMapa [02] --> OK");

    } else if(reescalado == 1) {

        console.log("d3plantExplorerGenerarMapa [03] --> OK");

    } else {    

        rootSvg = d3.select(svgQueryName);
        rootSvg.selectAll("rect").data([]).exit().remove();
        rootSvg.attr("width", w)
            .attr("height", h); 

        imgs = d3.select(svgBackgrounImageQueryId);
        imgs.attr("width", w)
            .attr("height", h); 

        console.log("d3plantExplorerGenerarMapa [04] --> OK");
    }


    //
    // Añadimos los racks
    //

    svg = rootSvg.append("g");
    //svg = rootSvg;
    svg.selectAll("rect") 
       .data(svgJsonData) 
       .enter() 
       .append("rect")
       .on("mousedown", function(d) {
           d3.event.stopPropagation();
           d3.event.preventDefault();
           console.log("rect [mousedown] --> OK");

           d3highlightElement(d.cfgInsRack_identificador, fillColorOn, true);
       })
       .on("click", function(d) {
            d3.event.stopPropagation();
            d3.event.preventDefault();
            console.log("rect [click] --> OK");

            // Actualizamos el tooltip
            d3.select("#d3TooltipRackSeleccionado").text(d.cfgInsRack_info);
            d3.select("#d3TooltipRackNoSeleccionado").text(d.cfgInsRack_info);
            d3generateMouseTooltip(d.cfgInsRack_identificador, null, 0);

            // Comprobamos el click
            if(d3CiSelected != d.cfgInsRack_identificador) {
                // Marcamos el elemento actual como selccionado
                d3highlightElement(d3CiSelected, null, false);
                d3CiSelected = d.cfgInsRack_identificador;
                d3highlightElement(d3CiSelected, fillColorOn, true);

                // Mostramos y ocultamos el panel de información de rack
                //d3ShowHideElement("d3PanelOperacionesRacks",1,0,0);
                d3ShowHideElement("d3PanelOperacionesRacksRackSeleccionado",1,0,0);
                d3ShowHideElement("d3PanelOperacionesRacksRackNoSeleccionado",0,0,0);

                // Notificamos a Movilab el CI que estamos seleccionando.
                d3plantExplorerClickHandler(2, d3.mouse(this), d);

            } else {

            }

        })
        .on("dblclick", function(d) {
            d3.event.stopPropagation();
            d3.event.preventDefault();
            //d3plantExplorerClickHandler(2, d3.mouse(this), d);
            //d3plantExplorerClickHandler(51, d3.mouse(this), d);
        })
        .on("contextmenu", function(d) {
            d3.event.stopPropagation();
            d3.event.preventDefault();

            // Actualizamos el tooltip
            d3.select("#d3TooltipRackSeleccionado").text(d.cfgInsRack_info);
            d3.select("#d3TooltipRackNoSeleccionado").text(d.cfgInsRack_info);

            //d3plantExplorerClickHandler(2, d3.mouse(this), d);
            //d3plantExplorerClickHandler(51, d3.mouse(this), d);
            // Mostramos el panel de informacion
            //d3PanelInfoCiShowHide(0);
            // Menu contextual para el plano
            d3ContextMenuAttach(d.cfgInsRack_identificador, contextualMenuRectDataSet, "contextmenu");

        })
        .on("mouseover", function(d) {
            if(!isDragging) {
                d3.event.stopPropagation();
                d3.event.preventDefault();
                console.log("rect [mouseover] --> OK");

                // Menu contextual para el plano
                d3highlightElement(d.cfgInsRack_identificador, fillColorOn, true);

                // Actualizamos el tooltip
                d3.select("#d3TooltipRackNoSeleccionado").text(d.cfgInsRack_info);

                // Tooltip
                d3generateMouseTooltip(d.cfgInsRack_identificador, d.cfgInsRack_info + "<br>Estado: " + d.cfgInsRack_estado, 1);    
            }
            // Notificamos a Movilab el CI que estamos seleccionando.
            //d3plantExplorerClickHandler(2, d3.mouse(this), d);

        })
        .on("mouseout", function(d) {
            d3.event.stopPropagation();
            d3.event.preventDefault();
            console.log("rect [mouseout] --> OK");

            d3.select("#d3TooltipRackNoSeleccionado").text("");
            d3generateMouseTooltip(d.cfgInsRack_identificador, null, 0);

            if(d3CiSelected != d.cfgInsRack_identificador) {
                d3highlightElement(d.cfgInsRack_identificador, d.color, false); 
            }
            //d3PanelInfoCiShowHide(0);
        })
       .call(drag)
       .attr("id", function(d) { return d.cfgInsRack_identificador; })
       .attr("x", function(d) {
           var px = widthScaleCm2Px(d.x_axisCm);
           console.log("d3Info [" + d.cfgInsRack_identificador + "] --> " + "(x)=px:" + px + ", cm:" + d.x_axisCm);
           d.x_axisPx = px;
           return d.x_axisPx; 
       })      
       .attr("y", function(d) {
           var px = heightScaleCm2Px(d.y_axisCm);
           console.log("d3Info [" + d.cfgInsRack_identificador + "] --> " + "(y)=px:" + px + ", cm:" + d.y_axisCm);
           d.y_axisPx = px;
           return d.y_axisPx;  
       })      
       .attr("width", function(d) {
           var px = widthScaleCm2Px(d.x_anchoCm);
           console.log("d3Info [" + d.cfgInsRack_identificador + "] --> " + "(width)=px:" + px + ", cm:" + d.x_anchoCm);
           d.x_anchoPx = px;
           return d.x_anchoPx; 
       })
       .attr("height", function(d) {
           var px = heightScaleCm2Px(d.y_altoCm);
           console.log("d3Info [" + d.cfgInsRack_identificador + "] --> " + "(height)=px:" + px + ", cm:" + d.y_altoCm);
           d.y_altoPx = px;
           return d.y_altoPx; 
       })
       //.attr("rx", 10)         // curva redondeada
       //.attr("ry", 10)        // curva redondeada
       .attr("stroke", strokeOpacityColorOff)
       .attr("fill", function(d) { return d.color; })
       .attr("stroke-opacity", strokeOpacityOff) //0.1
       .attr("fill-opacity", function(d){
           if(d.ocupado==1) {
               return 0.5;
           } 
           return 0.5;
       })
       .attr("display", function(d) {
           // Verificamos que tenemos que verlo, es decir que la posicion no sea 0,0
           if(d.x_axisCm == 0 || d.y_axisCm == 0) {
               return "none";
           }
           return "block";
       });


    console.log("d3plantExplorerGenerarMapa [07] --> OK");

    // Listener para ZOOM
    zoomListener(rootSvg);


    console.log("d3plantExplorerGenerarMapa [08] --> OK");

    // Eventos asociados al "RECT" que acabamos de crear-

    // Menu contextual para el plano
    d3ContextMenuAttach(svgName, contextualMenuSvgDataSet, "contextmenu");

    // Mostramos y ocultamos el panel de información de rack
    //d3ShowHideElement("d3PanelOperacionesRacks",0,0,0);
    d3ShowHideElement("d3PanelOperacionesRacksRackSeleccionado",0,0,0);
    d3ShowHideElement("d3PanelOperacionesRacksRackNoSeleccionado",1,0,0);

    // Al hacer click sobre el plano borramos todos los menus
    rootSvg.on("click", function(d) {
       d3ContextMenuRemove(null);

    })

    console.log("d3plantExplorerGenerarMapa [09] --> OK");

    // Marcamos el SVG como creado.
    svgCreated = true; //this.

    console.log("d3plantExplorerGenerarMapa --> fin");
}
MNM
  • 31
  • 1
  • 5
  • 1
    Show us the code you have attempted so far. Better yet, can you create a plunkr or jsfiddle demonstrating your problem? Take a look at this thread http://stackoverflow.com/help/how-to-ask – António Sérgio Simões Jul 18 '14 at 10:07
  • Sure!Thank your for your help. I need to put inside "id="d3AreaTrabajo" the RECS and limit dragging inside this area. The code is very large. I think a "simple" example should work. Thanks! – MNM Jul 18 '14 at 10:21
  • Here's a question with a similar problem, as in limiting drag and drop boundaries. Check the answer that it was given and see if you can work it out from there: http://stackoverflow.com/questions/15069959/d3-js-scatter-plot-zoom-drag-boundaries-zoom-buttons-reset-zoom-calculate-m – António Sérgio Simões Jul 18 '14 at 10:28
  • I can't solve the problem. Can you give me another tip? thanks. – MNM Jul 18 '14 at 12:37

1 Answers1

2

Problem solved.

1) Draw a SVG.

2) Draw areas (RECTANGLES) and class it with class "area Trabajo Limitada".

3) Draw your draggable elements (RECTANGLES).

4) If you drag an element inside limited area the script will revert it.

Notes:

  • With this script you can limit your drop area or detect collisions easily.

  • You can extend the methods for use circles instead of rects.

Thank to everyone for your help.

HTML

<div id="area">
</div>
<div id="info"></div>

CSS

.areaTrabajoLimitada {
    fill:green;
    fill-opacity: .1;
}
.objeto {
    fill:red;
    fill-opacity: .1;
}
.DnD {
    stroke: red;
}

Script:

//
// Area de trabajo
//
var jsonAreasTrabajo = [{
    "width": 200,
        "height": 200,
        "x": 10,
        "y": 10
}, {
    "width": 200,
        "height": 200,
        "x": 300,
        "y": 10
}];


var xwidth = 400;
var yheight = 400;
var xmin = 50;
var xmax = xmin + xwidth;
var ymin = 50;
var ymax = ymin + yheight;
var isDentroAreaLimitada = false;
var dragPxOrigen = 0;
var dragPxDestino = 0;
var isDragging = false;
var msgDnDNoPermitido = "No drop in this area!"


function puntoDentroArea(point, vs) {

    // Algoritmo basado en
    // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
    //
    //  (x,y) ------------ (xmax,y)
    //    |                    |
    //    |                    |   
    //  (x,ymax) --------- (xmax,ymax)
    //

    console.log("puntoDentroArea -->", point, vs);

    var x = point[0],
        y = point[1];

    var inside = false;
    for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
        var xi = vs[i][0],
            yi = vs[i][1];
        var xj = vs[j][0],
            yj = vs[j][1];

        var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
        if (intersect) inside = !inside;
    }

    console.log("puntoDentroArea -->",inside);

    return inside;
}


function dentroDelAreaLimitada(px, py, w, h) {

    // Creamos los puntos para evaluar. Los puntos son los cuatro puntos
    // del restangulo que estamos arrastrando
    var ipxw = px + parseFloat(w);
    var ipyh = py + parseFloat(h);
    var jsonPuntos = [ 
        {"x":px, "y":py},
        {"x":px, "y": ipyh},
        {"x":ipxw, "y":py},
        {"x":ipxw, "y":ipyh}];


    // Recorremos todas las areas de trabajo limitadas.
    // Estas areas son todos los rectangulos RECT en el SVG 
    // que tengan el atributo class="areaTrabajoLimitada"
    d3.selectAll(".areaTrabajoLimitada").each(function (d, i) {


        // Datos del poligono. Es un rectangulo que delimita el area        
        // Limitada.
        // - Para obtener los datos en NUMEROS hay que llamar a node().getBBox()
        var bbox = d3.select(this).node().getBBox();
        var x = bbox.x;
        var y = bbox.y;
        var width = bbox.width;
        var height = bbox.height;
        var xmax = x + width;
        var ymax = y + height;

        // El poligono respresenta el area limitada
        var poligono = [
            [x, y],
            [x, ymax],
            [xmax, ymax],
            [xmax, y]
        ];

        // Recorremos todos lo puntos del rectangulo q arrastramos.
        var resultado = false;
        for(var i=0; i<jsonPuntos.length;i++){
            // Evaluamos los resultados para cada uno de los puntos del cuadrado.
            //var punto = [px, py];
            var punto = [jsonPuntos[i].x, jsonPuntos[i].y];
            console.log("punto", punto);
            resultado = puntoDentroArea(punto, poligono);      
            if(resultado == true) {
                break;
            }
        }       
        if (resultado == true) {
            isDentroAreaLimitada = resultado;
        }    


    });
}

//
// Define drag behavior
//
//var drag = d3.behavior.drag().on("drag", dragmove);
var drag = d3.behavior.drag()
    .on("dragstart", d3dragstarted)
    .on("drag", d3dragged)
    .on("dragend", d3dragended);

function d3dragstarted(d) {
    isDragging = false; //this.
    d3.event.sourceEvent.stopPropagation(); // cancelamos listeners
    console.log("d3dragstarted");
    dragPxOrigen = d3.select(this).attr("x");
    dragPyOrigen = d3.select(this).attr("y");
}

function d3dragged(d) {
    isDragging = true; //realmente esta moviendolo. //this.
    if (isDragging == true) { //this.
        var x = d3.mouse(this)[0];
        var y = d3.mouse(this)[1];
        d3.select(this)
            .attr("x", x)
            .attr("y", y);
    }
}

function d3dragended(d) {
    var x = d3.mouse(this)[0];
    var y = d3.mouse(this)[1];
    var xoffset = d3.select(this).attr("width");
    var yoffset = d3.select(this).attr("height");

    dentroDelAreaLimitada(x, y, xoffset, yoffset);   
    d3.select("#info").text(x + "," + y + " --> " + isDentroAreaLimitada);

    if (isDragging == true) { //this.       
        if (isDentroAreaLimitada) {
            d3.select(this)
                .attr("x", dragPxOrigen)
                .attr("y", dragPyOrigen);
            alert(msgDnDNoPermitido);
        } else {
            d3.select(this)
                .attr("x", x)
                .attr("y", y);
        }
    }

    isDragging = false; //this.
    isDentroAreaLimitada = false;

    console.log("d3dragended");
}

function dragmove(d) {
    var x = d3.mouse(this)[0];
    var y = d3.mouse(this)[1];
    var xoffset = d3.select(this).attr("width");
    var yoffset = d3.select(this).attr("height");

    dentroDelAreaLimitada(x, y, xoffset, yoffset);
    d3.select("#info").text(x + "," + y + " --> " + isDentroAreaLimitada);
}


//
// Click
//
function click() {
    // Ignore the click event if it was suppressed
    if (d3.event.defaultPrevented) return;
    var x = d3.mouse(this)[0];
    var y = d3.mouse(this)[1];
    console.log(" --> " + veredicto);
}


//var root = d3.select("#d3PlantExplorerSvg");

var root = d3.select("#area").append("svg")
    .attr("id", "mysvg")
    .attr("width", 600)
    .attr("height", 600)
    .style("border", "1px solid black");

var areaTrabajo = root.append("g").selectAll("rect")
    .data(jsonAreasTrabajo)
    .enter()
    .append("rect")
    .attr("id", "areaTrabajo01")
    .attr("x", function (d) {
    return d.x;
})
    .attr("y", function (d) {
    return d.y;
})
    .attr("width", function (d) {
    return d.width;
})
    .attr("height", function (d) {
    return d.height;
})
    .attr("class", "areaTrabajoLimitada");


var objeto = root.append("g").append("rect")
    .attr("x", 300)
    .attr("y", 300)
    .attr("width", 30)
    .attr("height", 30)
    .classed("objeto", true)
    .classed("DnD", true)
    .on("click", click)
    .call(drag);

http://jsfiddle.net/manardella/sQx5m/1/

MNM
  • 31
  • 1
  • 5