D3: Cannot append simple path to svg container - javascript

I am not succeding in appending a path on a simple svg container.
It considers my "path" element as a text instead.
Here is my code:
// Create the SVG
var tracer = {};
tracer.$container = document.getElementById('container');
tracer.$svg = $('<svg class="svg-d3-table" style="width:100%; height:100%">');
tracer.$container.append(tracer.$svg);
// Specify the path points
var pathInfo = [{x:0, y:60},
{x:50, y:110},
{x:90, y:70},
{x:140, y:100}];
// Specify the function for generating path data
var pathLine = d3.line()
.x(function(d){return d.x;})
.y(function(d){return d.y;})
.curve(d3.curveBasis);
// Append path
tracer.$svg.append("path")
.attr("d", pathLine(pathInfo))
.attr("stroke", "white")
.attr("stroke-width", 8)
.attr("fill", "none");
Instead of having a new path element as
<path d="M314,352L314,352C314,352,314,352,..."></path>
It comes with the following:
<svg class="svg-d3-table" style="..." d="M0,60L8.3,68.3.7,...">path</svg>
What am I missing?
PS: Sorry, I come from c++ and can be struggled with some very basic js operations.
Thank you very much.
Kind Regards.

As others have pointed out in the comments, avoid mixing jquery and d3.
The root of your problems though is that your tracer.$svg is a jquery selector but you are treating it as a d3 selector. While both of them have an append method, they are two wholly different beasts (and as #altocumulus points out, jquery don't play so nice manipulatively SVG).
Here's your code as I would write it with just d3:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="container" style="background-color: steelblue"></div>
<script>
// Create the SVG
var tracer = {};
tracer.$container = d3.select('#container');
tracer.$svg = tracer.$container.append("svg")
.attr("class", "svg-d3-table")
.style("width", "100%")
.style("height", "100%");
// Specify the path points
var pathInfo = [{
x: 0,
y: 60
}, {
x: 50,
y: 110
}, {
x: 90,
y: 70
}, {
x: 140,
y: 100
}];
// Specify the function for generating path data
var pathLine = d3.line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
.curve(d3.curveBasis);
// Append path
tracer.$svg.append("path")
.attr("d", pathLine(pathInfo))
.attr("stroke", "white") //<-- need a color?
.attr("stroke-width", 8)
.attr("fill", "none");
</script>
</body>
</html>

Thanks to altocumulus:
jquery's append not working with svg element?
Is exactly the kind of information I was looking for better understanding of manipulating DOM (but still shows me how much front-end development can be akward).
Thank to Gerardo Furtado:
Indeed the need of different lib leads easily to confusing behavior and makes JS hard to handle when working on an existing project (how to determine namespace by reading code, avoid collision...)
I handled my problem by only managing my svg using D3 as you adviced:
tracer.$svg = tracer.capsule.$svg = d3.select("#" + tracer.defaultName)
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("id", tracer.defaultName + "-svg")
.attr("class", "svg-d3-table")
.attr("xmlns", "http://www.w3.org/2000/svg");
Thank you for your comments.

Related

Draw an arc between two points

I'm working on a proof of concept for an application that I think D3 might be a good fit for. Since I'm new to D3 I thought I would start off simple and build my way up to the application requirements. However, I seem to be hitting a snag on what I believe should be a very easy task with this library. I want to place two small circles on an SVG and then draw an arc or curve between them. Based on the documentation, I believe arcTo would be the best fit for this since I know the start and end points. For the life of me I cannot get it to draw the arc. The circles are drawn perfectly every time though.
var joints = [{x : 100, y : 200, r : 5},
{x : 150, y : 150, r : 5}];
var svg = d3.select("svg");
svg.selectAll("circle")
.data(joints)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; });
svg.selectAll("path").append("path").arcTo(100,200,150,150,50)
.attr("class", "link");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="800" />
I'm either going about this the wrong way or I don't fully understand how to append a path to an SVG. Can someone point me in the right direction? I haven't been able to find many examples of arcTo. Thank you!
You misunderstood what d3.path() is. According to the API:
The d3-path module lets you take [a HTML Canvas] code and additionally render to SVG.
And for d3.path():
d3.path(): Constructs a new path serializer that implements CanvasPathMethods.
As you can see, the d3-path module has only a bunch of methods that allow you to take a HTML canvas code and use it to draw SVG elements.
That being said, you cannot use arcTo straight away in the SVG, as you are doing right now. It should be:
var path = d3.path();
path.moveTo(100, 200);
path.arcTo(100,200,150,150,50)
... and then:
svg.append("path")
.attr("d", path.toString())
However, as an additional problem, arcTo is more complicated than that: the first two values are not the x and y of the starting point, but the coordinates of the first tangent.
Here is a demo, using different values for arcTo, which I think is what you want:
var joints = [{
x: 100,
y: 200,
r: 5
}, {
x: 150,
y: 150,
r: 5
}];
var svg = d3.select("svg");
svg.selectAll("circle")
.data(joints)
.enter().append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.r;
});
var path = d3.path();
path.moveTo(100, 200);
path.arcTo(100, 150, 150, 150, 50);
svg.append("path")
.attr("d", path.toString())
.attr("stroke", "firebrick")
.attr("stroke-width", 2)
.attr("fill", "none");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height="250" />
An easy alternative is simply dropping the d3.path() and doing all this using just SVG code. There are plenty of examples showing how to draw an SVG arc from point A to point B with a given radius.

