How can I add labels inside the points in a scatterplot? - javascript

I am trying to add state abbreviations within a scatterplot like so:
Here's a snippet of the CSV file I am working with:
id abbr healthcare poverty
1 AL 13.9 19.3
2 AK 15 11.2
3 AZ 14.4 18.2
4 AR 16.3 18.9
5 CA 14.8 16.4
6 CO 12.8 12
7 CT 8.7 10.8
8 DE 8.7 12.5
Here's my JavaScript code:
// #TODO: YOUR CODE HERE!
var svgWidth = 750;
var svgHeight = 500;
var margin = {
top: 20,
right: 40,
bottom: 60,
left: 100
};
var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
// Create an SVG wrapper, append an SVG group that will hold our chart and shift the latter by left and top margins
var svg = d3.select("#scatter")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
var chartGroup = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// Import Data
d3.csv("data.csv").then(function(censusData) {
// Parse Data & Cast as numbers
censusData.forEach(function(data) {
data.healthcare = +data.healthcare;
data.poverty = +data.poverty;
});
// Create scale functions
var xLinearScale = d3.scaleLinear()
.domain(d3.extent(censusData, d => d.poverty))
.range([0, width]);
var yLinearScale = d3.scaleLinear()
.domain([0, d3.max(censusData, d => d.healthcare)])
.range([height, 0]);
// Create axis functions
var bottomAxis = d3.axisBottom(xLinearScale);
var leftAxis = d3.axisLeft(yLinearScale);
// Append axes to the chart
chartGroup.append("g")
.attr("transform", `translate(0, ${height})`)
.call(bottomAxis);
chartGroup.append("g")
.call(leftAxis);
// Create circles
var circlesGroup = chartGroup.selectAll("Circle")
.data(censusData)
.enter()
.append("circle")
.attr("cx", d => xLinearScale(d.poverty))
.attr("cy", d => yLinearScale(d.healthcare))
.attr("r", "15")
.attr("fill", "rgb(117, 145, 197)")
.attr("opacity", "0.5");
// Add state labels to the points
var circleLabels = circlesGroup.selectAll("text").data(censusData).enter().append("text");
circleLabels
.attr("x", function(d) { return d.poverty; })
.attr("y", function(d) { return d.healthcare; })
.text(function(d) { return d.abbr; })
.attr("font-family", "sans-serif")
.attr("font-size", "5px")
.attr("fill", "white");
// Create axes labels
chartGroup.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left + 40)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.attr("class", "axisText")
.style("text-anchor", "middle")
.text("Lacks Healthcare (%)");
chartGroup.append("text")
.attr("transform", `translate(${width / 2}, ${height + margin.top + 30})`)
.attr("class", "axisText")
.style("text-anchor", "middle")
.text("In Poverty (%)");
// Initialize tooltip
var toolTip = d3.tip()
.attr("class", "tooltip")
.offset([80, -60])
.html(function(d) {
return `${d.state}<br>Poverty: ${d.poverty}<br>Healthcare: ${d.healthcare}<br>`;
});
// Create tooltip in the chart
chartGroup.call(toolTip);
// Create event listeners to display and hide the tooltip
circlesGroup.on("mouseover", function(data) {
toolTip.show(data, this);
})
// onmouseout event
.on("mouseout", function(data, index) {
toolTip.hide(data);
});
});
I attempted to add them in the circleLabels part of the code but to no avail.
Can anyone tell me what I am doing wrong in this part:
// Add state labels to the points
var circleLabels = circlesGroup.selectAll("text").data(censusData).enter().append("text");
circleLabels
.attr("x", function(d) { return d.poverty; })
.attr("y", function(d) { return d.healthcare; })
.text(function(d) { return d.abbr; })
.attr("font-family", "sans-serif")
.attr("font-size", "5px")
.attr("fill", "white");
Any suggestions or changes are welcome.

