d3如何限制拖放区域

d3 How to limit drag and drop area

本文关键字:区域 拖放 何限制 d3      更新时间:2023-09-26

我有一些d3代码与拖放支持。我有一个导入的SVG,里面有一个有限的多边形区域。我需要将RECS添加到该区域,并在该区域边界内限制拖放支持。有人知道怎么做吗?主要的问题是我不能做一个函数来计算这个面积,因为它是可变的。

谢谢你的帮助!

注意:我创建了一个jsfiddle链接。

http://jsfiddle.net/k3LS3/

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调用函数

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

问题解决

1)绘制SVG。

2)绘制区域(矩形),并将其分类为"area Trabajo Limitada"。

3)绘制可拖动元素(矩形)。

4)如果你把一个元素拖到有限的区域内,脚本将恢复它。

指出:

  • 使用此脚本,您可以限制您的掉落区域或轻松检测碰撞

  • 你可以扩展方法来使用圆而不是矩形

感谢大家的帮助。

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

脚本:

//
// 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/