Reputation: 1043
I have the following d3 code:
var json = [
{
date: "05/17",
numTags: 23
}
];
d3.select('summary-graph').selectAll('*').remove();
var svg = d3.select("summary-graph"),
margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom;
var parseTime = d3.timeParse("%m/%y");
var svg = d3.select("summary-graph").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Get the data
var data = json;
// format the data
data.forEach(function (d) {
console.log(d);
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function (d) {
return d.date;
}))
.nice();
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function (d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function (d) {
return xScale(d.date);
})
.y(function (d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) { return "blue" })
.attr("cx", function(d, i) { return xScale(d.date) })
.attr("cy", function(d, i) { return yScale(d.numTags) })
.attr("r", function(d, i) { return 10 });
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size","14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
Which produces the following visualization:
I'm trying to figure out how to center the tick mark and show the x-axis label when there is only 1 data point like this. At 2 data points, I don't love how it still sets the tick marks at the extreme ends of the x-axis. For 3 data points and above, it looks nice (thanks to .nice() it seems).
Any help?
Based on Gerado's response, I was able to get close. The last sticking point is that the left side of the X-axis now has the month (March) despite no data for that day.
Fixed: changed .ticks(json.length) to .ticks(d3.timeMonth.every(1))
Upvotes: 4
Views: 1656
Reputation: 102198
Since you are using only a single data point, your date scale has a domain in which the lower and upper values are the same:
[
Mon May 01 2017 00: 00: 00 GMT + 1000,
Mon May 01 2017 00: 00: 00 GMT + 1000
]
For putting that circle in the middle of the x axis you have to set different values for the scale's domain.
There are several ways for doing that. My proposed solution here involves verifying if the domain's values are the same...
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
... and, if they are, changing them. In this case, I'm subtracting one day from the lower limit and adding one day to the upper limit:
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
Check the result:
var json = [{
date: "05/17",
numTags: 23
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Get the data
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>
EDIT: As you asked in your edit, when you have two data values my solution will create additional ticks on the limits, which is the expected behaviour:
var json = [{
date: "05/17",
numTags: 23
}, {
date: "05/17",
numTags: 17
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Get the data
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>
There are several ways for removing those ticks. One of them is using tickValues
:
.tickValues(data.map(function(d){ return d.date}))
Here is the demo:
var json = [{
date: "05/17",
numTags: 23
}, {
date: "05/17",
numTags: 17
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Get the data
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickValues(data.map(function(d){ return d.date}))
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>
Upvotes: 3