Copy and Insert in d3 selection - javascript

In a d3 program I need to get a node (with d3.selection) and then I want to insert in the same svg.
I know there are some functions like append, and insert, but these functions are for new elements.
var node = d3.select("rect#someId"); //node with some attributes and listeners
Now my var node got the following attributes:
{_groups, _parents}
var anotherNode = d3.select("anotherNode").insert(node); //It work but it would be great a similar function or a workaround
Note. I need to preserve the listeners of the node

New answer
D3 v5.0 introduced selection.clone, which:
Inserts clones of the selected elements immediately following the selected elements and returns a selection of the newly added clones.
Here is a demo:
var copy = d3.select("#group").clone(true).attr("transform", "translate(120,100)");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200" height="200">
<g id="group">
<rect x="10" y="10" width="50" height="20" fill="teal"></rect>
<circle cx="35" cy="40" r="20" fill="red"></circle>
</g>
</svg>
Note that, just as the solution in the original answer, selection.clone will not clone the listeners.
Original answer
Use this function to clone your selection:
function clone(selector) {
var node = d3.select(selector).node();
return d3.select(node.parentNode.insertBefore(node.cloneNode(true), node.nextSibling));
}
Then, you can call it with clone("#foo") (by ID) or clone(".foo") (by class).
Here is an example, where the group (one rect and one circle) with ID "group" is cloned (the translate is just to better see the clone):
function clone(selector) {
var node = d3.select(selector).node();
return d3.select(node.parentNode.insertBefore(node.cloneNode(true),
node.nextSibling));
}
var copy = clone("#group").attr("transform", "translate(120,100)");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="200" height="200">
<g id="group">
<rect x="10" y="10" width="50" height="20" fill="teal"></rect>
<circle cx="35" cy="40" r="20" fill="red"></circle>
</g>
</svg>
PS: This will not clone the listeners. Also, this function is not mine, it was written by Bostock.

Related

Extract Text From SVG [duplicate]

