Drawing an SVG Selection Box with Batik - svg

I notice on many SVG builder tools that almost each element can be resized and rotated. As shown below
What is the most common way to implement that whenever an element whenever is clicked, a bordering line will appear a long side with the small rectangles used for resizing as well as rotating ?
Are these objects "hidden" by default and only visible during mouse click ? Or they have to be drawn on every mousedown and removed on every mouseup ?

Are these objects "hidden" by default and only visible during mouse
click ?
Unless you're using a library that provides that functionality i.e FabricJS, then no you have to draw that selection rectangle yourself.
All the tools you see floating around essentially have code somewhere to create this rectangle you see exactly on top of the selected item - this rectangle is called the visible Bounding Box of the element - in modern browsers this is fetched using the el.getBBox() method.
The returned bounding box gives you all the information you need(x, y , width, height) to draw that selection rectangle yourself.
Typically you have a canvas on which you work (might be a <div>, might be an SVG rectangle etc...) - the concept is not exclusive to HTML5 canvas.
You draw the selection rectangle when clicking an element.
You remove the selection rectangle when clicking on a canvas empty area.
Here is a snippet I've made for this which also allows multi-selection by holding 'Shift'.
const svgns = 'http://www.w3.org/2000/svg'
// Draw selection box if we click on one or more elements.
$('.element').click(function() {
const bbox = $(this)[0].getBBox()
if (shiftKeyDown) {
drawSelectionRect(bbox.x, bbox.y, bbox.width, bbox.height)
return
}
$('.selectionRect').remove()
drawSelectionRect(bbox.x, bbox.y, bbox.width, bbox.height)
})
// Remove selection box(es) if we click on empty area.
$('#svgCanvas').click(() => {
$('.selectionRect').remove()
})
// Helper function which draws a selection box.
const drawSelectionRect = (x, y, width, height) => {
var rect = document.createElementNS(svgns, 'rect')
rect.setAttributeNS(null, 'x', x)
rect.setAttributeNS(null, 'y', y)
rect.setAttributeNS(null, 'height', height)
rect.setAttributeNS(null, 'width', width)
rect.setAttributeNS(null, 'stroke-width', '2px')
rect.setAttributeNS(null, 'stroke', 'red')
rect.setAttributeNS(null, 'fill', 'none')
rect.setAttributeNS(null, 'stroke-dasharray', '5,1')
rect.setAttributeNS(null, 'class', 'selectionRect')
document.getElementById('workarea').appendChild(rect)
}
// Determine if Shift key is being pressed.
let shiftKeyDown = false
$(document).keydown(e => {
if (e.keyCode == 16) {
shiftKeyDown = true
}
})
$(document).keyup(e => {
shiftKeyDown = false
})
#container {
display: block;
height: 320px;
background-color: #eee;
}
.element {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<ul>
<li>Click on any element to draw it's selection box.</li>
<li>Hold down "Shift" to allow multi-selection.</li>
<li>Click anywhere else on the canvas to remove selections.</li>
</ul>
<div id="container">
<svg id="workarea" width="100%" height="100%">
<rect id="svgCanvas" width="100%" height="100%" x="0" y="0" style="fill:transparent"/>
<rect class="element" width="100" height="100" x="50" y="100" style="fill:#009688;" />
<rect class="element" width="75" height="100" x="250" y="150" style="fill:#009688;" />
</svg>
</div>

Related

SVG elements to zoom whole SVG group on click or mouseover

I would like to use the circles within my SVG file to trigger a zoom in centred on the circle. I have got it working with a div acting as the trigger for the zoom but if I instead apply id="pin" to one of the circle elements within the SVG it no longer zooms in. Can anyone tell me why this is?
Is there a better way for me to achieve what I am trying to do? Ideally, I would like it to be possible to click to zoom and then to access other interactivity within the SVG while zoomed in.
If this is not possible is there a simple way to zoom and pan an SVG and to be able to access SVG interactivity while zoomed?
If I have missed something obvious please forgive me, I’m very much still learning the basics!
Rough example:
CodePen link
<div id="pin">click to trigger zoom</div>
<div class="map" id="mapFrame">
<svg class="image" id="mapSVG" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1920 1442.5"" style="
enable-background:new 0 0 1920 924.9;" xml:space="preserve">
<g id="Layer_7" data-name="Layer 7">
<image width="1800" height="1350" transform="translate(0) scale(1.069)" opacity="0.3"
xlink:href="https://media.npr.org/assets/img/2020/07/04/seamus-coronavirus-d3-world-map-20200323_wide-a3888a851b91a905e9ad054ea03e177e23620015.png" />
</g>
<g id="one">
<circle cx="929.664" cy="944.287" r="81.191"/>
</g>
<g id="two">
<circle cx="638.164" cy="456.863" r="81.191" />
</g>
<g id="three">
<circle cx="1266.164" cy="498.868" r="81.191" />
</g>
</svg>
</div>
<script src="app.js"></script>
svg {
width: 100%;
height: auto;
}
#pin {
position: absolute;
height: 65px;
width: 75px;
top: 300px;
left: 550px;
padding: 10px;
background-color: yellow;
}
let imgElement = document.querySelector('#mapFrame');
let pinElement = document.querySelector('#pin');
pinElement.addEventListener('click', function (e) {
imgElement.style.transform = 'translate(-' + 0 + 'px,-' + 0 + 'px) scale(2)';
pinElement.style.display = 'none';
});
imgElement.addEventListener('click', function (e) {
imgElement.style.transform = null;
pinElement.style.display = 'block';
});
When you click on the circle, you are also clicking on the background image as well, triggering two events which is essentially cancelling the zoom. You can see this if you place alert('click 1'); and alert('click 2'); in your listeners.
This doesn't happen on the #pin element because it's outside background div and avoids the event bubbling up. This is solved by adding event.stopPropagation();
Code from your CodePen:
let imgElement = document.querySelector('#mapFrame');
let pinElement = document.querySelector('#one'); //changed to #one
pinElement.addEventListener('click', function (e) {
imgElement.style.transform = 'translate(-' + 0 + 'px,-' + 0 + 'px) scale(2)';
pinElement.style.display = 'none';
event.stopPropagation(); //added to prevent bubbling
});
imgElement.addEventListener('click', function (e) {
imgElement.style.transform = null;
pinElement.style.display = 'block';
});

