Forum Discussion
Overlapping Tick Marks on X-Axis
- 6 years ago
In case anyone runs into the same problem, this is how I solved the issue.
I put the creation of the X axis inside a do/while loop. The loop begins with creating the axis then loops through each text element and gets the width of the text. The width is added to an array from which the largest width is determined and multiplied by the total number of ticks to get an estimate of how much space all the labels would need. This is then compared to the plot width and if it is greater (i.e. the labels require more room than is available in the plot area and are overlapping) the whole loop is rerun (and the axis recreated) with one fewer ticks until there is no overlapping. Generally it only needs to run 5 or fewer times, but I put in an iteration counter and added it to the while condition to avoid an infinite loop.
I am pasting the code below in case it is helpful. It needs to be cleaned up considerably (I have a lot of console logs to help with debugging), but here it is for what it's worth.
If any one has a more efficient way I would love to hear it.
do { var xAxis = d3.axisBottom(xScale) .tickFormat((d) => valueFormatter.format(d)) .ticks(tickMarks); this.xAxisContainer .attr("class", "xAxis") .attr("transform", "translate(" + plotArea.x + "," + (plotArea.height + plotArea.y) + ")") .call(xAxis) .style("font-size", viewModel.XAxisFontSize) .style("font-family", viewModel.FontFamily) console.log("******************") console.log("iteration" + m) console.log("View Port Width:" + options.viewport.width) var gCounter = 0 var g = 0 var gArray = [] var labelWidth = 0 var gMax = 0 d3.select(".xAxis").selectAll("text") .each(function (d,i) { console.log("starting") let textProperties: TextProperties = { text: valueFormatter.format(d), fontFamily: viewModel.FontFamily, fontSize: "" + viewModel.XAxisFontSize } let h = textMeasurementService.measureSvgTextRect(textProperties).width gCounter++ g = h + g gArray.push(h) gMax = d3.max(gArray) labelWidth = ((gCounter - .5) * gMax) console.log("----------------------") console.log("Font Family: " + textProperties.fontFamily) console.log("Font Size: " + textProperties.fontSize) console.log("width of " + textProperties.text + ":" + h) console.log("Counter:" + gCounter) console.log("g:" + g) console.log("gArray: " + gArray) console.log("gMax: " + gMax) console.log("Label Width:" + labelWidth) console.log("Plot Width: " + plotArea.width) return(labelWidth) }); --tickMarks console.log("TickMarks:" + tickMarks) ++m console.log("Plot Width: " + plotArea.width) console.log("Label Width:" + labelWidth) console.log("Test: ", plotArea.width < labelWidth) } while (m<100 && plotArea.width < labelWidth)
In case anyone runs into the same problem, this is how I solved the issue.
I put the creation of the X axis inside a do/while loop. The loop begins with creating the axis then loops through each text element and gets the width of the text. The width is added to an array from which the largest width is determined and multiplied by the total number of ticks to get an estimate of how much space all the labels would need. This is then compared to the plot width and if it is greater (i.e. the labels require more room than is available in the plot area and are overlapping) the whole loop is rerun (and the axis recreated) with one fewer ticks until there is no overlapping. Generally it only needs to run 5 or fewer times, but I put in an iteration counter and added it to the while condition to avoid an infinite loop.
I am pasting the code below in case it is helpful. It needs to be cleaned up considerably (I have a lot of console logs to help with debugging), but here it is for what it's worth.
If any one has a more efficient way I would love to hear it.
do {
var xAxis = d3.axisBottom(xScale)
.tickFormat((d) => valueFormatter.format(d))
.ticks(tickMarks);
this.xAxisContainer
.attr("class", "xAxis")
.attr("transform", "translate(" + plotArea.x + "," + (plotArea.height + plotArea.y) + ")")
.call(xAxis)
.style("font-size", viewModel.XAxisFontSize)
.style("font-family", viewModel.FontFamily)
console.log("******************")
console.log("iteration" + m)
console.log("View Port Width:" + options.viewport.width)
var gCounter = 0
var g = 0
var gArray = []
var labelWidth = 0
var gMax = 0
d3.select(".xAxis").selectAll("text")
.each(function (d,i) {
console.log("starting")
let textProperties: TextProperties = {
text: valueFormatter.format(d),
fontFamily: viewModel.FontFamily,
fontSize: "" + viewModel.XAxisFontSize
}
let h = textMeasurementService.measureSvgTextRect(textProperties).width
gCounter++
g = h + g
gArray.push(h)
gMax = d3.max(gArray)
labelWidth = ((gCounter - .5) * gMax)
console.log("----------------------")
console.log("Font Family: " + textProperties.fontFamily)
console.log("Font Size: " + textProperties.fontSize)
console.log("width of " + textProperties.text + ":" + h)
console.log("Counter:" + gCounter)
console.log("g:" + g)
console.log("gArray: " + gArray)
console.log("gMax: " + gMax)
console.log("Label Width:" + labelWidth)
console.log("Plot Width: " + plotArea.width)
return(labelWidth)
});
--tickMarks
console.log("TickMarks:" + tickMarks)
++m
console.log("Plot Width: " + plotArea.width)
console.log("Label Width:" + labelWidth)
console.log("Test: ", plotArea.width < labelWidth)
} while (m<100 && plotArea.width < labelWidth)