Unable to display legend for a quantile scale in D3

I am trying to display a legend for a heat map I have created, but am unable to do that. Here is my HTML
<html>
<head>
<title>Heat Map Data Visualization</title>
</head>
<body>
<div class='chart-area'>
<svg class='chart'></svg>
<svg class='legend'></svg>
</div>
</body>
</html>
Here is the code for the legend I am trying to create
var legend = d3.select('.legend')
.data([0].concat(colorScale.quantiles()), function(d){
return d;
});
legend.enter()
.append('g')
.attr('class', 'legend-element');
legend.append("rect")
.attr("x", function(d, i) {
console.log(i);
return legendElementWidth * i + (width - legendElementWidth * buckets);
})
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", gridHeight / 2)
.style("fill", function(d, i) {
return colors[i];
});
When I use Chrome Developer Tools to inspect the element, I see that the required g elements have been created but they all have dimensions of 0x0.
I read somewhere that rect elements can only be appended to an svg element, which is why I changed my HTML code to include an svg element with a class of legend, however I am still not getting any result.
Here is a link to the codepen for this program
http://codepen.io/redixhumayun/pen/eBqamb?editors=0010
I have modified your pen like this:
// Save the legend svg in a variable
// also changed the translate in order to keep the legend within the svg
// and place it on the right side just for the example's sake
var legendSvg = svg.append('g')
.attr('class', 'legend')
.attr("transform","translate("+ (width - 40) + ",20)")
// Define the legend as you did
var legend = d3.legendColor()
.useClass(true)
.shape('rect')
.orient('vertical')
.title('Temperature Variance')
.shapeWidth(legendElementWidth)
.scale(colorScale);
// And then call legend on the legendSvg not on svg itself
legendSvg.call(legend);
Hope this helps, good luck!

Circle in D3.js only displaying text without circle

I am trying to draw a circle using d3.js with text inside it.
Here is my code:
<script src="//d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.min.js"></script>
<div class="circles"></div>
<script>
$(document).ready(function () {
circles();
$(".circles").show();
function circles() {
var svg = d3.select(".circles");
var groups = svg.selectAll("div")
.data("DEMO")
.enter()
.append("div");
groups.attr("transform","translate(" +100+ "," +100+ ")");
var circles = groups.append("circle")
.attr("cx", "100")
.attr("cy","100")
.attr("r", "100")
.attr("fill", "red")
.attr("stroke-width","2.4192")
.attr("stroke","#00ffff");
var label = groups.append("text")
.text(function(d){
return d;
})
.attr({
"alignment-baseline": "middle",
"text-anchor": "middle",
"font-family":"Arial",
"font-size":"30",
"fill":"white"
});
}
});
</script>
But it displays only the text and the circle is not being displayed.
I am unable to fix it.
Can anyone please point out my mistake and help me fix it.
You're appending a <circle> element in HTML, not SVG. It doesn't have any meaning there, so it doesn't get rendered. You need to append an svg element, then append your circle and text nodes to that.
Fill, stroke-width, and stroke should also be applied with .style instead of .attr.
Some relevant code changes are:
var svg = d3.select('.circles').append('svg') // added .append('svg')
.attr('height', /* set the height */)
.attr('width', /* set the width */);
var groups = svg.selectAll('g') // 'g' instead of 'div'
.data('DEMO')
.enter().append('g');

