Data points and ticks in the scaleBand axis are not aligned - javascript

My data points and the values in the scaleBand y axis are not aligned. I am not able to align them properly, when I read the documentation, saw that by default the alignment is 0.5 and that's why my data points are plotted between the two points in the axis. But I tried to override the alignment my giving the alignment as 0, but there seems to be no change.
The following is my code.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>D3 v4 - linechart</title>
<style>
#graph {
width: 900px;
height: 500px;
}
.tick line {
stroke-dasharray: 2 2 ;
stroke: #ccc;
}
.y path{
fill: none;
stroke: none;
}
</style>
</head>
<body>
<div id="graph"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.1.1/d3.min.js"></script>
<script>
!(function(){
"use strict"
var width,height
var chartWidth, chartHeight
var margin
var svg = d3.select("#graph").append("svg")
var axisLayer = svg.append("g").classed("axisLayer", true)
var chartLayer = svg.append("g").classed("chartLayer", true)
var xScale = d3.scaleLinear()
var yScale = d3.scaleBand()
var align = 0
//d3.tsv("data1.tsv", cast, main)
d3.json("http://localhost/d32.json",cast)
//データの方変換
function cast(data) {
console.log("got it");
data.forEach(function(data) {
console.log(data.Letter);
data.Letter = data.Letter;
data.Freq = +data.Freq;
});
main(data);
}
function main(data) {
console.log("in main");
setSize(data)
drawAxis()
drawChart(data)
}
function setSize(data) {
width = document.querySelector("#graph").clientWidth
height = document.querySelector("#graph").clientHeight
margin = {top:40, left:100, bottom:40, right:0 }
chartWidth = width - (margin.left+margin.right+8)
chartHeight = height - (margin.top+margin.bottom)
svg.attr("width", width).attr("height", height)
axisLayer.attr("width", width).attr("height", height)
chartLayer
.attr("width", chartWidth)
.attr("height", chartHeight)
.attr("transform", "translate("+[margin.left, margin.top]+")")
xScale.domain([0, d3.max(data, function(d) { return d.Freq; })]).range([0,chartWidth])
yScale.domain(data.map(function(d) { return d.Letter; })).range([chartHeight, 0]).align(1)
}
function drawChart(data) {
console.log("in drawChart");
var t = d3.transition()
.duration(8000)
.ease(d3.easeLinear)
.on("start", function(d){ console.log("transiton start") })
.on("end", function(d){ console.log("transiton end") })
var lineGen = d3.line()
.x(function(d) { return xScale(d.Freq) })
.y(function(d) { return yScale(d.Letter) })
.curve(d3.curveStepAfter)
var line = chartLayer.selectAll(".line")
.data([data])
line.enter().append("path").classed("line", true)
.merge(line)
.attr("d", lineGen)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width","2px")
.attr("stroke-dasharray", function(d){ return this.getTotalLength() })
.attr("stroke-dashoffset", function(d){ return this.getTotalLength() })
chartLayer.selectAll(".line").transition(t)
.attr("stroke-dashoffset", 0)
chartLayer.selectAll("circle").classed("circle",true)
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("fill","none")
.attr("stroke","black")
.attr("cx", function(d) { return xScale(d.Freq); })
.attr("cy", function(d) { return yScale(d.Letter); })
.attr("r", 4);
chartLayer.selectAll(".logo").transition(t)
.attr("stroke-dashoffset", 0)
}
function drawAxis(){
var yAxis = d3.axisLeft(yScale)
.tickSizeInner(-chartWidth)
axisLayer.append("g")
.attr("transform", "translate("+[margin.left, margin.top]+")")
.attr("class", "axis y")
.call(yAxis);
var xAxis = d3.axisBottom(xScale)
axisLayer.append("g")
.attr("class", "axis x")
.attr("transform", "translate("+[margin.left, chartHeight+margin.top]+")")
.call(xAxis);
}
}());
</script>
</body>
</html>
The output is shown here:

The band scale is the wrong tool in this situation. The main reason is that a band scale has an associated bandwidth.
You can tweak the paddingInner and paddingOuter values of the band scale to give you the expected result. However, the easiest solution is using a point scale instead. Point scales:
...are a variant of band scales with the bandwidth fixed to zero. (emphasis mine)
So, it should be:
var yScale = d3.scalePoint()

