Multiple Lines from Multiple Data D3js - d3.js

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

Related

Using complex data structure in d3js

I'm making an interactive line chart and I'm having troubles capturing the data from a complex data structure
I passed an array of "paths" and it works:
path = [[{x:int,y:int},{x:int,y:int},{x:int,y:int}],
[{x:int,y:int},{x:int,y:int},{x:int,y:int}],
[{x:int,y:int},{x:int,y:int},{x:int,y:int}]]
but this is the data structure that I'm using:
data:[{
id: 0,
image:[int values],
path:[{x:int,y:int},{x:int,y:int},{x:int,y:int}],
pixel:[int values]
},
id: 1,
image:[int values],
path:[{x:int,y:int},{x:int,y:int},{x:int,y:int}],
pixel:[int values]
}]
And I make a line chart with a standard valueline function:
var valueline = d3.line()
.x(function(d) { return xScale(d.x); })
.y(function(d) { return yScale(d.y); })
.curve(d3.curveMonotoneX);
chart.selectAll(".line-group")
.data(path)
.enter()
.append("g")
.attr("class", "line-group")
.append("path")
.attr("d", valueline)
I'd like to pass the entire data array to d3js and that the function "valueline", captures x and y from the "path" key of each image in the data array.
Any ideas? thanks!
chart.selectAll(".line-group")
.data(data)
.enter()
.append("g")
.attr("class", "line-group")
.append("path")
.attr("d", d => valueline(d.path))
I ended up using rioV8 solution.
I also found another workaround from https://amber.rbind.io/blog/2017/05/02/d3nest/:
chart.selectAll(".line-group")
.data(data)
.enter()
.append("g")
.attr("class", "line-group")
.selectAll(".lineChart")
.data(function(d) { return [d.path] })
.enter()
.append("path")
.attr("class", "lineChart")
.attr("d",valueline)

Updating each iteration using d3.js

I am working on a line graph in d3.js and am unsure how to to iterate through each country and update my graph's points. I want to draw each country on my map. In my code I have only hard coded the first country and the output shown in the following images. Have attached my csv file to show the column names. I am unsure whether I need to alter my csv file to do so.
any help is appreciated
function init(){
var w = 600;
var h = 600;
var barPadding = 20;
var dataset;
var rowConverter = function(d){
return {
year: parseFloat(d.year),
Afghanistan: (d.Afghanistan),
Albania: (d.Albania),
Algeria: (d.Algeria),
Andorra: (d.Andorra),
Angola: (d.Angola)
};
}
d3.csv("hello.csv", rowConverter, function(data){
dataset = data;
if (data==null){
alert("Error, data has not been loaded!");
}
else{
draw(dataset);
console.log(dataset);
}
});
function draw(){
var xScale = d3.scaleLinear()
.domain([d3.min(dataset,function(d){
return d.year;
}),
d3.max(dataset,function(d){
return d.year;
})])
.range([barPadding,w-barPadding]);
var yScale = d3.scaleLinear()
.domain([0,100])
.range([h-barPadding,barPadding*2]);
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
var valueline = d3.line()
.x(function(d) { return xScale(d.year); })
.y(function(d) { return yScale(d.Afghanistan); });
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d.year);
})
.attr("cy", function(d) {
return yScale(d.Afghanistan);
})
.attr("r", 5)
.attr("fill","slategrey")
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d.year + "," + d.Afghanistan;
})
.attr("x", function(d) {
return xScale(d.year);
})
.attr("y", function(d) {
return yScale(d.Afghanistan);
})
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "blue");
svg.append("path")
.data([dataset])
.attr("class", "line")
.attr("d", valueline);
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(0," + (h - barPadding) + ")")
.call(xAxis);
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(" + barPadding + ",0)")
.call(yAxis);
}
}
window.onload=init;
As a selectAll(null).data(dataArray).enter() uses a data array to enter an element for each item in the data array, we need to create an array for each line we wish to enter. Currently you have an array for each year, but we want to enter a path for each data series/country. So we need to create an array where each item in that array represents a path.
This requires altering the structure of our data from:
[
{year: 2000, series1: number, series2: number... },
{year: 2001, series1: number, series2: number... },
....
]
To an array with an item for each line:
[
{ year: 2000, series1: number },
{ year: 2001, series1: number },
...
{ year: 2000, series2: number },
{ year: 2001, series2: number },
...
]
I'm using this approach because it is commonly seen in d3 cannonical examples such as this.
This is relatively easy to do. After we parse in our csv/tsv/dsv with d3, we can access the columns of the dataset with dataset.columns. The first column isn't a series we want to plot, it represents the x axis, so we can slice it off with dataset.columns.slice(1). Ok, with the remaining columns we can iterate through each series and create the data array above:
I'm using csvParse in the snippet, which replicates d3.csv except that it doesn't use a callback function for the returned data, letting me define the dataset with var dataset = d3.csvParse(... rather than d3.csv("file.csv", function(error, dataset) {...})
var csv = "year,series1,series2,series3\n"+
"2000,5,2,8\n"+
"2001,6,4,7\n"+
"2002,9,3,5\n"+
"2003,10,6,7\n"+
"2004,9,7,8"
var data = d3.csvParse(csv);
var series = data.columns // get the columns
.slice(1) // drop the first column(years)
.map(function(series) { // for each series:
return { // return a new object:
series: series, // name it
values: data.map(function(d) { // get the data:
return { year: d.year, value: d[series] };
})
}
});
console.log(series);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
Now we have an item in the data array for each series we want to draw a line for. Now we're cooking with gas. So we can now use selectAll().data(series) to enter a line for each item in the data array, creating a line for each series.
In keeping with Mike Bostock's example I linked to above, I've created an property which identifies which series each item represents, as well as a property which holds the arrays of year/value pairings.
Here's a quick demo:
var csv = "year,series1,series2,series3\n"+
"2000,5,2,8\n"+
"2001,6,4,7\n"+
"2002,9,3,5\n"+
"2003,10,6,7\n"+
"2004,9,7,8"
var data = d3.csvParse(csv);
var series = data.columns
.slice(1)
.map(function(series) {
return {
series: series,
values: data.map(function(d) {
return { year: d.year, value: d[series] };
})
}
});
var x = d3.scaleLinear().domain([2000,2004]).range([0,500]);
var y = d3.scaleLinear().domain([0,10]).range([300,0]);
var color = d3.scaleOrdinal().range(d3.schemeCategory20);
var line = d3.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.value); });
d3.select("svg")
.selectAll("path")
.data(series)
.enter()
.append("path")
.attr("fill","none")
.attr("stroke", function(d,i) { return color(i) })
.attr("d",function(d) { return line(d.values) });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>

I have to append text in my d3.js code but it's not displaying text

script type="text/javascript">
d3.csv("mydata.csv", function(data){
var svgcontainer = d3.select("body").append("svg")
.attr("width",500)
.attr("height",500)
svgcontainer.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width" ,function (d) { return d.age * 10;})
.attr("height" ,45)
.attr("y",function(d,i) { return i*50; })
.attr("fill","blue")
svgcontainer.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("fill","white")
//.attr("y",function(d,i) { return i*50 ; })
.text( function (d) { return d.name; })
})
I have to append text in my d3.js code but it's not displaying text.I am new to d3.js so please any one help me. Here is my code-
From the first observation i will get to know the issue related with the text element position in svg. In SVG Coordinate positions are very important to achieve the graphical representation. In above code snippet x and y positions are missing. sample code below:-
svgcontainer.append("text")
.attr("x", function(d) { return x(d.value) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d.value; });
Example

Conditionally fill/color of voronoi segments

I'm trying to conditionally color these voronoi segments based on the 'd.lon' value. If it's positive, I want it to be green, if it's negative I want it to be red. However at the moment it's returning every segment as green.
Even if I swap my < operand to >, it still returns green.
Live example here: https://allaffects.com/world/
Thank you :)
JS
// Stating variables
var margin = {top: 20, right: 40, bottom: 30, left: 45},
width = parseInt(window.innerWidth) - margin.left - margin.right;
height = (width * .5) - 10;
var projection = d3.geo.mercator()
.center([0, 5 ])
.scale(200)
.rotate([0,0]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()
.projection(projection);
var voronoi = d3.geom.voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.clipExtent([[0, 0], [width, height]]);
var g = svg.append("g");
// Map data
d3.json("/world-110m2.json", function(error, topology) {
// Cities data
d3.csv("/cities.csv", function(error, data) {
g.selectAll("circle")
.data(data)
.enter()
.append("a")
.attr("xlink:href", function(d) {
return "https://www.google.com/search?q="+d.city;}
)
.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5)
.style("fill", "red");
});
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
});
var voronoi = d3.geom.voronoi()
.clipExtent([[0, 0], [width, height]]);
d3.csv("/cities.csv", function(d) {
return [projection([+d.lon, +d.lat])[0], projection([+d.lon, +d.lat]) [1]];
}, function(error, rows) {
vertices = rows;
console.log(vertices);
drawV(vertices);
}
);
function polygon(d) {
return "M" + d.join("L") + "Z";
}
function drawV(d) {
svg.append("g")
.selectAll("path")
.data(voronoi(d), polygon)
.enter().append("path")
.attr("class", "test")
.attr("d", polygon)
// This is the line I'm trying to get to conditionally fill the segment.
.style("fill", function(d) { return (d.lon < 0 ? "red" : "green" );} )
.style('opacity', .7)
.style('stroke', "pink")
.style("stroke-width", 3);
}
JS EDIT
d3.csv("/static/cities.csv", function(data) {
var rows = [];
data.forEach(function(d){
//Added third item into my array to test against for color
rows.push([projection([+d.lon, +d.lat])[0], projection([+d.lon, +d.lat]) [1], [+d.lon]])
});
console.log(rows); // data for polygons and lon value
console.log(data); // data containing raw csv info (both successfully log)
svg.append("g")
.selectAll("path")
.data(voronoi(rows), polygon)
.enter().append("path")
.attr("d", polygon)
//Trying to access the third item in array for each polygon which contains the lon value to test
.style("fill", function(data) { return (rows[2] < 0 ? "red" : "green" );} )
.style('opacity', .7)
.style('stroke', "pink")
.style("stroke-width", 3)
});
This is what's happening: your row function is modifying the objects of rows array. At the time you get to the function for filling the polygons there is no d.lon anymore, and since d.lon is undefined the ternary operator is evaluated to false, which gives you "green".
Check this:
var d = {};
console.log(d.lon < 0 ? "red" : "green");
Which also explains what you said:
Even if I swap my < operand to >, it still returns green.
Because d.lon is undefined, it doesn't matter what operator you use.
That being said, you have to keep your original rows structure, with the lon property in the objects.
A solution is getting rid of the row function...
d3.csv("cities.csv", function(data){
//the rest of the code
})
... and creating your rows array inside the callback:
var rows = [];
data.forEach(function(d){
rows.push([projection([+d.lon, +d.lat])[0], projection([+d.lon, +d.lat]) [1]])
});
Now you have two arrays: rows, which you can use to create the polygons just as you're using now, and data, which contains the lon values.
Alternatively, you can keep everything in just one array (just changing your row function), which is the best solution because it would make easier to get the d.lon values inside the enter selection for the polygons. However, it's hard providing a working answer without testing it with your actual code (it normally ends up with the OP saying "it's not working!").

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.

Resources