Setting different images for D3 force-directed layout nodes

I've been trying to make a specific type of force direct graph, similar to this (http://bl.ocks.org/mbostock/950642):
However, instead of having all the same images I wish to have different images representing different information on the graph.
My first step to this is being able to change all the circle images to random linked shapes. Whatever I try to implement in my code, the circles I have just disappear, instead of being replaced by different shapes. Any help on this problem would be great. Here is the code. Sorry, I'm also new to this site.
// nodes
var nodeSelecton = svg.selectAll(".node").data(nodes).enter().append("g").attr({
class : "node"
}).call(force.drag);
nodeSelecton.append("circle").attr({
r : nodeRadius
}).style("fill", function(d) {
return color(d.group);
});
nodeSelecton.append("svg:text").attr("text-anchor", "middle").attr('dy', ".35em").text(function(d) {
return d.name;
});
// Add a new random shape.
// nodes.push({
// type: d3.svg.symbolTypes[~~(Math.random() * d3.svg.symbolTypes.length)],
// size: Math.random() * 300 + 100
This is a jsfiddle that is equivalent to the first example that you linked. I just changed getting data to be from the JavaScript code instead of json file, since jsfiddle doesn't support external json files that well.
First Solution
Now, let's replace constant image with a set of different images
Instead of this code:
.attr("xlink:href", "https://github.com/favicon.ico")
we'll insert this code:
.attr("xlink:href", function(d) {
var rnd = Math.floor(Math.random() * 64 + 1);
var imagePath =
"http://www.bigbiz.com/bigbiz/icons/ultimate/Comic/Comic"
+ rnd.toString() + ".gif";
console.log(imagePath);
return imagePath;
})
and we'll get this:
Second Solution
As you suggested in your code from the question, one could use built-in SVG symbols.
Instead of this whole segment for inserting images:
node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
we could use this code:
node.append("path")
.attr("d", d3.svg.symbol()
.size(function(d) {
return 100;
})
.type(function(d) {
return d3.svg.symbolTypes[~~(Math.random() * d3.svg.symbolTypes.length)];
}))
.style("fill", "steelblue")
.style("stroke", "white")
.style("stroke-width", "1.5px")
.call(force.drag);
and we'll get this:
Hope this helps.

outer selectAll: what is selected?