SVG Foreign Object sizing inconsistent

I'm trying to make 2 html objects in SVGs and further use them inside Vis.js graphs. My first svg (Button) works as intended and looks good. My problem is that when I try to insert the table div the width/height are not what I have set them to be.
Here's what I get:
As you can see the button is larger than the red box even though the red box has a larger width and height (1000px x 800px versus 220px x 68px)!
Here's my JavaScript:
// THE RED BOX
const tableComponent =
`<svg xmlns="http://www.w3.org/2000/svg" width="1000px" height="800px">
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" style="height: 100%; width: 100%; background-color: red;">
<p>Here is a paragraph that requires word wrap</p>
</div>
</foreignObject>
</svg>`;
// THE ORANGE BUTTON
const buttonComponent =
`<svg xmlns="http://www.w3.org/2000/svg" width="220px" height="68px">
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" style="height: 100%; width: 100%;">
<button style="width: 100%; height: 100%; font-size: 20px; color: white; background-color: #FF9700; border-radius: 8px; border: none;">Order Filling</button>
</div>
</foreignObject>
</svg>`;
const tableComponentUrl = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(tableComponent);
const buttonComponentUrl = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(buttonComponent);
const nodes = new DataSet([
{ id: 1, image: buttonComponentUrl, shape: 'image' },
{ id: 2, image: tableComponentUrl, shape: 'image' },
{ id: 3, label: 'Node 3', shape: 'box' }
]);
// create an array with edges
const edges = new DataSet([
{ from: 1, to: 2, arrows: 'to' },
{ from: 1, to: 3, arrows: 'to' }
]);
// create a network
const container = document.querySelector('.data-source-container');
const data = {
nodes: nodes,
edges: edges
};
const options = {
edges: { smooth: false }
};
const network = new Network(container, data, options);
Well, SVG is scalable vector graphics, the inner sizes has nothing to do with the sizes a user sees (more accurately: x dimensions of objects that user sees are x_orig*scale where x_orig is the x dimension inside SVG and scale is a factor set by the x dimension of the whole SVG element).
In other words (assuming the other parts work and if they do, you have invented an interesting hack to extend vis.js' possibilities of inserting html) you have to set the dimensions of your pictures. Try to use size, scale or shapeProperties.useImageSize in corresponding nodes' options. I can't see an option to adjust aspect ratio, though, so this may require additional tweaking inside SVG itself (like setting its dimensions even).
Let me know how it goes, that's quite an interesting approach that you have.

Maintain orientation of some elements in dynamic rotation?

I'm animating rotations of groups of SVG elements using d3.js. However, I want to preserve the orientation of some elements. For example, in this fiddle (code below), the blue dot is the center of the blue circle. The blue dot is displaced vertically from the black dot, which rotates around the yellow center. I want to maintain the vertical relationship between these two dots.
In the fiddle, I maintain the vertical orientation by rotating the "shift" <g> group backwards the same amount that its enclosing group is rotating forwards. This is the same method given in cmonkey's answer here. That works, but I'm wondering whether there are other methods. Is there any way to preserve orientation without an inverse rotation?
Why?
The inverse rotation strategy means that one has to carefully keep the inverse rotations in sync with changes to rotations of outer groups. In full-fledged versions of this code, I use rotations within rotations (within rotations), as in this example. That means summing up all of the outer groups' rotations in order to determine what the inverse rotation should be. I also want to add text labels to SVG elements. Since different text labels will fall within different numbers of rotation groups, each text label will need its own customized rotation if I want to keep the text upright.
Feel free to suggest more D3. I only hand-coded the SVG in these versions in order to get clarity about how I would dynamically generate the SVG with D3 in a later version.
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
.cycle {
stroke : #000000;
fill : none;
}
.movingPointOnCycle {
stroke : #000000;
fill : #000000;
}
#pointB {
stroke : #0000FF;
fill : #0000FF;
}
#epicycle2 {
stroke : #0000FF;
}
.centerOfUniverse {
stroke : #000000;
fill : #000000;
}
.sun {
stroke : #000000;
fill : #F0F000;
}
.mars {
stroke : #000000;
fill : #A00000;
}
.earth {
stroke : #000000;
fill : #00A000;
}
</style>
</head>
<body>
<svg width="400" height="400">
<g transform="translate(200,200) scale(1.3)">
<circle class="sun" r="10"></circle>
<g class="cycle"speed="0.01">
<circle id="deferent" class="cycle" r="60"></circle>
<g class="epicycleCenter" transform="translate(60,0)">
<circle id="pointD" class="movingPointOnCycle" r="2"></circle>
<g class="shift" speed="-0.01" displacement="-25">
<circle id="pointB" class="movingPointOnCycle" r="2"></circle>
<line x1="0" y1="0" x2="0" y2="25" stroke-dasharray="1,2"></line>
<g class="cycle"speed="0.01">
<circle id="epicycle2" class="cycle" r="75"></circle>
</g>
</g>
</g>
</g>
</g>
</svg>
<script type="text/javascript">
var t0 = Date.now();
var svg = d3.select("svg");
d3.timer(function() {
var delta = (Date.now() - t0);
svg.selectAll(".cycle").attr("transform", function(d) {
return "rotate(" + delta * d3.select(this).attr("speed") + ")";
});
svg.selectAll(".shift").attr("transform", function(d) {
return "rotate(" + delta * d3.select(this).attr("speed") + ")"
+
"translate(0," + d3.select(this).attr("displacement") + ")"
;
});
});
</script>
</body>
</html>