You have three problems:
circlesGroup is a circles' selection. You cannot append <text> elements to <circle> elements. Therefore, change it to chartGroup:
var circleLabels = chartGroup.selectAll("text")//etc...
and that brings us to the second problem:
There are text elements in that selection. So, to avoid binding data to existing elements (which reduces the size of the enter selection), use selectAll(null:
var circleLabels = chartGroup.selectAll(null)//etc...
to read more about selectAll(null), read my Q/A pair here: Selecting null: what is the reason behind 'selectAll(null)' in D3.js?
You are not using the scales for positioning the texts.
Finally, use text-anchor: middle for entering the texts.
Here is your code with those changes:
var csv = `id,abbr,healthcare,poverty
1,AL,13.9,19.3
2,AK,15,11.2,
3,AZ,14.4,18.2
4,AR,16.3,18.9
5,CA,14.8,16.4
6,CO,12.8,12
7,CT,8.7,10.8
8,DE,8.7,12.5`;
const censusData = d3.csvParse(csv)
var svgWidth = 960;
var svgHeight = 500;
var margin = {
top: 20,
right: 40,
bottom: 60,
left: 100
};
var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
// Create an SVG wrapper, append an SVG group that will hold our chart and shift the latter by left and top margins
var svg = d3.select("#scatter")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
var chartGroup = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// Parse Data & Cast as numbers
censusData.forEach(function(data) {
data.healthcare = +data.healthcare;
data.poverty = +data.poverty;
});
// Create scale functions
var xLinearScale = d3.scaleLinear()
.domain(d3.extent(censusData, d => d.poverty))
.range([0, width]);
var yLinearScale = d3.scaleLinear()
.domain([0, d3.max(censusData, d => d.healthcare)])
.range([height, 0]);
// Create axis functions
var bottomAxis = d3.axisBottom(xLinearScale);
var leftAxis = d3.axisLeft(yLinearScale);
// Append axes to the chart
chartGroup.append("g")
.attr("transform", `translate(0, ${height})`)
.call(bottomAxis);
chartGroup.append("g")
.call(leftAxis);
// Create circles
var circlesGroup = chartGroup.selectAll("Circle")
.data(censusData)
.enter()
.append("circle")
.attr("cx", d => xLinearScale(d.poverty))
.attr("cy", d => yLinearScale(d.healthcare))
.attr("r", "15")
.attr("fill", "blue")
.attr("opacity", "0.5");
var circleLabels = chartGroup.selectAll(null).data(censusData).enter().append("text");
circleLabels
.attr("x", function(d) {
return xLinearScale(d.poverty);
})
.attr("y", function(d) {
return yLinearScale(d.healthcare);
})
.text(function(d) {
return d.abbr;
})
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("text-anchor", "middle")
.attr("fill", "white");
// Create axes labels
chartGroup.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left + 40)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.attr("class", "axisText")
.text("Lacks Healthcare (%)");
chartGroup.append("text")
.attr("transform", `translate(${width / 2}, ${height + margin.top + 30})`)
.attr("class", "axisText")
.text("In Poverty (%)");
<head>
<meta charset="UTF-8">
<title>D3Times</title>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" href="assets/css/style.css">
<link rel="stylesheet" href="assets/css/d3Style.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12 col-md-12">
<h1>D3Times</h1>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-9">
<div id="scatter">
<!-- We append our chart here. -->
</div>
</div>
</div>
</div>
<!-- Footer-->
<div id="footer">
<p>The Coding Boot CampĀ©2016</p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.5.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.js"></script>
<script type="text/javascript" src="assets/js/app.js"></script>
</body>

Related

How to plot x axis values over bars?

