简体   繁体   English

d3如何限制拖放区域

[英]d3 How to limit drag and drop area

I have some d3 code with drag and drop support. 我有一些带有拖放支持的d3代码。 I have an imported SVG with a limited polygon area inside. 我有一个导入的SVG,其中的多边形区域有限。 I need to add RECS to this area and limit drag and drop support inside this area boundaries. 我需要将RECS添加到该区域,并在该区域边界内限制拖放支持。 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. 注意:我创建了一个jsfiddle链接。

http://jsfiddle.net/k3LS3/ http://jsfiddle.net/k3LS3/

SVG File XML (image data has been eliminated) SVG文件XML(图像数据已删除)

<?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 D3通话功能

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");
}

Problem solved. 问题解决了。

1) Draw a SVG. 1)绘制SVG。

2) Draw areas (RECTANGLES) and class it with class "area Trabajo Limitada". 2)绘制区域(矩形)并将其分类为“区域Trabajo Limitada”。

3) Draw your draggable elements (RECTANGLES). 3)绘制可拖动元素(矩形)。

4) If you drag an element inside limited area the script will revert it. 4)如果将元素拖动到有限区域内,脚本将还原它。

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 HTML

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

CSS 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/ http://jsfiddle.net/manardella/sQx5m/1/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM