For learning how to use d3.js, I was trying to use titanic dataset for learning available on kaggle.
I am trying to achieve the objective:
Make a scatterplot of age vs fare with age in x axis and fare in y axis
Use the sex column to have male as square and female as circles in the scatterplot
Have opacity to indicate the condition - survived or not survived.
I have used the following code:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 600])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
.attr("r", 1.5)
.style("fill", "#69b3a2")
})
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
The condition I want to implement is:
if(d.sex == "female"){
return d3.symbolCircle;
} else if (d.sex == "male"){
return d3.symbolSquare;
}
But, being absolute new to the syntax, I am not understanding how. Also, How to have two colors for the 3rd objective of indicating survivor vs dead.
Can anyone help me please. I really thank you in advance.
In case you do not have the dataset, it can also be found here.
Firstly, you need to look at your data. Your properties are all uppercase, and reading a CSV file always means that you need to parse your rows: numbers and dates are still strings, you need to cast them as such:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
// All values are strings here, so we need to parse some of them.
// You can do that using `+x` or `Number(x)`, where `x = "123"`
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
// cabin: d.Cabin,
// embarked: e.Embarked,
fare: Number(d.Fare),
// name: d.Name,
// parch: Number(d.Parch),
// passengerId: Number(d.PassengerId)
// pclass: Number(Pclass),
sex: d.Sex,
// sibSp: Number(d.SibSp),
survived: d.Survived === "1"
// ticket: d.Ticket,
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 600])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
.attr("r", 1.5)
.style("fill", "#69b3a2")
})
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
Secondly, circles can only be circles. To draw both squares and circles, you'll need to use <path>. Your d3.symbol* were correct, but you need to access their .draw() function. d3.path is a generator to easily draw the d attribute of a path:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
// All values are strings here, so we need to parse some of them.
// You can do that using `+x` or `Number(x)`, where `x = "123"`
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
// cabin: d.Cabin,
// embarked: e.Embarked,
fare: Number(d.Fare),
// name: d.Name,
// parch: Number(d.Parch),
// passengerId: Number(d.PassengerId)
// pclass: Number(Pclass),
sex: d.Sex,
// sibSp: Number(d.SibSp),
survived: d.Survived === "1"
// ticket: d.Ticket,
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 600])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("transform", function(d) {
return "translate(" + [x(d.age), y(d.fare)] + ")";
})
.attr("d", function(d) {
const path = d3.path();
const shape = d.sex == "female" ? d3.symbolCircle : d3.symbolSquare;
shape.draw(path, 8);
return path.toString();
})
.style("fill", "#69b3a2")
})
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
(As an aside, you also could have drawn rects for both, but given the female ones rounded corners with the rx attribute).
Finally, you can use a colour scale for fill, with d3.scaleOrdinal, but if you have only two colours and don't use it to colour multiple things (like a line chart and a legend), just use an if statement:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
// All values are strings here, so we need to parse some of them.
// You can do that using `+x` or `Number(x)`, where `x = "123"`
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
// cabin: d.Cabin,
// embarked: e.Embarked,
fare: Number(d.Fare),
// name: d.Name,
// parch: Number(d.Parch),
// passengerId: Number(d.PassengerId)
// pclass: Number(Pclass),
sex: d.Sex,
// sibSp: Number(d.SibSp),
survived: d.Survived === "1"
// ticket: d.Ticket,
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 600])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("transform", function(d) {
return "translate(" + [x(d.age), y(d.fare)] + ")";
})
.attr("d", function(d) {
const path = d3.path();
const shape = d.sex == "female" ? d3.symbolCircle : d3.symbolSquare;
shape.draw(path, 8);
return path.toString();
})
.style("fill", function(d) {
return d.survived ? "#69b3a2" : "#ddd";
})
})
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
One interesting thing in SVG is that a <rect> with the rx and ry attributes equal to half its width or height (those being the same, of course) becomes effectively a circle.
So, supposing you have
var diameter = 3;
All you need is:
.attr("rx", function(d) {
return d.Sex === "male" ? 0 : diameter / 2
})
.attr("ry", function(d) {
return d.Sex === "male" ? 0 : diameter / 2
})
And, of course, subtract the x and y positions by half the diameter (i.e., the radius).
That seems like a hack, but the advantage of that approach is that it's quite easy to transition between the square and the "circle" by just changing the rx/ry values (one can transition paths, but that's a bit more complicated). Have a look at this transition, with an exaggerated radius and a clipped domain:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 10,
left: 60
},
width = 660 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var diameter = 12;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", row, function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 300])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.Age) - diameter / 2;
})
.attr("y", function(d) {
return y(d.Fare) - diameter / 2;
})
.attr("width", diameter)
.attr("height", diameter)
.style("fill", "#69b3a2")
.transition()
.duration(2000)
.attr("rx", function(d) {
return d.Sex === "male" ? 0 : diameter / 2
})
.attr("ry", function(d) {
return d.Sex === "male" ? 0 : diameter / 2
})
.style("fill", function(d) {
return +d.Survived ? "#69b3a2" : "tan"
});
})
function row(d) {
d.Age = +d.Age;
d.Fare = +d.Fare;
return d;
}
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
I have an issue with D3 scatterplot where the data are not correctly plot
(plotted to 1 horizontal line rather than a scattered plot, the actual data is also scattered)
and the x-axis not able to show up.
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S.%L%Z");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Get the data
data = data.rows;
// format the data
data.forEach(function(d) {
var momentTemp = moment(d[0]).format("YYYY-MM-DDTHH:mm:ss.SSSZ");
var parseTemp = parseTime(momentTemp);
d.date = parseTemp;
d.close += d[1];
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.close;
})]);
var xValue = function(d) {
return d.date;
}
var yValue = function(d) {
return d.close;
}
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 1.5)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.close);
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("transform", "translate(" + margin.left + " ,0)")
.call(d3.axisLeft(y));
That happens when your data is invalid. Are you sure the field close is correct? Your data calls the column ratio. I made some sample data and everything works:
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S.%L%Z");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var data = new Array(100)
.fill(undefined)
.map(function(d, i) {
return {
date: new Date(Number(new Date("01/01/2000")) + i * 24 * 60 * 60 * 1000),
close: Math.random(),
};
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.close;
})]);
var xValue = function(d) {
return d.date;
}
var yValue = function(d) {
return d.close;
}
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 1.5)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.close);
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("transform", "translate(" + margin.left + " ,0)")
.call(d3.axisLeft(y));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
I have this area chart going on and I am trying to do a specific color scale with it.
I am not getting any errors in my console so i am assuming i'm loading in the data right something just might be in the wrong place.
I am curious to see what you have to say i am not sure where to go from here. Thank you.
// set the dimensions and margins of the graph
var margin = {top: 80, right: 25, bottom: 30, left: 40},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/Nataliemcg18/Data/master/NASA_Surface_Temperature.csv",
// When reading the csv, I must format variables:
function(d){
return { variable : d3.timeParse("%Y")(d.variable), value : d.value }
},
// Now I can use this dataset:
function(data) {
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.variable; }))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([-1.2, d3.max(data, function(d) { return +d.value; })])
.range([ height, -10 ]);
svg.append("g")
.call(d3.axisLeft(y));
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([1.40, -.80])
// Add the area
svg.append("path")
.datum(data)
.attr("fill", d=>myColor())
.attr("stroke", "#69b3a2")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) { return x(d.variable) })
.y0(y(0))
.y1(function(d) { return y(d.value) })
)
})
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
I have a dataset that has 3 fields a name, a min value (value2) and a max value (value) that represent a range.
//The data
var data =[{"name": 'Scotty', "value2":0, "value":17},
{"name":'Dick', "value2":10, "value":17},
{"name":'James', "value2":5, "value":null},
{"name":'Max', "value2":2, "value":9}]
Currently I have it represented with a bar chart using this code that works ok except in cases where the points value2 and value are very close or there is a null value.
//Chart size parameters
var margin = {top: 20, right: 30, bottom: 90, left: 40},
width = 830 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//Chart axis
var x = d3.scaleBand()
.domain(data.map(function(d) { return d.name; }))
.range([2, width])
.scaleBand(0.10);
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.value; })])
.range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
//Initialize chart
var chart = d3.select("#mychart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Adding both axis
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
//Where the box is drawn
chart.selectAll(".box")
.data(data)
.enter().append("rect")
.attr("class", "box")
.attr("x", function(d) { return x(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return y(d.value2) - y(d.value); }) //Plot range
.attr("width", x.bandwidth()); //spacing for bars
Is there a way to plot the data with the following:
if two points are present plot both of them with a path connecting them
if one point is present with a null in field value only plot the one point
I am trying to populate a data set into D3's Bar chart data. I am using this example from the d3:
https://bl.ocks.org/mbostock/1134768
var causes = ["wounds", "other", "disease"];
var parseDate = d3.time.format("%m/%Y").parse;
var margin = {top: 20, right: 50, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.csv", function(error, crimea) {
if (error) throw error;
var layers = d3.layout.stack()(causes.map(function(c) {
return crimea.map(function(d) {
return {x: parseDate(d.date), y: +d[c]};
});
}));
var x = d3.scale.ordinal()
.domain([0,1])
.rangeRoundBands([0, width], 0.1, 0);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var z = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
console.log(layers);
x.domain(layers[0].map(function(d) { return d.x; }));
y.domain([0, d3.max(layers[layers.length - 1], function(d) { return d.y0 + d.y; })]).nice();
var ticks = x.domain().filter(function(d,i){ return !(i%20); } );
xAxis.tickValues( ticks );
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return z(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y + d.y0); })
.attr("height", function(d) { return y(d.y0) - y(d.y + d.y0); })
.attr("width", x.rangeBand() - 1);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(" + 0 + ",0)")
.call(yAxis);
});
This issue I am getting is that I have some white space which looks ugly. This space come before the 1st bar and also after the last bar of the chart. I have tried tweaking the x value of the bar, But I think that is not a good way to do.
This space does not come when the data set is small. But when dataset is large then this space comes up. How can I remove this space from the start and from end.
JSFiddle For the Above code is
https://jsfiddle.net/7qnngbdc/
See here --> https://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal_rangeRoundBands
"Note that rounding necessarily introduces additional outer padding which is, on average, proportional to the length of the domain. For example, for a domain of size 50, an additional 25px of outer padding on either side may be required. Modifying the range extent to be closer to a multiple of the domain length may reduce the additional padding."
After you've set the domain, try this -->
var mult = Math.max (1, Math.floor (width / x.domain().length));
x.rangeRoundBands ([0, (x.domain().length * mult)], 0.1, 0);
Changed in https://jsfiddle.net/7qnngbdc/1/