Related

Update multi-line graph with D3

I am trying to play around with D3 and do some visualization using COVID-19 data, but I cannot seem to figure out how to update the multi-line graph with a button. This is what I currently have:
<!DOCTYPE html>
<meta charset="utf-8" />
<html lang="en">
<style>
.line1 {
fill: none;
stroke: darkcyan;
stroke-width: 2.5px;
}
.line2 {
fill: none;
stroke: red;
stroke-width: 2.5px;
}
.line3 {
fill: none;
stroke: green;
stroke-width: 2.5px;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.legend rect {
fill: white;
stroke: black;
opacity: 0.8;
}
</style>
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<h1>Total Confirmed Coronavirus Cases in Germany</h1>
<div id="option">
<input name="updateButton" type="button" value="Linear" onclick="linear()" />
<input name="updateButton" type="button" value="Logarithmic" onclick="logarithmic()" />
</div>
<script>
// write your d3 code here..
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S%Z");
var formatDate = d3.timeFormat("%m-%d");
// set the ranges
var x = d3.scaleUtc().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var logy = d3.scaleLog().range([height, 0]);
// Define the axes
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var logyAxis = d3.axisLeft(logy);
// define the 1st line
var valueline1 = d3
.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.confirmed);
});
// define the 2nd line
var valueline2 = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.deaths);
});
// define the 3rd line
var valueline3 = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.recovered);
});
// 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 + ")");
// gridlines in x axis function
function x_gridlines() {
return d3.axisBottom(x)
}
// gridlines in y axis function
function y_gridlines() {
return d3.axisLeft(y)
}
// gridlines in y axis function
function logy_gridlines() {
return d3.axisLeft(logy)
}
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function(data) {
data.forEach(function(d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmed = d.Confirmed;
d.deaths = d.Deaths;
d.recovered = d.Recovered;
});
// 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 Math.max(d.confirmed, d.deaths, d.recovered);
})]);
// Add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line1")
.attr("d", valueline1);
// Add the valueline2 path.
svg.append("path")
.data([data])
.attr("class", "line2")
.attr("d", valueline2);
// Add the valueline3 path.
svg.append("path")
.data([data])
.attr("class", "line3")
.attr("d", valueline3);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// add the X gridlines
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(x_gridlines()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "y grid")
.call(y_gridlines()
.tickSize(-width)
.tickFormat("")
)
legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(50,30)")
.style("font-size", "12px")
.call(d3.legend)
});
// ** Update data section (Called from the onclick)
function logarithmic() {
// Get the data again
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function(data) {
data.forEach(function(d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmedlog = Math.log(d.Confirmed);
d.deathslog = Math.log(d.Deaths);
d.recoveredlog = Math.log(d.Recovered);
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0,
d3.max(data, function(d) {
return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
})
]);
// Select the section we want to apply our changes to
var svg = d3.select("body").data(data).transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1(d.confirmedlog);
})
svg.select(".line2") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1(d.deathslog);
});
svg.select(".line3") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1(d.recoveredlog);
});
svg.select(".y.axis") // change the y axis
.duration(750)
.call(logyAxis);
svg.select(".y.grid")
.duration(750)
.call(logy_gridlines()
.tickSize(-width)
.tickFormat(""))
});
}
// ** Update data section (Called from the onclick)
function linear() {
// Get the data again
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function(data) {
data.forEach(function(d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmed = d.Confirmed;
d.deaths = d.Deaths;
d.recovered = d.Recovered;
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.confirmed, d.deaths, d.recovered;
})]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", valueline1(data));
svg.select(".line2") // change the line
.duration(750)
.attr("d", valueline2(data));
svg.select(".line3") // change the line
.duration(750)
.attr("d", valueline3(data));
svg.select(".x.axis") // change the x axis
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
svg.select(".y.grid") // change the y gridlines
.duration(750)
.call(y_gridlines()
.tickSize(-width)
.tickFormat("")
);
});
}
</script>
</body>
</html>
What I'm having issues with is toggling from the default to the Logarithmic view. Whenever I click on the button to toggle, the lines all disappear. I have a function called: logarithmic() and the code within the function is as below:
function logarithmic() {
// Get the data again
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function (data) {
data.forEach(function (d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmedlog = Math.log(d.Confirmed);
d.deathslog = Math.log(d.Deaths);
d.recoveredlog = Math.log(d.Recovered);
});
// Scale the range of the data again
x.domain(d3.extent(data, function (d) { return d.date; }));
y.domain([0,
d3.max(data, function (d) {return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
})]);
// Select the section we want to apply our changes to
var svg = d3.select("body").data(data).transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", function(d) { return valueline1(d.confirmedlog); })
svg.select(".line2")// change the line
.duration(750)
.attr("d", function(d) { return valueline1(d.deathslog); });
svg.select(".line3") // change the line
.duration(750)
.attr("d", function(d) { return valueline1(d.recoveredlog); });
svg.select(".y.axis") // change the y axis
.duration(750)
.call(logyAxis);
svg.select(".y.grid")
.duration(750)
.call(logy_gridlines()
.tickSize(-width)
.tickFormat(""))
});
}
Any help would be greatly appreciated! Or if there are any best practices for updating multi-line graphs, any tips would be greatly appreciated as well. Thank you very much
Well, unfortunately your code has several issues and right now it's far from the best D3 (or JavaScript) practices.
Besides binding data to the body and reassigning selection names, among other things, the very first issue that caught my eye was downloading the (same) data again for the updates. That's completely unnecessary.
In this very quick refactor I'm simply calculating the log values already. Pay attention to the fact that logarithm of zero in JS is minus infinity. So, just check it:
d.confirmedlog = d.Confirmed ? Math.log(d.Confirmed) : 0;
d.deathslog = d.Deaths ? Math.log(d.Deaths) : 0;
d.recoveredlog = d.Recovered ? Math.log(d.Recovered) : 0;
Then, for each line, change the y method of the line generator. For instance:
svg.select(".line1") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1.y(function(e) {
return y(e.confirmedlog);
})(d);
})
Here is the code:
<!DOCTYPE html>
<meta charset="utf-8" />
<html lang="en">
<style>
.line1 {
fill: none;
stroke: darkcyan;
stroke-width: 2.5px;
}
.line2 {
fill: none;
stroke: red;
stroke-width: 2.5px;
}
.line3 {
fill: none;
stroke: green;
stroke-width: 2.5px;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.legend rect {
fill: white;
stroke: black;
opacity: 0.8;
}
</style>
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<h1>Total Confirmed Coronavirus Cases in Germany</h1>
<div id="option">
<input name="updateButton" type="button" id="option1" value="Linear" />
<input name="updateButton" type="button" id="option2" value="Logarithmic" />
</div>
<script>
// write your d3 code here..
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S%Z");
var formatDate = d3.timeFormat("%m-%d");
// set the ranges
var x = d3.scaleUtc().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var logy = d3.scaleLog().range([height, 0]);
// Define the axes
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var logyAxis = d3.axisLeft(logy);
// define the 1st line
var valueline1 = d3
.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.confirmed);
});
// define the 2nd line
var valueline2 = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.deaths);
});
// define the 3rd line
var valueline3 = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.recovered);
});
// 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 + ")");
// gridlines in x axis function
function x_gridlines() {
return d3.axisBottom(x)
}
// gridlines in y axis function
function y_gridlines() {
return d3.axisLeft(y)
}
// gridlines in y axis function
function logy_gridlines() {
return d3.axisLeft(logy)
}
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function(data) {
data.forEach(function(d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmed = d.Confirmed;
d.deaths = d.Deaths;
d.recovered = d.Recovered;
d.confirmedlog = d.Confirmed ? Math.log(d.Confirmed) : 0;
d.deathslog = d.Deaths ? Math.log(d.Deaths) : 0;
d.recoveredlog = d.Recovered ? Math.log(d.Recovered) : 0;
});
// 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 Math.max(d.confirmed, d.deaths, d.recovered);
})]);
// Add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line1")
.attr("d", valueline1);
// Add the valueline2 path.
svg.append("path")
.data([data])
.attr("class", "line2")
.attr("d", valueline2);
// Add the valueline3 path.
svg.append("path")
.data([data])
.attr("class", "line3")
.attr("d", valueline3);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// add the X gridlines
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(x_gridlines()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "y grid")
.call(y_gridlines()
.tickSize(-width)
.tickFormat("")
)
d3.select("#option1").on("click", linear);
d3.select("#option2").on("click", logarithmic);
// ** Update data section (Called from the onclick)
function logarithmic() {
y.domain([0,
d3.max(data, function(d) {
return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
})
]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1.y(function(e) {
return y(e.confirmedlog);
})(d);
})
svg.select(".line2") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1.y(function(e) {
return y(e.deathslog);
})(d);
});
svg.select(".line3") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1.y(function(e) {
return y(e.recoveredlog);
})(d);
});
svg.select(".y.axis") // change the y axis
.duration(750)
.call(logyAxis);
svg.select(".y.grid")
.duration(750)
.call(logy_gridlines()
.tickSize(-width)
.tickFormat(""))
}
// ** Update data section (Called from the onclick)
function linear() {
y.domain([0, d3.max(data, function(d) {
return d.confirmed, d.deaths, d.recovered;
})]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", valueline1);
svg.select(".line2") // change the line
.duration(750)
.attr("d", valueline2);
svg.select(".line3") // change the line
.duration(750)
.attr("d", valueline3);
svg.select(".x.axis") // change the x axis
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
svg.select(".y.grid") // change the y gridlines
.duration(750)
.call(y_gridlines()
.tickSize(-width)
.tickFormat("")
);
};
});
</script>
</body>
</html>
However, I'm afraid that your code has so many issues that it should be completely refactored (please don't take it personally). Now that it's a (kind of) working code I believe you can ask for help on Code Review. But, if you do, make sure before posting that you read their help section, since Code Review is quite different from Stack Overflow.

