'Line chart point not aligned with x axis ticks D3.js
Hi I have a line graph but the point indicated by the line aren't aligned with the x axis points, they seem to be in-between the points.
Anyone know that the cause could be and how to fix it?
I have added my code for this graph below. Please note that the code below was originally used for a bar chart. I am just trying to add lines to the bar chart. Therefore some bits of code may not seem relevant.
Thank you in advance!
let barWidth;
let labelLengths = [];
const response = _.cloneDeep(payloadResponse);
const d3ChartData = [];
const horizontal = options.horizontal
response.data.forEach(m => {
m.metric0 = parseFloat(m.metric0);
});
const total = _.sumBy(response.data, 'metric0')
const { percentValues } = options;
const metrics = [];
response.metrics.forEach(m => {
metrics.push({ key: Object.keys(m)[0], name: Object.values(m)[0] });
});
const dimensions = [];
response.dims.forEach(d => {
dimensions.push({ key: Object.keys(d)[0], name: Object.values(d)[0] });
});
response.data.forEach(row => {
const newobject = {};
newobject["category"] = row[dimensions[0].key];
newobject["values"] = row[metrics[0].key];//Y axis metrics
metrics.forEach((s, i) => {
row[metrics[i].key] = percentValues ? percentage(row[metrics[i].key] / total) : row[metrics[i].key]
newobject[s.name] = row[metrics[i].key]
})
d3ChartData.push(newobject)
})
// List of groups = species here = value of the first column called group -> show them on the X axis
const groups = d3.map(d3ChartData, function (d) { return d["category"] }).keys();
const valueGroups = d3.map(d3ChartData, function (d) { return d["values"] }).keys();//Y axis group
subgroups = d3.map(metrics, function (d) { return d["name"] }).keys();
subgroups = _.sortBy(subgroups);
const newdataset = d3ChartData;
const maxarray = [];
metrics.forEach((s, i) => {
maxarray.push((Math.max.apply(Math, response.data.map(function (o) {
return Math.round(o[metrics[i].key]);
}))))
})
// set the dimensions and margins of the graph
let max = Math.max.apply(Math, maxarray)
console.log("max","",max);
max = max * 1.1;
let longestLabel = Math.max.apply(Math, maxarray).toString().length;
if(longestLabel < 5)
{
longestLabel = 5;
}
let chartBottom = Math.max(...(groups.map(el => el.length)));
let longestValue = Math.max(...(valueGroups.map(el => Math.trunc(el).toString().length)));
let ySpacing = longestValue*10 >= 60 ? longestValue*10 == 70 ? longestValue*( (10 + Math.ceil(longestValue/3))+3) : longestValue*( (10 + Math.ceil(longestValue/3) )) : 80;
let margin = { top: 10, right: 20, bottom: horizontal ? ySpacing<150 ? ySpacing : 130 : chartBottom < 14 ? 100 : (chartBottom * 8), left: !horizontal ? ySpacing : chartBottom < 14 ? 100 : (chartBottom * 8)},
width = 340,
height = 310;
// append the svg object to the body of the page
let svg = d3.select('#result-chart-' + response.chartNo)
.append("svg")
.attr("id", "svg-chart-" + response.chartNo)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let sizeDimensions = {
margin: margin,
height: height,
width: width
}
let svgCanvas = d3.select('#svg-chart-' + response.chartNo)
let x, y;
// Add X axis
x = d3.scaleBand()
.domain(groups)
.range([0, width])
// Add Y axis
y = d3.scaleLinear()
.domain([0, max])
.range([height, 0]);
let makeYLines = () => d3.axisRight()
.scale(y);
svg.append('g')
.attr('transform', `translate(${width}, 0)`)
.attr("class", "gridlines")
.call(makeYLines().tickSize(-width, 0, 0).tickFormat(''));
svg.append("g")
.call(d3.axisLeft(y).tickSizeOuter(0))
.selectAll("text")
.attr("class", "axis-text")
//.attr("transform", function (d) {return !horizontal ? "rotate(0)" : "rotate(-30)"})
.attr("text-anchor", "end");
// Another scale for subgroup position?
let xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, (!horizontal ? x.bandwidth() : y.bandwidth())])
.padding([0.05])
// color palette = one color per subgroup
let color = d3.scaleOrdinal()
.domain(subgroups)
.range(_config.theme)
let tooltip = d3.select('#chatbot-message-container')
.append("div")
.attr("class", "toolTip")
.style("border-color", _config.theme[0]);
//============================================
//line chart start
let line = d3.line()
.x(function(d) { return x(d.category)})
.y(function(d) { return y(d.values) })
svg.append("path")
.datum(newdataset)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", line)
let line2 = d3.line()
.x(function(d) { return x(d.category)})
.y(function(d) { return y(d.Leavers) })
svg.append("path")
.datum(newdataset)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1.5)
.attr("d", line2)
//============================================
//line chart end
//gets width of each bar
svg.selectAll('rect') // select all the text elements
.each(function(d){ barWidth = this.getBBox().width})
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
.selectAll("text")
.each(function(d){ labelLengths.push(this.getBBox().width)})
.attr("transform", function (d) {return horizontal ? percentValues ? "rotate(0)" : longestValue < 2 ? "rotate(0)" : "rotate(270)" : barWidth>Math.max(...labelLengths) ? "rotate(0)" : "rotate(270)"})
.attr("y", function (d) {return horizontal ? percentValues ? 8 : longestValue < 2 ? 8 : -4 : barWidth>Math.max(...labelLengths) ? 8 : -4})
.attr("x", function (d) {return horizontal ? percentValues ? 0 : longestValue < 2 ? 0 : -7 : barWidth>Math.max(...labelLengths) ? 0 : -7})
.attr("class", "axis-text x-axis-text")
.attr("text-anchor", function (d) {return horizontal ? percentValues ? "middle" : longestValue < 2 ? "middle" : "end" : barWidth>Math.max(...labelLengths) ? "middle" : "end"});
// Animation
svg.selectAll("rect")
.transition()
.duration(1000)
.ease(d3.easePoly)
.attr(!horizontal ? "y" : "x", function (d) {
return !horizontal ? y(d.value) : x(0);
})
.attr(!horizontal ? "height" : "width", function (d) {
return !horizontal ? (height - y(d.value)) : (x(d.value))
})
.delay(function (d, i) { return (i * 30) })
if (metrics.length > 1) {
buildLegend(svgCanvas, color, subgroups, sizeDimensions, 15, tooltip, 'result-chart-' + response.chartNo, response.chartNo);
} else {
buildAxisNames(svgCanvas, metrics, options, sizeDimensions);
}
UPDATE:
I found some sources mentioning using scalePoint, this worked perfectly for the line but broke the bars when reintroducing the code responsible for drawing them. I can only assume that this is because bar charts need to used scaleBand() ?
I then looked into changing the paddingInner/outer after finding this great resource on scaleBand() https://observablehq.com/@d3/d3-scaleband
Changing the padding as so seemed to work great for the line:
x = d3.scaleBand()
.domain(groups)
.range([0, width])
.paddingOuter(0.2)
.paddingInner(0.9)
But when adding the bars back in they were also changed:
The bars should actually look like this:
My next thought is to introduce a second x variable, eg: x2 and set a different paddingInner/outer for that so that both the line an d bars are using different x's. Is this the correct approach?
I understand that it may be difficult help/debug with this issue as I havened added a jsfiddle. This is simple due to me not having access to the data, I will see if I can hardcode some example data. Until then I hope someone can help with this.
UPDATE: JsFiddle with code found here https://jsfiddle.net/MBanski/0ke5u3qy/1/
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|