D3 data not showing all the elements - d3.js

I have the following code
JS:
var cities = [
{ name: "Moscow", x: 585, y: 565 },
{ name: "Kiev", x: 735, y: 765 },
];
HTML:
<svg .....>
// My SVG code
</svg>
<script>
// d3.select("svg").append("text").text(cities[0].name).attr("x", cities[0].x).attr("y", cities[0].y).attr("font-size",18).attr("fill", "black");
// d3.select("svg").append("text").text(cities[1].name).attr("x", cities[1].x).attr("y", cities[1].y).attr("font-size",18).attr("fill", "black");
d3.select("svg").data(cities).enter().append("text").text(function(d) { return d.name; } ).attr("x", function(d) { return d.x; } ).attr("y", function(d) { return d.y; } ).attr("font-size",18).attr("fill", "black");
</script>
I'm new to D3. I was trying to convert my commented out code, which works, to some code which iterates over the array I have defined. However, I only get the last element printed with the non commented out code. Why and how to correct this?

You have to create a selection using selectAll and then bind data to it before you can start the enter phase.
What you want is something on these lines:
d3.select("svg")
.selectAll('text.city-name')
.data(cities)
.enter()
.append("text")
.classed('city-name', true)
.text(function(d) { return d.name; } )
.attr("x", function(d) { return d.x; } )
.attr("y", function(d) { return d.y; } )
.attr("font-size",18)
.attr("fill", "black");
Working example: Demo.
In your case, what is happening is that the existing svg element is getting bound to the first element in city and then the second city's text element gets created in the .enter() phase and is added to the body.
To understand how .data joining works and how .select and .selectAll differ, I think thinking with joins article is a great place to start.

Related

Multiple Lines from Multiple Data D3js

I would expect the following code to plot two different lines on the same svg but it only returns two empty path>
<body>
<svg width="960" height="500"></svg>
<script>
data = [
{ Name: "line1", color: "blue", Points: [{x:0, y:5 }, {x:25, y:7 }, {x:50, y:13}] },
{ Name: "line2", color: "green", Points: [{x:0, y:10}, {x:25, y:30}, {x:50, y:60}] }
];
var line = d3.line()
.x(function(d, i) {return d.x})
.y(function(d, i) {return d.y})
var lines = d3.select("svg")
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", line);
</script>
</body>
I cannot find multiple line charts example that have a data structure similar to mine.
The issue is that you are passing the whole object from your array of data to the line() function, which is expecting an array of points. One alternative is to change the calling function to pass in only the Points array, something like this (untested):
.enter()
.append("path")
.attr("d", function(d) { return line(d.Points); })
In fact you need to access the Points field of each element of the data array:
Within:
var lines = d3.select("svg")
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", line);
replace
.data(data)
by
.data(data.map( function(d) { return d.Points }))

add a sub element in d3js using data binding

I have a table data like
ID, Name, Value
001, ABC, 123
002, DEF, 345
and I would like to display it like:
<div id="001">
<h1>ABC</h1> <p>123</p>
I tried the following:
var divs = root.selectAll("div").data(data)
.enter()
.append("div")
.attr("id", function (d) { return d.ID; })
.append("h1")
.text(function (d) { return d.Name; })
.selectAll("div p").data(function (d) { return d; })
.enter().append("p")
.text(function (d) { return d.Value; })
However it only display the first level. I tried a few other options but was unsuccessful, either display nothing or display all values in each element.
I have the feeling I am missing something obvious, any pointer would be appreciated.
Two main changes:
firstly, you have to break your selection, appending the <h> and the <p> elements to divs. So, you'll have as many divs as objects in your data array. However, for each div, you'll append one <h> and one <p> element. Right now, you're kind of appending (more on that below) the <p> elements to the <h>, not to the divs.
Secondly, you should not create a nested selection rebinding data
(since you don't have inner arrays here, but only objects inside an
array). That would make sense if you had several <p> elements for each div, but you don't.
So, this should be your code:
var divs = root.selectAll(null)
.data(data)
.enter()
.append("div")
.attr("id", function(d) {
return d.ID;
});
divs.append("h1")
.text(function(d) {
return d.Name;
});
divs.append("p")
.text(function(d) {
return d.Value;
});
And this creates this structure:
<div id="001">
<h1>ABC</h1>
<p>123</p>
</div>
<div id="002">
<h1>DEF</h1>
<p>345</p>
</div>
Here is the demo:
var data = [{
ID: "001",
Name: "ABC",
Value: "123"
}, {
ID: "002",
Name: "DEF",
Value: "345"
}];
var root = d3.select("body");
var divs = root.selectAll(null)
.data(data)
.enter()
.append("div")
.attr("id", function(d) {
return d.ID;
});
divs.append("h1")
.text(function(d) {
return d.Name;
});
divs.append("p")
.text(function(d) {
return d.Value;
});
<script src="https://d3js.org/d3.v4.min.js"></script>

Conditionally change background image of D3 circles