D3.js add circle points to line graph

I'm brand new to D3.js and I'm trying to add a circle point on the graph where each data point is.
This is an example of the raw data:
TIME,GEO,CITIZEN,GENDER,VALUE
2011M01,Germany (until 1990 former territory of the FRG),Belgium,Males,0
But in the code, I format this into a variable called nestedData, and then using this plot two line graphs UK.values and germany.values.
How can I add circle points onto each of the two line graphs?
Thanks.
This is what I have currently:
<html>
<head>
<script src="https://d3js.org/d3.v5.js"></script>
<title> D3 Tutorial </title>
<style>
body {
margin: 20px;
}
svg {
padding: 50px;
}
.line {
fill: none;
stroke: black;
}
#UKLine {
fill: none;
stroke: red;
}
#germanyLine {
fill: none;
stroke: blue;
}
</style>
</head>
<body>
<h2>D3 Linegraph</h2>
<script>
var dataPath = "data/migration.csv";
var width = 800; //specifies the width, height and margins of our SVG element
var height = 600;
var margin = 100;
var rowConverter = function (d) {
var timeData = d.TIME;
var datum = timeData.split("M");
return {
date: new Date(datum[0], datum[1] - 1),
geo: d.GEO,
value: d.VALUE
}
};
d3.csv(dataPath, rowConverter)
.then(function (data) {
console.log(data);
// console.table(data); //loads table in a nice format - just to try it out (probably not super practical for this tutorial)
var nestedData = d3.nest()
.key(function (d) {
return d.geo;
})
.key(function (d) {
return d.date;
})
.rollup(function (leaves) {
return d3.sum(leaves, function (d) {
return parseInt(d.value);
});
})
.entries(data);
console.log(nestedData);
//work out time extent of ALL THE DATA NOT JUST THE NESTED ONES
//work with unnested data
var timeExtent = d3.extent(data, function (d) {
return d.date;
});
console.log(timeExtent);
var xScale = d3.scaleTime().domain(timeExtent).range([0, width]);
var migrantCounts = new Array();
nestedData.forEach(function (d) {
(d.values).forEach(function (e) {
migrantCounts.push(e.value);
});
});
console.log(migrantCounts);
var migrantExtent = d3.extent(migrantCounts, function (d) {
return parseInt(d);
});
console.log(migrantExtent);
var yScale = d3.scaleLinear().domain(migrantExtent).range([height, 0]);
var x_axis = d3.axisBottom(xScale);
var y_axis = d3.axisLeft(yScale);
var svg = d3.select("body") //creates an SVG element in the body
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin);
d3.select("svg")
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin + "," + height + ")")
.call(x_axis
.tickFormat(d3.timeFormat("%y-%m-%d")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-0.8em")
.attr("dy", "0.15em")
.attr("transform", "rotate(-65)");
d3.select("svg")
.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin + ",0)")
.call(y_axis);
var germany = nestedData[0];
var UK = nestedData[1];
var lineGenerator = d3.line()
.x(function (d) {
return margin + xScale(new Date(d.key));
})
.y(function (d) {
return yScale(parseInt(d.value));
});
svg.append("path")
.datum(germany.values)
.attr("class", "line")
.attr("id", "germanyLine")
.attr("d", lineGenerator);
svg.append("path")
.datum(UK.values)
.attr("class", "line")
.attr("id", "UKLine")
.attr("d", lineGenerator);
});
</script>
</body>
</html>
you probably need to add two more steps after you create the paths
// these two selections are adding paths
svg.append("path")
.datum(germany.values)
.attr("class","line")
.attr("id","germanyLine")
.attr("d",lineGenerator);
svg.append("path")
.datum(UK.values)
.attr("class","line")
.attr("id", "UKLine")
.attr("d", lineGenerator);
//you want to add circles
svg.selectAll(".circle-germany")
.data(germany.values)
.join("circle") // enter append
.attr("class", "circle-germany")
.attr("r", "1") // radius
.attr("cx", d=> margin + xScale(new Date(d.key))) // center x passing through your xScale
.attr("cy", d=> yScale(parseInt(d.value))) // center y through your yScale
svg.selectAll(".circle-uk")
.data(UK.values)
.join("circle") // enter append
.attr("class", "circle-uk")
.attr("r", "1") // radius
.attr("cx", d=> margin + xScale(new Date(d.key))) // center x passing through your xScale
.attr("cy", d=> yScale(parseInt(d.value))) // center y through your yScale

