Consider the following:
page.html:
<!DOCTYPE html>
<!-- https://bl.ocks.org/mbostock/3886208 -->
<style>
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
pointer-events: none;
}
.monthNumbersLabel {
position:relative;top:15px;
}
/*Line Graph*/
.line {
fill: none;
stroke-width: 2px;
}
/*Points on line graph*/
.point {
r: 3.5;
}
svg:first-of-type {
margin-top: 30px;
}
</style>
<script src="https://d3js.org/d3.v4.js"></script>
<body>
<script>
var margin = {top: 50, right: 20, bottom: 50, left: 80},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%m/%d/%Y");
var svg = d3.select("body").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 x = d3.scaleTime()
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
.range(['#4472C4','#ED7D31','#FFC000','#C00000',
'#E495A5']);
var xMonthAxis = d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")); // label every month
var xYearAxis = d3.axisBottom(x)
.ticks(d3.timeYear.every(1))
.tickFormat(d3.timeFormat("%Y")); // label every year
var yAxis = d3.axisLeft(y).tickFormat(d3.format(".0%")).tickSize(-width+margin.left+margin.right);
// load .csv file
d3.csv("test_data.csv" + '?' + Math.floor(Math.random() * 1000), // for avoiding caching: see https://stackoverflow.com/questions/13053096/avoid-data-caching-when-using-d3-text
function(error, data){
if (error) throw error;
data.forEach(function(d) {
d.Week_Ending = parseDate(d.Week_Ending);
d.Percent = +d.Percent;
});
data.sort(function(a, b) { return b.Week_Ending - a.Week_Ending; });
x.domain(d3.extent( data, function(d){ return d.Week_Ending; }) );
var max = x.domain()[1];
var min = x.domain()[0];
x.domain([min,max]);
y.domain([0, d3.max(data, function(d) { return d.Percent; })]).nice();
// x-axis
var monthAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xMonthAxis);
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11){ // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) { return !d.getMonth()});
if(tickValues.length && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) { return !d.getMonth()});
if(tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
var yearAxis = g.append("g")
.attr("class", "yearaxis axis")
.attr("transform", "translate(0," + (height + 25) + ")")
.call(xYearAxis);
var valueAxis = g.append("g")
.attr("class", "y axis grid")
.call(yAxis);
monthAxis.selectAll("g").select("text");
var dataByCategory = d3.nest()
.key(function(d) { return d.Site; })
.entries(data);
// https://stackoverflow.com/a/15030117/3625022
function flatten(arr) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
var dataFrame = flatten(dataByCategory.map(function(a) {return a.values;}));
x.domain(d3.extent(data, function(d) { return d.Week_Ending; }));
y.domain([0, d3.max(data, function(d) { return d.Percent; })]);
var valueline = d3.line()
.x(function(d) { return x(d.Week_Ending); })
.y(function(d) { return y(d.Percent); });
g.selectAll('path')
.data(dataByCategory)
.enter()
.append("path")
.attr("class", function(d){
return "line " + d.key;
})
.attr("stroke", function(d){
return z(d.key);
})
.attr("d", function(d) {
return valueline(d.values);
});
g.selectAll('dot')
.data(dataFrame) // have to use a "flat" version to get the points
.enter()
.append("circle")
.attr("class", function(d){
return "point " + d.Site;
})
.attr("fill", function(d){
return z(d.Site);
})
.attr("cx", function (d, i){
return x(d.Week_Ending);
})
.attr("cy", function (d, i){
return y(d.Percent);
});
}
);
</script>
</body>
test_data.csv:
Site,Percent,Week_Ending
1,0.01999,8/27/2016
1,0.02564,9/24/2016
1,0.0287,10/22/2016
1,0.02751,11/19/2016
1,0.02624,12/17/2016
1,0.02826,1/14/2017
1,0.02698,2/11/2017
1,0.026,3/11/2017
1,0.031479367,4/8/2017
1,0.030840815,5/6/2017
1,0.03475,6/3/2017
1,0.03592,7/1/2017
1,0.03729,7/29/2017
1,0.041,8/26/2017
1,0.03856,9/23/2017
1,0.03886,10/21/2017
1,0.03723,11/18/2017
1,0.03513,12/16/2017
1,0.03172,1/13/2018
2,0.03819,8/27/2016
2,0.021,9/24/2016
2,0.02641,10/22/2016
2,0.01769,11/19/2016
2,0.01597,12/17/2016
2,0.01894,1/14/2017
2,0.01881,2/11/2017
2,0.01572,3/11/2017
2,0.014007761,4/8/2017
2,0.012521765,5/6/2017
2,0.01319,6/3/2017
2,0.0188,7/1/2017
2,0.01517,7/29/2017
2,0.02421,8/26/2017
2,0.01511,9/23/2017
2,0.01333,10/21/2017
2,0.0127,11/18/2017
2,0.01023,12/16/2017
2,0.01205,1/13/2018
3,0.01157,8/27/2016
3,0.01265,9/24/2016
3,0.00864,10/22/2016
3,0.01176,11/19/2016
3,0.01069,12/17/2016
3,0.01086,1/14/2017
3,0.01228,2/11/2017
3,0.01108,3/11/2017
3,0.00915436,4/8/2017
3,0.005970321,5/6/2017
3,0.01039,6/3/2017
3,0.01026,7/1/2017
3,0.01368,7/29/2017
3,0.01323,8/26/2017
3,0.00651,9/23/2017
3,0.00753,10/21/2017
3,0.00776,11/18/2017
3,0.00827,12/16/2017
3,0.0097,1/13/2018
4,0.0322,8/27/2016
4,0.02678,9/24/2016
4,0.03054,10/22/2016
4,0.04087,11/19/2016
4,0.02543,12/17/2016
4,0.04023,1/14/2017
4,0.02782,2/11/2017
4,0.03351,3/11/2017
4,0.036742785,4/8/2017
4,0.032985858,5/6/2017
4,0.03487,6/3/2017
4,0.03926,7/1/2017
4,0.03093,7/29/2017
4,0.03565,8/26/2017
4,0.03464,9/23/2017
4,0.03243,10/21/2017
4,0.0351,11/18/2017
4,0.03647,12/16/2017
4,0.03273,1/13/2018
5,0.02887,8/27/2016
5,0.04095,9/24/2016
5,0.04198,10/22/2016
5,0.05206,11/19/2016
5,0.0473,12/17/2016
5,0.04028,1/14/2017
5,0.05827,2/11/2017
5,0.05404,3/11/2017
5,0.055141464,4/8/2017
5,0.044402436,5/6/2017
5,0.04225,6/3/2017
5,0.03228,7/1/2017
5,0.03833,7/29/2017
5,0.03042,8/26/2017
5,0.03674,9/23/2017
5,0.03439,10/21/2017
5,0.0348,11/18/2017
5,0.03562,12/16/2017
5,0.03496,1/13/2018
Why is it that some paths are not being drawn, while others are?
I don't think it's because of missing data, at a quick glance. If this does happen to be because of missing data, I would at least prefer that points in adjacent months be connected. I've looked at http://bl.ocks.org/d3noob/38744a17f9c0141bcd04 as well as https://bocoup.com/blog/showing-missing-data-in-line-charts#solution2.
答案 0 :(得分:2)
The issue is here:
g.selectAll('path')
.data(dataByCategory)
.enter()
.append("path")
An enter selection is used to add one element to the DOM for each item in the data array that does not have a corresponding element (in the DOM). If you have no corresponding elements (empty selection), every item in the data array will result in an element in the enter selection.
But, you don't have an empty selection. You are selecting three paths, which means the first three elements in your data array won't be entered - they already exist and don't need entering.
What are you selecting though? You are selecting paths in your three axes:
Instead, try something like:
g.selectAll(".line") // select the series of the chart
.data(data)
.enter()
.append("path")
.attr("class","line"); // identify each series as series
Or a null selection:
g.selectAll(null) // empty
.data(data)
.enter()
.append("path") // enter each item in the data array.