简体   繁体   中英

How to use javascript to find the (svg) position of preexisting elements in an inline svg

I am trying to use javascript to draw lines between different svg elements. The svg elements are in a preexisting svg image (created with inkScape) which is loaded inline.

I am getting hung up finding the proper coordinates of the existing svg elements relative to the svg document itself for purposes of drawing the lines.

See the following snippet for example. I'm trying to implement the FindSVGPosition function.

 $(function() { $("[id^=sys_]").addClass("sysGroup"); $(".sysGroup").click(function(e) { var sysElement = $(this); var id = $(sysElement).attr("id"); var sysName = id.substr(4); var metaNameId = "meta_" + sysName; var metaElmnt = $("#" + metaNameId); if (metaElmnt != undefined) { //showCenter('system1'); if ($(metaElmnt).is(":visible")) { $(metaElmnt).hide("fast"); } else { var position = findDOMPosition(sysName); $(metaElmnt).css( "top", window.scrollY + position.height + position.y + 2 ); $(metaElmnt).css("left", window.scrollX + position.x); $(metaElmnt).show("fast"); } } }); function drawPath(fromSystemName, toSystemName) { var pathBetween = findPathBetween(fromSystemName, toSystemName); var newLine = document.createElementNS( "http://www.w3.org/2000/svg", "line" ); newLine.setAttribute( "id", pathBetween.fromSystemName + "_" + pathBetween.toSystemName ); newLine.setAttribute("x1", pathBetween.fromX); newLine.setAttribute("y1", pathBetween.fromY); newLine.setAttribute("x2", pathBetween.toX); newLine.setAttribute("y2", pathBetween.toY); newLine.setAttribute("stroke", "red"); $("#svg8").append(newLine); return pathBetween; } function findPathBetween(fromSystemName, toSystemName) { var fromPosition = findSVGPosition(fromSystemName); var toPosition = findSVGPosition(toSystemName); if (fromPosition == null || toPosition == null) return null; //center to center var response = { fromSystem: fromSystemName, fromX: fromPosition.x + fromPosition.width / 2, fromY: fromPosition.top + fromPosition.height / 2, fromSide: "center", toSystem: toSystemName, toX: toPosition.x + toPosition.width / 2, toY: toPosition.top + toPosition.height / 2, toSide: "center" }; return response; } function findSVGPosition(systemName) { //dx, dy, d.top, d.right, d.bottom, d.height } function findDOMPosition(systemName) { //dx, dy, d.top, d.right, d.bottom, d.height var systemElement = $("#sys_" + systemName); if (systemElement == null) return null; return $(systemElement) .get(0) .getBoundingClientRect(); } }); 
 #systemDiagram{ display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .meta{ width: 350px; font-size: smaller; } p, ul{ margin-top: 0px; } .metaHeader{ font-size: large; } text:hover{ cursor: default; } .sysGroup:hover{ cursor: pointer; } .sysGroup text:hover{ cursor: pointer; } #info-box, .meta { display: none; position: absolute; top: 0px; left: 0px; z-index: 1; background-color: #eaeded; border: 2px solid #239b56; border-radius: 5px; padding: 5px; font-family: arial; } 
 <script src="https://code.jquery.com/jquery-2.2.4.min.js"> </script> <div> <p>lorem ipsum, lorem ipsum, lorem ipsum - lorem other ipsum. lorem ipsum, lorem ipsum, lorem ipsum - lorem other ipsum. lorem ipsum, lorem ipsum, lorem ipsum - lorem other ipsum.</p> <p>lorem ipsum, lorem ipsum, lorem ipsum - lorem other ipsum. lorem ipsum, lorem ipsum, lorem ipsum - lorem other ipsum. lorem ipsum, lorem ipsum, lorem ipsum - lorem other ipsum.</p> </div> <div id="info-box"></div> <div class="meta" id="meta_system1"> <div><span class="metaHeader">Description</span></div> <p>description</p> </div> <div class="meta" id="meta_system2"> <div>Something here for system2</div> </div> <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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="14in" height="8.5in" viewBox="0 0 355.6 215.9" version="1.1" id="svg8" inkscape:version="0.92.4 (5da689c313, 2019-01-14)" sodipodi:docname="drawing.02.svg"> <defs id="defs2" /> <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.0404412" inkscape:cx="485.6749" inkscape:cy="543.80097" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1920" inkscape:window-height="1018" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" units="in" /> <metadata id="metadata5"> <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></dc:title> </cc:Work> </rdf:RDF> </metadata> <g inkscape:groupmode="layer" id="layer2" inkscape:label="systemGroups" /> <g inkscape:groupmode="layer" id="layer3" inkscape:label="ancillary" style="display:none" sodipodi:insensitive="true"> <rect style="display:inline;opacity:0.4;fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.23668619" id="rect175" width="115.25188" height="27.459745" x="127.27588" y="14.725033" /> </g> <g inkscape:label="systems" inkscape:groupmode="layer" id="layer1" transform="translate(0,-81.1)" style="display:inline"> <g id="sys_system1" transform="translate(15.595292,0.08990834)" inkscape:label="#g164"> <rect style="fill:#000080;fill-opacity:1;stroke-width:0.1389997" y="102.51232" x="48.695206" height="12.662203" width="23.878363" id="rect47" inkscape:label="#rect4524-9" /> <text id="text51" y="110.01817" x="51.767498" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;line-height:1.25;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" xml:space="preserve"><tspan style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';fill:#ffffff;fill-opacity:1;stroke-width:0.26458332" y="110.01817" x="51.767498" id="tspan49" sodipodi:role="line">System1</tspan></text> </g> <rect style="fill:none;fill-opacity:1;stroke-width:0.26458332" id="rect95" width="177.46696" height="27.528765" x="10.156241" y="95.21151" /> <rect style="fill:none;fill-opacity:1;stroke-width:0.26458332" id="rect97" width="133.36749" height="39.555889" x="19.777945" y="94.142426" /> <g id="sys_system2" inkscape:label="#g90" transform="translate(-5.0449918)"> <rect style="fill:#aa8800;fill-opacity:1;stroke-width:0.13352577" y="139.75128" x="70.257324" height="12.662203" width="22.034693" id="rect47-1" inkscape:label="#rect4524-9" /> <text id="text51-2" y="147.25714" x="73.32962" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;line-height:1.25;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" xml:space="preserve"><tspan style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';fill:#000000;fill-opacity:1;stroke-width:0.26458332" y="147.25714" x="73.32962" id="tspan49-8" sodipodi:role="line">System2</tspan></text> </g> </g> </svg> 

I am able to find the coordinates of the svg elements relative to the DOM and position a div correctly under those elements.

I am able to draw a line on the svg using javascript at arbitrary start & endpoints.

I am unable to access the svg attributes (x,y,height,width) of the existing svg elements relative to the svg image.

Most of the examples I can find utilize the contentDocument of the svg element. But in my case, no matter what I've tried - the contentDocument is null.

Since I'm not triggering this action on load of the page - I'm confident that the svg should be fully loaded in the dom when the code runs.

It seems like this should be easily possible without any libraries, but I've been trying to figure it out for too long, and could really use some help.

  1. Firstly, you are making things hard for yourself because of all the transform attributes on the elements in the SVG. My first recommendation would be to get rid of them all.

    The easiest way to do that in Inkscape is to ungroup and then regroup the objects.

  2. If you do that, your function is simple:

     function findSVGPosition(systemName) { return document.getElementById(systemName).getBBox(); } 

    The getBBox() function returns the bounding box of the SVG element. But the bounding box does not account for the transforms on the element or its parent elements. The bounds won't be useful if there are transforms. That's the reason why we got rid of them in step #1.

  3. Next, fix the typos in findPathBetween() : The .top should .y .

  4. Finally, you need to call the drawPath() function.

     drawPath('sys_system1', 'sys_system2'); 
  5. But the line is on top of the boxes. So you'll probably want to do a .prepend() , instead of an .append() . So that the line is before - and thus underneath - the boxes.

     $("#svg8").prepend(newLine); 

Then you end up with this.

 function drawPath(fromSystemName, toSystemName) { var pathBetween = findPathBetween(fromSystemName, toSystemName); var newLine = document.createElementNS( "http://www.w3.org/2000/svg", "line" ); newLine.setAttribute( "id", pathBetween.fromSystemName + "_" + pathBetween.toSystemName ); newLine.setAttribute("x1", pathBetween.fromX); newLine.setAttribute("y1", pathBetween.fromY); newLine.setAttribute("x2", pathBetween.toX); newLine.setAttribute("y2", pathBetween.toY); newLine.setAttribute("stroke", "red"); $("#svg8").prepend(newLine); return pathBetween; } function findPathBetween(fromSystemName, toSystemName) { var fromPosition = findSVGPosition(fromSystemName); var toPosition = findSVGPosition(toSystemName); if (fromPosition == null || toPosition == null) return null; //center to center var response = { fromSystem: fromSystemName, fromX: fromPosition.x + fromPosition.width / 2, fromY: fromPosition.y + fromPosition.height / 2, fromSide: "center", toSystem: toSystemName, toX: toPosition.x + toPosition.width / 2, toY: toPosition.y + toPosition.height / 2, toSide: "center" }; return response; } function findSVGPosition(systemName) { return document.getElementById(systemName).getBBox(); } drawPath('sys_system1', 'sys_system2'); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <?xml version="1.0" encoding="UTF-8" standalone="no"?> <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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="14in" height="8.5in" viewBox="0 0 355.6 215.9" version="1.1" id="svg8" inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" sodipodi:docname="line.svg"> <defs id="defs2" /> <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.0404412" inkscape:cx="166.09894" inkscape:cy="543.80097" inkscape:document-units="mm" inkscape:current-layer="svg8" showgrid="false" inkscape:window-width="2560" inkscape:window-height="1378" inkscape:window-x="1592" inkscape:window-y="-8" inkscape:window-maximized="1" units="in" /> <metadata id="metadata5"> <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:groupmode="layer" id="layer2" inkscape:label="systemGroups" style="display:inline" /> <g inkscape:groupmode="layer" id="layer3" inkscape:label="ancillary" style="display:none" sodipodi:insensitive="true"> <rect style="display:inline;opacity:0.4;fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.23668619" id="rect175" width="115.25188" height="27.459745" x="127.27588" y="14.725033" /> </g> <g id="g864" inkscape:label="systems"> <g id="sys_system1"> <rect style="fill:#000080;fill-opacity:1;stroke-width:0.1389997" y="21.502226" x="64.290497" height="12.662203" width="23.878363" id="rect47" inkscape:label="#rect4524-9" /> <text id="text51" y="29.008078" x="67.362793" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;line-height:1.25;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" xml:space="preserve"><tspan style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';fill:#ffffff;fill-opacity:1;stroke-width:0.26458332" y="29.008078" x="67.362793" id="tspan49" sodipodi:role="line">System1</tspan></text> </g> <rect style="fill:none;fill-opacity:1;stroke-width:0.26458332" id="rect95" width="177.46696" height="27.528765" x="10.156241" y="14.111509" /> <rect style="fill:none;fill-opacity:1;stroke-width:0.26458332" id="rect97" width="133.36749" height="39.555889" x="19.777945" y="13.042425" /> <g id="sys_system2"> <rect style="fill:#aa8800;fill-opacity:1;stroke-width:0.13352577" y="58.651283" x="65.212334" height="12.662203" width="22.034693" id="rect47-1" inkscape:label="#rect4524-9" /> <text id="text51-2" y="66.157143" x="68.28463" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;line-height:1.25;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" xml:space="preserve"><tspan style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';fill:#000000;fill-opacity:1;stroke-width:0.26458332" y="66.157143" x="68.28463" id="tspan49-8" sodipodi:role="line">System2</tspan></text> </g> </g> </svg> 

Update - better version

Here's a better version that doesn't require you to remobve all the transforms in Inkscape.

In this version, we pick our line endpoints, then use getCTM() to find the total transform from the element to the page.

The slight complication is that getCTM() includes all the transformations. Including the transform that is caused by the viewBox scaling. But we don't want the transform to include that last step. So, to fix that, what we do is call getCTM for the root <svg> element, then use that to cancel out the viewBox part of the transform.

 function drawPath(fromSystemName, toSystemName) { var pathBetween = findPathBetween(fromSystemName, toSystemName); var newLine = document.createElementNS( "http://www.w3.org/2000/svg", "line" ); newLine.setAttribute( "id", pathBetween.fromSystem + "_" + pathBetween.toSystem ); newLine.setAttribute("x1", pathBetween.fromX); newLine.setAttribute("y1", pathBetween.fromY); newLine.setAttribute("x2", pathBetween.toX); newLine.setAttribute("y2", pathBetween.toY); newLine.setAttribute("stroke", "red"); $("#svg8").prepend(newLine); return pathBetween; } function findPathBetween(fromSystemName, toSystemName) { var fromPosition = findSVGPosition(fromSystemName); var toPosition = findSVGPosition(toSystemName); if (fromPosition == null || toPosition == null) return null; //center to center var response = { fromSystem: fromSystemName, fromX: fromPosition.x, fromY: fromPosition.y, fromSide: "center", toSystem: toSystemName, toX: toPosition.x, toY: toPosition.y, toSide: "center" }; return response; } function findSVGPosition(systemName) { var elem = document.getElementById(systemName); var bbox = elem.getBBox(); var point = elem.ownerSVGElement.createSVGPoint(); point.x = bbox.x + bbox.width / 2; point.y = bbox.y + bbox.height / 2; var elemToViewportTransform = elem.getCTM(); // includes SVG scaling var svgToViewportTransform = getViewportTransform(elem); return point.matrixTransform(elemToViewportTransform) // apply elem transform .matrixTransform(svgToViewportTransform.inverse()); // but take off the svg scaling } function getViewportTransform(elem) { if (elem.ownerSVGElement.getCTM) { var result = elem.ownerSVGElement.getCTM() if (result != null) return result; } // Workaround for Firefox and other browsers that don't support transform // on the `<svg> element yet (it's an SVG2 thing). var svg = elem.ownerSVGElement; var g = document.createElementNS(svg.namespaceURI, "g"); svg.appendChild(g); var result = g.getCTM(); svg.removeChild(g); return result; } drawPath('sys_system1', 'sys_system2'); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="14in" height="8.5in" viewBox="0 0 355.6 215.9" version="1.1" id="svg8" inkscape:version="0.92.4 (5da689c313, 2019-01-14)" sodipodi:docname="drawing.02.svg"> <defs id="defs2" /> <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.0404412" inkscape:cx="485.6749" inkscape:cy="543.80097" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1920" inkscape:window-height="1018" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" units="in" /> <metadata id="metadata5"> <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></dc:title> </cc:Work> </rdf:RDF> </metadata> <g inkscape:groupmode="layer" id="layer2" inkscape:label="systemGroups" /> <g inkscape:groupmode="layer" id="layer3" inkscape:label="ancillary" style="display:none" sodipodi:insensitive="true"> <rect style="display:inline;opacity:0.4;fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.23668619" id="rect175" width="115.25188" height="27.459745" x="127.27588" y="14.725033" /> </g> <g inkscape:label="systems" inkscape:groupmode="layer" id="layer1" transform="translate(0,-81.1)" style="display:inline"> <g id="sys_system1" transform="translate(15.595292,0.08990834)" inkscape:label="#g164"> <rect style="fill:#000080;fill-opacity:1;stroke-width:0.1389997" y="102.51232" x="48.695206" height="12.662203" width="23.878363" id="rect47" inkscape:label="#rect4524-9" /> <text id="text51" y="110.01817" x="51.767498" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;line-height:1.25;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" xml:space="preserve"><tspan style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';fill:#ffffff;fill-opacity:1;stroke-width:0.26458332" y="110.01817" x="51.767498" id="tspan49" sodipodi:role="line">System1</tspan></text> </g> <rect style="fill:none;fill-opacity:1;stroke-width:0.26458332" id="rect95" width="177.46696" height="27.528765" x="10.156241" y="95.21151" /> <rect style="fill:none;fill-opacity:1;stroke-width:0.26458332" id="rect97" width="133.36749" height="39.555889" x="19.777945" y="94.142426" /> <g id="sys_system2" inkscape:label="#g90" transform="translate(-5.0449918)"> <rect style="fill:#aa8800;fill-opacity:1;stroke-width:0.13352577" y="139.75128" x="70.257324" height="12.662203" width="22.034693" id="rect47-1" inkscape:label="#rect4524-9" /> <text id="text51-2" y="147.25714" x="73.32962" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;line-height:1.25;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" xml:space="preserve"><tspan style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333311px;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI';fill:#000000;fill-opacity:1;stroke-width:0.26458332" y="147.25714" x="73.32962" id="tspan49-8" sodipodi:role="line">System2</tspan></text> </g> </g> </svg> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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