I changed the attributes of the X axis to plot its values over the bars of the chart. But anywhere I put the code, the values are always plotted before ("behind") the bars and therefore we cannot see it.
//This part of the code is OUTSIDE of the update function (line 44 of the fiddle)
//append group to plot X axis
const xAxisGroup = g.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${HEIGHT})`)
//This part of the code is INSIDE the update function (line 92)
const xAxisCall = d3.axisBottom(x)
xAxisGroup.call(xAxisCall)
.selectAll("text")
.attr("x", "-5") // <<<--- I change this to 50
.attr("y", "10")
.attr("text-anchor", "end")
.attr("transform", "rotate(-45)") // <<<--- I changed this to -90
How would be possible to plot this values over the bars instead?
This is the fiddle of the original chart and this is the modified one. The month values may be behind the bars... :-/
In an SVG, whatever is painted later stays on top. So, just append your x axis <g> element after painting the rectangles. Alternatively, raise it:
xAxisGroup.raise()
Here's your code with that change:
//set general margin, width and height values
const MARGIN = {
LEFT: 128,
RIGHT: 8,
TOP: 32,
BOTTOM: 128
}
const WIDTH = 400 - MARGIN.LEFT - MARGIN.RIGHT
const HEIGHT = 300 - MARGIN.TOP - MARGIN.BOTTOM
//append svg plot area into div chart area
const svg = d3.select("#chart-area").append("svg")
.attr("width", WIDTH + MARGIN.LEFT + MARGIN.RIGHT)
.attr("height", HEIGHT + MARGIN.TOP + MARGIN.BOTTOM)
//append group into svg
const g = svg.append("g")
.attr("transform", `translate(${MARGIN.LEFT}, ${MARGIN.TOP})`)
//X label
g.append("text")
.attr("class", "x axis-label")
.attr("x", WIDTH / 2)
.attr("y", HEIGHT + 60)
.attr("font-size", "20px")
.attr("text-anchor", "middle")
.text("Month")
//Y label
g.append("text")
.attr("class", "y axis-label")
.attr("x", -(HEIGHT / 2))
.attr("y", -60)
.attr("font-size", "20px")
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90)")
.text("Value")
//set scale for X axis
const x = d3.scaleBand()
.range([0, WIDTH])
.paddingInner(0.3)
.paddingOuter(0.2)
//set scale for Y axis
const y = d3.scaleLinear()
.range([HEIGHT, 0])
//append group to plot X axis
const xAxisGroup = g.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${HEIGHT})`)
//append group to plot Y axis
const yAxisGroup = g.append("g")
.attr("class", "y axis")
//import data
d3.csv("https://raw.githubusercontent.com/dbahiense/sotabook/main/revenues.csv").then(data => {
//parse values
data.forEach(d => {
d.revenue = Number(d.revenue)
d.profit = Number(d.profit)
})
//listen drop-down lists and trigger update function on change
//state
d3.select("#state")
.on("change", function(event, d) {
update(data)
})
//round
d3.select("#round")
.on("change", function(event, d) {
update(data)
})
//plot chart on page first load
update(data)
})
// update chart function
function update(data) {
//drop-down list listened values
let state = d3.select("#state").property("value")
let round = d3.select("#round").property("value")
//filter data by drop-down list values
let filteredData = data.filter(function(d) {
return d.state == state & d.round == round
})
//set domains for X and Y axes
x.domain(filteredData.map(d => d.month))
y.domain([0, d3.max(filteredData, d => d.revenue)])
const xAxisCall = d3.axisBottom(x)
const yAxisCall = d3.axisLeft(y)
//.tickFormat(d => d + "m")
yAxisGroup.call(yAxisCall)
// JOIN new data with old elements.
const rects = g.selectAll("rect")
.data(filteredData)
// EXIT old elements not present in new data.
rects.exit().remove()
// UPDATE old elements present in new data.
rects
.attr("y", d => y(d.revenue))
.attr("x", (d) => x(d.month))
.attr("width", x.bandwidth)
.attr("height", d => HEIGHT - y(d.revenue))
// ENTER new elements present in new data.
rects.enter().append("rect")
.attr("y", d => y(d.revenue))
.attr("x", (d) => x(d.month))
.attr("width", x.bandwidth)
.attr("height", d => HEIGHT - y(d.revenue))
.attr("fill", "steelblue")
xAxisGroup.raise()
.call(xAxisCall)
.selectAll("text")
.attr("x", "50")
.attr("y", "10")
.attr("text-anchor", "end")
.attr("transform", "rotate(-90)")
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="">
<title>5.4</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<!-- Custom styling -->
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- Bootstrap grid setup -->
<div class="container">
<div class="row">
<select id="state">
<option value="US">US</option>
<option value="EU">EU</option>
<option value="AS">AS</option>
</select>
<select id="round">
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
<div class="row">
<div id="chart-area"></div>
</div>
</div>
<!-- External JS libraries -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<!-- Custom JS below-->
</body>
</html>

D3.js heatmap with color

Hi I am trying to add in a color scale for my heat map. I Specifically want to use d3.schemeRdYlBu this color scheme but I am having a hard time implementing it. At the moment it just does black. I also have a hover feature with this so I would like that to still work but i am more concerned with just getting the color to work. Obviously having the lower numbers be blue and the higher numbers be red to indicate temp
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<!-- Load color palettes -->
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {top: 80, right: 25, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 1000 - 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", function(data) {
// Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
var myGroups = d3.map(data, function(d){return d.group;}).keys()
var myVars = d3.map(data, function(d){return d.variable;}).keys()
// Build X scales and axis:
var x = d3.scaleBand()
.range([ 0, width ])
.domain(myGroups)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0))
.select(".domain").remove()
// Build Y scales and axis:
var y = d3.scaleBand()
.range([ height, 0 ])
.domain(myVars)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.call(d3.axisLeft(y).tickSize(0))
.select(".domain").remove()
// Build color scale
var myColor = (d3.schemeRdYlBu[2])
// create a tooltip
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function(d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "green")
.style("opacity", 1)
}
var mousemove = function(d) {
tooltip
.html("The exact value of this cell is: " + d.value, )
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mouseleave = function(d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8)
}
// add the squares
svg.selectAll()
.data(data, function(d) {return d.group+':'+d.variable;})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.group) })
.attr("y", function(d) { return y(d.variable) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) { return myColor(d.value)} )
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
})
// Add title to graph
svg.append("text")
.attr("x", 0)
.attr("y", -50)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("A d3.js heatmap");
// Add subtitle to graph
svg.append("text")
.attr("x", 0)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.style("max-width", 400)
.text("A short description of the take-away message of this chart.");
</script>
You can use arrow function instead of the regular function to use your own binding of this for accessing myColor variable.
<!DOCTYPE html>
<meta charset="utf-8" />
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<!-- Load color palettes -->
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = { top: 80, right: 25, bottom: 30, left: 40 },
width = 1000 - margin.left - margin.right,
height = 1000 - 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",
function (data) {
// Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
var myGroups = d3
.map(data, function (d) {
return d.group;
})
.keys();
var myVars = d3
.map(data, function (d) {
return d.variable;
})
.keys();
// Build X scales and axis:
var x = d3.scaleBand().range([0, width]).domain(myGroups).padding(0.05);
svg
.append("g")
.style("font-size", 15)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0))
.select(".domain")
.remove();
// Build Y scales and axis:
var y = d3.scaleBand().range([height, 0]).domain(myVars).padding(0.05);
svg
.append("g")
.style("font-size", 15)
.call(d3.axisLeft(y).tickSize(0))
.select(".domain")
.remove();
// Build color scale
var myColor = d3.schemeRdYlBu[3][2];
// create a tooltip
var tooltip = d3
.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px");
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function (d) {
tooltip.style("opacity", 1);
d3.select(this).style("stroke", "green").style("opacity", 1);
};
var mousemove = function (d) {
tooltip
.html("The exact value of this cell is: " + d.value)
.style("left", d3.mouse(this)[0] + 70 + "px")
.style("top", d3.mouse(this)[1] + "px");
};
var mouseleave = function (d) {
tooltip.style("opacity", 0);
d3.select(this).style("stroke", "none").style("opacity", 0.8);
};
// add the squares
svg
.selectAll()
.data(data, function (d) {
return d.group + ":" + d.variable;
})
.enter()
.append("rect")
.attr("x", function (d) {
return x(d.group);
})
.attr("y", function (d) {
return y(d.variable);
})
.attr("rx", 4)
.attr("ry", 4)
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.style("fill", (d) => {
return myColor;
})
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
}
);
// Add title to graph
svg
.append("text")
.attr("x", 0)
.attr("y", -50)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("A d3.js heatmap");
// Add subtitle to graph
svg
.append("text")
.attr("x", 0)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.style("max-width", 400)
.text("A short description of the take-away message of this chart.");
</script>
This is another way to get the desired results
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([1.37, -.81])

