import multiple svgs in one xml with d3.js - javascript

currently I'm fiddling with D3.js. It looks awesome!I was wondering if D3.js is suitable to draw multiple svg elements in a webpage. I found the d3.xml method and got it working.
Initially I was playing with Raphaeljs but, for me as a JS-newbie, useful tuts/docs are hard to find. I was converting SVG's to JSON-objects with webtool and then inculde them in 1 js-file it's a lot of work stripping tags and stuff. This saves HTTP-requests. Now I want to try the same with D3.js and see which one will suit me.
Any ideas for importing a multiple-SVG-file with D3?

There is no limit number of SVGs you can write using D3. All D3 does is access the DOM and modify it so that the browser knows to draw an SVG, or how to manipulate the SVG. D3 simply allows you to more easily access the elements and work with them.
If you are reading the SVGs from JSON objects, you just need to read in the multiple JSONS.
Take the first two examples from the D3js.org website as an example...
d3.csv("morley.csv", function(error, csv) {
var data = [];
csv.forEach(function(x) {
var e = Math.floor(x.Expt - 1),
r = Math.floor(x.Run - 1),
s = Math.floor(x.Speed),
d = data[e];
if (!d) d = data[e] = [s];
else d.push(s);
if (s > max) max = s;
if (s < min) min = s;
});
chart.domain([min, max]);
var svg = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "box")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart);
d3.select("button").on("click", function() {
svg.datum(randomize).call(chart.duration(1000)); // TODO automatic transitions
});
});
This, from the Box Plot example, will open up "morley.csv" and work with the data.
d3.json("flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
});
This, from the Bubble Chart example, will open up "flare.json" and start doing stuff with that. There is nothing stopping you from doing both of these actions in the same file.
What you will have to look out for is variable references, as with any other program. Each SVG will need its own reference. The above two code blocks use the same variable names for several things, so they'd step on each other. If you wanted both these on one page, you would simply need to rename the variables so they are unique.

d3.xml("./svgs.xml", "application/xml", function(xml, error) {
var data = xml.documentElement,
sections = data.getElementsByTagName("section"),
svgWrapper = "center";
for (var i = 0, lenS = sections.length; i < lenS; i++) {
var name = sections[i].getElementsByTagName("name")[0].childNodes[0].nodeValue,
images = sections[i].getElementsByTagName("image");
for (var j = 0, lenI = images.length; j < lenI ; j++) {
//<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
var image = images[j].firstElementChild;
//assuming I only target 1 unique HTML5-element
if (document.getElementsByTagName(name)[0] !== undefined) {
//in search of wrapper-class and append data to it
document.getElementsByTagName(name)[0].getElementsByClassName(svgWrapper)[0].appendChild(image);
// If node "name" is not a element then it's an id
} else if (document.getElementById(name) !== undefined) {
document.getElementById(name).getElementsByClassName(svgWrapper)[0].appendChild(image);
} else {
console.alert("what the hell went wrong while inserting svgs?!");
alert("What the hell went wrong while inserting svgs?!");
}
}
}
});
If somebody got pointers to maybe improve this for like IE9 cause only test this for Chrome and Firefox (have to install Windows VM). How do I catch and handle the error-parameter in the XHR?

Related

Append two elements in svg at the same level

I'm using d3.js
Hi, I'm having an issue finding how to append two elements (path and image) to the same g (inside my svg) from the same data. I know how to do this, but the tricky thing is I need to get the BBox values of the "path" elements in order to place the "image" elements in the middle... My goal is actually to place little clouds in the center of cities on a map like this : this is the map I am trying to reproduce
On the map it's not centered but I have to do so. So this is my current code:
// Draw the map
svg.append("g")
.selectAll("path")
.data(mapEPCI.features)
.enter()
.append("path")
.attr("fill", d => d.properties.color)
.attr("d", d3.geoPath().projection(projection))
.style("stroke", "white")
.append("image")
.attr("xlink:href", function(d) {
if (d.properties.plan_air == 1)
return ("data/page8_territoires/cloud.png")
else if (d.properties.plan_air == 2)
return ("data/page8_territoires/cloudgray.png")
})
.attr("width", "20")
.attr("height", "15")
.attr("x", function (d) {
let bbox = d3.select(this.parentNode).node().getBBox();
return bbox.x + 30})
.attr("y", function (d) {
return d3.select(this.parentNode).node().getBBox().y + 30})
This gets the right coordinates for my images but it's because the parent node is actually the path... If I append the image to the g element, is there a way to get the "BrotherNode", or maybe the last child of the "g" element ? I don't know if I'm clear enough but I hope you get my point.
I'm kinda new to js so maybe I'm missing something simple I just don't know yet
Thanks for your help
I would handle your data at the g level and create a group for every map feature (country) which contains the path and a sibling image:
<!doctype html>
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<svg width="600" height="600"></svg>
<script>
let svg = d3.select('svg'),
mapEPCI = {
features: [100, 200, 300, 400]
};
let g = svg.selectAll('g')
.data(mapEPCI.features)
// enter selection is collection of g
let ge = g.enter().append("g");
// append a path to each g according to data
ge.append('path')
.attr("d", (d) => "M" + d + ",10L" + d + ",100")
.style("stroke", "black");
// append a sibling image
ge.append("image")
.attr("xlink:href", "https://placeimg.com/20/15/animals")
.attr("width", "20")
.attr("height", "15")
.attr("transform", function(d) {
// find my sibling path to get bbox
let sibling = this.parentNode.firstChild;
let bbox = sibling.getBBox();
return "translate(" + (bbox.x - 20 / 2) + "," + (bbox.y + bbox.height / 2 - 15 / 2) + ")"
});
</script>
</body>
</html>

