D3 svg click event and circle click event overlapping - svg

The problem I am trying to solve is the following :
I create lets say an svg of 500x500 pixels.
In this svg I append a circle of 50px radius.
I add a click event on the circle so that it's color changes to red.
I add a click event on the rest of the svg so that the circle's color changes to green.
Is this possible?
I have tryed to place the click event on the svg and it covers the entire 500x500px preventing the click event attached to the circle to work.
I have been hinted to use stopPropagation, but was unsuccessfull using it.
var svg = d3.select("#cv")
.attr("height", 300)
.attr("width", 300)
.style("border", "1px solid red")
.on("click", function(d) {
circ.style("fill", "green")
});
var circ = svg
.append("circle")
.attr("r", 100)
.attr("cx", 100)
.attr("cy", 100)
.attr("stroke", "black")
.style("fill", "grey")
.on("click", function(d) {
circ.style("fill", "red")
});
#cv {
width: 300px;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="cv">
<defs>
<pattern id="image" x="0" y="0" patternUnits="userSpaceOnUse" height="200" width="200">
<image x="0" y="0" href="https://i.stack.imgur.com/skwGx.jpg" width="200" height="200"></image>
</pattern>
</defs>
</svg>

You can use a trick for this: append a white rectangle with the same dimensions as the SVG, and add the click listener to it. Make sure you add it first, because SVG has no z-index, but instead works by drawing each item over all previous items. So the first item you define is essentially the background.
var svg = d3.select("#cv")
.attr("height", 300)
.attr("width", 300)
.style("border", "1px solid red");
var bg = svg.append("rect")
.attr("width", 300)
.attr("height", 300)
.attr("fill", "white")
.on("click", function(d) {
circ.style("fill", "green")
});
var circ = svg
.append("circle")
.attr("r", 100)
.attr("cx", 100)
.attr("cy", 100)
.attr("stroke", "black")
.style("fill", "grey")
.on("click", function(d) {
circ.style("fill", "red")
});
#cv {
width: 300px;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="cv">
<defs>
<pattern id="image" x="0" y="0" patternUnits="userSpaceOnUse" height="200" width="200">
<image x="0" y="0" href="https://i.stack.imgur.com/skwGx.jpg" width="200" height="200"></image>
</pattern>
</defs>
</svg>

Related

SVG - have line centered vertically without knowing svg height

