add a sub element in d3js using data binding - d3.js

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>

Related

How can i get the key of an element in stack-layout in d3?

I want to add a toolkit that show the type of the disaster, which is the key of the stack datum, how can i get it?
The format of .csv file is like this: (Forgive me can not take pictures)
AllNaturalDisasters,Drought,Earthquake,ExtremeTemperature,ExtremeWeather,Flood,Impact,Landslide,MassMovementDry,VolcanicActivity,Wildfire,Year
5,2,null,null,1,1,null,null,null,1,null,1900
2,null,2,null,null,null,null,null,null,null,null,1901
Here I create a stack
var stack = d3.stack()
.keys(["Drought", "Earthquake", "ExtremeTemperature", "ExtremeWeather", "Flood", "Impact", "Landslide", "MassMovementDry", "VolcanicActivity", "Wildfire"]);
and then I pass it my data:var series = stack(dataset);. dataset is the all data from the csv file. Then I create a chart using stack-layout, like this:
var groups = svg.selectAll("g")
.data(series)
.enter()
.append("g")
.style("fill", function(d, i) {
return colors(i);
});
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]);
})
.attr("width", xScale.bandwidth())
.append("title")
.text(function (d) {
return d.data.Year;
});
The problem is right here:
.append("title")
.text(function (d) {
return d.data.Year;
});
I want to add a toolkit to show the type of the disaster, which is the key of this datum in series , how can I get it instead of the year?!
Each rectangle contains information on the column (year of disaster), but each g has information on the "row" (type of disaster).
The stack produces a nested array, the parent level (which we use to create the g elements) contains the key, or type of disaster
The child level represents the columns, which contains the year.
The grandchild level just contains individual rectangles.
So, we can get a key by selecting the parent g:
.append("title")
.text(function() {
var rect = this.parentNode; // the rectangle, parent of the title
var g = rect.parentNode; // the g, parent of the rect.
return d3.select(g).datum().key; // now we get the key.
})
Of course this could be simplified a bit, but I broke it out to comment it better.
This allows for more flexible sorting - rather than relying on fixed indexes.
Here it is using your data:
var csv = d3.csvParse(d3.select("pre").text());
var stack = d3.stack().keys(["Drought", "Earthquake", "ExtremeTemperature", "ExtremeWeather", "Flood", "Impact", "Landslide", "MassMovementDry", "VolcanicActivity", "Wildfire"]);
var series = stack(csv);
var colors = d3.scaleOrdinal()
.range(d3.schemeCategory10);
var xScale = d3.scaleBand()
.domain([0,1])
.range([0,300])
var yScale = d3.scaleLinear()
.domain([0,6])
.range([200,0]);
var svg = d3.select("svg");
var groups = svg.selectAll("g")
.data(series)
.enter()
.append("g")
.style("fill", function(d, i) {
return colors(i);
});
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]);
})
.attr("width", xScale.bandwidth())
.append("title")
.text(function (d) {
var rect = this.parentNode;
var g = rect.parentNode;
return d3.select(g).datum().key;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="300"></svg>
<pre>AllNaturalDisasters,Drought,Earthquake,ExtremeTemperature,ExtremeWeather,Flood,Impact,Landslide,MassMovementDry,VolcanicActivity,Wildfire,Year
5,2,0,0,1,1,0,0,0,1,0,1900
2,0,2,0,0,0,0,0,0,0,0,1901</pre>
Well, I have fixed this problem by a very 'low' method. I have created a simple function:
function getKeys(d) {
return series[parseInt(groups.selectAll("rect").data().indexOf(d) / series[0].length)].key;
}
Well, it so simple and crude, and I still want to know a more efficient method!!!

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 }))

Accessing nested data in D3

I'm having trouble understanding when and how to use nested data.
In this example I have a CSV with names ('Name') and locations ('starting point'). By assigning keys to the locations I am able to make a dropdown containing them all, and I would like to use this to filter the names associated with each location.
However I am unable to find the data's values, in this case 'd.Name'
Here inside the update function I have tried to access the 'values' on the data join.
var adventurer = canvas
.selectAll(".adventurer")
.data(function(d) {
return d.values;
})
Ive also tried creating an extra data variable but thats not working for me either.
Sorry I can't make a jsfiddle but here is a plunk
DATA
,,Name,First names,s,r,Nat,born,starting point,starting date,arrival date,days,km,Assist,Support,Style,note,arrival date 2
1,1,KAGGE,Erling,,,Nor,1/15/1963,Berkner Island,11/18/1992,1/7/1993,50,appr. 1300,n,n,solo,first solo unassisted,
2,2,ARNESEN,Liv,f,,Nor,6/1/1953,Hercules Inlet,11/4/1994,12/24/1994,50,1130,n,n,solo,first woman unassisted,
3,3,HAUGE,Odd Harald,,,Nor,1956,Berkner Island,11/4/1994,12/27/1994,54,appr. 1300,n,n,,,
HTML
<div id="menu"></div>
<div id="chart"></div>
SCRIPT
d3.csv("data.csv", function(csv_data) {
var data = d3.nest()
.key(function(d) {
return d['starting point'];})
.sortKeys(d3.ascending)
.entries(csv_data)
console.log(data);
//create dropdown select
var list = d3.select("#menu").append("select")
list.selectAll("option")
.data(data)
.enter().append("option")
.attr("value", function(d) {
return d.key;
})
.text(function(d) {
return d.key;
});
//chart config
var w = 375,
h = 1000;
var canvas = d3.select('#chart')
.append('svg')
.attr('width', w)
.attr('height', h)
.append('g')
.attr('transform', 'translate (0,50)');
//function (bind, add, remove, update)
function updateLegend(data) {
var adventurer = canvas
.selectAll(".adventurer")
.data(function(d) {
return d.values;
})
var adventurerEnter = adventurer
.enter().append("g")
.attr('class', 'adventurer');
adventurerEnter
.append("text")
.attr('class', 'name')
.attr('x', 0);
adventurer.select('.name')
.text(function(d, i) {
return d.Name;
})
.attr('y', function(d, i) {
return i * 30;
});
// remove old elements
adventurer.exit().remove();
};
// generate initial legend
updateLegend(data);
});
// handle on click event
d3.select('#menu')
.on('change', function() {
var data = eval(d3.select(this).property('value'));
console.log(data)
updateLegend(data);
});
You need to display both the locations and the names. You have a nest (in the plunk but not in your question) of location/name, but you also need a distinct list of names, or possibly a list of name/location:
var locations = d3.nest()
.key(function(d) {return d['starting point'];})
.sortKeys(function(a,b){ return a > b && 1 || b > a && -1 || 0})
.key(function(d) {return d.Name;})
.entries(csv_data)
var names = d3.nest()
.key(function(d) {return d.Name;})
.sortKeys(function(a,b){ return a > b && 1 || b > a && -1 || 0})
.key(function(d) {return d['starting point'];})
.entries(csv_data)
Then you have to display your names however you want. Then you need an .on('change', function()...) handler (or on click or whatever fits your needs) that actually filters the names wherever those are displayed.
I also fixed your sorting. d3.ascending is for numbers, not strings.

D3 data not showing all the elements

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.

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