updating a d3js tree map

I'm trying to render a tree map using d3.js that periodically fetches data and animates/transitions based on changes in mostly static data (few values change). I'm working from the example here.
So I have something along the lines of:
var w = 960,
h = 500,
color = d3.scale.category20c();
var treemap = d3.layout.treemap()
.size([w, h])
//.sticky(true)
.value(function(d) { return d.size; });
var div = d3.select("#chart").append("div")
.style("position", "relative")
.style("width", w + "px")
.style("height", h + "px");
function update (json) {
var d = div.data([json]).selectAll("div")
.data(treemap.nodes, function (d) { return d.name; });
d.enter().append("div")
.attr("class", "cell")
.style("background", function(d) { return d.children ? color(d.name) : null; })
.call(cell)
.text(function(d) { return d.children ? null : d.name; });
d.exit().remove();
};
d3.json("flare.json", update);
setTimeout(function () {
d3.json("flare2.json", update);
}, 3000);
function cell() {
this
.style("left", function(d) { return d.x + "px"; })
.style("top", function(d) { return d.y + "px"; })
.style("width", function(d) { return d.dx - 1 + "px"; })
.style("height", function(d) { return d.dy - 1 + "px"; });
}
Where flare2.json is a copy of flare.json found here, but with one node removed.
➜ test git:(master) ✗ diff flare.json flare2.json
10d9
< {"name": "AgglomerativeCluster", "size": 3938},
380c379
< }
\ No newline at end of file
---
> }
The problem is, after 3 seconds, the data is fetched and the text for the AgglomerativeCluster is removed, but not the box it was in. I can't say that I fully understand d3js enough to know what exactly I'm doing wrong.
After RTFM [1, 2, 3], I learned that d3.js separates the ideas of updating existing nodes, adding new nodes, and removing dead nodes. I had the code for adding and removing, but I was missing the update code. Simply adding this did the trick:
d.transition().duration(750).call(cell);
After creating var d but before the call to d.enter().

d3 node-tooltip values multi-line

var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click)
.on("mouseover",function (d){
if(d.name=="Purchased"){
jQuery.getJSON("RequestHandler?usercommand=jsoncompany&subcommand=propCount&useraction=d3Tree_frm&mgmtId="+d.id+"&type="+d.name, function(json){
var count=JSON.stringify(json.prop_purchased_count);
result="Purchased Property :"+count;
});
}
var g = d3.select(this);
var info = g.append("text")
.classed('info', true)
.attr('x', 30)
.attr('y', 30)
.text(result); //result="Amount":200\n"Amount":300\n"Amount":400
})
.on("mouseout", function() {
d3.select(this).select('text.info').remove();
});
//result="Amount":200\n"Amount":300\n"Amount":400
i want to display tooltip such that after \n it appears in a new line within that tooltip.
How to achieve it ?
Thanks in advance
I can see result being set in the code you posted, however what we cannot see is where result is being set to get the content you are 'saying' it contains. That way we could help you interpret the application of these proven recommendations a lot better. But in anycase, have a look at these resources to help you in your quest.
http://bl.ocks.org/mbostock/7555321
How to dynamically display a multiline text in D3.js?
In the else part add "Amount:"+ count + "\n" while you are concatenating the newline

d3 is not defined - ReferenceError