D3.js Stem plot with axis

I have problem with adding the axis to stem plot. I'm using D3.js in version 3.
I draw my own stems that consist of circles and lines.
I have two scenarios:
1.
1.1 First I add the stems
1.2 Then I add axis
The Y-axis is plotted on the stem. On the following image the magenta line covers green one (I want the opposite, stem should cover axis)
2
2.1 First I add axis
2.2 Then I add the stems
The lines of stems are not plotted.
I need someone to explain me why the lines are not drawn.
File js/stem-functions.js
var svgParams = {
// Graph field
graphWidth : 200,
graphHeight : 120,
// Margins
leftPadding : 30,
rightPadding : 10,
upPadding : 15,
downPadding : 25,
}
function setParametersSvg() {
// Size of the SVG object
this.svgWidth = this.graphWidth + this.leftPadding + this.rightPadding;
this.svgHeight = this.graphHeight + this.upPadding + this.downPadding;
}
setParametersSvg.apply(svgParams);
// Create Scale functions
var xScale = (function ustawSkaleX(minX, maxX, svgParam) {
var xSc = d3.scale.linear()
.domain([minX, maxX])
.range([svgParam.leftPadding, svgParam.leftPadding + svgParam.graphWidth]);
return xSc;
} (0, 5, svgParams) );
var yScale = (function ustawSkaleY(minY, maxY, svgParam) {
var ySc = d3.scale.linear()
.domain([minY, maxY])
.range([svgParam.upPadding + svgParam.graphHeight, svgParam.upPadding]);
return ySc;
} (0, 0.5, svgParams) );
function addAxis(svg, svgParam, xScale, yScale) {
// Functions drawing axis X
var xAxis = d3.svg.axis();
xAxis.scale(xScale) // Scale function for X
.orient("bottom") // Location of label
.ticks(7); // Ticks
// Add group
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (svgParam.svgHeight - svgParam.downPadding) +")")
.call(xAxis);
// Functions drawing axis Y
var yAxis = d3.svg.axis();
yAxis.scale(yScale) // Scale function for Y
.orient("left") // Location of label
.ticks(4); // Ticks
// Add group
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + (svgParam.leftPadding) + ", 0)")
.call(yAxis);
}
function addStems(svg, dataset, xScale, yScale) {
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("fill", "green")
.attr("cx", function(d) { return xScale(d.x); } )
.attr("cy", function(d) { return yScale(d.n); } )
.attr("r", 4);
var lines = svg.selectAll("line")
.data(dataset)
.enter()
.append("line")
.attr("class", "stem-line")
.attr("stroke", "green")
.attr("stroke-width", "1")
.attr("x1", function(d) { return xScale(d.x); } )
.attr("x2", function(d) { return xScale(d.x); } )
.attr("y1", function(d) { return yScale(0); } )
.attr("y2", function(d) { return yScale(d.n); } );
}
File js/stem-examples.js
// Data set
var p = [
{ x: 0, n: 0.15 },
{ x: 1, n: 0.25 },
{ x: 2, n: 0.40 },
{ x: 3, n: 0.15 },
{ x: 4, n: 0.05 }
];
console.log('probabilities ', p);
d3.select("body").append("h4").html("Call Stems printing before Axis printing");
// Stems before Axis => Axis is over stem
var svg1 = d3.select("body")
.append("svg")
.attr("width", svgParams.svgWidth)
.attr("height", svgParams.svgHeight);
addStems(svg1, p, xScale, yScale);
addAxis(svg1, svgParams, xScale, yScale);
d3.select("body").append("br");
d3.select("body").append("h4").html("Call Axis printing before Stems printing");
// Axis before Stems => stem lines are gone (why?)
var svg2 = d3.select("body")
.append("svg")
.attr("width", svgParams.svgWidth)
.attr("height", svgParams.svgHeight);
addAxis(svg2, svgParams, xScale, yScale);
addStems(svg2, p, xScale, yScale);
File css/styl.js
svg { border: teal 1px solid; }
.axis path, .axis line {
fill: none;
stroke: magenta;
stroke-width: 1;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 12px;
fill: DarkViolet;
}
File index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Stem plot with axis</title>
<link rel="stylesheet" href="css/styl.css">
</head>
<body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script> <!-- V3 -->
<!-- TODO: try version 4 of D3.js --> <!-- V4 -->
<script src="js/stem-functions.js"></script>
<script src="js/stem-examples.js"></script>
</body>
</html>
Appending the axis first and then the stems is the correct approach. The problem is not that.
The problem is that, when you do this...
var lines = svg.selectAll("line")
.data(dataset)
.enter()
.append("line")
//etc...
... you are selecting lines that already exist in that SVG, and binding data to them.
Solution
Do this:
var lines = svg.selectAll(null)
.data(dataset)
.enter()
.append("line")
//etc...
To understand why I'm selecting null, have a look at this question/answer of mine here: Selecting null: what is the reason of using 'selectAll(null)' in D3.js?