Zoom on multiple areas in d3.js

I'm planning to have a geoJSON map inside my svg alongside other svg elements. I would like to be able to zoom (zoom+pan) in the map and keep the map in the same location with a bounding box. I can accomplish this by using a clipPath to keep the map within a rectangular area. The problem is that I also want to enable zooming and panning on my entire svg. If I do d3.select("svg").call(myzoom); this overrides any zoom I applied to my map. How can I apply zoom to both my entire svg and to my map? That is, I want to be able to zoom+pan on my map when my mouse is in the map's bounding box, and when the mouse is outside the bounding box, zoom+pan on the entire svg.
Here's example code: http://bl.ocks.org/nuernber/aeaac0e8edcf7ca93ade.
<svg id="svg" width="640" height="480" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<clipPath id="rectClip">
<rect x="150" y="25" width="400" height="400" style="stroke: gray; fill: none;"/>
</clipPath>
</defs>
<g id="outer_group">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
<g id="svg_map" style="clip-path: url(#rectClip);">
</g>
</g>
</svg><br/>
<script type="text/javascript">
var svg = d3.select("#svg_map");
var mapGroup = svg.append("g");
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.on("zoom", zoomed);
mapGroup.call(zoom);
var pan = d3.behavior.zoom()
.on("zoom", panned);
d3.select("svg").call(pan);
mapGroup.attr("transform", "translate(200,0) scale(2,2)");
d3.json("ne_110m_admin_0_countries/ne_110m_admin_0_countries.geojson", function(collection) {
mapGroup.selectAll("path").data(collection.features)
.enter().append("path")
.attr("d", path)
.attr("id", function(d) { return d.properties.name.replace(/\s+/g, "")})
.style("fill", "gray").style("stroke", "white").style("stroke-width",1);
}
);
function panned() {
var x = d3.event.translate[0];
var y = d3.event.translate[1];
d3.select("#outer_group").attr("transform", "translate("+x+","+y+") scale(" + d3.event.scale + ")");
}
function zoomed() {
previousScale = d3.event.scale;
projection.translate(d3.event.translate).scale(d3.event.scale);
translationOffset = d3.event.translate;
mapGroup.selectAll("path").attr("d", path);
}
</script>
You need two zoom behaviours for that. The first one would be attached to the SVG and the second one to the map. In the zoom handlers you would have to take care of taking the appropriate action for each.

