Ok, so I've found solution. It's not as simple as is should (for such basic problem), but is very customizable.
LineChartView (via BarLineChartViewBase)
has property xAxisRenderer
. You can subclass ChartXAxisRenderer
which is default one, and override drawLabels
function. In my case, I've copied original code and modified logic behind calculation what label and where should be drawn.
Edit: My custom renderer with overlap logic. Probably overcomplicated, but it works so far.
class ChartXAxisDateRenderer: ChartXAxisRenderer {
internal static let FDEG2RAD = CGFloat(M_PI / 180.0)
/// draws the x-labels on the specified y-position
override func drawLabels(context context: CGContext, pos: CGFloat, anchor: CGPoint) {
guard let xAxis = xAxis else { return }
let paraStyle = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
paraStyle.alignment = .Center
let labelAttrs = [NSFontAttributeName: xAxis.labelFont,
NSForegroundColorAttributeName: xAxis.labelTextColor,
NSParagraphStyleAttributeName: paraStyle]
let labelRotationAngleRadians = xAxis.labelRotationAngle * ChartXAxisDateRenderer.FDEG2RAD
let valueToPixelMatrix = transformer.valueToPixelMatrix
let minLabelsMargin: CGFloat = 4.0
var position = CGPoint(x: 0.0, y: 0.0)
var labelMaxSize = CGSize()
if (xAxis.isWordWrapEnabled) {
labelMaxSize.width = xAxis.wordWrapWidthPercent * valueToPixelMatrix.a
}
var positions = [CGPoint]()
var widths = [CGFloat]()
var labels = [String]()
var originalIndices = [Int]()
for i in 0...xAxis.values.count-1 {
let label = xAxis.values[i]
if (label == nil || label == "")
{
continue
}
originalIndices.append(i)
labels.append(label!)
position.x = CGFloat(i)
position.y = 0.0
position = CGPointApplyAffineTransform(position, valueToPixelMatrix)
positions.append(position)
let labelns = label! as NSString
let width = labelns.boundingRectWithSize(labelMaxSize, options: .UsesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
widths.append(width)
}
let newIndices = findBestPositions(positions, widths: widths, margin: minLabelsMargin)
for index in newIndices {
let label = labels[index]
let position = positions[index]
let i = originalIndices[index]
if (viewPortHandler.isInBoundsX(position.x)) {
drawLabel(context: context, label: label, xIndex: i, x: position.x, y: pos, attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
}
}
}
// Best position indices - minimum "n" without overlapping
private func findBestPositions(positions: [CGPoint], widths: [CGFloat], margin: CGFloat) -> [Int] {
var n = 1
var overlap = true
// finding "n"
while n < widths.count && overlap {
overlap = doesOverlap(n, positions: positions, widths: widths, margin: margin)
if overlap {
n += 1
}
}
var newPositions = [Int]()
var i = 0
// create result indices
while i < positions.count {
newPositions.append(i)
i += n
}
return newPositions
}
// returns whether drawing only n-th labels will casue overlapping
private func doesOverlap(n: Int, positions: [CGPoint], widths: [CGFloat], margin: CGFloat) -> Bool {
var i = 0
var newPositions = [CGPoint]()
var newWidths = [CGFloat]()
// getting only n-th records
while i < positions.count {
newPositions.append(positions[i])
newWidths.append(widths[i])
i += n
}
// overlap with next label checking
for j in 0...newPositions.count - 2 {
if newPositions[j].x + newWidths[j] + margin > newPositions[j+1].x {
return true
}
}
return false
}
}