How do I externalise my Data

I am new to d3.js and Javascript and currently trying to build a graph using d3. My data is placed in a dataArray var currently but I want to externalise it. Every time I try use either another file or different data format it does not work. Here is my code:
<!DOCTYPE html>
<html>
<head>
<meta>
<meta http-equiv="refresh" content="">
<meta name="description" content="Drawing Shapes w/ D3 - " />
<meta charset="utf-8">
<title>Resources per Project</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style type="text/css">
h1 {
font-size: 35px;
color: darkgrey;
font-family: Helvetica;
border-bottom-width: 3px;
border-bottom-style: dashed;
border-bottom-color: black;
}
h2 {
font-size: 20px;
color: black;
font-family: Helvetica;
text-decoration: underline;
margin-left: 290px;
margin-top: 0px;
}
</style>
</head>
<body>
<h1>Resources used per Project</h1>
<script type="text/javascript">
var width = 600
var height = 500
var emptyVar = 0
var dataArray = [0.35, 1.66, 3.04, 1.54, 3.45, 2.56, 2.29, 1.37];
var emptyData = [];
var widthScale = d3.scale.linear()
.domain([0, 5])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, 5])
.range(["#000066", "#22abff"])
var axis = d3.svg.axis()
.ticks("10")
.scale(widthScale);
var canvas = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(30, 0)");
var bars = canvas.selectAll("rect")
.data(dataArray)
.enter()
.append("rect")
.attr("width", emptyVar)
.attr("height", 50)
.attr("fill", function(d) {
return color(d)
})
.attr("y", function(d, i) {
return i * 55
})
canvas.append("g")
.attr("transform", "translate(0, 430)")
.attr("font-family", "Helvetica")
.attr("font-size", "15px")
.call(axis);
bars.transition()
.duration(1500)
.delay(200)
.attr("width", function(d) {
return widthScale(d);
})
</script>
<h2>Resources</h2>
</body>
</html>
Attached are 2 files (one JSON and one CSV) which have the data I am trying to use. The data under "resources" is the current data in the dataArray. How do I externalise this data?
If you want to load external data into D3. You have 2 option:
Option A: one single file (JSON or CSV or TSV)
d3.json("your.json", function (data) {
// stuff here
})
or
d3.csv("your.csv", function (data) {
// stuff here
})
or
d3.tsv("your.tvs", function (data) {
// stuff here
})
Option B: multiples files (JSONs, CSVs and TSVs)
queue()
.defer(d3.json, 'json_data.json')
.defer(d3.csv, 'csv_data.csv')
.defer(d3.tsv, 'tsv_data.tsv')
.await(makeGraph);
function makeGraph (json_data, csv_data, tsv_data, error) {
//your stuff here
}
To use queue you must to include the script at your header:
<script src="http://d3js.org/queue.v1.min.js"></script>
Queue await for all data to arrival and then execute the function.
JSON: JavaScript Object Notation
CSV: Comma Separated Values
TSV: Tab Separated Values
Then your code:
var width = 600
var height = 500
var emptyVar = 0
var dataArray = [0.35, 1.66, 3.04, 1.54, 3.45, 2.56, 2.29, 1.37];
var emptyData = [];
var widthScale = d3.scale.linear()
.domain([0, 5])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, 5])
.range(["#000066", "#22abff"])
var axis = d3.svg.axis()
.ticks("10")
.scale(widthScale);
var canvas = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(30, 0)");
queue()
.defer(d3.json, 'json_data.json')
.defer(d3.csv, 'csv_data.csv')
.await(makeGraph);
function makeGraph(json_data, csv_data, error) {
if (error) {
alert ("something went wrong!!");
}
var bars = canvas.selectAll("rect")
.data(your_data_from_json_or_csv)
.enter()
.append("rect")
.attr("width", emptyVar)
.attr("height", 50)
.attr("fill", function(d) {
return color(d)
})
.attr("y", function(d, i) {
return i * 55
})
canvas.append("g")
.attr("transform", "translate(0, 430)")
.attr("font-family", "Helvetica")
.attr("font-size", "15px")
.call(axis);
bars.transition()
.duration(1500)
.delay(200)
.attr("width", function(d) {
return widthScale(d);
})
}