I am trying to use a "fancy graph" found at http://bl.ocks.org/kerryrodden/7090426:
The way I've done it was to download the code and simply edit the CSV file to match my data. Then I simply open the .html-file in Firefox to see the interactive graph. However, using it at a another computer I get the following errors:
ReferenceError: d3 is not defined sequences.js:25
ReferenceError: d3 is not defined index.html:28
As I have almost no knowledge of d3 or javascript I am a bit lost.
Can any of you give me a hint to what is causing the errors and how I should correct the code?
I've done a single alteration to the code making it the following:
Javascript:
// Dimensions of sunburst.
var width = 750;
var height = 600;
var radius = Math.min(width, height) / 2;
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = {
w: 75, h: 30, s: 3, t: 10
};
// Mapping of step names to colors.
var colors = {
"G0": "#5687d1",
"G1": "#5c7b61",
"G2": "#de783b",
"G3": "#6ab975",
"G4": "#a173d1",
"G5": "#72d1a1",
"Afgang": "#615c7b"
};
// Total size of all segments; we set this later, after loading the data.
var totalSize = 0;
var vis = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
// Use d3.text and d3.csv.parseRows so that we do not need to have a header
// row, and can receive the csv as an array of arrays.
d3.text("sequences.csv", function(text) {
var csv = d3.csv.parseRows(text);
var json = buildHierarchy(csv);
createVisualization(json);
});
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(json) {
// Basic setup of page elements.
initializeBreadcrumbTrail();
drawLegend();
d3.select("#togglelegend").on("click", toggleLegend);
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
vis.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
// For efficiency, filter nodes to keep only those large enough to see.
var nodes = partition.nodes(json)
.filter(function(d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
nodes = nodes.filter(function(d) {
return (d.name != "end"); // BJF: Do not show the "end" markings.
});
var path = vis.data([json]).selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("display", function(d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function(d) { return colors[d.name]; })
.style("opacity", 1)
.on("mouseover", mouseover);
// Add the mouseleave handler to the bounding circle.
d3.select("#container").on("mouseleave", mouseleave);
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
};
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseover(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
d3.select("#percentage")
.text(percentageString);
d3.select("#explanation")
.style("visibility", "");
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray, percentageString);
// Fade all the segments.
d3.selectAll("path")
.style("opacity", 0.3);
// Then highlight only those that are an ancestor of the current segment.
vis.selectAll("path")
.filter(function(node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style("opacity", 1);
}
// Restore everything to full opacity when moving off the visualization.
function mouseleave(d) {
// Hide the breadcrumb trail
d3.select("#trail")
.style("visibility", "hidden");
// Deactivate all segments during transition.
d3.selectAll("path").on("mouseover", null);
// Transition each segment to full opacity and then reactivate it.
d3.selectAll("path")
.transition()
.duration(1000)
.style("opacity", 1)
.each("end", function() {
d3.select(this).on("mouseover", mouseover);
});
d3.select("#explanation")
.transition()
.duration(1000)
.style("visibility", "hidden");
}
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray, percentageString) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.name + d.depth; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", function(d) { return colors[d.name]; });
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
// Set position for entering and updating nodes.
g.attr("transform", function(d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function drawLegend() {
// Dimensions of legend item: width, height, spacing, radius of rounded rect.
var li = {
w: 75, h: 30, s: 3, r: 3
};
var legend = d3.select("#legend").append("svg:svg")
.attr("width", li.w)
.attr("height", d3.keys(colors).length * (li.h + li.s));
var g = legend.selectAll("g")
.data(d3.entries(colors))
.enter().append("svg:g")
.attr("transform", function(d, i) {
return "translate(0," + i * (li.h + li.s) + ")";
});
g.append("svg:rect")
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.style("fill", function(d) { return d.value; });
g.append("svg:text")
.attr("x", li.w / 2)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.key; });
}
function toggleLegend() {
var legend = d3.select("#legend");
if (legend.style("visibility") == "hidden") {
legend.style("visibility", "");
} else {
legend.style("visibility", "hidden");
}
}
// Take a 2-column CSV and transform it into a hierarchical structure suitable
// for a partition layout. The first column is a sequence of step names, from
// root to leaf, separated by hyphens. The second column is a count of how
// often that sequence occurred.
function buildHierarchy(csv) {
var root = {"name": "root", "children": []};
for (var i = 0; i < csv.length; i++) {
var sequence = csv[i][0];
var size = +csv[i][1];
if (isNaN(size)) { // e.g. if this is a header row
continue;
}
var parts = sequence.split("-");
var currentNode = root;
for (var j = 0; j < parts.length; j++) {
var children = currentNode["children"];
var nodeName = parts[j];
var childNode;
if (j + 1 < parts.length) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k]["name"] == nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {"name": nodeName, "children": []};
children.push(childNode);
}
currentNode = childNode;
} else {
// Reached the end of the sequence; create a leaf node.
childNode = {"name": nodeName, "size": size};
children.push(childNode);
}
}
}
return root;
};
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Flow for G1 customers</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<link rel="stylesheet" type="text/css"
href="https://fonts.googleapis.com/css?family=Open+Sans:400,600">
<link rel="stylesheet" type="text/css" href="sequences.css"/>
</head>
<body>
<div id="main">
<div id="sequence"></div>
<div id="chart">
<div id="explanation" style="visibility: hidden;">
<span id="percentage"></span><br/>
of G1 customers follow this flow.
</div>
</div>
</div>
<div id="sidebar">
<input type="checkbox" id="togglelegend"> Legend<br/>
<div id="legend" style="visibility: hidden;"></div>
</div>
<script type="text/javascript" src="sequences.js"></script>
<script type="text/javascript">
// Hack to make this example display correctly in an iframe on bl.ocks.org
d3.select(self.frameElement).style("height", "700px");
</script>
</body>
</html>
Had the same issue, though I initially thought it is because of security restrictions of browser, that wasn't the case. It worked when I added the charset to the script tag as shown below:
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
The same is shown in the d3 docs, though it doesn't mention this issue specifically: http://d3js.org/
Yes, having it locally works too.
<script src="d3.min.js"></script>
Here is the full example:
<!doctype html>
<html>
<head>
<title>D3 tutorial</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<!--<script src="d3.min.js"></script>-->
</head>
<body>
<script>
var canvas = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
var circle = canvas.append("circle")
.attr("cx",250)
.attr("cy", 250)
.attr("r", 50)
.attr("fill", "red");
</script>
</body>
</html>
There may be security restrictions that prevent your browser from downloading the D3 script. What you can do is to download the scripts, place them in the same folder as your files, and change the referenced paths in your source.
You may also need to add:
<meta charset="utf-8">
or
<meta content="utf-8" http-equiv="encoding">
to your head section
in case browser does not prevent it from downloading and still getting the error, d3.js should be placed before jquery.
I just moved my reference to the package as the first import in my head tag:
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
...
...
</head>
Seemed to work for me
Replace <meta charset="ISO-8859-1"> with <meta charset="UTF-8">
I had to do a grunt build to get get rid of this error. (Using Yeoman and Ember.js.)
And for JavaScript noobs like me - problem could be that you don't import it correctly. Try reading import docs and things like:
import * as d3 from 'd3-transition'
If you are using Visual Studio you can go to Tools -> Options -> Text Editor -> JavaScript -> IntelliSense and check the box "Download remote references". That did the trick for me.
UPDATE: there is now a d3-webpack-loader package which makes it easy to load d3 in webpack. I am not the creator of the package, I've only used it to see if it works. Here's a quick example.
// Install the loader
npm install --save d3-webpack-loader
// Install the d3 microservices you need
npm install --save d3-color
npm install --save d3-selection
In our entry.js file we'll require d3 using the d3-webpack-loader with:
const d3 = require('d3!');
and can then use some of the d3 methods with:
d3.selectAll("p").style("color", d3.color("red").darker());
Super late to this response but none of the above solutions worked out for me. I found a fix though!
I am on macOS Catalina. For some bizarre reason, it turned out to be a decompression/unpack issue with the .tgz file from Observable's website. I use an application called The Unarchiver to decompress files, but in this case, the .tgz file did not properly unpack. There was a folder and file missing compared to a friend's computer not using the same program.
Solution: I unpacked .tgz without a third party program – just used macOS (simply double clicking on the file). Then, I loaded the page locally and it worked!
If double clicking on the file fails to unpack, try running tar -xzf filename.tgz in Terminal.
I assume that you are importing the d3 from online.
In your HTML, make sure that you are importing the d3 before connecting your JavaScript file.
// Importing D3.js
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8" defer></script>
// Importing D3-Scale
<script src="https://d3js.org/d3-scale.v3.min.js"></script>
// Connecting my JS file
<script src="app.js" defer></script>

Display an SVG image at the middle of an SVG path

I have a force directed graph with different size nodes. I want to display a custom icon in the middle of each path connecting two nodes. From the d3 examples I found the way to display images within the nodes. However, when I try the same technique on the paths, the images are not shown.
var path = svg.append("svg:g").selectAll("path").data(force.links());
var pathEnter = path.enter().append("svg:path");
pathEnter.attr("class", function(d) {
return "link " + d.target.type;
})
pathEnter.append("svg:g").append("image")
.attr("xlink:href","http://127.0.0.1:8000/static/styles/images/add.png")
.attr("x",0).attr("y",0).attr("width",12).attr("height", 12)
.attr("class", "type-icon");
I guess I need a bit more patience before asking a question. The way I solved the problem is:
var icon = svg.append("svg:g").selectAll("g")
.data(force.links()).enter().append("svg:g");
icon.append("image").attr("xlink:href","imagePath")
.attr("x", -20)
.attr("y", -2)
.attr("width", 12).attr("height", 12)
.attr("class", "type-icon");
And then in the tick function:
icon.attr("transform", function(d) {
return "translate(" +((d.target.x+d.source.x)/2) + "," +
((d.target.y+d.source.y))/2 + ")";
});
to get the center point between the two nodes.

Categories