Reputation: 97
var treeData = [
{
"name": "Top Level",
"parent": "null",
"remark":"yes",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"remark":"yes",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A",
"remark":"null"
},
{
"name": "Daughter of A",
"parent": "Level 2: A",
"remark":"null"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level",
"remark":"null"
}
]
}
];
//*************************************************************************************
// 1. Create the button
var button = document.createElement("button");
button.innerHTML = "download file";
//*************************************************************************************
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
rectW = 100,
rectH = 30,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
//swap x and y for vertical
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
// .projection(function(d) { return [d.x, d.y]; });
});
//var gElement = document.createElement('svg:g');
var gElement = document.createElementNS(d3.ns.prefix.svg, 'g');
gElement.setAttribute("id", "fg");
console.log(gElement);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append(function() { return gElement; }) //The argument to .append() has to be a function, you can't just pass element to it.
// .append("svg:g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
//swap x and y for vertical
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })
.on("click", click);
nodeEnter.append("rect")
//nodeEnter.append("circle")
// .attr("r", 1e-6)
.attr("width", rectW)
.attr("height", rectH)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -13 : 13; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
/*
var svgimg = document.createElementNS('http://www.w3.org/2000/svg','image');
svgimg.setAttributeNS(null,'height','50');
svgimg.setAttributeNS(null,'width','200');
svgimg.setAttributeNS('http://www.w3.org/1999/xlink','href', 'http://localhost/images/title_2.svg');
svgimg.setAttributeNS(null,'x', '-'+rectW/2);
svgimg.setAttributeNS(null,'y','-'+rectH/2);
svgimg.setAttributeNS(null, 'visibility', 'visible');
*/
// IAH 20/01/2019 Filter to put tooltip only on nodes that have information associated with it
var nodesWithInfo = nodeEnter.filter(function(d) { return (d.remark != 'null' ) })
nodesWithInfo
.append("circle")
.attr("export-ignore", true)
.attr("cx", 96)
.attr("cy", 7)
.attr("r", 10);
nodesWithInfo
.append("text")
.attr("export-ignore", true)
.attr("id", "infoText")
.attr("x", 96)
.attr("y", 7)
.attr("dy", ".30em")
.style("font-style", "italic")
.style("font-family", "serif")
.style("font-weight", "bold")
.text("i");
// Transition nodes to their new position.
//swap x and y for vertical layout
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
//nodeUpdate.select("circle")
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.style("stroke", "black")
// .attr("r", 10)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
//vertical switch x and y positions
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; })
.remove();
//nodeExit.select("circle")
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.style("stroke", "black");
// .attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke", "#ccc")
.attr("fill", "none")
.attr("stroke-width", "2px")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
//*************************************************************************************
// 2. Append button somewhere
var body = document.getElementsByTagName("body")[0];
body.appendChild(button)
.setAttribute("transform", "translate(" + 0 + "," + 0 + ")");
//*************************************************************************************
function svgDataURL(svg) {
var svgAsXML = (new XMLSerializer).serializeToString(svg);
var dataURL = "data:image/svg+xml," + encodeURIComponent(svgAsXML);
return dataURL;
}
button.onclick = function() {
var groupElement = document.getElementById('fg');
const bb = groupElement.getBBox();
groupElement.setAttribute("transform", "translate(0,0)");
var titlePosX = bb.width/6 + bb.x+ 25;
var imageWidth = bb.width/4;
var imageHeight = imageWidth*.33;
var newVboxH = bb.height + imageHeight+10;
var globePos = imageHeight+20;
var svgImg = document.createElementNS('http://www.w3.org/2000/svg','image');
svgImg.setAttributeNS(null,'id','titleImg');
svgImg.setAttributeNS(null,'height',imageHeight);
svgImg.setAttributeNS(null,'width',imageWidth); //make the width half the size of the group element
svgImg.setAttributeNS('http://www.w3.org/1999/xlink','href', 'https://upload.wikimedia.org/wikipedia/commons/9/94/Wikipedia_valued_picture_banner.svg');
svgImg.setAttributeNS(null, 'visibility', 'visible');
svgImg.setAttributeNS(null,'x',titlePosX);
svgImg.setAttributeNS(null,'y','-'+imageHeight);
//add the Instagram logo and address below .... text for instagram address needs to be added!!!!
var InstaImg = document.createElementNS('http://www.w3.org/2000/svg','image');
InstaImg.setAttributeNS('http://www.w3.org/1999/xlink','href', 'https://upload.wikimedia.org/wikipedia/commons/e/e7/Instagram_logo_2016.svg');
InstaImg.setAttributeNS(null, 'visibility', 'visible');
InstaImg.setAttributeNS(null,'height',"24");
InstaImg.setAttributeNS(null,'width',"24");
InstaImg.setAttributeNS(null,'x',bb.x);
InstaImg.setAttributeNS(null,'y','-'+imageHeight);
//add the www address below ....
var wwwImg = document.createElementNS('http://www.w3.org/2000/svg','image');
wwwImg.setAttributeNS(null,'id','wwwImg');
wwwImg.setAttributeNS('http://www.w3.org/1999/xlink','href', 'https://upload.wikimedia.org/wikipedia/commons/f/fa/Globe.svg');
wwwImg.setAttributeNS(null, 'visibility', 'visible');
wwwImg.setAttributeNS(null,'height',"24");
wwwImg.setAttributeNS(null,'width',"24");
wwwImg.setAttributeNS(null,'x',bb.x);
wwwImg.setAttributeNS(null,'y',+globePos);
groupElement.setAttribute("transform", "translate(0,"+(imageHeight+10)+")scale(.995,.995)"); //scale down by .05% to fit nicely
var svgContent = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgContent.setAttribute('viewBox',' '+bb.x+' '+bb.y+' '+bb.width+' '+newVboxH);
svgContent.setAttribute("width", "100%");
svgContent.setAttribute("height", "100%");
svgContent.setAttribute("preserveAspectRatio", "xMidYMid meet");
//put the border around the cloned SVG element
var borderRect = document.createElementNS('http://www.w3.org/2000/svg','rect');
borderRect.setAttribute("width", bb.width);
borderRect.setAttribute("height", newVboxH);
borderRect.setAttribute("x", bb.x);
borderRect.setAttribute("y", bb.y);
borderRect.setAttribute("style","fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9");
// clone the svg to avoid destroying it while appending to the svg namespace
let clonedGroupElement = groupElement.cloneNode(true);
clonedGroupElement.append(svgImg); //image becomes part of g element, position is at bb.x
clonedGroupElement.append(InstaImg);
clonedGroupElement.append(wwwImg);
svgContent.append(borderRect); //append border to the cloned svg
// try to remove the information icon before downloading
clonedGroupElement.querySelectorAll('[export-ignore]').forEach(function(node) {
node.remove();
});
svgContent.appendChild(clonedGroupElement); // use the cloned nodes
var dl = document.createElement('a');
document.body.appendChild(dl);
dl.setAttribute("href", svgDataURL(svgContent)); // function svgDataURL expects a node
dl.setAttribute("download", "test.svg");
dl.click();
dl.remove();
svgContent.removeChild(clonedGroupElement);
};
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1px;
}
.node text {
font: 12px sans-serif;
}
.link {/*
fill: none;
stroke: #ccc;
stroke-width: 2px;*/
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
</head>
I have created a D3js tree and managed to append images to the graph. The external images are vector graphs for the image logo (title), Instagram logo and address, www logo and address, background, and border.
What I see is that the appended images appear but treated as raster images and become blurry on zooming.
For the background and border I appended a rect
element and set its background and stroke color. I would like to replace the rect
background and border to load from external SVG files as well as the other images that are appended earlier.
Upvotes: 0
Views: 131
Reputation: 97
To load the external svg file, use the following d3.xml function as in the below code, as the file loading is asynchronous, all the append functions are done within the d3.xml function.
// This event is used to download SVG document
button.onclick = function() {
var groupElement = document.getElementById('fg');
const bb = groupElement.getBBox();
var titlePosX = bb.width/6 + bb.x+ 25; //added offeset of 25 to align to center as much as possible
var imageWidth = bb.width/4;
var imageHeight = imageWidth*.33;
var wwwPosY = imageHeight/2;
var newVboxH = bb.height + imageHeight+10;
//add the Instagram logo and address below .... text for instagram address needs to be added!!!!
var instaImg = document.createElementNS('http://www.w3.org/2000/svg','image');
instaImg.setAttributeNS(null,'id','instaImg');
instaImg.setAttributeNS('http://www.w3.org/1999/xlink','href', 'images/hawashim_instagram_logo_resized.svg');
instaImg.setAttributeNS(null, 'visibility', 'visible');
instaImg.setAttributeNS(null,'x',bb.x);
instaImg.setAttributeNS(null,'y','-'+imageHeight);
//add the www address below ....
//add the internet logo and address below .... text for internet address needs to be added!!!!
var wwwImg = document.createElementNS('http://www.w3.org/2000/svg','image');
wwwImg.setAttributeNS(null,'id','wwwImg');
wwwImg.setAttributeNS('http://www.w3.org/1999/xlink','href', 'images/internet_globe_resized.svg');
wwwImg.setAttributeNS(null, 'visibility', 'visible');
wwwImg.setAttributeNS(null,'x',bb.x);
wwwImg.setAttributeNS(null,'y',+wwwPosY);
//used if the tilte image is outside of the group element to adust the title
groupElement.setAttribute("transform", "translate(0,"+(imageHeight+10)+")scale(.995,.995)"); //scale down by .05% to fit nicely
var svgContent = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgContent.setAttribute('viewBox',' '+bb.x+' '+bb.y+' '+bb.width+' '+newVboxH);
svgContent.setAttribute("width", "100%");
svgContent.setAttribute("height", "100%");
svgContent.setAttribute("preserveAspectRatio", "xMidYMid meet");
//how to put the border around the cloned SVG element
var borderRect = document.createElementNS('http://www.w3.org/2000/svg','rect');
borderRect.setAttribute("id", "bRect");
borderRect.setAttribute("width", bb.width);
borderRect.setAttribute("height", newVboxH);
borderRect.setAttribute("x", bb.x);
borderRect.setAttribute("y", bb.y);
borderRect.setAttribute("style","fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9");
//clone the svg to avoid destroying it while appending to the svg namespace
let clonedGroupElement = groupElement.cloneNode(true);
d3.xml("images/title_2.svg").mimeType("image/svg+xml").get(function(error, xml) {
if (error) throw error;
xml.documentElement.setAttribute("transform", "translate(0,0)scale(.995,.995)");
xml.documentElement.setAttribute('viewBox',' '+bb.x+' '+bb.y+' '+bb.width+' '+newVboxH);
xml.documentElement.setAttributeNS(null,'x',titlePosX);
xml.documentElement.setAttributeNS(null,'y','-'+imageHeight);
xml.documentElement.setAttributeNS(null,'width',imageWidth);
clonedGroupElement.appendChild(xml.documentElement);
clonedGroupElement.append(instaImg);
clonedGroupElement.append(wwwImg);
svgContent.append(borderRect); //append border to the cloned svg
//Remove the information icon from the cloned group before downloading
clonedGroupElement.querySelectorAll('[export-ignore]').forEach(function(node) {
node.remove();
});
svgContent.appendChild(clonedGroupElement); //use the cloned nodes
var dl = document.createElement('a');
document.body.appendChild(dl);
dl.setAttribute("href", svgDataURL(svgContent)); //function svgDataURL expects a node
dl.setAttribute("download", "test.svg");
dl.click();
dl.remove();
//bring back to original position
groupElement.setAttribute("transform", 'translate('+margin.left+','+margin.top+')scale(.7)');
});
});
Upvotes: 1