How can we make SVG transparent on Canvas?

how can we achieve this?
I got the SVG in the function, how can i make it transparent on top of canvas?? Currently i have all my functions working on the canvas. But I found out that SVG can do the add and remove function. How can I go about it?
function Add() {
var id = Math.floor(Math.random()*101+1);
x = Math.random() * 550;
y = Math.random() * 250;
if (document.getElementById('amount').value < 50){
document.getElementById('amount').value++;
svg = document.getElementById("main");
// construct uniqueid for the images
uniqueid = "frog" + document.getElementById('amount').value;
//namespaces for SVG
svgNS="http://www.w3.org/2000/svg";
xlinkNS="http://www.w3.org/1999/xlink";
// create a image element
image = document.createElementNS(svgNS, 'image');
// set id and other attributes
image.setAttributeNS(null, "id", uniqueid);
image.setAttributeNS(xlinkNS, "href","jef-frog.gif");
image.setAttributeNS(null, "x", x);
image.setAttributeNS(null, "y", y);
image.setAttributeNS(null, "width", "50");
image.setAttributeNS(null, "height", "50");
// append to svg
svg.appendChild(image);
} else {
alert("we got 50");
}
}
Assuming you are asking about transparency in SVG <image> elements, I'm pleased to say that it works just fine:
Demo: http://jsfiddle.net/XBCEK/
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink">
<image xl:href="http://phrogz.net/tmp/alphaball.png"
x="20" y="30" width="128" height="128" />
<image xl:href="http://phrogz.net/tmp/hand.gif"
x="220" y="30" width="32" height="32" />
</svg>​
If you embed that SVG on a page along with the following CSS:
body { background:url(http://phrogz.net/tmp/grid.gif) }
svg { background:rgba(255,0,0,0.3) /*…*/ }
…then you will see that:
The background of the SVG is transparent by default. We can even provide a low-opacity color background that lets the background of the page (the grid) show through.
The background of both 8-bit-transparency PNG (the ball) and 1-bit transparency GIF (the hand) allow the background of the SVG/page to shine through correctly.
​

Resources