Click and drag to select and move points on a SVG - javascript

With a SVG with lines and points like this:
svg line { stroke: black; stroke-width: 2px; }
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
<circle id="1a" cx="20" cy="20" r="3"></circle><circle id="1b" cx="20" cy="120" r="3"></circle><line id="1c" x1="20" y1="20" x2="20" y2="120"></line>
<circle id="2a" cx="20" cy="120" r="3"></circle><circle id="2b" cx="60" cy="80" r="3"></circle><line id="2c" x1="20" y1="120" x2="60" y2="80"></line>
<circle id="3a" cx="60" cy="80" r="3"></circle><circle id="3b" cx="100" cy="120" r="3"></circle><line id="3c" x1="60" y1="80" x2="100" y2="120"></line>
<circle id="4a" cx="100" cy="120" r="3"></circle><circle id="4b" cx="140" cy="100" r="3"></circle><line id="4c" x1="100" y1="120" x2="140" y2="100"></line>
<circle id="5a" cx="140" cy="100" r="3"></circle><circle id="5b" cx="100" cy="20" r="3"></circle><line id="5c" x1="140" y1="100" x2="100" y2="20"></line>
</svg>
is there a way with a HTML <svg> to be able to click and drag to select points? So that we can then mouse drag to move these points?
Before re-inventing the wheel, are there ways to do this directly with a <svg>?
For example, how to use the Drag and Drop API (see https://coursesweb.net/javascript/drag-drop-html5-attributes_t) to achieve this:
<tag draggable='true' ondragstart='handler(event)' id='draggable_elm'>Content</tag>
for SVG elements?

Related

SVG connect two points with a line, and automatically update the line if a point is moved

I'd like to connect two points (circles) with a line:
window.onclick = () => {
document.getElementById('c2').setAttribute("cx", 150);
}
<svg>
<circle cx="10" cy="10" r="2" id="c1" />
<circle cx="90" cy="50" r="2" id="c2" />
<line x1="10" y1="10" x2="90" y2="50" stroke="black" />
</svg><br>
Click here to move a circle.
such that if I modify the center of any <circle> with setAttribute("cx", 150) then the line automatically follows the new circle position.
Is there a way to do this with <use>? Something like (pseudo-code):
<svg>
<circle cx="10" cy="10" r="2" id="c1" />
<circle cx="90" cy="50" r="2" id="c2" />
<use x1=xlink:c1:cx y1=xlink:c1:cy x2=xlink:c2:cx y2=xlink:c2:cy stroke="black" type="line" />
</svg>
Goal: I don't want to have to set the coordinates two times, in both the circle and line. Instead I would like to set the coordinates once, and that the line uses a reference to the circle elements.
Note: I have read SVG connect two points with a line but it did not help.
You can use a <marker> that can be placed on start, middle and end of an element.
window.onclick = () => {
document.getElementById('l1').setAttribute("x2", 150);
}
<svg viewBox="0 0 200 100" width="200">
<defs>
<marker id="circle" viewBox="0 0 4 4" refX="2"
refY="2" markerWidth="4" markerHeight="4">
<circle cx="2" cy="2" r="2" />
</marker>
</defs>
<line id="l1" x1="10" y1="10" x2="90" y2="50" stroke="black"
marker-start="url(#circle)" marker-end="url(#circle)"/>
</svg><br>
Click here to move a circle.

Onclick JavaScript Variables

Java script -
I am trying to do this task to get into coding but it's getting a bit confusing
My HTML:
<svg height="400" width="400">
<circle cx="205" cy="203" r="150" fill="black" />
<circle cx="200" cy="200" r="150" fill="#FFCE54"/>
<circle cx="150" cy="160" r="43" fill="black" />
<circle cx="150" cy="160" r="40" fill="white" />
<circle cx="250" cy="160" r="43" fill="black" />
<circle cx="250" cy="160" r="40" fill="white" />
<circle cx="150" cy="160" r="20" fill="black"/>
<circle cx="250" cy="160" r="20" fill="black"/>
<circle cx="145" cy="154" r="5" fill="white"/>
<circle cx="245" cy="154" r="5" fill="white"/>
<path d="M 135.5,260 q 65,45 130,0"
stroke="red" stroke-width="12" fill-opacity="0" />
id="mouth"/>
</svg>
My Javascript code:
faceShape.onclick = function(){
faceShape.setAttribute("fill", "#A0D468");
};
The problem is that when I run the code, I get this error -> Uncaught TypeError: Cannot set property 'onclick' of null"
Please help. Am I doing something wrong?
faceShape does not appear anywhere in your markup. I am guessing based on what you provided that you want to add an id="faceShape" to your <circle> elements or something similar. The error message is saying that there is nothing assigned to faceShape that is why it can't read any property of null.
<svg height="400" width="400">
<circle cx="205" cy="203" r="150" fill="black" />
<circle id="faceShape" cx="200" cy="200" r="150" fill="#FFCE54"/> //added id
<circle cx="150" cy="160" r="43" fill="black" />
<circle cx="150" cy="160" r="40" fill="white" />
<circle cx="250" cy="160" r="43" fill="black" />
<circle cx="250" cy="160" r="40" fill="white" />
<circle cx="150" cy="160" r="20" fill="black"/>
<circle cx="250" cy="160" r="20" fill="black"/>
<circle cx="145" cy="154" r="5" fill="white"/>
<circle cx="245" cy="154" r="5" fill="white"/>
<path d="M 135.5,260 q 65,45 130,0"
stroke="red" stroke-width="12" fill-opacity="0" />
id="mouth"/>
</svg>
I fully changed your javascript to make the click work:
document.getElementById("faceShape").addEventListener("click", change);
function change() {
document.getElementById("faceShape").setAttribute("fill", "#A0D468")
};
Here is a working jsfiddle of what I think you're trying to accomplish: https://jsfiddle.net/g0k5joxq/1/
If not then you can use the code to extrapolate to your circumstances.

JavaScript SVG Zoom moves the SVG

I've written this code for my SVG, when the user scrolls, the svg zooms in/zooms out.
svgRootNode.addEventListener('wheel', function (e) {
let transformationMatrix = this.getAttribute("transform").replace("matrix(", "").replace(")", "").trim().split(" ");
let deltaZoom = e.wheelDelta / 1800;
transformationMatrix[0] = Number(transformationMatrix[0]) + deltaZoom;
transformationMatrix[3] = Number(transformationMatrix[3]) + deltaZoom;
if (transformationMatrix[0] <= 0) {
transformationMatrix[0] = 0.05;
}
if (transformationMatrix[3] <= 0) {
transformationMatrix[3] = 0.05;
}
this.setAttribute("transform", "matrix(" + transformationMatrix.join(" ") + ")");
});
Here's my SVG Markup:
<svg width="2500" height="240" transform="matrix(1 0 0 1 0 0)">
<rect data-id="1" x="20" y="20" width="100" height="100" fill="#03A9F4"></rect>
<circle cx="30" cy="130" r="4" fill="red"></circle>
<circle cx="10" cy="50" r="4" fill="red"></circle>
<text x="70" y="70" fill="rgb(255, 255, 255)" font-family="Verdana" font-size="12" text-anchor="middle" dominant-baseline="middle">Meeting Room</text>
<rect data-id="2" x="140" y="20" width="200" height="100" fill="#8BC34A"></rect>
<circle cx="130" cy="40" r="4" fill="red"></circle>
<text x="240" y="70" fill="rgb(255, 255, 255)" font-family="Verdana" font-size="12" text-anchor="middle" dominant-baseline="middle">Pantry</text>
<rect data-id="3" x="0" y="140" width="500" height="100" fill="#795548"></rect>
<circle cx="250" cy="130" r="4" fill="red"></circle>
<text x="250" y="190" fill="rgb(255, 255, 255)" font-family="Verdana" font-size="12" text-anchor="middle" dominant-baseline="middle">Workspace</text>
<rect data-id="4" x="500" y="140" width="2000" height="100" fill="#000000"></rect>
<circle cx="750" cy="130" r="4" fill="red"></circle>
<text x="1500" y="190" fill="rgb(255, 255, 255)" font-family="Verdana" font-size="12" text-anchor="middle" dominant-baseline="middle">Workspace 2</text>
<circle cx="10" cy="130" r="2" fill="black"></circle>
<circle cx="130" cy="130" r="2" fill="black"></circle>
<circle cx="340" cy="130" r="2" fill="black"></circle>
<circle cx="130" cy="10" r="2" fill="black"></circle>
<circle cx="30" cy="130" r="2" fill="black"></circle>
<circle cx="130" cy="40" r="2" fill="black"></circle>
<circle cx="250" cy="130" r="2" fill="black"></circle>
<circle cx="10" cy="10" r="2" fill="black"></circle>
<circle cx="10" cy="50" r="2" fill="black"></circle>
<line x1="30" y1="120" x2="30" y2="130" stroke="green"></line>
<line x1="30" y1="130" x2="130" y2="130" stroke="green"></line>
<line x1="130" y1="130" x2="130" y2="40" stroke="green"></line>
<line x1="130" y1="40" x2="140" y2="40" stroke="green"></line>
</svg>
Before I scroll, it looks like this:
After I zoom-out using scroll, it becomes this way. It leaves a while gap. How do I prevent that gap?

Create a polygon in SVG from a group of elements in JS

I have an SVG with groups of elements (which are all a uniform shape but the group shape can vary. Obviously putting an outline on the group will give me a rectangle shape. What I am trying to achieve is a polygon that outlines the group giving a rough trace of the items:
http://codepen.io/wroughtec/pen/OXvRrq
(below is an example with circles although have also included a path example as that is what I am actually being given)
<h2>No Outline</h2>
<svg viewbox="0 0 1000 200">
<g>
<circle cx="20" cy="20" r="10" fill="grey" />
<circle cx="50" cy="20" r="10" fill="grey" />
<circle cx="50" cy="50" r="10" fill="grey" />
<circle cx="80" cy="50" r="10" fill="grey" />
<circle cx="110" cy="50" r="10" fill="grey" />
<circle cx="140" cy="50" r="10" fill="grey" />
<circle cx="50" cy="80" r="10" fill="grey" />
<circle cx="80" cy="80" r="10" fill="grey" />
<circle cx="110" cy="80" r="10" fill="grey" />
<circle cx="140" cy="80" r="10" fill="grey" />
<circle cx="170" cy="80" r="10" fill="grey" />
<circle cx="200" cy="80" r="10" fill="grey" />
<circle cx="170" cy="110" r="10" fill="grey" />
<circle cx="200" cy="110" r="10" fill="grey" />
</g>
</svg>
I need to be able to do this automatically in JS but getting the x and y of the circles gives me the center point so my manual one I have had to cheat to expand to the outer shape of the circles and remove unnecessary points (i.e. points on the same line or in the middle).
It sounds like what you're looking for is the convex hull of the points.
D3.js implements this, and a good example can be found here. (The code is rather lengthy, so I won't reproduce it here; the main function of interest is d3.geom.hull)
Note that D3.js v4 was just recently released, and almost all code online (including what I linked) is written for v3. There are significant changes between the two, but v4 still has a convex hull function.

Change an inline SVG's x and y with ecmascript

I am using an inline SVG in an SVG and have set some default x and y values. When I change them, the inline SVG moves accordingly. I am trying to change it with
var inlineSVG = document.getElementById("inlineSVG");
inlineSVG.style.x = "90";
and that adds style="x:90px;" but that doesn't actually affect the element.
It's weird (in my head) because this works with a rect but not with an svg.
Here is my actual code:
<?xml version='1.0' encoding='UTF-8'?>
<svg width='1000' height='360'
xmlns='http://www.w3.org/2000/svg'
xmlns:xlink="http://www.w3.org/1999/xlink"
onload='init(evt)'
>
<script type='text/ecmascript'>
function init(event){
var wing1 = document.getElementById("wing1");
wing1.style.x = "90";
}
</script>
<circle cx="200" cy="140" r="5" fill="red" />
<circle cx="220" cy="170" r="5" fill="red" />
<circle cx="180" cy="170" r="5" fill="red" />
<circle cx="220" cy="220" r="5" fill="red" />
<circle cx="180" cy="220" r="5" fill="red" />
<svg id="wing1" x="280" y="100" viewBox="0 0 350 300">
<g>
<g>
<g>
<ellipse fill="#E6E7E8" cx="229.505" cy="117.813" rx="5.862" ry="4.547"/>
</g>
<g>
<ellipse fill="#E6E7E8" cx="265.931" cy="117.819" rx="5.862" ry="4.547"/>
</g>
</g>
<g>
<g>
<ellipse fill="#E6E7E8" cx="229.191" cy="125.538" rx="5.862" ry="4.547"/>
</g>
<g>
<ellipse fill="#E6E7E8" cx="265.617" cy="125.531" rx="5.861" ry="4.547"/>
</g>
</g>
</g>
<ellipse fill="#E6E7E8" cx="247.244" cy="121.796" rx="20.635" ry="38.017"/>
</svg>
<rect id="square" x="0" y="470" width="50" height="50" fill="#BADA55" style="fill-opacity : 0.5" />
<line x1="0" y1="0" x2="1000" y2="360" style="stroke: yellowgreen;
stroke-width: 1;
stroke-dasharray: 10 1;"></line>
<line x1="0" y1="360" x2="1000" y2="0" style="stroke: yellowgreen;
stroke-width: 1;
stroke-dasharray: 10 1;"></line>
I tried adding !important to the value but it didn't work ( because I guess it doesn't count it as a valid number? ).
The solution is to directly change the x attribute like so:
selector.setAttribute("attr",val);

Categories

Resources