I have different SVGs in the project. I need to be able to programatically add a line element at the exact vertical middle of each SVG.
I tried to set the y1 and y2 coordinates as ‘50%’ but that is not honored when the SVG is scaled either by transform scale or viewBox. One of my other requirements is to have those SVGs scaled often.
I could ,of course, start calculating bounding box of each SVG on each scale change, and from there the vertical input but that sounds not elegant
The example is just something to work on. It has a line vertical coordinates set to 50% which are not honoured when a viewBox is set (button click). The blue line is no longer at the middle of the SVG when scaled...
function myFunction(){
document.getElementById("maxi").setAttribute("viewBox","0,0,492,124");
}
<svg id="maxi" version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="246" height="62" font-size="23px" xml:space="preserve" >
<line id="greenline" x1="0" y1="31" x2="232" y2="31" stroke="#00FF00" stroke-width="4"/>
<line id="blueline" x1="0" y1="50%" x2="232" y2="50%" stroke="#0000FF"/>
<path class="cutContour" fill="none" stroke="#EC008C" stroke-miterlimit="3.8637" d="M6.8,2.3H225
c2.3,0,4.3,1.9,4.3,4.3v48.2c0,2.3-1.9,4.3-4.3,4.3H6.8c-2.3,0-4.3-1.9-4.3-4.3V6.6C2.5,4.2,4.4,2.3,6.8,2.3z"/>
</svg>
<input type="button" value="Click Me" onClick="myFunction();">
If the position of the line in the image doesn't need to vary - in other words if it's a static image - then transforms or viewBox manipulation is not required to resize the image. You can vary the CSS (or HTML attributes) of the SVG tag itself to resize the image. It is common to express the coordinates of the line as absolute values rather than percentages, but as you can see from #line2 below, the effect is identical:
For example (with jQuery):
<style>
svg {
outline: 1px dotted grey;
width: 300px;
}
#shrink {
display: none;
}
#line1 {
stroke: red;
stroke-width: 5px;
}
#line2 {
stroke: white;
stroke-width: 2px;
}
</style>
<p>
<button id="grow">Grow</button>
<button id="shrink">Shrink</button>
</p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-100 0 300 100">
<line x1="-50" y1="50" x2="150" y2="50" id="line1"/>
<line x1="-40" y1="50%" x2="140" y2="50%" id="line2"/>
</svg>
<script>
$(function() {
$('#grow').click(function() {
$('svg').animate({
width: "800px"
}, 500, function() {
$('#grow').hide();
$('#shrink').show();
});
});
$('#shrink').click(function() {
$('svg').animate({
width: "300px"
}, 500, function() {
$('#shrink').hide();
$('#grow').show();
});
});
});
</script>
Codepen: https://codepen.io/MSCAU/pen/eLMOVj with additional CIRCLE and RECT behind the lines.
I'm not very sure I understand what you are asking. Please take a look & tell me if this is what you need.
function myFunction(){
let newWidth = 492;
document.getElementById("maxi").setAttribute("viewBox",`0,0,${newWidth},124`);
blueline.setAttributeNS(null,"x1", newWidth/2);
blueline.setAttributeNS(null,"x2", newWidth/2);
blueline.setAttributeNS(null,"y2", 124);
}
svg{border:1px solid;}
<svg id="maxi" version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="246" height="62" font-size="23px" xml:space="preserve" >
<line id="greenline" x1="0" y1="31" x2="232" y2="31" stroke="#00FF00" stroke-width="4"/>
<line id="blueline" x1="123" y1="0" x2="123" y2="62" stroke="#0000FF"/>
<path class="cutContour" fill="none" stroke="#EC008C" stroke-miterlimit="3.8637" d="M6.8,2.3H225
c2.3,0,4.3,1.9,4.3,4.3v48.2c0,2.3-1.9,4.3-4.3,4.3H6.8c-2.3,0-4.3-1.9-4.3-4.3V6.6C2.5,4.2,4.4,2.3,6.8,2.3z"/>
</svg>
<input type="button" value="Click Me" onClick="myFunction();">

cannot draw proper position on transformed SVG [duplicate]