I have this javascript...
window.writeText = function(form) {
var text;
form.catnumber2.value = "PING";
text = document.getElementByName('cat2Number').innerHtml;
return alert(text);
};
But I get no alert box as expected.
The svg does not show up when I view source via view/developer/view source (I'm in chrome) BUT when I use view/developer/developer tools....I can see the following svg....
<svg height="594">
<g ID="MasterG">
<text name="cat2Number">"$1234"</text>
</g>
</svg>
Any idea what I'm doing wrong? Why is it I can't see the svg code in "view source" but I can in "developer tools" ? Is that the cause of my problem? Is that why my alert box won't "alert"?
After a couple of seconds of googling and finding this https://stackoverflow.com/a/9602772/1217408
I created this JSFiddle example of using textContent: http://jsfiddle.net/hVyTJ/1/
The original http://jsfiddle.net/hVyTJ/ uses standard DOM traversal to get to the text element from the root SVG element. While the update targets the text element directly by ID.
As for finding attribute values you can use getAttributeNS as described here: http://www.carto.net/svg/manipulating_svg_with_dom_ecmascript/
EDIT:
As pointed out by Phrogz, a simple getAttribute call is often sufficient. Read the comment for more details.
you can invoke text() to return the text content of an svg:text element.
// assume svgCont is an svg element
var label = svgCont.append("svg:text").text("hello, world!");
// print the text content to the console
console.log( label.text() );
Without all the unnecessary discussion:
Grab your SVG element:
svg = document.getElementById("my_svg_id");
Grab the inner text from the SVG:
var text = svg.textContent
For the ones who need only the displayed text, you can use the Selection API and its Range interface.
Simply using .textContent would also grab all text nodes that aren't displayed:
const svg = document.querySelector("svg");
console.log(svg.textContent);
<svg>
<defs>
<desc>This text is not displayed, it shouldn't be grabbed</desc>
<!-- same for all the new-lines in the markup -->
</defs>
<rect width="100%" height="100%" fill="red" />
<circle cx="150" cy="100" r="80" fill="green" />
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>
Since SVG elements don't have an innerText property, we need to iterate ourselves over all text nodes, and check if when selecting them we get a BBox.
const svg = document.querySelector("svg");
const range = new Range();
let textContent = "";
const walker = document.createTreeWalker(svg, NodeFilter.SHOW_TEXT, null);
while(walker.nextNode() && walker.currentNode) {
range.selectNode(walker.currentNode);
if (range.getClientRects().length) {
textContent += walker.currentNode.textContent;
}
}
console.log(textContent);
<svg>
<defs>
<desc>This text is not displayed, it shouldn't be grabbed</desc>
<!-- same for all the new-lines in the markup -->
</defs>
<rect width="100%" height="100%" fill="red" />
<circle cx="150" cy="100" r="80" fill="green" />
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>
just using the dom method
const svg = document.querySelector(`[data-uuid="live_map_svg"]`);
const shape = svg.querySelector(`text`);
const text = shape.innerHTML;
// const text = shape.textContent;
setTimeout(() => {
svg.insertAdjacentHTML(`beforebegin`, text);
}, 1000);
.svg-box{
width: 500px;
height: 500px;
background: #ccc;
color: red;
font-size: 16px;
}
[data-uuid="live_map_svg"]{
font-size: 16px;
}
<div class="svg-box">
<svg
data-uuid="live_map_svg" id="live_map_svg"
width="100%" height="100%"
viewBox="0 0 100 100"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<text x="30" y="50" fill="#369acd">A Area</text>
</svg>
</div>

Is there a way to calculate the mixed color (in RGB) from n (n>1) RGBA colors in a specified order in JavaScript?

Say I have a svg like this:
<svg>
<rect id="background" x="0" y="0" width="100" height="100" fill="rgba(10,10,10,1)" />
<rect x="5" y="5" width="100" height="100" fill="rgba(255,125,0,.25)" />
<rect x="25" y="25" width="100" height="100" fill="rgba(0,125,0,.55)" />
<rect x="45" y="45" width="100" height="100" fill="rgba(255,225,25,.66)" />
</svg>
It will rendered as:
How can I get the RGB color of this by JavaScript calculation?
(I got the result (178,178,18) by using a color picker tool.
I am looking for some function like this:
function getMixedRGBByColors(bg_color_in_rgb, [colors_in_rgba_arr]) {
// bg_color_in_rgb defines the background color, it's not transaparent
// colors_in_rgba_arr is the array of the
// colors above the background, in a specific order
// (it needs to be an array because changing the order
// will change the outcome)
....
};
// Usage:
getMixedRGBByColors(
"10,10,10"
[
"255,125,0,.25",
"0,125,0,.55",
"255,255,25,.66"
]);
The best explanation is that the http://en.wikipedia.org/wiki/RGB_color_model is somewhat unintuitive for us humans.
And following is pretty well worked.
NewColor.R = Color1.R - (Color1.R - Color2.R)/2
NewColor.G = Color1.G - (Color1.G - Color2.G)/2
NewColor.B = Color1.B - (Color1.B - Color2.B)/2
https://github.com/benjholla/ColorMixer also will help you.

Adding Javascript events to SVG <def> elements instead of directly on the children?

I know that it is possible to attach plain-old Javascript event handlers to elements within an SVG tag. I am looking to duplicate very many instances of an object, so I am planning to use the <def> and <use> tags to simplify things a bit. However each of my children items will need to handle "click" and other events. I could define those events for each child, but it would be nice if I could somehow define them ONCE in the initial items, and then just "reuse" that click event. Is this possible?
https://jsfiddle.net/khyp1o0w/
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<rect id="someDef" x="0" y="0" width="60" height="30" fill="#f00" />
<!-- ^^ would like to define an event here... -->
</defs>
<use x="150" y="150" xlink:href="#someDef" />
<use x="250" y="250" xlink:href="#someDef" />
<!-- ^^ ... that is used by these guys -->
</svg>
EDIT: Adding example SVG.
IIRC elements targeted by a use element should be internally copied into the <use> element just like if the <use> were an iframe, and the content were cloned.
So this means that events attached on the original node won't be copied onto the copies, unless this event is part of the node markup (inlined). But chrome don't really follow specs here and won't even make it work... So here is an example for FF :
// this won't work
document.getElementById('someDef').addEventListener('click', function() {
this.setAttribute('stroke', randomColor());
});
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<script type="application/javascript">
function randomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[~~(Math.random() * 15)];
}
return color;
}
</script>
<!-- this will work in FF -->
<rect id="someDef" onclick="this.setAttribute('fill',randomColor())" x="0" y="0" width="60" height="30" fill="#f00" />
</defs>
<use x="150" y="150" xlink:href="#someDef" />
<use x="250" y="250" xlink:href="#someDef" />
</svg>
You could of course use event delegation by listening for click events on your root svg element, but this would work only for direct target elements, not for nested ones :
var svg = document.querySelector('svg');
svg.addEventListener('click', function(e) {
var usedTargetID = e.target.getAttributeNS("http://www.w3.org/1999/xlink", 'href');
switch (usedTargetID) {
case '#direct':
clickDirect(e);
break;
case '#nested':
clickNested(e);
break;
default:
return;
}
});
function clickDirect(e) {
// what you seem to want
e.target.setAttribute('fill', randomColor());
}
function clickNested(e) {
// will set both nested...
e.target.setAttribute('fill', randomColor());
}
function randomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[~~(Math.random() * 15)];
}
return color;
}
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<rect id="direct" x="0" y="0" width="60" height="30" />
<g id="nested">
<!-- you can only access this -->
<!-- not its content individually -->
<rect x="80" y="0" width="60" height="30" />
<rect x="20" y="70" width="60" height="30" />
</g>
</defs>
<use x="150" y="50" xlink:href="#direct" />
<use x="250" y="150" xlink:href="#nested" />
</svg>
Or you could also add an event per element, but this would have the same limitations as delegation...

SVG Clear group<g>

Suppose i have an svg element like so
<svg id="svgCanvas" class="pan" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="none">
<g id="viewport">
//Filled with an arbitrary amount of lines and cirles - examples below.
<line x1="632" y1="357.5" x2="682" y2="270.89745962155615" class="line" style="stroke: rgb(128, 128, 128); stroke-width: 1.3px;"></line>
<circle cx="82.08376766398476" cy="367.0988235405059" r="16.5" stroke="blue" fill="white" class="circle"></circle>
</g>
</svg>
how would i go about clearing everything from the that group, whilst also keeping the group element itself?
Just using DOM methods without the need for any framework you could go for:
var el = document.getElementById("viewport");
while (el.firstChild) {
el.removeChild(el.firstChild);
}
document.addEventListener('load', function(){
document.getElementById("viewport").innerHTML = "";
});

D3 transition not working

I have the following SVG element:
<svg id='svgTest' xmlns="http://www.w3.org/2000/svg" version="1.1">
<g id="test">
<rect height="20" width="50" fill="blue"/>
</g>
</svg>
I want to add a transition for the blue rectangle. I tried with the following code with D3:
var rect = d3.select("#test");
rect.transition().duration(5000).attr('height',200);
But it doesn't seem to do anything. What's wrong?
You need to select the 'rect' element. Try this:
var rect = d3.select("#test rect");
rect.transition().duration(5000).attr('height',200);
If you want to update multiple elements, use d3.selectAll().

Categories

Resources