Multiples with x/y axis and mouseover example - line path shows wrong values

I am trying to combine severeal D3.js examples based on example. I managed to get mouseover for each multiples chart in part working (values are not displayed at mouse pointer yet but via console.log). By checking those values I realized that my line paths at the upper two charts are off in relation to the Y-Axis, also causing the mouseover focus to be in the wrong place. I am new to D3, so I am still having trouble to pin down the problem beeing caused by domain/scale/axis etc. You can see the example here
This is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
margin: 0;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
//shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.area {
//fill: #e7e7e7;
fill: transparent;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var margin = {top: 8, right: 10, bottom: 20, left: 30},
width = 960 - margin.left - margin.right,
height = 138 - margin.top - margin.bottom;
var parseDate = d3.time.format("%b %Y").parse,
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(",.2f"),
formatCurrency = function(d) { return formatValue(d); };
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.price); });
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.price); });
var xAxis = d3.svg.axis()
.scale(x) // x is the d3.time.scale()
.orient("bottom") // the ticks go below the graph
.ticks(4); // specify the number of ticks
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(4);
d3.csv("stocks_chart2.csv", type, function(error, data) {
// Nest data by symbol.
var symbols = d3.nest()
.key(function(d) { return d.symbol; })
.entries(data);
// Compute the maximum price per symbol, needed for the y-domain.
symbols.forEach(function(s) {
s.maxPrice = d3.max(s.values, function(d) { return d.price; });
});
// Compute the minimum and maximum date across symbols.
// We assume values are sorted by date.
x.domain([
d3.min(symbols, function(s) { return s.values[0].date; }),
d3.max(symbols, function(s) { return s.values[s.values.length - 1].date; })
]);
// Add an SVG element for each symbol, with the desired dimensions and margin.
var svg = d3.select("body").selectAll("svg")
.data(symbols)
.enter().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 area path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "area")
.attr("d", function(d) { y.domain([0, d.maxPrice]); return area(d.values); });
// Add the line path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "line")
.attr("d", function(d) { y.domain([0, d.maxPrice]); return line(d.values); });
// Add a small label for the symbol name.
svg.append("text")
.attr("x", width - 6)
.attr("y", height - 6)
.style("text-anchor", "end")
.text(function(d) { return d.key; });
svg.append('g') // create a <g> element
.attr('class', 'x axis') // specify classes
.attr("transform", "translate(0," + height + ")")
.call(xAxis); // let the axis do its thing
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value");
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var date, index;
date = x.invert(d3.mouse(this)[0]);
index = 0;
var focus = svg.selectAll(".focus");
focus.attr("transform", function(d) {
index = bisectDate(d.values, date, 0, d.values.length - 1);
console.log(index, d.values[index].symbol, d.values[index].date, d.values[index].price);
return "translate(" + x(d.values[index].date) + "," + y(d.values[index].price) + ")"
});
focus.selectAll("text", function(d) {
return formatCurrency(d.values[index].price);
});
}
});
function type(d) {
d.price = +d.price;
d.date = parseDate(d.date);
return d;
}
</script>
How do I assign the correct Y-Axis to each individual multiples chart causing the line path and mouseover values to be at the correct position? Any help would be greatly appreciated! Thank you!
This is an interesting problem. The example you link to uses a single y scale and yAxis for all 4 sub-plots. In your situation, though, your data has a very different domain for each sub-plot and when you add the dynamic mouse over a shared scale just won't work. So, my solution would be to create a different y scale and yAxis for each subplot.
...
// variable to hold our scales
var ys = {};
var area = d3.svg.area()
.x(function(d) {
return x(d.date);
})
.y0(height)
.y1(function(d) {
return ys[d.symbol](d.price); //<-- call the y function matched to our symbol
});
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d, i) {
return ys[d.symbol](d.price); //<-- call the y scale function matched to our symbol
});
...
// for each symbol create our scale
symbols.forEach(function(s) {
var maxPrice = d3.max(s.values, function(d) {
return d.price;
});
ys[s.key] = d3.scale.linear() //<-- create a scale for each "symbol" (ie Sensor 1, etc...)
.range([height, 0])
.domain([0, maxPrice]);
});
...
// build 4 y axis
var axisGs = svg.append("g"); //<-- create a collection of axisGs
axisGs
.attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value");
axisGs.each(function(d, i) { //<-- for each axisG create an axis with it's scale
var self = d3.select(this);
self.call(
d3.svg.axis()
.scale(ys[d.key])
.orient("left")
.ticks(4)
);
});
...
// adjust mouseover to use appropriate scale
focus.attr("transform", function(d) {
index = bisectDate(d.values, date, 0, d.values.length - 1);
console.log(index, d.values[index].symbol, d.values[index].date, d.values[index].price);
return "translate(" + x(d.values[index].date) + "," + ys[d.key](d.values[index].price) + ")"; //<-- finally in our mouse move use the appropriate scale
});
Fully working code here.
As far as best practices are concerned when you are dealing with n number of datasets you have to go for n number of y scales and their corresponding y axis. It is good for seperation of concern and keeps the visulization intact. here you can see the example.
http://grafitome.github.io/advanced-charts.html#(first chart)

Categories

Resources