I'm using the svg jquery plugin by Keith Wood, not the HTML5 canvas.
I define my svg image like this to scale my svg triangle image to fit its div container:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" viewBox="0 0 299 215" >
<g>
<polygon points="1,1 299,1 149,210" fill="blue" stroke="blue" stroke-width="0" class="votenow"/>
</g>
</svg>
But how do I then match the coordinate systems?
I want to capture the mouse location at some point over the triangle and draw a circle at those X Y coordinates, but the circle gets drawn in a different location because the coordinate systems don't match.
So a circle would be drawn at point 10,10 but appear to be at 50,60 for example.
How do people cope with this?
Thanks.
Final Solution: Using the JQuery plugin to draw the circle and getScreenCTM() to calculate the points.
Perhaps I no longer require the JQuery plugin but it will do for now. Couldn't see how to do it using only the plugin.
$('#cvtriangle .tri').on( "click", function(e) {
jqsvg = $('#cvtriangle').svg('get');
svg = document.querySelector("svg");
var pt = svg.createSVGPoint();
pt.x = e.clientX;
pt.y = e.clientY;
pt = pt.matrixTransform(svg.getScreenCTM().inverse());
jqsvg.circle(pt.x, pt.y, 5, {class: 'vote', fill: 'white', stroke: 'white', strokeWidth: 2, cursor: 'pointer'});
});
See http://msdn.microsoft.com/en-us/library/hh535760%28v=vs.85%29.aspx
It's my sample code.
For this usage, getScreenCTM method is very useful.
<svg viewBox="0 0 300 300" onload="
var c = document.getElementById('c');
var cx = c.cx.baseVal;
var cy = c.cy.baseVal;
var svg = this;
var point = svg.createSVGPoint();
svg.onmousemove = function(e){
point.x = e.clientX;
point.y = e.clientY;
var ctm = c.getScreenCTM();
var inverse = ctm.inverse();
var p = point.matrixTransform(inverse);
cx.value = p.x;
cy.value = p.y;
};
">
<rect width="100%" height="100%" fill="yellow"/>
<circle id="c" r="10" fill="blue"/>
</svg>
If you call the function getScreenCTM() on the SVG element, it will return the transform matrix used to convert document coordinates to screen coordinates. You want the transform matrix for the other direction, so call inverse() on the matrix object.
var transform = svg.getScreenCTM().inverse();
Now you can transform a point object to do the final conversion:
pt = pt.matrixTransform(transform);
Working demo here
var x = document.getElementById("x"),
y = document.getElementById("y"),
svg = document.querySelector("svg");
svg.addEventListener("mousemove", function(evt) {
var pt = svg.createSVGPoint();
pt.x = evt.pageX;
pt.y = evt.pageY;
pt = pt.matrixTransform(svg.getScreenCTM().inverse());
x.innerHTML = pt.x;
y.innerHTML = pt.y;
}, false);
#container {
width: 200px;
height: 200px;
}
div {
float: left;
margin-left: 1em;
}
<div id="container">
<svg version="1.0" viewbox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" fill="blue"/>
</svg>
</div>
<div>
x = <span id="x"></span><br/>
y = <span id="y"></span>
</div>
If the above version (using pageX/Y) doesn't work for you, try this version instead.
var x = document.getElementById("x"),
y = document.getElementById("y"),
svg = document.querySelector("svg");
svg.addEventListener("mousemove", function(evt) {
var pt = svg.createSVGPoint();
pt.x = evt.clientX;
pt.y = evt.clientY;
pt = pt.matrixTransform(evt.target.getScreenCTM().inverse());
x.innerHTML = pt.x;
y.innerHTML = pt.y;
}, false);
#container {
width: 200px;
height: 200px;
}
div {
float: left;
margin-left: 1em;
}
<div id="container">
<svg version="1.0" viewbox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" fill="blue"/>
</svg>
</div>
<div>
x = <span id="x"></span><br/>
y = <span id="y"></span>
</div>

SVG in viewBox, CSS in the "box-sizing: border-box;" When used in combination, position becomes point