I am learning D3, and how to nest or append elements to the page using D3's data binding mechanism.
I have modified code found on http://www.recursion.org/d3-for-mere-mortals/ . I understand how to set up the svg canvas and I also understand the loops binding data to the rect, text and line elements.
What I don't understand are the calls to selectAll('Anything1/2/3/4') below. They are clearly necessary, but what exactly am I selecting, and how do they fit in the data binding mechanism? Thank you.
<html>
<head>
<title>D3 Test</title>
<script type="text/javascript" src="d3/d3.v2.js"></script>
</head>
<body>
<script type="text/javascript">
var dat = [ { title:"A", subtitle:"a", year: 2006, books: 54, avg:10 },
{ title:"B", subtitle:"b", year: 2007, books: 43, avg:10 },
{ title:"C", subtitle:"c", year: 2008, books: 41, avg:10 },
{ title:"D", subtitle:"d", year: 2009, books: 44, avg:10 },
{ title:"E", subtitle:"e", year: 2010, books: 35, avg:10 } ];
var width = 560,
height = 500,
margin = 20,
innerBarWidth = 20,
outerBarWidth = 40;
var x = d3.scale.linear().domain([0, dat.length]).range([0, width]);
var y = d3.scale.linear()
.range([0, height - 2 * margin])
.domain([ 0 , 100 ]);
var z = d3.scale.category10();
var n = d3.format(",d"),
p = d3.format("%");
var canvas = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 2 * margin + "," + margin + ")");
// outerbars
var outerBars = d3.select("svg")
.selectAll("Anything1").data(dat).enter().append("rect")
.attr("x", function(datum, index) { return x(index); })
.attr("y", function(datum) { return height - y(datum.books); })
.attr("height", function(datum) { return y(datum.books); })
.attr("width", outerBarWidth)
.attr("fill", "blue")
// innerbars
var innterBars = d3.select("svg")
.selectAll("Anything2").data(dat).enter().append("rect")
.attr("x", function(datum, index) { return x(index)+innerBarWidth/2; })
.attr("y", function(datum) { return height - y(datum.books)/2; })
.attr("height", function(datum) { return y(datum.books); })
.attr("width", innerBarWidth)
.attr("fill", "red");
// avg references
var barlabels = d3.select("svg")
.selectAll("Anything3").data(dat).enter().append("line")
.attr("x1", function(datum, index) { return x(index); })
.attr("x2", function(datum, index) { return x(index)+outerBarWidth; })
.attr("y1", function(datum) { return height - y(datum.books)/2; })
.attr("y2", function(datum) { return height - y(datum.books)/2; })
.style("stroke", "#ccc");
// titles
var barlabels = d3.select("svg")
.selectAll("Anything4").data(dat).enter().append("text")
.attr("x", function(datum, index) { return x(index)+innerBarWidth/2; })
.attr("y", height )
.attr("text-anchor", "end")
.text(function (d) {return d.title} );
</script>
</body>
</html>
Perhaps the most important, yet most difficult concept to understand in d3 is the selection (I highly recommend you bookmark and familiarize yourself with the API). On the surface, selections provide similar functionality to many other JavaScript libraries, such as jQuery:
jQuery:
var paragraphs = $("p");
d3:
var paragraphs = d3.selectAll("p");
Both these lines create "selection objects", which are essentially DOM elements which have been grouped into a single object which gives you better control over the elements. Like other libraries, you can manipulate these "selected" elements in d3 using functions that are provided in the library.
jQuery:
var paragraphs = $("p").css("color", "red");
d3:
var paragraphs = d3.selectAll("p").style("color", "red");
Again, on the surface this is fairly easy to understand. What makes d3 so powerful is that it lets you take this a step further by allowing you to bind arbitrary data to the selected elements.
Let's say you have a blank document and you want to add a couple paragraphs of text - and you have each paragraph of text stored in individual elements in an array:
var text = ["First", "Second", "Third", "Fourth"];
Since we haven't yet created these paragraphs, the following call will return an empty selection:
var paragraphs = d3.selectAll("p");
console.log(paragraphs.empty()); // true
Note that paragraphs is still a selection, it is just empty. This is a fundamental point in d3. You can bind data to an empty selection, and then use the data to add new elements using the entering selection. Let's start over from our previous example and walk through this process. First, create your empty selection and bind the text array to it:
var paragraphs = d3.select("body").selectAll("p").data(text);
Then, using the entering selection, append the <p> elements to the body:
paragraphs.enter().append("p").text(function(d) { return d; });
Your DOM will now have:
<body>
<p>First</p>
<p>Second</p>
<p>Third</p>
<p>Fourth</p>
</body>
There's a lot that could definitely confuse you at this point, but I think this should give you a good start.
See also: Thinking with Joins.
Here are some readings to get you started:
Understanding selectAll, data, enter, append sequence
Binding Data: Scott Murray D3 Tutorials
From the second link its explained:
The answer lies with enter(), a truly magical method. Here’s our final code for this example, which I’ll explain:
d3.select("body").selectAll("p")
.data(dataset)
.enter()
.append("p")
.text("New paragraph!");
.selectAll("p") — Selects all paragraphs in the DOM. Since none exist yet, this returns an empty selection. Think of this empty selection as representing the paragraphs that will soon exist.
Basically, you are selecting DOM elements that do not exist yet and then appending data to these non-existent elements and then appending them after the data is bound.

Categories

Resources