Draw line chart on top of area chart

I am building a chart using D3js v5.12.0.
I have already done the area chart that has the variable year in X axis and variable earth_footprint on Y axis.
The data is in this link: https://raw.githubusercontent.com/cvrnogueira/CODWorkData/master/database/final_data_set.json
I wish to draw a line chart on the top of the area chart. This line chart should have the variable year in X axis and pop_total on the Y axis.
pop_total is another variable that is on the data.
But I can't manage how to, I saw some tutorials of how to draw a line in bar chart, but when I adapt to my code that is a area chart it does not work.
Thanks in advance
CSS
#area-chart {
text-align: center;
margin-top: 40px;
}
.selection {
fill: none;
}
HTLM
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="area-chart"></div>
</body>
</html>
JS
var url = "http://raw.githubusercontent.com/cvrnogueira/CODWorkData/master/database/final_data_set.json";
d3.json(url)
.then(function(data) {
data = data.filter(dataPoint => dataPoint.country_code == 'BRA');
data = data.filter(element => element.hasOwnProperty("earth_footprint"));
const heightValue = 300;
const widthValue = 600;
// Create SVG and padding for the chart
const svg = d3
.select("#area-chart")
.append("svg")
.attr("viewBox", `0 0 ${widthValue} ${heightValue}`)
;
const strokeWidth = 1.5;
const margin = { top: 0, bottom: 20, left: 30, right: 20 };
const chart = svg.append("g").attr("transform", `translate(${margin.left},0)`);
const width = 600 - margin.left - margin.right - (strokeWidth * 2);
const height = 300 - margin.top - margin.bottom;
const grp = chart
.append("g")
.attr("transform", `translate(-${margin.left - strokeWidth},-${margin.top})`);
// Create scales
const yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, dataPoint => dataPoint.earth_footprint)]);
const xScale = d3
.scaleLinear()
.range([0, width])
.domain(d3.extent(data, dataPoint => dataPoint.year));
const area = d3
.area()
.x(dataPoint => xScale(dataPoint.year))
.y0(height)
.y1(dataPoint => yScale(dataPoint.earth_footprint));
// Add area
grp
.append("path")
.attr("transform", `translate(${margin.left},0)`)
.datum(data)
.style("fill", "lightblue")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", strokeWidth)
.attr("d", area);
// Add the X Axis
chart
.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale).ticks(data.length));
// Add the Y Axis
chart
.append("g")
.attr("transform", `translate(0, 0)`)
.call(d3.axisLeft(yScale));
chart.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Number of Earths");
chart.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 20) + ")")
.style("text-anchor", "middle")
.text("Year");
});
For showing that line you need a line generator:
const line = d3.area()
.x(dataPoint => xScale(dataPoint.year))
.y(dataPoint => yScale(dataPoint.pop_total));
However, your yScale gets the maximum of earth_footprint, and the pop_total values would be way out of scale. So, you'll need another scale for that line generator:
const yScale2 = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, dataPoint => dataPoint.pop_total)]);
After that, just append the path:
grp.append("path")
.attr("d", line);
The biggest problem now is that you have two visual encodings (the area and the line) which have different scales. Therefore, you'll need an additional axis for the line. I'll leave that work to you.
Here is the resulting code:
var url = "https://raw.githubusercontent.com/cvrnogueira/CODWorkData/master/database/final_data_set.json";
d3.json(url)
.then(function(data) {
data = data.filter(dataPoint => dataPoint.country_code == 'BRA');
data = data.filter(element => element.hasOwnProperty("earth_footprint"));
const heightValue = 300;
const widthValue = 600;
// Create SVG and padding for the chart
const svg = d3
.select("#area-chart")
.append("svg")
.attr("viewBox", `0 0 ${widthValue} ${heightValue}`);
const strokeWidth = 1.5;
const margin = {
top: 0,
bottom: 20,
left: 30,
right: 20
};
const chart = svg.append("g").attr("transform", `translate(${margin.left},0)`);
const width = 600 - margin.left - margin.right - (strokeWidth * 2);
const height = 300 - margin.top - margin.bottom;
const grp = chart
.append("g")
.attr("transform", `translate(-${margin.left - strokeWidth},-${margin.top})`);
// Create scales
const yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, dataPoint => dataPoint.earth_footprint)]);
const yScale2 = d3
.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, dataPoint => dataPoint.pop_total)]);
const xScale = d3
.scaleLinear()
.range([0, width])
.domain(d3.extent(data, dataPoint => dataPoint.year));
const area = d3
.area()
.x(dataPoint => xScale(dataPoint.year))
.y0(height)
.y1(dataPoint => yScale(dataPoint.earth_footprint));
const line = d3.area()
.x(dataPoint => xScale(dataPoint.year))
.y(dataPoint => yScale2(dataPoint.pop_total));
// Add area
grp
.append("path")
.attr("transform", `translate(${margin.left},0)`)
.datum(data)
.style("fill", "lightblue")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", strokeWidth)
.attr("d", area);
grp
.append("path")
.attr("transform", `translate(${margin.left},0)`)
.datum(data)
.style("fill", "none")
.attr("stroke", "red")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", strokeWidth)
.attr("d", line);
// Add the X Axis
chart
.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale).ticks(data.length));
// Add the Y Axis
chart
.append("g")
.attr("transform", `translate(0, 0)`)
.call(d3.axisLeft(yScale));
chart.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Number of Earths");
chart.append("text")
.attr("transform",
"translate(" + (width / 2) + " ," +
(height + margin.top + 20) + ")")
.style("text-anchor", "middle")
.text("Year");
});
#area-chart {
text-align: center;
margin-top: 40px;
}
.selection {
fill: none;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="https://d3js.org/d3.v5.min.js"></script>
<title>JS Bin</title>
</head>
<body>
<div id="area-chart"></div>
</body>
</html>

Horizontal bar chart exit method not working

I have a D3 horizontal bar chart that updates the data off a selection filter. When the chart first renders everything works as expected. When the filter selects any of the dropdowns the chart looks as expected, however when I select the "All" option again, the y-axis has the correct domain names and the x-axis resets to the correct scale. However, the problem occurs that the whole svg chart element is a grey block. If you hover over the bar the right size of the bar is selected, so I believe old data isn't exiting correctly, as can be seen in the image below.
I'm stuck as I thought my bars.exit() call was removing elements, I'm missing a key point in my understanding of what is happening. I think i'm very close but not sure what I'm missing?
****** UPDATE *********
Selecting all appears to insert an additional "rect" element that isn't present when it is first run.
Looking at the console log I can't understand where it is generated from as the array length is 3 not 4.
var topicData = [{
"name": "Vehicle - Poor",
"value": 5
},
{
"name": "Problems - Unresolved",
"value": 3
},
{
"name": "Reliability - Poor",
"value": 2
}
]
var margin = {
top: 10,
right: 10,
bottom: 100,
left: 200
}
var width = 1000 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("#graphic_two").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var time = 0;
var data;
// Set the X Label
var xLabel = g.append("text")
.attr("class", "x label")
.attr("y", height + 50)
.attr("x", width / 2)
.attr("font-size", "15px")
.attr("text-anchor", "middle")
.text("Topic Count");
// Set the Y Label
var yLabel = g.append("text")
.attr("class", "y axisLabel")
.attr("transform", "rotate(-90)")
.attr("y", -(120))
.attr("x", -350)
.attr("font-size", "15px")
.attr("text-anchor", "middle")
.text("Topic Names")
// Scales
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleBand()
.range([height, 0]);
// X-axis
var xAxisCall = d3.axisBottom()
var xAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
// Y-axis
var yAxisCall = d3.axisLeft()
var yAxis = g.append("g")
.attr("class", "y axis");
//console.log(data);
formattedData = topicData;
update(formattedData)
$("#survey")
.on("change", function() {
update(formattedData);
})
function step() {
// At the end of our data, loop back
time = (time < 214) ? time + 1 : 0
update(formattedData);
}
function update(data) {
console.log("I'm in the update function")
var survey = $("#survey").val();
var data = data.filter(function(d) {
if (survey == "all") {
return true;
} else {
return d.name == survey;
}
})
console.log(data)
// X Scale
x.domain([0, d3.max(data, function(d) {
return d.value;
})]);
y.domain(data.map(function(d) {
return d.name;
}));
// Update axes
xAxisCall.scale(x);
xAxis.transition().call(xAxisCall);
yAxisCall.scale(y);
yAxis.transition().call(yAxisCall);
// JOIN new data with old elements
var bars = g.selectAll(".bars").data(data, function(d) {
return d.name;
});
// EXIT old elements not present in new data.
bars.exit()
.attr("class", "exit")
.remove();
// ENTER new elements present in new data.
bars.enter()
.append("rect")
.attr("width", function(d) {
return x(d.value);
}) // Width corresponds with the value from the data
.attr("y", function(d) {
return y(d.name);
}) // Maps the name to the bar
.attr("height", y.bandwidth())
.attr("fill", "grey")
.on("mouseover", function() {
d3.select(this)
.attr("fill", "blue");
})
.on("mouseout", function(d, i) {
d3.select(this)
.attr("fill", "grey");
})
.on('click', function click(element) {
selection = element.name;
url = "http://127.0.0.1:5000/table"
window.location = url;
window.localStorage.setItem("topic", selection)
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.jquery.min.js"></script>
<select id="survey" data-placeholder="Select..." class="chosen-select" style="width:350px;" tabindex="3">
<option selected value="all">All</option>
<option value="Vehicle - Poor">Vehicle - Poor</option>
<option value="Problems - Unresolved">Problems - Unresolved</option>
<option value="Reliability - Poor">Reliability - Poor</option>
</select>
<div class="col-md-12">
<div id="graphic_two"></div>
</div>
The accepted answer is not correct (removing elements before updating a D3 dataviz is definitely not the idiomatic way to do it) and, what's more, it doesn't explain why your exit selection was not working.
Your problem is just that you're selecting a class in your update selection...
var bars = g.selectAll(".bars")
//etc...
... but you never set that class in the enter selection. Therefore, just do:
bars = bars.enter()
.append("rect")
.attr("class", "bars")
Also, you have other problems:
You never change the update selection. Pay attention to the merge() in my code below;
You're changing the domain of the band scale. So, the width of each bar depends on the number of bars. I have a feeling that this is not what you want. If that's correct, refactor that part of the code. In my snippet below I'm using a simple paddingOuter math to change the width of the bars.
Here is your code with those changes:
var topicData = [{
"name": "Vehicle - Poor",
"value": 5
},
{
"name": "Problems - Unresolved",
"value": 3
},
{
"name": "Reliability - Poor",
"value": 2
}
];
var margin = {
top: 10,
right: 10,
bottom: 100,
left: 200
}
var width = 1000 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("#graphic_two").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var time = 0;
var data;
// Set the X Label
var xLabel = g.append("text")
.attr("class", "x label")
.attr("y", height + 50)
.attr("x", width / 2)
.attr("font-size", "15px")
.attr("text-anchor", "middle")
.text("Topic Count");
// Set the Y Label
var yLabel = g.append("text")
.attr("class", "y axisLabel")
.attr("transform", "rotate(-90)")
.attr("y", -(120))
.attr("x", -350)
.attr("font-size", "15px")
.attr("text-anchor", "middle")
.text("Topic Names")
// Scales
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleBand()
.range([height, 0]);
// X-axis
var xAxisCall = d3.axisBottom()
var xAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
// Y-axis
var yAxisCall = d3.axisLeft()
var yAxis = g.append("g")
.attr("class", "y axis");
//console.log(data);
formattedData = topicData;
update(formattedData)
$("#survey")
.on("change", function() {
update(formattedData);
})
function step() {
// At the end of our data, loop back
time = (time < 214) ? time + 1 : 0
update(formattedData);
}
function update(data) {
var survey = $("#survey").val();
var data = data.filter(function(d) {
if (survey == "all") {
return true;
} else {
return d.name == survey;
}
})
// X Scale
x.domain([0, d3.max(data, function(d) {
return d.value;
})]);
y.domain(data.map(function(d) {
return d.name;
}))
.paddingOuter(1 / data.length);
// Update axes
xAxisCall.scale(x);
xAxis.transition().call(xAxisCall);
yAxisCall.scale(y);
yAxis.transition().call(yAxisCall);
// JOIN new data with old elements
var bars = g.selectAll(".bars").data(data, function(d) {
return d.name;
});
// EXIT old elements not present in new data.
bars.exit()
.attr("class", "exit")
.remove();
// ENTER new elements present in new data.
bars = bars.enter()
.append("rect")
.attr("class", "bars")
.merge(bars)
.attr("width", function(d) {
return x(d.value);
}) // Width corresponds with the value from the data
.attr("y", function(d) {
return y(d.name);
}) // Maps the name to the bar
.attr("height", y.bandwidth())
.attr("fill", "grey")
.on("mouseover", function() {
d3.select(this)
.attr("fill", "blue");
})
.on("mouseout", function(d, i) {
d3.select(this)
.attr("fill", "grey");
})
.on('click', function click(element) {
selection = element.name;
url = "http://127.0.0.1:5000/table"
window.location = url;
window.localStorage.setItem("topic", selection)
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.jquery.min.js"></script>
<select id="survey" data-placeholder="Select..." class="chosen-select" style="width:350px;" tabindex="3">
<option selected value="all">All</option>
<option value="Vehicle - Poor">Vehicle - Poor</option>
<option value="Problems - Unresolved">Problems - Unresolved</option>
<option value="Reliability - Poor">Reliability - Poor</option>
</select>
<div class="col-md-12">
<div id="graphic_two"></div>
</div>
PS: do not mix jQuery and D3. That's not only unnecessary but also harmful sometimes.

How to add a d3js graph in slideshow instead of body?

I am very new to d3js, css, html and trying to practice different examples of d3js. I am trying to add a d3js graph in a slideshow instead of adding it to a body of webpage. I am kind of stuck on how to do this. How do i place a graph in slideshow ? For your reference below is my attempted code-
<div class="mySlides fade">
<div class="numbertext">2 / 3</div>
Something goes here in slide 2
</div>
</div>
<div class="mySlides fade">
<div class="numbertext">3 / 4</div>
<h1 style="font-size:400%;"><u>Data</u></h1>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
/*
* value accessor - returns the value to encode for a given data object.
* scale - maps value to a visual display encoding, such as a pixel position.
* map function - maps from data value to display value
* axis - sets up axis
*/
// setup x
var xValue = function(d) { return d.Contributions;}, // data -> value
xScale = d3.scale.linear().range([0, width]), // value -> display
xMap = function(d) { return xScale(xValue(d));}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
// setup y
var yValue = function(d) { return d.Deaths;}, // data -> value
yScale = d3.scale.linear().range([height, 0]), // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup fill color
var cValue = function(d) { return d.State;},
color = d3.scale.category10();
// add the graph canvas to the body of the webpage
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 + ")");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// load data
d3.csv("data.csv", function(error, data) {
// change string (from CSV) into number format
data.forEach(function(d) {
d.Contributions = +d.Contributions;
d.Deaths = +d.Deaths;
// console.log(d);
});
// // don't want dots overlapping axis, so add in buffer to data domain
xScale.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// // x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Conntributions");
// // y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Deaths");
// draw dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d["State"] + "<br/> (" + xValue(d)
+ ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate("+ (i * 20 + 137) + ", 6)";
});
// draw legend colored rectangles
var boxSize = 17;
legend.append("rect")
.attr("x", 0)
.attr("width", boxSize)
.attr("height", boxSize)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", -8.2)
.attr("y", 19)
.attr("dy", ".35em")
.style("text-anchor", "end")
.attr("transform","rotate(-43)")
.text(function(d) { return d;})
});
</script>
</div>
This code gets me a graph in all pages of slideshow which i don't want but instead i would like to add a graph in page 3 of my slideshow.
This is an interesting question: normally, the answer here would be just "select the div you want by ID or any other CSS selector that suits you" (or, since that this has been answered many many times, just a comment and a vote to close). The basis for that answer is that this...
var svg = d3.select("body").append("svg")
... will append the SVG at the end of the body. So far, nothing new or difficult.
But why did I said that this is an interesting question?
Because of the funny way (if you don't mind me saying so) you tried to select the div: you thought that D3 would create the chart inside that div just by putting the respective script inside it.
Of course putting the script inside the container div is not the way of doing this. But just for the sake of curiosity, there is a way of doing what you thought you were doing (again, selecting the element that contains the script): by using document.currentScript, which:
Returns the element whose script is currently being processed.
So, all we need in your situation is:
var container = document.currentScript.parentNode;
var svg = d3.select(container)
.append("svg")
//etc...
Which appends the SVG in the <div> (or any other containing element) that contains the <script>.
Here is a basic demo:
<script src="https://d3js.org/d3.v5.min.js"></script>
<div>
<h3>I am div 1</h3>
</div>
<div>
<h3>I am div 2, I have a chart</h3>
<script>
var container = document.currentScript.parentNode;
var svg = d3.select(container)
.append("svg");
var data = d3.range(10).map(d => Math.random() * 150);
var scale = d3.scaleBand()
.domain(data)
.range([0, 300])
.padding(0.4);
var bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.style("fill", "steelblue")
.attr("x", d => scale(d))
.attr("width", scale.bandwidth())
.attr("height", d => 150 - d)
.attr("y", Number)
</script>
</div>
<div>
<h3>I am div 3</h3>
</div>
<div>
<h3>I am div 4</h3>
</div>
Note that document.CurrentScript doesn't work on IE (if you care for IE, just give the div an ID and select it).

Categories

Resources