Why, "box-sizing: border-box;" only when it is used, and become a point?
Please tell me whether guided by what formula?
Chrome Version
44.0.2403.157 m
Do not use box-sizing
<style>
svg {
border: 1px solid #ddd;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<svg preserveAspectRatio="none" viewBox="0 0 2000 400" width="500" height="400">
<rect id="hoge" x="50" y="50" width="50" height="50"/>
</svg>
<script>
console.log($("#hoge").offset()); //Object {top: 59, left: 21.5}
</script>
use box-sizing
<style>
svg {
border: 1px solid #ddd;
}
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<svg preserveAspectRatio="none" viewBox="0 0 2000 400" width="500" height="400">
<rect id="hoge" x="50" y="50" width="50" height="50"/>
</svg>
<script>
console.log($("#hoge").offset()); //Object {top: 58.75, left: 21.450000762939453}
</script>
When you use border-box the width of content is changed, instead of using 400px in height it can now only use (400px - 2px) 398px. Because of that the svg itself has to displayed smaller.
The underlaying calculations for your example looks as follows:
offset.top = margin of body + border + (svg content height / viewBox height * rect y)
In your first example this means
:8px (default value) + 1px + (400px / 2000 * 50) = 59
Your second example:
8px (default value) + 1px + (398px / 400 * 50) = 58.75
Read this css-tricks article for further explanation of the box model.

Animate width attribute (not CSS property) of a SVG rect element using web-animations

I'm using web-animations API to animate a SVG element by changing its width:
<svg id="svg" width="300px" height="500px">
<rect id="backgroundBar" x="15" y="8" width="4" height="10" rx="2" ry="2"/>
</svg>
var elem = document.querySelector('#backgroundBar');
elem.animate([
{width: "0px"},
{width: "100px"}
], {
direction: 'alternate',
duration: 5000,
iterations: Infinity,
fill: 'forwards'
});
but it animates the CSS property width and not the element's attribute width which results in no changes in the element at all.
Is there a way to apply the animation over the attribute width?
Thanks

svg element layer / overlap / overlay / superimposition

With a view to making them hot pluggable / draggable / scaleable, I've been playing around with Mike Bostock's d3 three little circles examples.
While doing this, I stumbled across an issue (using Firefox 16.0.2, btw - normally fine for svg) which, though in itself not serious, somehow bugs me: namely that though always present in the resulting HTML, any attempt at overlaying the rectangular viewing area with the button element fails.
I've tried following each piece of advice at the foot of this exchange, but these have had no impact.
Here my base code, whereby the button is shown outside the circle's containing svg view area. The groupings are part of preparations for experiments with drag n drop / scalability:
var root = d3.selectAll("g#tool-2");
var g0 = root
.append("g")
.attr("class", "g0")
.attr("id", function (d, i) { return d.tool});
var g01 = g0
.append("g")
.attr("class", "g01")
.attr("id", function (d, i) { return d.tool});
var g02 = g0
.insert("g")
.attr("class", "g02")
.attr("id", function (d, i) { return d.tool});
var svg = g01
.insert("svg")
.attr("width", width)
.attr("height", height);
var button = g02
.append("div")
.attr("class", "button")
.attr("id", function (d, i) { return d.tool})
.append("button")
.text(function(d) { return "Run" });
svg.selectAll(".little")
.data(data)
.enter()
.append("circle")
.attr("class", "little")
.attr("cx", x)
.attr("cy", y)
.attr("r", 12);
console.log("Got past circle creation");
button
.on("click", function() {
svg.selectAll(".select").remove();
svg.selectAll(".select")
.data(data)
.enter()
.append("circle")
.attr("class", "select")
.attr("cx", x)
.attr("cy", y)
.attr("r", 60)
.style("fill", "none")
.style("stroke", "red")
.style("stroke-opacity", 1e-6)
.style("stroke-width", 3)
.transition()
.duration(750)
.attr("r", 12)
.style("stroke-opacity", 1);
});
Appended to any of root, g0, g01 or g02, the button is shown outside the rectangular container. All well and good. Here, for example, the html resulting from the code shown above:
<g id="tool-2" class="g0">
<g id="tool-2" class="g01">
<svg height="180" width="360">
<circle r="12" cy="45" cx="180" class="little"></circle>
<circle r="12" cy="90" cx="60" class="little"></circle>
<circle r="12" cy="135" cx="300" class="little"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="45" cx="180" class="select"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="90" cx="60" class="select"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="135" cx="300" class="select"></circle>
</svg>
</g>
<g id="tool-2" class="g02">
<div id="tool-2" class="button">
<button>Run</button>
</div>
</g>
</g>
Regardless of append element used, however, whether using
z-index
x & y coordinates
dx & dy coordinates
with or without id
with or without class
with or without positioning
..the button, though present in the resulting html, either continues to be shown outside the container, or is simply not displayed.
There seems to be an issue with svg overlaying with which I'm really not familiar. Any hints?
Thanks
Thug
As far as I can see you're mixing HTML elements into SVG. That's not valid. You can wrap the HTML elements in a <foreignObject> element and see if you're more lucky with that.
You have to make sure that the proper namespace for the foreign content is added. Here is a complete working example (try on jsfiddle):
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<g class="g0">
<g class="g01">
<svg height="180" width="360">
<circle r="12" cy="45" cx="180" class="little"></circle>
<circle r="12" cy="90" cx="60" class="little"></circle>
<circle r="12" cy="135" cx="300" class="little"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="45" cx="180" class="select"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="90" cx="60" class="select"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="135" cx="300" class="select"></circle>
</svg>
</g>
<g class="g02">
<foreignObject width="100" height="50">
<div class="button" xmlns="http://www.w3.org/1999/xhtml">
<button>Run</button>
</div>
</foreignObject>
</g>
</g>
</svg>

Resources