I am trying to conditionally change the background of some circles created with D3 but I am unable to get the if else logic to correctly pick out the correct picture. The code goes right to the final else statement for the default.gif. I don't see any errors in the console. All the images are in the same directory as the html file.
var diameter = 500, //max size of the bubbles
format = d3.format(",d"),
color = d3.scaleOrdinal(d3.schemeCategory20c); //color category
var bubble = d3.pack()
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body")
.append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.csv("fruit.csv", function(error, data){
if (error) throw error;
//convert numerical values from strings to numbers
data = data.map(function(d){ d.value = +d["Amount"]; return d; });
//Sets up a hierarchy of data object
var root = d3.hierarchy({children:data})
.sum(function(d) { return d.value; })
.sort(function(a, b) { return b.value - a.value; });
//Once we have hierarchal data, run bubble generator
bubble(root);
//setup the chart
var bubbles = svg.selectAll(".bubble")
.data(root.children)
.enter();
//create the bubbles
bubbles.append("circle")
.attr("class", "circle")
.attr("r", function(d){ return d.r; })
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; })
.append("defs")
.append("pattern")
.append("image")
.attr("xlink:href", function(d) {
if ( d.Fruit == "Apple") {
return "apple.jpg";
}
else if (d.Fruit == "Pear") {
return "pear.jpg"
}
else if (d.Fruit == "Banana") {
return "banana.jpg";
}
else if (d.Fruit == "Strawberry") {
return "strawberry.jpg";
}
else if (d.Fruit == "Grapes") {
return "grapes.jpg";
}
else { return "default.gif"; }
});
//.style("fill", function(d) { return color(d.value); });
//format the text for each bubble
bubbles.append("text")
.attr("x", function(d){ return d.x; })
.attr("y", function(d){ return d.y + 5; })
.attr("text-anchor", "middle")
.text(function(d){ return d.data["Fruit"]; })
.style("fill","white")
.style("font-family", "Helvetica Neue, Helvetica, Arial, san-serif")
.style("font-size", "12px");
});
The csv file contains the following data:
Fruit,Amount
Apple,32
Pear,13
Banana,25
Grapes,29
Strawberry,36
As I said in my comment, I'm surprised that you're seeing anything as the circles' background, since your code for creating/referencing the patterns is not correct. For a quick explanation, check my answer here.
However, I'll address only your main question here, which is the if statement.
The problem is that you're using d3.hierarchy to create your data array. Because of that, all data are inside node.data. According the API:
The returned node and each descendant has the following properties:
node.data - the associated data, as specified to the constructor.
Therefore, for your conditionals, instead of:
d.Fruit == "Apple"
It should be:
d.data.Fruit == "Apple"
And the same for d.data.Fruit == "Pear", d.data.Fruit == "Banana" etc...

Adding point and value to line chart... how to color the point like the line?

I've started from the multiline example http://bl.ocks.org/mbostock/3884955
I extended it to show the dots along the line, but I'm not able to give to the circle the same color of the line...
I'm really new in d3.js and I really need an advice.
here the example page: http://www.danielepennati.com/d3/linea.html
I've change some variable name to make the script more general so there is some difference with the original example code. the main one is the name of the variable that contain the mapped data: it is "column" and not "cities"
d3.tsv(surce_data, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "id"; }));
var column = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {id: d.id, value: +replace(d[name])};
})
};
});
the second main difference is the x axsis: in my code it is ordinal and not linear.
so to draw the line the code is:
var tracciato = svg.selectAll(".line-group")
.data(column)
.enter().append("g")
.attr("class", "line-group");
tracciato.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
to make the points along the line I've wrote this code:
var point = tracciato.append("g")
.attr("class", "line-point");
point.selectAll('circle')
.data(function(d,i){ return d.values})
.enter().append('circle')
.attr("cx", function(d, i) {
return x(i) + x.rangeBand() / 2;
})
.attr("cy", function(d, i) { return y(d.value) })
.attr("r", 5);
I'd link the points color be the same of the line, but the problem is the colors are assigned to the "column" object. I don't know how to give to each new circle within the same column the same column color...
I don't know if my problem is clear, please ask me if you need any more specification.
Thanks

Appended text not appearing when using D3

I'm trying to get this text to display on mouseover but it's not working, can anyone give some insights? There are multiple circles in the document and I want each one to display overhead text on mouseover. Current form should be displaying "hello"s everywhere but there's nothing.
d3.selectAll("circle")
.on("mouseover",function(d){
var x = parseFloat( d3.select(this).attr("cx") );
var y = parseFloat( d3.select(this).attr("cy") );
d3.selectAll("circle")
.append("text")
.attr("class","tooltipText")
.attr("x",x)
.attr("y",y)
.attr("stroke-width",1)
.attr("fill", "white")
.attr("font-size", "13px")
.attr("text-anchor", "middle")
.text(function(){
return "hello";
});
});
You should encapsulate your circles inside of g elements. These will act as <div> tags in HTML. You can add a class attribute to these elements so they will be easily selected afterwards.
//circles creation
var circles = d3.selectAll('svg').append('svg')
.append('g').attr('class', 'circles').data( someData );
circles.enter().append('g').attr('class', 'circle')
.append('circle')
.attr('cx', function(d) { return xScale(d); })
.attr('cy', function(d) { return yScale(d); })
.append('text').attr('style', 'display:none;')
.text(function(d) { return d.title; })
.attr('x', function(d) { return xScale(d); })
.attr('y', function(d) { return yScale(d) + 2*radius(d); });
d3.selectAll('.circle').on('mouseover', function( data, index, element ) {
d3.select( element ).attr('style', '');
});
Notice that xScale, yScale, radius are all function of the data.

Resources